17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-06 02:18:35 +01:00

feat(vulkan): vulkan 1.4 core with the user interface and the launcher, shaders split into ogl46/vk14 with SPIR-V binaries loaded first

This commit is contained in:
2026-10-24 05:47:11 +02:00
parent 68fa5dc2da
commit bd589f272d
235 changed files with 9397 additions and 3975 deletions

View File

@@ -0,0 +1,689 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/common/shadersource.h"
#include <algorithm>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <mutex>
#include <sstream>
#include "utilities/Logs.h"
namespace gfx
{
namespace
{
bool ends_with(std::string_view Value, std::string_view Ending)
{
return (Ending.size() <= Value.size()) && (Value.compare(Value.size() - Ending.size(), Ending.size(), Ending) == 0);
}
// the content of a file, empty with Found false when it can't be read
std::string read_text(std::string const &Path, bool &Found)
{
std::ifstream file(Path, std::ios::binary);
Found = file.is_open();
if (false == Found)
{
return {};
}
std::stringstream stream;
stream << file.rdbuf();
return stream.str();
}
std::string read_file(std::string const &Path)
{
bool found{false};
auto text{read_text(Path, found)};
if ((false == found) || text.empty())
{
throw shader_exception("cannot read shader: " + Path);
}
return text;
}
std::unordered_map<std::string, shader_components> const components_mapping = {
{"R", shader_components::R}, {"RG", shader_components::RG}, {"RGB", shader_components::RGB}, {"RGBA", shader_components::RGBA}, {"sRGB", shader_components::sRGB}, {"sRGB_A", shader_components::sRGB_A}};
std::unordered_map<std::string, shader_param_default> const defaultparams_mapping = {
{"required", shader_param_default::required}, {"nan", shader_param_default::nan}, {"zero", shader_param_default::zero}, {"one", shader_param_default::one},
{"ambient", shader_param_default::ambient}, {"diffuse", shader_param_default::diffuse}, {"specular", shader_param_default::specular}, {"glossiness", shader_param_default::glossiness}};
void log_error(std::string const &Name, std::string const &Message)
{
ErrorLog("bad shader: " + Name + ": " + Message, logtype::shader);
}
// the position of the next directive Magic ("texture", "param") of the materials: #pragma <magic>(...), or the older #<magic> (...)
std::size_t find_directive(std::string const &Text, std::string const &Magic, std::size_t const From, std::size_t &Arguments)
{
auto start{From};
while ((start = Text.find('#', start)) != std::string::npos)
{
auto position{start + 1};
auto const skip_blanks = [&]() {
while ((position < Text.size()) && ((Text[position] == ' ') || (Text[position] == '\t')))
{
++position;
}
};
skip_blanks();
if (Text.compare(position, 6, "pragma") == 0)
{
position += 6;
skip_blanks();
}
if ((Text.compare(position, Magic.size(), Magic) == 0) && (position + Magic.size() < Text.size()))
{
position += Magic.size();
skip_blanks();
if ((position < Text.size()) && (Text[position] == '('))
{
Arguments = position;
return start;
}
}
++start;
}
return std::string::npos;
}
// the arguments of a directive, between the brackets, split at the commas; the directive is taken out of the text
std::vector<std::string> take_directive(std::string &Text, std::size_t const Start, std::size_t const Arguments)
{
std::vector<std::string> result;
auto const end{Text.find(')', Arguments)};
if (end == std::string::npos)
{
Text.erase(Start, 1);
return result;
}
std::istringstream stream(Text.substr(Arguments + 1, end - Arguments - 1));
std::string token;
while (std::getline(stream, token, ','))
{
std::istringstream tokenstream(token);
std::string value;
tokenstream >> value;
result.emplace_back(value);
}
Text.erase(Start, end - Start + 1);
return result;
}
void parse_texture_entries(std::string &Text, shader_source &Source)
{
std::size_t start{0}, arguments{0};
while ((start = find_directive(Text, "texture", start, arguments)) != std::string::npos)
{
auto const args{take_directive(Text, start, arguments)};
if (args.size() != 3)
{
log_error(Source.name, "invalid argument count to #pragma texture");
continue;
}
shader_texture_entry conf{};
try
{
conf.id = static_cast<std::size_t>(std::stoul(args[1]));
}
catch (std::exception const &)
{
log_error(Source.name, "invalid texture binding: " + args[1]);
continue;
}
auto const components{components_mapping.find(args[2])};
if (components == components_mapping.end())
{
log_error(Source.name, "unknown components: " + args[2]);
continue;
}
conf.components = components->second;
if (args[0].empty())
{
log_error(Source.name, "empty name");
}
else if (conf.id >= MAX_SHADER_TEXTURES)
{
log_error(Source.name, "invalid texture binding: " + std::to_string(conf.id));
}
else
{
Source.texture_conf.try_emplace(args[0], conf);
}
}
}
void parse_param_entries(std::string &Text, shader_source &Source)
{
std::size_t start{0}, arguments{0};
while ((start = find_directive(Text, "param", start, arguments)) != std::string::npos)
{
auto const args{take_directive(Text, start, arguments)};
if (args.size() != 5)
{
log_error(Source.name, "invalid argument count to #pragma param");
continue;
}
shader_param_entry conf{};
try
{
conf.location = static_cast<std::size_t>(std::stoul(args[1]));
conf.offset = static_cast<std::size_t>(std::stoul(args[2]));
conf.size = static_cast<std::size_t>(std::stoul(args[3]));
}
catch (std::exception const &)
{
log_error(Source.name, "invalid param: " + args[0]);
continue;
}
auto const defaultparam{defaultparams_mapping.find(args[4])};
if (defaultparam == defaultparams_mapping.end())
{
log_error(Source.name, "unknown param default: " + args[4]);
continue;
}
conf.defaultparam = defaultparam->second;
if (args[0].empty())
{
log_error(Source.name, "empty name");
}
else if (conf.location >= MAX_SHADER_PARAMS)
{
log_error(Source.name, "invalid param binding: " + std::to_string(conf.location));
}
else if (conf.offset > 3)
{
log_error(Source.name, "invalid offset: " + std::to_string(conf.offset));
}
else if (conf.offset + conf.size > 4)
{
log_error(Source.name, "invalid size: " + std::to_string(conf.size));
}
else
{
Source.param_conf.try_emplace(args[0], conf);
}
}
}
std::size_t count_lines(std::string const &Text, std::size_t const End)
{
return static_cast<std::size_t>(std::count(Text.begin(), Text.begin() + std::min(End, Text.size()), '\n'));
}
// the number of the file in the list of the files of the source, added when it isn't there
std::size_t file_number(shader_source &Source, std::string const &File)
{
auto const lookup{std::find(Source.files.begin(), Source.files.end(), File)};
if (lookup != Source.files.end())
{
return static_cast<std::size_t>(lookup - Source.files.begin());
}
Source.files.emplace_back(File);
return Source.files.size() - 1;
}
// replaces the includes of Text (the content of the file number Number) with the content of the files they name, with #line
// directives which keep the numbers of the lines of each file in the messages of the compiler. an include is a line which
// starts with #include (blanks before it allowed)
void expand_includes(std::string &Text, std::size_t const Number, std::string const &Basedir, shader_source &Source, int const Depth)
{
if (Depth > 32)
{
throw shader_exception("includes nested too deep in " + Source.name);
}
std::string result;
result.reserve(Text.size());
std::size_t linestart{0};
std::size_t linenumber{1};
while (linestart < Text.size())
{
auto lineend{Text.find('\n', linestart)};
if (lineend == std::string::npos)
{
lineend = Text.size();
}
std::string_view const line{Text.data() + linestart, lineend - linestart};
auto position{line.find_first_not_of(" \t")};
bool included{false};
if ((position != std::string_view::npos) && (line.compare(position, 8, "#include") == 0))
{
position = line.find_first_not_of(" \t", position + 8);
if (position != std::string_view::npos)
{
auto const open{line[position]};
auto const close{(open == '<') ? '>' : (open == '"') ? '"' : '\0'};
auto const end{(close != '\0') ? line.find(close, position + 1) : std::string_view::npos};
if (end != std::string_view::npos)
{
std::string filename{line.substr(position + 1, end - position - 1)};
if (filename == "common")
{
// the name the shaders used for the block of the game before it became a file
filename = "common.glsl";
}
auto const number{file_number(Source, filename)};
auto content{read_file(Basedir + filename)};
expand_includes(content, number, Basedir, Source, Depth + 1);
result += "#line 1 " + std::to_string(number) + "\n";
result += content;
if ((false == content.empty()) && (content.back() != '\n'))
{
result += '\n';
}
result += "#line " + std::to_string(linenumber + 1) + " " + std::to_string(Number);
included = true;
}
}
}
if (false == included)
{
result.append(line);
}
if (lineend < Text.size())
{
result += '\n';
}
linestart = lineend + 1;
++linenumber;
}
Text = std::move(result);
}
std::mutex manifest_mutex;
// the manifest of the binaries of a directory: binary -> (source file, hash)
struct manifest_data
{
std::filesystem::file_time_type time{};
bool found{false};
std::unordered_map<std::string, std::vector<std::pair<std::string, std::uint64_t>>> entries;
};
std::unordered_map<std::string, manifest_data> manifests;
manifest_data const &manifest_of(std::string const &Directory)
{
auto &manifest{manifests[Directory]};
auto const path{Directory + "manifest.txt"};
std::error_code error;
auto const time{std::filesystem::last_write_time(path, error)};
if (error)
{
manifest = manifest_data{};
return manifest;
}
if (manifest.found && (manifest.time == time))
{
return manifest;
}
manifest = manifest_data{};
manifest.found = true;
manifest.time = time;
std::ifstream file(path);
std::string line;
while (std::getline(file, line))
{
if (line.empty() || (line[0] == '#'))
{
continue;
}
std::istringstream stream(line);
std::string binary, source, hash;
if (stream >> binary >> source >> hash)
{
try
{
manifest.entries[binary].emplace_back(source, std::stoull(hash, nullptr, 16));
}
catch (std::exception const &)
{
}
}
}
return manifest;
}
} // namespace
char const *shader_api_name(shader_api const Api)
{
return (Api == shader_api::vulkan14 ? "vulkan 1.4" : "opengl 4.6");
}
std::string shader_source_dir(shader_api const Api)
{
return (Api == shader_api::vulkan14 ? "shaders/vk14/" : "shaders/ogl46/");
}
std::string shader_binary_dir(shader_api const Api)
{
return (Api == shader_api::vulkan14 ? "shaders/vk14bin/" : "shaders/ogl46bin/");
}
shader_stage shader_stage_of(std::string const &Name)
{
if (ends_with(Name, ".vert"))
{
return shader_stage::vertex;
}
if (ends_with(Name, ".frag"))
{
return shader_stage::fragment;
}
if (ends_with(Name, ".geom"))
{
return shader_stage::geometry;
}
if (ends_with(Name, ".comp"))
{
return shader_stage::compute;
}
throw shader_exception("unknown shader " + Name);
}
char const *shader_stage_define(shader_stage const Stage)
{
switch (Stage)
{
case shader_stage::vertex:
return "STAGE_VERTEX";
case shader_stage::fragment:
return "STAGE_FRAGMENT";
case shader_stage::geometry:
return "STAGE_GEOMETRY";
case shader_stage::compute:
return "STAGE_COMPUTE";
}
return "STAGE_VERTEX";
}
std::string shader_binary_name(std::string const &Name, std::string const &Variant)
{
return Name + (Variant.empty() ? "" : "." + Variant) + ".spv";
}
std::string shader_variant_of(std::string const &Defines, bool &Plain)
{
Plain = true;
std::string result;
std::istringstream stream(Defines);
std::string line;
while (std::getline(stream, line))
{
std::istringstream linestream(line);
std::string directive, name, value, rest;
if (false == static_cast<bool>(linestream >> directive))
{
continue;
}
if ((directive != "#define") || (false == static_cast<bool>(linestream >> name)))
{
Plain = false;
continue;
}
linestream >> value;
if ((false == value.empty()) && (value != "1"))
{
Plain = false;
}
if (linestream >> rest)
{
Plain = false;
}
result += (result.empty() ? "" : ".") + name;
}
return result;
}
std::uint64_t shader_file_hash(std::string_view const Text)
{
std::uint64_t hash{14695981039346656037ull};
for (std::size_t i = 0; i < Text.size(); ++i)
{
auto const c{Text[i]};
if ((c == '\r') && (i + 1 < Text.size()) && (Text[i + 1] == '\n'))
{
continue;
}
hash ^= static_cast<std::uint8_t>(c);
hash *= 1099511628211ull;
}
return hash;
}
shader_source preprocess_shader(shader_api const Api, std::string const &Name, std::string const &Defines, std::string const &Preamble, bool const Allowmissing, std::string const &Basedir)
{
shader_source source;
source.api = Api;
source.name = Name;
source.stage = shader_stage_of(Name);
bool plain{true};
source.variant = shader_variant_of(Defines, plain);
source.has_binary_name = plain;
auto const basedir{Basedir.empty() ? shader_source_dir(Api) : Basedir};
bool found{false};
auto text{read_text(basedir + Name, found)};
if ((false == found) || text.empty())
{
if (Allowmissing)
{
source.files.emplace_back(Name);
return source;
}
throw shader_exception("cannot read shader: " + basedir + Name);
}
source.has_source = true;
source.files.emplace_back(Name);
// what goes after #version: the variant, the renderer, the stage where the file doesn't say it
std::string preamble{Defines};
if ((false == preamble.empty()) && (preamble.back() != '\n'))
{
preamble += '\n';
}
preamble += Preamble;
if ((false == preamble.empty()) && (preamble.back() != '\n'))
{
preamble += '\n';
}
if (text.find(std::string("#define ") + shader_stage_define(source.stage)) == std::string::npos)
{
preamble += std::string("#define ") + shader_stage_define(source.stage) + " 1\n";
}
// the #version line, which has to stay first
std::size_t versionend{std::string::npos};
{
auto version{text.find("#version")};
if ((version != std::string::npos) && ((version == 0) || (text[version - 1] == '\n')))
{
versionend = text.find('\n', version);
}
}
expand_includes(text, 0, basedir, source, 0);
parse_texture_entries(text, source);
parse_param_entries(text, source);
if (versionend != std::string::npos)
{
// the includes come after the #version line, which is where it was
versionend = text.find('\n', text.find("#version"));
auto const line{count_lines(text, versionend) + 2};
text.insert(versionend + 1, preamble + "#line " + std::to_string(line) + " 0\n");
}
else
{
text = "#version 460 core\n" + preamble + "#line 1 0\n" + text;
}
source.text = std::move(text);
return source;
}
std::vector<std::uint32_t> load_shader_binary(shader_source const &Source, std::string &Reason)
{
Reason.clear();
if (false == Source.has_binary_name)
{
Reason = "the variant isn't one of plain defines";
return {};
}
auto const directory{shader_binary_dir(Source.api)};
auto const name{shader_binary_name(Source.name, Source.variant)};
bool found{false};
auto const data{read_text(directory + name, found)};
if (false == found)
{
return {};
}
if ((data.size() < 20) || (data.size() % 4 != 0))
{
Reason = name + " isn't SPIR-V";
return {};
}
std::vector<std::uint32_t> words(data.size() / 4);
std::memcpy(words.data(), data.data(), data.size());
if (words[0] != 0x07230203u)
{
Reason = name + " isn't SPIR-V";
return {};
}
// the sources the binary was compiled from, as the manifest has them: a binary older than its sources isn't taken
std::lock_guard<std::mutex> lock(manifest_mutex);
auto const &manifest{manifest_of(directory)};
auto const entry{manifest.entries.find(name)};
if (entry != manifest.entries.end())
{
auto const sourcedir{shader_source_dir(Source.api)};
for (auto const &[file, hash] : entry->second)
{
bool sourcefound{false};
auto const text{read_text(sourcedir + file, sourcefound)};
if (false == sourcefound)
{
// the sources aren't there, so they can't be newer
continue;
}
if (shader_file_hash(text) != hash)
{
Reason = name + " was compiled from another " + file + " than the one in " + sourcedir;
return {};
}
}
}
return words;
}
std::vector<std::uint32_t> spirv_specialization_ids(std::vector<std::uint32_t> const &Words)
{
std::vector<std::uint32_t> result;
// the header is 5 words; each instruction starts with its word count (high 16 bits) and opcode (low 16 bits)
std::size_t index{5};
while (index < Words.size())
{
auto const count{Words[index] >> 16};
auto const opcode{Words[index] & 0xffffu};
if ((count == 0) || (index + count > Words.size()))
{
break;
}
// OpDecorate %target SpecId <id>
if ((opcode == 71) && (count >= 4) && (Words[index + 2] == 1))
{
result.push_back(Words[index + 3]);
}
index += count;
}
return result;
}
std::unordered_map<std::string, std::string> shader_file_defines(shader_api const Api, std::string const &Name, std::string const &Basedir)
{
std::unordered_map<std::string, std::string> result;
bool found{false};
auto const text{read_text((Basedir.empty() ? shader_source_dir(Api) : Basedir) + Name, found)};
std::istringstream stream(text);
std::string line;
while (std::getline(stream, line))
{
std::istringstream linestream(line);
std::string directive, name, value;
if ((linestream >> directive >> name) && (directive == "#define"))
{
std::getline(linestream, value);
auto const first{value.find_first_not_of(" \t")};
auto const last{value.find_last_not_of(" \t\r")};
result[name] = (first == std::string::npos ? std::string{} : value.substr(first, last - first + 1));
}
}
return result;
}
bool shader_constants_match(shader_api const Api, std::string const &Name, std::string const &Preamble, std::string const &Basedir, std::vector<std::string> &Differences)
{
Differences.clear();
auto const file{shader_file_defines(Api, Name, Basedir)};
if (file.empty())
{
Differences.emplace_back(Name + " can't be read");
return false;
}
// the same number however it's written: 0.2 and 0.200000, 3 and 3U
auto const normalized = [](std::string Value) {
if ((false == Value.empty()) && ((Value.back() == 'U') || (Value.back() == 'u')))
{
Value.pop_back();
}
try
{
std::size_t used{0};
auto const number{std::stod(Value, &used)};
if (used == Value.size())
{
return std::to_string(number);
}
}
catch (std::exception const &)
{
}
return Value;
};
std::istringstream stream(Preamble);
std::string line;
while (std::getline(stream, line))
{
std::istringstream linestream(line);
std::string directive, name, value;
if (!(linestream >> directive >> name >> value) || (directive != "#define"))
{
continue;
}
auto const lookup{file.find(name)};
if (lookup == file.end())
{
Differences.emplace_back(name + " is missing");
}
else if (normalized(lookup->second) != normalized(value))
{
Differences.emplace_back(name + " is " + lookup->second + " in the file and " + value + " in the game");
}
}
return Differences.empty();
}
} // namespace gfx

View File

@@ -0,0 +1,173 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the shader files of the renderers, whichever api compiles them: where they are, how a file is made ready to compile (its
// includes, the directives of the materials, what the renderer puts before the source), and the SPIR-V binaries compiled from
// them ahead of time, which the renderers load before they compile the source.
//
// each api has a directory of sources and one of binaries: shaders/ogl46/ and shaders/ogl46bin/ for opengl 4.6,
// shaders/vk14/ and shaders/vk14bin/ for vulkan 1.4. a binary is named after its source, <file>.spv, and a variant of the
// shader (the defines of the renderer, e.g. MODEL_DRAWS) <file>.<DEFINE>.spv. the sources can be compiled by glslc as they are
// (shaders/compile_spirv.py), which also writes the manifest of the binaries: the sources each binary was compiled from, so a
// binary older than its sources isn't taken.
//
// no graphics api here: the file is also built into the shader validator (ci_shadervalidator)
#pragma once
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace gfx
{
class shader_exception : public std::runtime_error
{
using runtime_error::runtime_error;
};
enum class shader_api
{
opengl46,
vulkan14
};
enum class shader_stage
{
vertex,
fragment,
geometry,
compute
};
// textures and parameters a material shader can take, the same as gl::MAX_TEXTURES and gl::MAX_PARAMS
constexpr std::size_t MAX_SHADER_TEXTURES = 8;
constexpr std::size_t MAX_SHADER_PARAMS = 3;
// what the texture of a unit holds, as #pragma texture(name, unit, components) gives it. the values are those of opengl
// (GL_RED ... GL_SRGB_ALPHA), which the textures take as the hint of their format
enum class shader_components : int
{
R = 0x1903,
RG = 0x8227,
RGB = 0x1907,
RGBA = 0x1908,
sRGB = 0x8C40,
sRGB_A = 0x8C42
};
struct shader_texture_entry
{
std::size_t id;
shader_components components;
};
enum class shader_param_default
{
required,
nan,
zero,
one,
ambient,
diffuse,
specular,
glossiness
};
// #pragma param(name, location, offset, size, default): a part of one of the vec4 parameters of the model block
struct shader_param_entry
{
std::size_t location;
std::size_t offset;
std::size_t size;
shader_param_default defaultparam;
};
using shader_texture_conf = std::unordered_map<std::string, shader_texture_entry>;
using shader_param_conf = std::unordered_map<std::string, shader_param_entry>;
// a program of a material as the materials see it, whichever api draws it: the textures and the parameters it takes, the
// name it was fetched by (gfx_renderer::Fetch_Shader)
class shader_program
{
public:
virtual ~shader_program() = default;
shader_texture_conf texture_conf;
shader_param_conf param_conf;
std::string name;
};
// a shader file made ready to compile
struct shader_source
{
shader_api api{shader_api::opengl46};
shader_stage stage{shader_stage::vertex};
// the file as asked for, e.g. mat_default.frag
std::string name;
// the names of the defines of the variant joined with dots (MODEL_DRAWS), empty for the shader itself. a variant whose
// defines aren't all plain flags (#define NAME or #define NAME 1) has no binary: invalid_variant
std::string variant;
bool has_binary_name{true};
// whether the source file is there; a binary can be loaded without it, but the material directives come from the source
bool has_source{false};
// the source with the preamble put after #version and the includes expanded, for a compiler of the source. the files are
// told apart in the messages of the compiler by their numbers in files (#line <line> <number>)
std::string text;
// the file and its includes, relative to the directory of the sources, by their numbers
std::vector<std::string> files;
shader_texture_conf texture_conf;
shader_param_conf param_conf;
};
// "opengl 4.6", "vulkan 1.4"
char const *shader_api_name(shader_api Api);
// the directories of the sources and of the binaries of an api: shaders/ogl46/, shaders/ogl46bin/ ...
std::string shader_source_dir(shader_api Api);
std::string shader_binary_dir(shader_api Api);
// the stage of a file by its extension (.vert, .frag, .geom, .comp). throws shader_exception for another one
shader_stage shader_stage_of(std::string const &Name);
// the define the files set for their stage: STAGE_VERTEX ...
char const *shader_stage_define(shader_stage Stage);
// the file name of the binary of a shader: mat_default.frag.spv, vertex.vert.MODEL_DRAWS.spv
std::string shader_binary_name(std::string const &Name, std::string const &Variant);
// the variant named by the defines of a renderer ("#define MODEL_DRAWS 1\n" -> MODEL_DRAWS). Plain: false when a define isn't
// a plain flag (the variant can't have a binary then)
std::string shader_variant_of(std::string const &Defines, bool &Plain);
// the hash the manifest of the binaries keeps of a source file: fnv-1a 64 of the text with the line ends made \n
std::uint64_t shader_file_hash(std::string_view Text);
// reads the shader Name of Api and makes it ready to compile: the includes expanded (#include "file", #include <file>; <common>
// stands for common.glsl), #pragma texture(...) and #pragma param(...) read and taken out of the text, and after the #version
// line (#version 460 core put first when the file has none) the defines of the variant, the preamble of the renderer and,
// where the file doesn't set it, the define of its stage. Basedir: the directory of the sources, shader_source_dir() when empty.
// a missing file is an error unless Allowmissing (the source then comes back with has_source false). throws shader_exception
shader_source preprocess_shader(shader_api Api, std::string const &Name, std::string const &Defines, std::string const &Preamble, bool const Allowmissing = false,
std::string const &Basedir = {});
// the SPIR-V binary of a shader from the directory of the binaries of its api. empty when there's none, when the manifest of the
// binaries says the binary was compiled from other sources than those there now, or when the file isn't SPIR-V; Reason says
// which (empty when there's simply no binary)
std::vector<std::uint32_t> load_shader_binary(shader_source const &Source, std::string &Reason);
// the ids of the specialization constants of a SPIR-V module (OpDecorate ... SpecId)
std::vector<std::uint32_t> spirv_specialization_ids(std::vector<std::uint32_t> const &Words);
// the #define lines of a source, NAME -> value as written, for the checks of the constants the binaries were compiled with
std::unordered_map<std::string, std::string> shader_file_defines(shader_api Api, std::string const &Name, std::string const &Basedir = {});
// whether the constants a renderer puts before the sources (the #define lines of Preamble) are those the file Name of Api writes
// for itself (the binaries are compiled by glslc with the values of the file). Differences: what isn't the same
bool shader_constants_match(shader_api Api, std::string const &Name, std::string const &Preamble, std::string const &Basedir, std::vector<std::string> &Differences);
} // namespace gfx

View File

@@ -107,7 +107,7 @@ public:
IMaterial const *
Material( material_handle const Material ) const override { return m_materials.at(Material - 1).get(); }
// shader methods
auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gl::program> override { throw std::runtime_error("not impl"); }
auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gfx::shader_program> override { throw std::runtime_error("not impl"); }
// texture methods
texture_handle
Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) override { throw std::runtime_error("not impl"); }

View File

@@ -95,19 +95,19 @@ bool opengl46_grass::init()
return false;
}
m_vao = std::make_unique<gl::vao>();
auto const location = [&](char const *Name) { return ::glGetUniformLocation(*m_program, Name); };
m_uniforms.tile = location("grass_tile");
m_uniforms.part = location("grass_part");
m_uniforms.chunk = location("grass_chunk");
m_uniforms.camera = location("grass_camera");
m_uniforms.lod = location("grass_lod");
m_uniforms.wind = location("grass_wind");
m_uniforms.base = location("grass_base");
m_uniforms.tip = location("grass_tip");
m_uniforms.shape = location("grass_shape");
m_uniforms.share = location("grass_share");
m_uniforms.dry = location("grass_dry");
m_uniforms.season = location("grass_season");
// the locations grass.vert gives its uniforms (a program loaded from SPIR-V doesn't know their names)
m_uniforms.tile = 0;
m_uniforms.part = 1;
m_uniforms.chunk = 2;
m_uniforms.camera = 3;
m_uniforms.lod = 4;
m_uniforms.wind = 5;
m_uniforms.base = 6;
m_uniforms.tip = 10;
m_uniforms.shape = 14;
m_uniforms.share = 18;
m_uniforms.dry = 19;
m_uniforms.season = 23;
m_state = 1;
return true;
}

View File

@@ -200,8 +200,8 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
return false;
}
scene_ubo = std::make_unique<gl::ubo>(sizeof(gl::scene_ubs), 0);
model_ubo = std::make_unique<gl::stream_ubo>(sizeof(gl::model_ubs), 1, Global.gfx_bufferstreaming);
scene_ubo = std::make_unique<gl::ubo>(sizeof(gl::scene_ubs), gl::SCENE_UBO_BINDING);
model_ubo = std::make_unique<gl::stream_ubo>(sizeof(gl::model_ubs), gl::MODEL_UBO_BINDING, Global.gfx_bufferstreaming);
light_ssbo = std::make_unique<gl::ssbo>(sizeof(gl::light_data), gl::LIGHT_SSBO_BINDING);
light_ssbo->bind_base();
{
@@ -673,6 +673,8 @@ std::unique_ptr<gl::program> opengl46_renderer::make_shader(std::string v, std::
bool opengl46_renderer::Render()
{
// the texture names bound before may have been given to other textures since the last frame
opengl_texture::reset_unit_cache();
Timer::subsystem.gfx_total.start();
std::optional<int64_t> result = m_timequery->result();
@@ -3270,7 +3272,7 @@ bool opengl46_renderer::Blendable_Material(material_handle const Material)
return m_materials.blendable(Material);
}
std::string opengl46_renderer::Shader_Name(std::shared_ptr<gl::program> const &Program) const
std::string opengl46_renderer::Shader_Name(std::shared_ptr<gfx::shader_program> const &Program) const
{
for (auto const &shader : m_shaders)
if (shader.second == Program)
@@ -3278,7 +3280,7 @@ std::string opengl46_renderer::Shader_Name(std::shared_ptr<gl::program> const &P
return {};
}
std::shared_ptr<gl::program> opengl46_renderer::Fetch_Shader(const std::string &name)
std::shared_ptr<gfx::shader_program> opengl46_renderer::Fetch_Shader(const std::string &name)
{
auto it = m_shaders.find(name);
if (it == m_shaders.end())
@@ -3472,7 +3474,7 @@ void opengl46_renderer::Bind_Material( material_handle const Material, TSubModel
if (lastdiff)
glBindTextures(0, lastdiff, &opengl_texture::units[0]);
auto *program{material.shader.get()};
auto *program{static_cast<gl::program *>(material.shader.get())};
if (m_bindmultidraw)
{
// the replay of the instances drawn from the gpu culling with multi draws (Render_Instances_Indirect_Colour())
@@ -4887,7 +4889,7 @@ int opengl46_renderer::Prepass_Class( material_handle const Material )
if( Material == null_handle ) { return 0; }
auto const &material { m_materials.material( Material ) };
if( ( false == material.is_good ) || ( material.shader == nullptr ) ) { return 0; }
auto const lookup { m_prepassclasses.find( material.shader.get() ) };
auto const lookup { m_prepassclasses.find( static_cast<gl::program const *>( material.shader.get() ) ) };
if( lookup != m_prepassclasses.end() ) { return lookup->second; }
// names the programs are fetched by: mat_<name>.frag
@@ -4904,7 +4906,7 @@ int opengl46_renderer::Prepass_Class( material_handle const Material )
prepassclass = ( alphatested.count( shader.first ) > 0 ? 2 : solid.count( shader.first ) > 0 ? 1 : 0 );
break;
}
m_prepassclasses.emplace( material.shader.get(), prepassclass );
m_prepassclasses.emplace( static_cast<gl::program const *>( material.shader.get() ), prepassclass );
return prepassclass;
}
@@ -5678,7 +5680,7 @@ gl::program const *opengl46_renderer::Indirect_Program( material_handle Material
material = &m_materials.material( m_invalid_material );
if( false == material->is_good ) { return nullptr; }
}
return material->shader.get();
return static_cast<gl::program const *>( material->shader.get() );
}
gl::program *opengl46_renderer::Multidraw_Program( gl::program const *Program )
@@ -8867,7 +8869,11 @@ std::string const &opengl46_renderer::info_stats() const
void opengl46_renderer::MakeScreenshot()
{
screenshot_manager::make_screenshot();
// rows are tightly packed RGB: the renderer sets GL_PACK_ALIGNMENT to 1
auto img = new char[Global.fb_size.x * Global.fb_size.y * 3];
glReadPixels(0, 0, Global.fb_size.x, Global.fb_size.y, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid*)img);
//m7t: use pbo
screenshot_manager::save(img, Global.fb_size.x, Global.fb_size.y);
}
// model previews
@@ -10239,9 +10245,14 @@ void opengl46_renderer::opengl46_imgui_renderer::Render()
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
std::uint64_t opengl46_renderer::opengl46_imgui_renderer::Create_Image(std::uint8_t const *Rgba, int const Width, int const Height)
std::uint64_t opengl46_renderer::opengl46_imgui_renderer::Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
{
// the ui colours are made linear in its shaders, the image is too, by sampling it as srgb. the bindings the renderer
return Update_Image(0, Rgba, Width, Height, Options);
}
std::uint64_t opengl46_renderer::opengl46_imgui_renderer::Update_Image(std::uint64_t const Image, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
{
// the ui colours are made linear in its shaders, an srgb image is too, by sampling it as srgb. the bindings the renderer
// keeps track of are put back as they were
GLint texture{0}, unpack{0}, alignment{4};
glGetIntegerv(GL_TEXTURE_BINDING_2D, &texture);
@@ -10249,14 +10260,18 @@ std::uint64_t opengl46_renderer::opengl46_imgui_renderer::Create_Image(std::uint
glGetIntegerv(GL_UNPACK_ALIGNMENT, &alignment);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GLuint image{0};
glGenTextures(1, &image);
auto image{static_cast<GLuint>(Image)};
if (image == 0)
glGenTextures(1, &image);
glBindTexture(GL_TEXTURE_2D, image);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
auto const filter{Options.nearest ? GL_NEAREST : GL_LINEAR};
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, Options.mipmaps ? (Options.nearest ? GL_NEAREST_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR) : filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Rgba);
glTexImage2D(GL_TEXTURE_2D, 0, Options.srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Rgba);
if (Options.mipmaps)
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(texture));
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(unpack));
glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);

View File

@@ -117,8 +117,8 @@ class opengl46_renderer : public gfx_renderer {
void
Terrain_Grass( std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks ) override;
// shader methods
auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gl::program> override;
auto Shader_Name( std::shared_ptr<gl::program> const &Program ) const -> std::string override;
auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gfx::shader_program> override;
auto Shader_Name( std::shared_ptr<gfx::shader_program> const &Program ) const -> std::string override;
// texture methods
texture_handle
Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) override;
@@ -1178,7 +1178,9 @@ class opengl46_renderer : public gfx_renderer {
virtual void Shutdown() override;
virtual void BeginFrame() override;
virtual void Render() override;
virtual std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height) override;
using imgui_renderer::Create_Image;
virtual std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options) override;
virtual std::uint64_t Update_Image(std::uint64_t const Image, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options) override;
virtual void Release_Image(std::uint64_t const Image) override;
} m_imgui_renderer;

View File

@@ -92,8 +92,8 @@ class opengl46_terrain_materials
std::vector<slot> m_slots;
std::vector<std::size_t> m_free;
std::unordered_map<material_handle, std::size_t> m_slotof;
std::shared_ptr<gl::program> m_program; // of the chunks, mat_terrain
std::shared_ptr<gl::program> m_holesprogram; // of the chunks with holes, mat_terrain_holes
std::shared_ptr<gfx::shader_program> m_program; // of the chunks, mat_terrain
std::shared_ptr<gfx::shader_program> m_holesprogram; // of the chunks with holes, mat_terrain_holes
std::unique_ptr<gl::postfx> m_copy;
std::unique_ptr<gl::framebuffer> m_framebuffer;
};

View File

@@ -19,9 +19,9 @@ http://mozilla.org/MPL/2.0/.
struct lighting_data;
namespace gl
namespace gfx
{
class program;
class shader_program;
}
namespace gfx
@@ -132,9 +132,9 @@ public:
// again. chunks left out are dropped. backends which can't draw grass ignore it
virtual void Terrain_Grass( std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks ) {}
// shader methods
virtual auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gl::program> = 0;
virtual auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gfx::shader_program> = 0;
// the name the program was fetched by, empty where it wasn't fetched by Fetch_Shader()
virtual auto Shader_Name( std::shared_ptr<gl::program> const &Program ) const -> std::string { return {}; }
virtual auto Shader_Name( std::shared_ptr<gfx::shader_program> const &Program ) const -> std::string { return {}; }
// texture methods
virtual auto Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) -> texture_handle = 0;
virtual void Bind_Texture( texture_handle const Texture ) = 0;
@@ -228,16 +228,34 @@ private:
class imgui_renderer
{
public:
// how an image of the user interface is sampled
struct image_options
{
bool srgb{true}; // the colours are srgb, made linear by the sampling; plain values otherwise
bool nearest{false}; // the nearest texel instead of the linear filtering
bool mipmaps{false}; // mipmaps made of the image, for an image drawn smaller than it is
};
virtual bool Init() = 0;
virtual void Shutdown() = 0;
virtual void BeginFrame() = 0;
virtual void Render() = 0;
// images of the user interface itself (e.g. the previews of the node bank), apart from the textures of the scene: rgba, rows
// from the top, in srgb. returns: id for ImGui::Image, 0 if the renderer can't make them
virtual std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height)
// from the top. returns: id for ImGui::Image, 0 if the renderer can't make them
virtual std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
{
return 0;
}
std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height)
{
return Create_Image(Rgba, Width, Height, image_options{});
}
// the image given other content, of the same or another size (Image 0: a new one). returns: its id, which can be a new one
virtual std::uint64_t Update_Image(std::uint64_t const Image, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
{
Release_Image(Image);
return Create_Image(Rgba, Width, Height, Options);
}
virtual void Release_Image(std::uint64_t const Image) {}
};

View File

@@ -40,14 +40,9 @@ void screenshot_manager::screenshot_save_thread( char *img, int w, int h )
delete[] img;
}
void screenshot_manager::make_screenshot()
void screenshot_manager::save(char *Image, int Width, int Height)
{
// rows are tightly packed RGB: the renderer sets GL_PACK_ALIGNMENT to 1
auto img = new char[Global.fb_size.x * Global.fb_size.y * 3];
glReadPixels(0, 0, Global.fb_size.x, Global.fb_size.y, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid*)img);
//m7t: use pbo
std::thread t(screenshot_save_thread, img, Global.fb_size.x, Global.fb_size.y);
std::thread t(screenshot_save_thread, Image, Width, Height);
t.detach();
}

View File

@@ -3,5 +3,7 @@ class screenshot_manager
static void screenshot_save_thread(char *img , int w, int h);
public:
static void make_screenshot();
// writes an image of the window to a png file in the directory of the screenshots, on a thread of its own. Image: rgb rows,
// tightly packed, the bottom one first (taken over and deleted by the thread)
static void save(char *Image, int Width, int Height);
};

View File

@@ -0,0 +1,54 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the bindings of the shaders of the vulkan renderer, in one place for the c++ code and the shaders: the renderer puts them
// before the sources of shaders/vk14 as #define lines (vk14::binding_preamble()), and shaders/vk14/common.glsl writes the
// same values for the SPIR-V binaries, which glslc compiles without the renderer (the renderer checks they are the same).
//
// the descriptor sets (decision K6 of the plan): set 0 holds the resources of a pass, made when the renderer starts and bound
// with a dynamic offset in the ring of the frame; set 1 is pushed with the draws (vkCmdPushDescriptorSet2): the textures of
// the material and what changes between the draws. a layout can have only one set pushed, so the passes without a set of
// their own (the user interface, the compute passes) push set 0
#pragma once
#include <cstdint>
#include <string>
namespace vk14::bindings
{
// the user interface (imgui): one pushed set with the texture, the transform in the push constants
constexpr std::uint32_t UI_SET = 0;
constexpr std::uint32_t UI_TEXTURE = 0;
// the sets of the passes of the scene (from the stage D1 of the plan)
constexpr std::uint32_t PASS_SET = 0;
constexpr std::uint32_t MATERIAL_SET = 1;
// the textures of a material take the bindings of their units (#pragma texture(name, unit, ...)), 0 .. MAX_TEXTURES - 1
constexpr std::uint32_t MATERIAL_TEXTURE_FIRST = 0;
// the specialization constants of the shaders
// 1: the colour written to the window is encoded by the shader (a window without an srgb format, gfx.shadergamma yes)
constexpr std::uint32_t SPEC_OUTPUT_GAMMA = 0;
} // namespace vk14::bindings
namespace vk14
{
// the bindings above as #define lines, with MASZYNA_CONSTANTS, which tells shaders/vk14/common.glsl not to write its own
inline std::string binding_preamble()
{
auto const define = [](char const *Name, std::uint32_t const Value) { return std::string("#define ") + Name + " " + std::to_string(Value) + "\n"; };
return define("MASZYNA_CONSTANTS", 1) + define("UI_SET", bindings::UI_SET) + define("UI_TEXTURE", bindings::UI_TEXTURE) + define("PASS_SET", bindings::PASS_SET) + define("MATERIAL_SET", bindings::MATERIAL_SET) +
define("MATERIAL_TEXTURE_FIRST", bindings::MATERIAL_TEXTURE_FIRST) + define("SPEC_OUTPUT_GAMMA", bindings::SPEC_OUTPUT_GAMMA);
}
} // namespace vk14

View File

@@ -197,6 +197,8 @@ void query_capabilities(VkPhysicalDevice Physical, feature_chain &Queried, capab
Caps.driver_name = properties12.driverName;
Caps.driver_info = properties12.driverInfo;
Caps.timestamp_period = properties.properties.limits.timestampPeriod;
Caps.limits = properties.properties.limits;
std::copy(std::begin(properties.properties.pipelineCacheUUID), std::end(properties.properties.pipelineCacheUUID), Caps.pipeline_cache_uuid.begin());
VkPhysicalDeviceMemoryProperties memory{};
vkGetPhysicalDeviceMemoryProperties(Physical, &memory);
@@ -215,6 +217,17 @@ void query_capabilities(VkPhysicalDevice Physical, feature_chain &Queried, capab
return;
}
// the properties of 1.4, asked for once the driver is known to have them
{
VkPhysicalDeviceVulkan14Properties properties14{};
properties14.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_PROPERTIES;
VkPhysicalDeviceProperties2 properties2{};
properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
properties2.pNext = &properties14;
vkGetPhysicalDeviceProperties2(Physical, &properties2);
Caps.max_push_descriptors = properties14.maxPushDescriptors;
}
// features
Queried.reset();
vkGetPhysicalDeviceFeatures2(Physical, Queried.head());

View File

@@ -17,6 +17,8 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/vulkan14/vulkan14common.h"
#include <array>
namespace vk14
{
@@ -72,6 +74,11 @@ struct capabilities
std::uint32_t device_id{0};
VkDeviceSize device_local_memory{0};
float timestamp_period{0.f}; // nanoseconds per tick
// the limits of the device, the identity of its pipeline cache (VkPipelineCache data of another device or driver is
// thrown away) and the bindings a push descriptor set can have
VkPhysicalDeviceLimits limits{};
std::array<std::uint8_t, VK_UUID_SIZE> pipeline_cache_uuid{};
std::uint32_t max_push_descriptors{0};
// device extensions
bool ext_swapchain{false};

View File

@@ -127,6 +127,43 @@ bool verify_result(VkResult Result, char const *Call, char const *File, int Line
return false;
}
bool debug_utils_enabled{false};
void name_object(VkDevice Device, VkObjectType Type, std::uint64_t Handle, std::string const &Name)
{
if ((false == debug_utils_enabled) || (vkSetDebugUtilsObjectNameEXT == nullptr) || (Device == VK_NULL_HANDLE) || (Handle == 0) || Name.empty())
{
return;
}
VkDebugUtilsObjectNameInfoEXT info{};
info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
info.objectType = Type;
info.objectHandle = Handle;
info.pObjectName = Name.c_str();
vkSetDebugUtilsObjectNameEXT(Device, &info);
}
void begin_label(VkCommandBuffer Commands, char const *Name)
{
if ((false == debug_utils_enabled) || (vkCmdBeginDebugUtilsLabelEXT == nullptr))
{
return;
}
VkDebugUtilsLabelEXT label{};
label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
label.pLabelName = Name;
vkCmdBeginDebugUtilsLabelEXT(Commands, &label);
}
void end_label(VkCommandBuffer Commands)
{
if ((false == debug_utils_enabled) || (vkCmdEndDebugUtilsLabelEXT == nullptr))
{
return;
}
vkCmdEndDebugUtilsLabelEXT(Commands);
}
void log_info(std::string const &Message)
{
WriteLog("vulkan: " + Message);

View File

@@ -31,6 +31,26 @@ std::string memory_size_string(VkDeviceSize Bytes);
// logs a failed call and gives back false; true for VK_SUCCESS
bool verify_result(VkResult Result, char const *Call, char const *File, int Line);
// whether VK_EXT_debug_utils is on (set by vk14::instance): the names of the objects and the labels of the passes
extern bool debug_utils_enabled;
// the 64 bit value of a handle, for the names of the objects
template <typename Handle_> std::uint64_t handle_value(Handle_ Handle)
{
if constexpr (std::is_pointer_v<Handle_>)
{
return reinterpret_cast<std::uint64_t>(Handle);
}
else
{
return static_cast<std::uint64_t>(Handle);
}
}
// a name of an object for the messages of the validation layer and for captures; nothing without debug utils
void name_object(VkDevice Device, VkObjectType Type, std::uint64_t Handle, std::string const &Name);
// a label of a part of the commands (a pass), for captures and the messages of the validation layer
void begin_label(VkCommandBuffer Commands, char const *Name);
void end_label(VkCommandBuffer Commands);
// the log of the renderer, with the "vulkan: " prefix
void log_info(std::string const &Message);
void log_warning(std::string const &Message);

View File

@@ -0,0 +1,168 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "rendering/vulkan14/vulkan14device.h"
namespace vk14
{
// samplers
bool samplers::create(device const &Device, float Anisotropy, std::string &Reason)
{
auto const handle{Device.handle()};
auto const maxanisotropy{Device.caps().limits.maxSamplerAnisotropy};
auto const make = [&](VkSampler &Sampler, VkFilter Filter, VkSamplerAddressMode Address, bool Mipmaps, float Aniso, bool Compare, char const *Name) {
VkSamplerCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
info.magFilter = Filter;
info.minFilter = Filter;
info.mipmapMode = (Filter == VK_FILTER_LINEAR ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST);
info.addressModeU = Address;
info.addressModeV = Address;
info.addressModeW = Address;
info.maxLod = (Mipmaps ? VK_LOD_CLAMP_NONE : 0.f);
if (Aniso > 1.f)
{
info.anisotropyEnable = VK_TRUE;
info.maxAnisotropy = std::min(Aniso, maxanisotropy);
}
if (Compare)
{
// reverse z: the fragment is lit where it's nearer the light than the map, its depth greater
info.compareEnable = VK_TRUE;
info.compareOp = VK_COMPARE_OP_GREATER_OR_EQUAL;
info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
}
if (!VK14_VERIFY(vkCreateSampler(handle, &info, nullptr, &Sampler)))
{
Sampler = VK_NULL_HANDLE;
Reason = std::string("the sampler ") + Name + " can't be made";
return false;
}
name_object(handle, VK_OBJECT_TYPE_SAMPLER, handle_value(Sampler), Name);
return true;
};
return make(repeat_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_REPEAT, true, Anisotropy, false, "repeat aniso") && make(clamp_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, Anisotropy, false, "clamp aniso") &&
make(clamp_linear, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, 1.f, false, "clamp linear") && make(clamp_nearest, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, false, 1.f, false, "clamp nearest") &&
make(shadow, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, false, 1.f, true, "shadow");
}
void samplers::destroy(VkDevice Device)
{
for (auto *sampler : {&repeat_aniso, &clamp_aniso, &clamp_linear, &clamp_nearest, &shadow})
{
if (*sampler != VK_NULL_HANDLE)
{
vkDestroySampler(Device, *sampler, nullptr);
*sampler = VK_NULL_HANDLE;
}
}
}
// pushed sets
bool push_set::create(VkDevice Device, std::vector<VkDescriptorSetLayoutBinding> const &Bindings, std::uint32_t Set, VkPipelineBindPoint BindPoint, std::size_t Stride, std::string const &Name)
{
m_bindings = Bindings;
m_stride = Stride;
set = Set;
bind_point = BindPoint;
VkDescriptorSetLayoutCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
info.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT;
info.bindingCount = static_cast<std::uint32_t>(Bindings.size());
info.pBindings = Bindings.data();
if (!VK14_VERIFY(vkCreateDescriptorSetLayout(Device, &info, nullptr, &layout)))
{
layout = VK_NULL_HANDLE;
return false;
}
name_object(Device, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, handle_value(layout), Name);
return true;
}
bool push_set::create_template(VkDevice Device, VkPipelineLayout Layout, std::string const &Name)
{
std::vector<VkDescriptorUpdateTemplateEntry> entries;
for (std::size_t i = 0; i < m_bindings.size(); ++i)
{
VkDescriptorUpdateTemplateEntry entry{};
entry.dstBinding = m_bindings[i].binding;
entry.dstArrayElement = 0;
entry.descriptorCount = m_bindings[i].descriptorCount;
entry.descriptorType = m_bindings[i].descriptorType;
entry.offset = i * m_stride;
entry.stride = m_stride;
entries.push_back(entry);
}
VkDescriptorUpdateTemplateCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO;
info.descriptorUpdateEntryCount = static_cast<std::uint32_t>(entries.size());
info.pDescriptorUpdateEntries = entries.data();
info.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_PUSH_DESCRIPTORS;
info.descriptorSetLayout = layout;
info.pipelineBindPoint = bind_point;
info.pipelineLayout = Layout;
info.set = set;
if (!VK14_VERIFY(vkCreateDescriptorUpdateTemplate(Device, &info, nullptr, &update)))
{
update = VK_NULL_HANDLE;
return false;
}
name_object(Device, VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE, handle_value(update), Name);
return true;
}
void push_set::destroy(VkDevice Device)
{
if (update != VK_NULL_HANDLE)
{
vkDestroyDescriptorUpdateTemplate(Device, update, nullptr);
update = VK_NULL_HANDLE;
}
if (layout != VK_NULL_HANDLE)
{
vkDestroyDescriptorSetLayout(Device, layout, nullptr);
layout = VK_NULL_HANDLE;
}
}
void push_set::push(VkCommandBuffer Commands, VkPipelineLayout Layout, void const *Data) const
{
VkPushDescriptorSetWithTemplateInfo info{};
info.sType = VK_STRUCTURE_TYPE_PUSH_DESCRIPTOR_SET_WITH_TEMPLATE_INFO;
info.descriptorUpdateTemplate = update;
info.layout = Layout;
info.set = set;
info.pData = Data;
vkCmdPushDescriptorSetWithTemplate2(Commands, &info);
}
VkPipelineLayout make_pipeline_layout(VkDevice Device, std::vector<VkDescriptorSetLayout> const &Sets, std::vector<VkPushConstantRange> const &PushConstants, std::string const &Name)
{
VkPipelineLayoutCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
info.setLayoutCount = static_cast<std::uint32_t>(Sets.size());
info.pSetLayouts = Sets.data();
info.pushConstantRangeCount = static_cast<std::uint32_t>(PushConstants.size());
info.pPushConstantRanges = PushConstants.data();
VkPipelineLayout layout{VK_NULL_HANDLE};
if (!VK14_VERIFY(vkCreatePipelineLayout(Device, &info, nullptr, &layout)))
{
return VK_NULL_HANDLE;
}
name_object(Device, VK_OBJECT_TYPE_PIPELINE_LAYOUT, handle_value(layout), Name);
return layout;
}
} // namespace vk14

View File

@@ -0,0 +1,61 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the descriptors of the vulkan renderer without pools: the samplers every pass shares, the layouts of the pushed sets, and the
// templates the descriptors are pushed by with the draws (vkCmdPushDescriptorSetWithTemplate2). no descriptor is allocated or
// updated while a frame is made
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
namespace vk14
{
class device;
// the samplers of the renderer, made once
struct samplers
{
VkSampler repeat_aniso{VK_NULL_HANDLE}; // the textures of the scene: repeated, mipmapped, anisotropic (anisotropicfiltering)
VkSampler clamp_aniso{VK_NULL_HANDLE}; // the same clamped (textures with the s and t traits)
VkSampler clamp_linear{VK_NULL_HANDLE}; // images of the user interface, post-processing
VkSampler clamp_nearest{VK_NULL_HANDLE};
VkSampler shadow{VK_NULL_HANDLE}; // depth compared (shadow maps, reverse z)
bool create(device const &Device, float Anisotropy, std::string &Reason);
void destroy(VkDevice Device);
};
// a layout of a pushed set and its template
struct push_set
{
VkDescriptorSetLayout layout{VK_NULL_HANDLE};
VkDescriptorUpdateTemplate update{VK_NULL_HANDLE};
std::uint32_t set{0};
VkPipelineBindPoint bind_point{VK_PIPELINE_BIND_POINT_GRAPHICS};
// Bindings: one descriptor each, the data of the template is an array of VkDescriptorImageInfo / VkDescriptorBufferInfo in
// the order of the bindings (Stride bytes per binding)
bool create(VkDevice Device, std::vector<VkDescriptorSetLayoutBinding> const &Bindings, std::uint32_t Set, VkPipelineBindPoint BindPoint, std::size_t Stride, std::string const &Name);
// the template needs the pipeline layout the set is in
bool create_template(VkDevice Device, VkPipelineLayout Layout, std::string const &Name);
void destroy(VkDevice Device);
// pushes Data (laid out as create() was told)
void push(VkCommandBuffer Commands, VkPipelineLayout Layout, void const *Data) const;
private:
std::vector<VkDescriptorSetLayoutBinding> m_bindings;
std::size_t m_stride{0};
};
// a pipeline layout of the given sets and push constants
VkPipelineLayout make_pipeline_layout(VkDevice Device, std::vector<VkDescriptorSetLayout> const &Sets, std::vector<VkPushConstantRange> const &PushConstants, std::string const &Name);
} // namespace vk14

View File

@@ -281,6 +281,12 @@ bool device::create(instance const &Instance, std::string &Reason)
{
extensions.push_back("VK_KHR_portability_subset");
}
// the allocator keeps to the budget of the memory heaps where the driver tells it
m_memory_budget = m_capabilities.ext_memory_budget;
if (m_memory_budget)
{
extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
}
}
VkDeviceCreateInfo create{};

View File

@@ -71,6 +71,11 @@ class device
{
return m_graphics_queue;
}
// VK_EXT_memory_budget is on (the allocator keeps to the budget of the heaps)
bool memory_budget_enabled() const
{
return m_memory_budget;
}
private:
static queue_families find_queue_families(VkInstance Instance, VkPhysicalDevice Physical, VkSurfaceKHR Surface, std::uint32_t &TimestampBits);
@@ -82,6 +87,7 @@ class device
queue_families m_families;
feature_chain m_queried;
bool m_portability_subset{false};
bool m_memory_budget{false};
VkQueue m_graphics_queue{VK_NULL_HANDLE};
VkQueue m_compute_queue{VK_NULL_HANDLE};

View File

@@ -0,0 +1,341 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14imgui.h"
#include "rendering/vulkan14/vulkan14bindings.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "imgui/imgui.h"
#include <cstring>
namespace vk14
{
bool imgui_backend::create(resources const &Resources, shader_library &Shaders, pipeline_library &Pipelines, frame_ring &Ring, std::string &Reason)
{
m_resources = &Resources;
m_shaders = &Shaders;
m_pipelines = &Pipelines;
m_ring = &Ring;
auto const device{Resources.gpu->handle()};
VkDescriptorSetLayoutBinding texture{};
texture.binding = bindings::UI_TEXTURE;
texture.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
texture.descriptorCount = 1;
texture.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
if (false == m_set.create(device, {texture}, bindings::UI_SET, VK_PIPELINE_BIND_POINT_GRAPHICS, sizeof(VkDescriptorImageInfo), "ui texture"))
{
Reason = "no descriptor set layout for the user interface";
return false;
}
m_layout = make_pipeline_layout(device, {m_set.layout}, {VkPushConstantRange{VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float) * 4}}, "ui");
if ((m_layout == VK_NULL_HANDLE) || (false == m_set.create_template(device, m_layout, "ui texture")))
{
Reason = "no pipeline layout for the user interface";
return false;
}
try
{
m_vertex = Shaders.get("imgui.vert");
m_fragment = Shaders.get("imgui.frag");
}
catch (gfx::shader_exception const &e)
{
Reason = std::string("the shaders of the user interface: ") + e.what();
return false;
}
return true;
}
void imgui_backend::destroy()
{
if (m_resources == nullptr)
{
return;
}
auto const device{m_resources->gpu->handle()};
for (auto const image : m_images)
{
m_resources->images->remove(image, *m_resources->deletions, m_resources->uploads->current_value());
}
m_images.clear();
m_font = 0;
if (ImGui::GetCurrentContext() != nullptr)
{
// imgui outlives the renderer (ui_layer::shutdown() comes after it)
auto &io{ImGui::GetIO()};
io.Fonts->SetTexID(0);
io.BackendRendererName = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
}
m_set.destroy(device);
if (m_layout != VK_NULL_HANDLE)
{
vkDestroyPipelineLayout(device, m_layout, nullptr);
m_layout = VK_NULL_HANDLE;
}
m_vertex.reset();
m_fragment.reset();
m_drawdata = nullptr;
m_resources = nullptr;
}
bool imgui_backend::Init()
{
auto &io{ImGui::GetIO()};
io.BackendRendererName = "maszyna vulkan14";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
BeginFrame();
return (m_resources != nullptr);
}
void imgui_backend::Shutdown()
{
auto &io{ImGui::GetIO()};
if ((m_font != 0) && (m_resources != nullptr))
{
Release_Image(m_font);
m_font = 0;
}
io.Fonts->SetTexID(0);
io.BackendRendererName = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
m_drawdata = nullptr;
}
void imgui_backend::BeginFrame()
{
if (m_resources == nullptr)
{
return;
}
// the font, made again when imgui built it anew
auto &io{ImGui::GetIO()};
if ((m_font != 0) && io.Fonts->IsBuilt() && (io.Fonts->TexID == m_font))
{
return;
}
unsigned char *pixels{nullptr};
int width{0}, height{0};
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
image_options options;
// the alpha of the glyphs as it is, as the opengl renderer has it
options.srgb = false;
m_font = Update_Image(m_font, pixels, width, height, options);
io.Fonts->SetTexID(static_cast<ImTextureID>(m_font));
}
void imgui_backend::Render()
{
m_drawdata = ImGui::GetDrawData();
}
std::uint64_t imgui_backend::Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
{
return Update_Image(0, Rgba, Width, Height, Options);
}
std::uint64_t imgui_backend::Update_Image(std::uint64_t const Id, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
{
if ((m_resources == nullptr) || (Rgba == nullptr) || (Width <= 0) || (Height <= 0))
{
return Id;
}
// only the images made here are given new content; another id (released, or of a texture) gets a new image
auto const *current{(std::find(m_images.begin(), m_images.end(), Id) != m_images.end()) ? m_resources->images->find(Id) : nullptr};
auto const Image{current != nullptr ? Id : 0};
auto const format{Options.srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM};
auto const width{static_cast<std::uint32_t>(Width)}, height{static_cast<std::uint32_t>(Height)};
auto const mipmaps{Options.mipmaps && m_resources->uploads->can_blit(format)};
auto const sampler{Options.nearest ? m_resources->sampling->clamp_nearest : m_resources->sampling->clamp_linear};
std::shared_ptr<image> target;
if (current != nullptr)
{
auto const &desc{current->target->desc()};
if ((desc.extent.width == width) && (desc.extent.height == height) && (desc.format == format) && ((desc.mips > 1) == mipmaps))
{
// the same image given new content; the frames which drew it come first in the queue
target = current->target;
}
}
if (target == nullptr)
{
image_desc desc;
desc.format = format;
desc.extent = {width, height, 1};
desc.mips = (mipmaps ? static_cast<std::uint32_t>(std::floor(std::log2(std::max(width, height)))) + 1 : 1);
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (mipmaps ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0);
target = std::make_shared<image>();
if (false == target->create(*m_resources->memory, desc, "ui image"))
{
return Id;
}
}
if (false == m_resources->uploads->upload(target, Rgba, static_cast<VkDeviceSize>(width) * height * 4, {{0, {width, height, 1}}}, mipmaps))
{
return Id;
}
if (Image != 0)
{
if (current->target != target)
{
// the old image goes once the frames which drew it are done
m_resources->deletions->push(m_resources->uploads->current_value(), [old = current->target]() {});
}
m_resources->images->replace(Image, target, sampler);
return Image;
}
auto const id{m_resources->images->add(target, sampler)};
m_images.push_back(id);
return id;
}
void imgui_backend::Release_Image(std::uint64_t const Image)
{
if ((m_resources == nullptr) || (Image == 0))
{
return;
}
auto const lookup{std::find(m_images.begin(), m_images.end(), Image)};
if (lookup == m_images.end())
{
// not one of the images of the user interface
return;
}
m_images.erase(lookup);
m_resources->images->remove(Image, *m_resources->deletions, m_resources->uploads->current_value());
}
void imgui_backend::record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat Format, bool Srgb)
{
auto const *drawdata{m_drawdata};
m_drawdata = nullptr;
if ((drawdata == nullptr) || (drawdata->CmdListsCount == 0) || (drawdata->TotalVtxCount == 0) || (m_resources == nullptr))
{
return;
}
auto const scale{drawdata->FramebufferScale};
if ((drawdata->DisplaySize.x <= 0.f) || (drawdata->DisplaySize.y <= 0.f))
{
return;
}
pipeline_desc desc;
desc.stages = {m_vertex, m_fragment};
desc.layout = m_layout;
desc.vertices.bindings = {VkVertexInputBindingDescription{0, sizeof(ImDrawVert), VK_VERTEX_INPUT_RATE_VERTEX}};
desc.vertices.attributes = {VkVertexInputAttributeDescription{0, 0, VK_FORMAT_R32G32_SFLOAT, static_cast<std::uint32_t>(offsetof(ImDrawVert, pos))},
VkVertexInputAttributeDescription{1, 0, VK_FORMAT_R32G32_SFLOAT, static_cast<std::uint32_t>(offsetof(ImDrawVert, uv))},
VkVertexInputAttributeDescription{2, 0, VK_FORMAT_R8G8B8A8_UNORM, static_cast<std::uint32_t>(offsetof(ImDrawVert, col))}};
desc.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
desc.blending = blend_mode::alpha;
desc.colour_formats = {Format};
// the colour encoded by the shader where the image of the window doesn't do it
desc.specialization = {Srgb ? 0u : 1u};
desc.name = "ui";
auto const pipeline{m_pipelines->get(desc)};
if (pipeline == VK_NULL_HANDLE)
{
return;
}
// the vertices and the indices of the frame, through its ring
auto const vertexbytes{static_cast<VkDeviceSize>(drawdata->TotalVtxCount) * sizeof(ImDrawVert)};
auto const indexbytes{static_cast<VkDeviceSize>(drawdata->TotalIdxCount) * sizeof(ImDrawIdx)};
auto const vertices{m_ring->allocate(vertexbytes, sizeof(ImDrawVert))};
auto const indices{m_ring->allocate(indexbytes, 4)};
if (!vertices || !indices)
{
log_warning("the ring of the frame is too small for the user interface");
return;
}
auto *vertexdata{vertices.data};
auto *indexdata{indices.data};
for (int list = 0; list < drawdata->CmdListsCount; ++list)
{
auto const *commands{drawdata->CmdLists[list]};
std::memcpy(vertexdata, commands->VtxBuffer.Data, commands->VtxBuffer.Size * sizeof(ImDrawVert));
std::memcpy(indexdata, commands->IdxBuffer.Data, commands->IdxBuffer.Size * sizeof(ImDrawIdx));
vertexdata += commands->VtxBuffer.Size * sizeof(ImDrawVert);
indexdata += commands->IdxBuffer.Size * sizeof(ImDrawIdx);
}
vkCmdBindPipeline(Commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
set_draw_state(Commands, Extent);
vkCmdBindVertexBuffers(Commands, 0, 1, &vertices.buffer, &vertices.offset);
vkCmdBindIndexBuffer(Commands, indices.buffer, indices.offset, sizeof(ImDrawIdx) == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32);
// the pixels of the display (top left origin, y down) to the clip space of vulkan (y down too)
float transform[4];
transform[0] = 2.f / drawdata->DisplaySize.x;
transform[1] = 2.f / drawdata->DisplaySize.y;
transform[2] = -1.f - drawdata->DisplayPos.x * transform[0];
transform[3] = -1.f - drawdata->DisplayPos.y * transform[1];
VkPushConstantsInfo push{};
push.sType = VK_STRUCTURE_TYPE_PUSH_CONSTANTS_INFO;
push.layout = m_layout;
push.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
push.offset = 0;
push.size = sizeof(transform);
push.pValues = transform;
vkCmdPushConstants2(Commands, &push);
auto const clipoffset{drawdata->DisplayPos};
std::uint32_t vertexoffset{0}, indexoffset{0};
std::uint64_t pushed{~0ull};
for (int list = 0; list < drawdata->CmdListsCount; ++list)
{
auto const *commands{drawdata->CmdLists[list]};
for (int index = 0; index < commands->CmdBuffer.Size; ++index)
{
auto const &command{commands->CmdBuffer[index]};
if (command.UserCallback != nullptr)
{
// no callbacks in the user interface of the simulator; the one which resets the state is all there is to do
if (command.UserCallback != ImDrawCallback_ResetRenderState)
{
command.UserCallback(commands, &command);
}
continue;
}
// the clip rectangle in the pixels of the image
auto const minx{std::max((command.ClipRect.x - clipoffset.x) * scale.x, 0.f)};
auto const miny{std::max((command.ClipRect.y - clipoffset.y) * scale.y, 0.f)};
auto const maxx{std::min((command.ClipRect.z - clipoffset.x) * scale.x, static_cast<float>(Extent.width))};
auto const maxy{std::min((command.ClipRect.w - clipoffset.y) * scale.y, static_cast<float>(Extent.height))};
if ((maxx <= minx) || (maxy <= miny))
{
continue;
}
auto const *entry{m_resources->images->find(command.GetTexID())};
if (entry == nullptr)
{
// no image of that id (not made, or gone)
continue;
}
VkRect2D scissor{{static_cast<std::int32_t>(minx), static_cast<std::int32_t>(miny)}, {static_cast<std::uint32_t>(maxx - minx), static_cast<std::uint32_t>(maxy - miny)}};
vkCmdSetScissor(Commands, 0, 1, &scissor);
if (pushed != command.GetTexID())
{
VkDescriptorImageInfo info{entry->sampler, entry->target->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
m_set.push(Commands, m_layout, &info);
pushed = command.GetTexID();
}
vkCmdDrawIndexed(Commands, command.ElemCount, 1, command.IdxOffset + indexoffset, static_cast<std::int32_t>(command.VtxOffset + vertexoffset), 0);
}
vertexoffset += static_cast<std::uint32_t>(commands->VtxBuffer.Size);
indexoffset += static_cast<std::uint32_t>(commands->IdxBuffer.Size);
}
}
} // namespace vk14

View File

@@ -0,0 +1,75 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the user interface (imgui) drawn by the vulkan renderer (decision K10 of the plan): a backend of its own instead of
// imgui_impl_vulkan, which would need a pool of descriptors and a set per texture. the texture of a draw is pushed with it
// (push descriptors), the vertices and the indices go through the ring of the frame, and ImTextureID is the id of the image in
// the table of the renderer, the same for the textures (ITexture::get_id()), the images of the user interface (Create_Image())
// and the font. the colours are mixed as the user interface of the opengl renderer mixes them: in linear light, encoded by the
// srgb image of the window or, with gfx.shadergamma, by the shader
#pragma once
#include "rendering/renderer.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include "rendering/vulkan14/vulkan14texture.h"
struct ImDrawData;
namespace vk14
{
class imgui_backend : public imgui_renderer
{
public:
// what the backend draws with, from the renderer
bool create(resources const &Resources, shader_library &Shaders, pipeline_library &Pipelines, frame_ring &Ring, std::string &Reason);
void destroy();
// imgui_renderer
bool Init() override;
void Shutdown() override;
void BeginFrame() override;
void Render() override;
using imgui_renderer::Create_Image;
std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options) override;
std::uint64_t Update_Image(std::uint64_t const Image, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options) override;
void Release_Image(std::uint64_t const Image) override;
// whether imgui made a frame to draw since the last record()
bool has_frame() const
{
return m_drawdata != nullptr;
}
// records the frame of imgui into Commands, in a rendering to an image of Extent and Format begun by the renderer. Srgb: the
// image encodes the colour itself
void record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat Format, bool Srgb);
// the frame of imgui is dropped (nothing to draw it to)
void skip()
{
m_drawdata = nullptr;
}
private:
resources const *m_resources{nullptr};
shader_library *m_shaders{nullptr};
pipeline_library *m_pipelines{nullptr};
frame_ring *m_ring{nullptr};
push_set m_set;
VkPipelineLayout m_layout{VK_NULL_HANDLE};
std::shared_ptr<shader_code const> m_vertex;
std::shared_ptr<shader_code const> m_fragment;
std::uint64_t m_font{0};
ImDrawData *m_drawdata{nullptr};
// the images made by Create_Image()
std::vector<std::uint64_t> m_images;
};
} // namespace vk14

View File

@@ -249,6 +249,7 @@ bool instance::collect_extensions(std::vector<char const *> &Extensions, instanc
{
Extensions.clear();
m_debug_utils_enabled = false;
debug_utils_enabled = false;
std::uint32_t glfwcount{0};
auto const **glfwextensions{glfwGetRequiredInstanceExtensions(&glfwcount)};
@@ -281,6 +282,7 @@ bool instance::collect_extensions(std::vector<char const *> &Extensions, instanc
Extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
m_debug_utils_enabled = true;
debug_utils_enabled = true;
}
else
{
@@ -337,6 +339,7 @@ void instance::destroy()
}
m_validation_enabled = false;
m_debug_utils_enabled = false;
debug_utils_enabled = false;
}
} // namespace vk14

View File

@@ -0,0 +1,380 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include "rendering/vulkan14/vulkan14device.h"
namespace vk14
{
// allocator
allocator::~allocator()
{
destroy();
}
bool allocator::create(instance const &Instance, device const &Device, std::string &Reason)
{
VmaAllocatorCreateInfo info{};
info.vulkanApiVersion = VK_API_VERSION_1_4;
info.instance = Instance.handle();
info.physicalDevice = Device.physical();
info.device = Device.handle();
info.flags = VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT;
if (Device.memory_budget_enabled())
{
info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
}
// the functions as volk loaded them
VmaVulkanFunctions functions{};
if (!VK14_VERIFY(vmaImportVulkanFunctionsFromVolk(&info, &functions)))
{
Reason = "the functions of the memory allocator can't be loaded";
return false;
}
info.pVulkanFunctions = &functions;
if (!VK14_VERIFY(vmaCreateAllocator(&info, &m_allocator)))
{
m_allocator = VK_NULL_HANDLE;
Reason = "the memory allocator can't be made";
return false;
}
m_device = Device.handle();
VkPhysicalDeviceMemoryProperties memory{};
vkGetPhysicalDeviceMemoryProperties(Device.physical(), &memory);
m_heapcount = memory.memoryHeapCount;
m_deviceheaps.assign(m_heapcount, false);
for (std::uint32_t i = 0; i < m_heapcount; ++i)
{
m_deviceheaps[i] = ((memory.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0);
}
return true;
}
void allocator::destroy()
{
if (m_allocator != VK_NULL_HANDLE)
{
vmaDestroyAllocator(m_allocator);
m_allocator = VK_NULL_HANDLE;
}
m_device = VK_NULL_HANDLE;
}
void allocator::budget(VkDeviceSize &Usage, VkDeviceSize &Budget) const
{
Usage = 0;
Budget = 0;
if (m_allocator == VK_NULL_HANDLE)
{
return;
}
std::vector<VmaBudget> budgets(m_heapcount);
vmaGetHeapBudgets(m_allocator, budgets.data());
for (std::uint32_t i = 0; i < m_heapcount; ++i)
{
if (m_deviceheaps[i])
{
Usage += budgets[i].usage;
Budget += budgets[i].budget;
}
}
}
// buffer
buffer::buffer(buffer &&Other) noexcept
{
*this = std::move(Other);
}
buffer &buffer::operator=(buffer &&Other) noexcept
{
if (this != &Other)
{
destroy();
std::swap(m_allocator, Other.m_allocator);
std::swap(m_buffer, Other.m_buffer);
std::swap(m_allocation, Other.m_allocation);
std::swap(m_size, Other.m_size);
std::swap(m_mapped, Other.m_mapped);
}
return *this;
}
buffer::~buffer()
{
destroy();
}
bool buffer::create(allocator const &Allocator, VkDeviceSize Size, VkBufferUsageFlags Usage, memory_kind Kind, std::string const &Name)
{
destroy();
VkBufferCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
info.size = std::max<VkDeviceSize>(Size, 4);
info.usage = Usage;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VmaAllocationCreateInfo allocation{};
allocation.usage = VMA_MEMORY_USAGE_AUTO;
switch (Kind)
{
case memory_kind::device:
allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
break;
case memory_kind::upload:
allocation.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
break;
case memory_kind::readback:
allocation.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
break;
}
VmaAllocationInfo allocated{};
if (!VK14_VERIFY(vmaCreateBuffer(Allocator.handle(), &info, &allocation, &m_buffer, &m_allocation, &allocated)))
{
m_buffer = VK_NULL_HANDLE;
m_allocation = VK_NULL_HANDLE;
return false;
}
m_allocator = Allocator.handle();
m_size = Size;
m_mapped = static_cast<std::uint8_t *>(allocated.pMappedData);
name_object(Allocator.device_handle(), VK_OBJECT_TYPE_BUFFER, handle_value(m_buffer), Name);
return true;
}
void buffer::destroy()
{
if (m_buffer != VK_NULL_HANDLE)
{
vmaDestroyBuffer(m_allocator, m_buffer, m_allocation);
}
m_buffer = VK_NULL_HANDLE;
m_allocation = VK_NULL_HANDLE;
m_size = 0;
m_mapped = nullptr;
}
void buffer::flush(VkDeviceSize Offset, VkDeviceSize Size) const
{
if ((m_allocation != VK_NULL_HANDLE) && (Size > 0))
{
// vma does nothing for coherent memory
vmaFlushAllocation(m_allocator, m_allocation, Offset, Size);
}
}
// image
image::~image()
{
destroy();
}
VkImageAspectFlags image::aspect() const
{
switch (m_desc.format)
{
case VK_FORMAT_D16_UNORM:
case VK_FORMAT_D32_SFLOAT:
case VK_FORMAT_X8_D24_UNORM_PACK32:
return VK_IMAGE_ASPECT_DEPTH_BIT;
case VK_FORMAT_D24_UNORM_S8_UINT:
case VK_FORMAT_D32_SFLOAT_S8_UINT:
case VK_FORMAT_D16_UNORM_S8_UINT:
return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
default:
break;
}
return VK_IMAGE_ASPECT_COLOR_BIT;
}
bool image::create(allocator const &Allocator, image_desc const &Desc, std::string const &Name)
{
destroy();
m_desc = Desc;
VkImageCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
info.flags = Desc.flags;
info.imageType = Desc.type;
info.format = Desc.format;
info.extent = Desc.extent;
info.mipLevels = std::max(Desc.mips, 1u);
info.arrayLayers = std::max(Desc.layers, 1u);
info.samples = Desc.samples;
info.tiling = VK_IMAGE_TILING_OPTIMAL;
info.usage = Desc.usage;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VmaAllocationCreateInfo allocation{};
allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
VmaAllocationInfo allocated{};
if (!VK14_VERIFY(vmaCreateImage(Allocator.handle(), &info, &allocation, &m_image, &m_allocation, &allocated)))
{
m_image = VK_NULL_HANDLE;
m_allocation = VK_NULL_HANDLE;
return false;
}
m_allocator = Allocator.handle();
m_device = Allocator.device_handle();
m_bytes = allocated.size;
VkImageViewCreateInfo viewinfo{};
viewinfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewinfo.image = m_image;
viewinfo.viewType = Desc.view_type;
viewinfo.format = Desc.format;
viewinfo.components = Desc.components;
viewinfo.subresourceRange = {aspect(), 0, info.mipLevels, 0, info.arrayLayers};
if (!VK14_VERIFY(vkCreateImageView(m_device, &viewinfo, nullptr, &m_view)))
{
m_view = VK_NULL_HANDLE;
destroy();
return false;
}
state = image_state{};
name_object(m_device, VK_OBJECT_TYPE_IMAGE, handle_value(m_image), Name);
name_object(m_device, VK_OBJECT_TYPE_IMAGE_VIEW, handle_value(m_view), Name);
return true;
}
void image::destroy()
{
if (m_view != VK_NULL_HANDLE)
{
vkDestroyImageView(m_device, m_view, nullptr);
}
if (m_image != VK_NULL_HANDLE)
{
vmaDestroyImage(m_allocator, m_image, m_allocation);
}
m_view = VK_NULL_HANDLE;
m_image = VK_NULL_HANDLE;
m_allocation = VK_NULL_HANDLE;
m_bytes = 0;
state = image_state{};
}
void transition(VkCommandBuffer Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
{
auto const writes{VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT |
VK_ACCESS_2_HOST_WRITE_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
auto const &from{Image.state};
// reads after reads in the same layout need nothing
if ((from.layout == Layout) && ((from.access & writes) == 0) && ((Access & writes) == 0))
{
Image.state.stage |= Stage;
Image.state.access |= Access;
return;
}
VkImageMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
barrier.srcStageMask = (from.stage != VK_PIPELINE_STAGE_2_NONE ? from.stage : VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT);
barrier.srcAccessMask = (from.access & writes);
barrier.dstStageMask = Stage;
barrier.dstAccessMask = Access;
barrier.oldLayout = from.layout;
barrier.newLayout = Layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = Image.handle();
barrier.subresourceRange = {Image.aspect(), 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS};
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = 1;
dependency.pImageMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(Commands, &dependency);
Image.state = image_state{Layout, Stage, Access};
}
// deletion queue
void deletion_queue::push(std::uint64_t Value, std::function<void()> &&Deleter)
{
m_entries.emplace_back(Value, std::move(Deleter));
}
void deletion_queue::collect(std::uint64_t Completed)
{
// the values only grow, so the queue is in their order
while ((false == m_entries.empty()) && (m_entries.front().first <= Completed))
{
auto deleter{std::move(m_entries.front().second)};
m_entries.pop_front();
deleter();
}
}
void deletion_queue::flush()
{
while (false == m_entries.empty())
{
auto deleter{std::move(m_entries.front().second)};
m_entries.pop_front();
deleter();
}
}
// frame ring
bool frame_ring::create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name)
{
m_sizeperframe = SizePerFrame;
m_base = 0;
m_used = 0;
return m_buffer.create(Allocator, SizePerFrame * Frames, Usage, memory_kind::upload, Name) && (m_buffer.mapped() != nullptr);
}
void frame_ring::destroy()
{
m_buffer.destroy();
m_sizeperframe = 0;
m_used = 0;
}
void frame_ring::begin_frame(std::uint32_t Frame)
{
m_base = m_sizeperframe * Frame;
m_used = 0;
}
ring_slice frame_ring::allocate(VkDeviceSize Size, VkDeviceSize Alignment)
{
auto const alignment{std::max<VkDeviceSize>(Alignment, 4)};
auto const offset{(m_used + alignment - 1) / alignment * alignment};
if ((m_buffer.mapped() == nullptr) || (offset + Size > m_sizeperframe))
{
return {};
}
m_used = offset + Size;
ring_slice slice;
slice.buffer = m_buffer.handle();
slice.offset = m_base + offset;
slice.size = Size;
slice.data = m_buffer.mapped() + slice.offset;
return slice;
}
void frame_ring::flush() const
{
m_buffer.flush(m_base, m_used);
}
} // namespace vk14

View File

@@ -0,0 +1,241 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the memory of the vulkan renderer: the allocator (VMA), buffers and images which give their memory back when they go, the
// ring each frame in flight streams its data through, the uploads of data to the gpu through staging buffers, and the queue
// of objects deleted once the gpu is done with the frames which used them (by the value of the timeline semaphore)
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
#include <deque>
#include <functional>
#include <vk_mem_alloc.h>
namespace vk14
{
class instance;
class device;
// the allocator of the device memory
class allocator
{
public:
allocator() = default;
allocator(allocator const &) = delete;
allocator &operator=(allocator const &) = delete;
~allocator();
bool create(instance const &Instance, device const &Device, std::string &Reason);
void destroy();
VmaAllocator handle() const
{
return m_allocator;
}
VkDevice device_handle() const
{
return m_device;
}
// device memory used and the budget of the heaps which are local to the device, bytes
void budget(VkDeviceSize &Usage, VkDeviceSize &Budget) const;
private:
VmaAllocator m_allocator{VK_NULL_HANDLE};
VkDevice m_device{VK_NULL_HANDLE};
std::uint32_t m_heapcount{0};
std::vector<bool> m_deviceheaps;
};
// where the memory of a buffer is
enum class memory_kind
{
device, // device local, filled by copies
upload, // seen by the cpu, written in order (staging, streamed data); device local where the device has such memory
readback // seen by the cpu, cached, for the data read back from the gpu
};
class buffer
{
public:
buffer() = default;
buffer(buffer const &) = delete;
buffer &operator=(buffer const &) = delete;
buffer(buffer &&Other) noexcept;
buffer &operator=(buffer &&Other) noexcept;
~buffer();
bool create(allocator const &Allocator, VkDeviceSize Size, VkBufferUsageFlags Usage, memory_kind Kind, std::string const &Name = {});
void destroy();
VkBuffer handle() const
{
return m_buffer;
}
VkDeviceSize size() const
{
return m_size;
}
// the memory as the cpu sees it, null for memory the cpu doesn't see. the upload and readback memory stays mapped
std::uint8_t *mapped() const
{
return m_mapped;
}
// makes the writes of the cpu seen by the gpu where the memory isn't coherent
void flush(VkDeviceSize Offset, VkDeviceSize Size) const;
explicit operator bool() const
{
return m_buffer != VK_NULL_HANDLE;
}
private:
VmaAllocator m_allocator{VK_NULL_HANDLE};
VkBuffer m_buffer{VK_NULL_HANDLE};
VmaAllocation m_allocation{VK_NULL_HANDLE};
VkDeviceSize m_size{0};
std::uint8_t *m_mapped{nullptr};
};
// the state of an image the barriers start from: its layout and the last stage and access which used it
struct image_state
{
VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED};
VkPipelineStageFlags2 stage{VK_PIPELINE_STAGE_2_NONE};
VkAccessFlags2 access{VK_ACCESS_2_NONE};
};
struct image_desc
{
VkImageType type{VK_IMAGE_TYPE_2D};
VkImageViewType view_type{VK_IMAGE_VIEW_TYPE_2D};
VkFormat format{VK_FORMAT_R8G8B8A8_UNORM};
VkExtent3D extent{1, 1, 1};
std::uint32_t mips{1};
std::uint32_t layers{1};
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
VkImageUsageFlags usage{VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT};
VkImageCreateFlags flags{0};
// the channels the default view reads, e.g. a single channel texture seen as rrr1
VkComponentMapping components{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
};
class image
{
public:
image() = default;
image(image const &) = delete;
image &operator=(image const &) = delete;
~image();
// a device local image with a view of all its levels and layers
bool create(allocator const &Allocator, image_desc const &Desc, std::string const &Name = {});
void destroy();
VkImage handle() const
{
return m_image;
}
VkImageView view() const
{
return m_view;
}
image_desc const &desc() const
{
return m_desc;
}
VkImageAspectFlags aspect() const;
VkDeviceSize bytes() const
{
return m_bytes;
}
// the state the next barrier starts from (the pass list and the uploads keep it)
image_state state{};
explicit operator bool() const
{
return m_image != VK_NULL_HANDLE;
}
private:
VmaAllocator m_allocator{VK_NULL_HANDLE};
VkDevice m_device{VK_NULL_HANDLE};
VkImage m_image{VK_NULL_HANDLE};
VkImageView m_view{VK_NULL_HANDLE};
VmaAllocation m_allocation{VK_NULL_HANDLE};
VkDeviceSize m_bytes{0};
image_desc m_desc;
};
// a barrier of an image from its state to the given one, the state of the image updated. nothing when the image already is
// in the layout and was only read by the same stage
void transition(VkCommandBuffer Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access);
// objects destroyed once the gpu is done with the frame which used them last
class deletion_queue
{
public:
// Value: the value of the timeline semaphore the gpu reaches when it's done with the object
void push(std::uint64_t Value, std::function<void()> &&Deleter);
// runs the deleters of the values the gpu reached
void collect(std::uint64_t Completed);
// runs all of them (the device is idle)
void flush();
std::size_t size() const
{
return m_entries.size();
}
private:
std::deque<std::pair<std::uint64_t, std::function<void()>>> m_entries;
};
// a part of the ring of a frame: where the data goes on the cpu, and where the gpu reads it
struct ring_slice
{
std::uint8_t *data{nullptr};
VkBuffer buffer{VK_NULL_HANDLE};
VkDeviceSize offset{0};
VkDeviceSize size{0};
explicit operator bool() const
{
return data != nullptr;
}
};
// the data streamed by the frames: one mapped buffer split into a part for each frame in flight, the part of a frame taken
// from the start again once the gpu is done with the frame which used it before (the counterpart of gl::frame_stream)
class frame_ring
{
public:
bool create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name);
void destroy();
// the part of the frame Frame is used from its start again
void begin_frame(std::uint32_t Frame);
// Size bytes aligned to Alignment; empty when the part of the frame is full
ring_slice allocate(VkDeviceSize Size, VkDeviceSize Alignment);
// makes the writes of the frame seen by the gpu, where the memory isn't coherent
void flush() const;
VkDeviceSize used() const
{
return m_used;
}
VkDeviceSize capacity() const
{
return m_sizeperframe;
}
private:
buffer m_buffer;
VkDeviceSize m_sizeperframe{0};
VkDeviceSize m_base{0};
VkDeviceSize m_used{0};
};
} // namespace vk14

View File

@@ -0,0 +1,237 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14passes.h"
#include "rendering/vulkan14/vulkan14device.h"
namespace vk14
{
image_use use_of(image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
{
return image_use{Image.handle(), Image.aspect(), &Image.state, Layout, Stage, Access};
}
void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses)
{
auto const writes{VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT |
VK_ACCESS_2_HOST_WRITE_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
std::vector<VkImageMemoryBarrier2> list;
for (auto const &use : Uses)
{
if ((use.image == VK_NULL_HANDLE) || (use.state == nullptr))
{
continue;
}
auto &state{*use.state};
// reads after reads in the same layout need nothing
if ((state.layout == use.layout) && ((state.access & writes) == 0) && ((use.access & writes) == 0))
{
state.stage |= use.stage;
state.access |= use.access;
continue;
}
VkImageMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
barrier.srcStageMask = (state.stage != VK_PIPELINE_STAGE_2_NONE ? state.stage : VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT);
barrier.srcAccessMask = (state.access & writes);
barrier.dstStageMask = use.stage;
barrier.dstAccessMask = use.access;
barrier.oldLayout = state.layout;
barrier.newLayout = use.layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = use.image;
barrier.subresourceRange = {use.aspect, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS};
list.push_back(barrier);
state = image_state{use.layout, use.stage, use.access};
}
if (list.empty())
{
return;
}
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = static_cast<std::uint32_t>(list.size());
dependency.pImageMemoryBarriers = list.data();
vkCmdPipelineBarrier2(Commands, &dependency);
}
pass_list::~pass_list()
{
destroy();
}
bool pass_list::create(device const &Device, std::uint32_t Frames, std::string &Reason)
{
m_device = Device.handle();
m_period = Device.caps().timestamp_period;
// the bits of the timestamps of the queue the frames go to
std::uint32_t count{0};
vkGetPhysicalDeviceQueueFamilyProperties(Device.physical(), &count, nullptr);
std::vector<VkQueueFamilyProperties> families(count);
vkGetPhysicalDeviceQueueFamilyProperties(Device.physical(), &count, families.data());
auto const bits{(Device.families().graphics < count) ? families[Device.families().graphics].timestampValidBits : 64u};
m_mask = (bits >= 64 ? ~0ull : ((1ull << bits) - 1));
m_frames.resize(Frames);
for (std::uint32_t i = 0; i < Frames; ++i)
{
VkQueryPoolCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
info.queryType = VK_QUERY_TYPE_TIMESTAMP;
info.queryCount = max_passes * 2;
if (!VK14_VERIFY(vkCreateQueryPool(m_device, &info, nullptr, &m_frames[i].pool)))
{
m_frames[i].pool = VK_NULL_HANDLE;
Reason = "no query pool for the times of the passes";
return false;
}
// the queries are reset from the host before their first use (hostQueryReset)
vkResetQueryPool(m_device, m_frames[i].pool, 0, info.queryCount);
name_object(m_device, VK_OBJECT_TYPE_QUERY_POOL, handle_value(m_frames[i].pool), "pass times " + std::to_string(i));
}
return true;
}
void pass_list::destroy()
{
for (auto &frame : m_frames)
{
if (frame.pool != VK_NULL_HANDLE)
{
vkDestroyQueryPool(m_device, frame.pool, nullptr);
}
}
m_frames.clear();
m_device = VK_NULL_HANDLE;
}
void pass_list::begin_frame(VkCommandBuffer Commands, std::uint32_t Frame, std::uint64_t Number)
{
m_commands = Commands;
m_frame = Frame;
m_open = -1;
auto &frame{m_frames[Frame]};
frame.names.clear();
frame.used = 0;
frame.number = Number;
frame.recorded = true;
vkCmdResetQueryPool(Commands, frame.pool, 0, max_passes * 2);
}
void pass_list::begin_pass(char const *Name, std::vector<image_use> const &Uses)
{
if (m_open >= 0)
{
end_pass();
}
barriers(m_commands, Uses);
begin_label(m_commands, Name);
auto &frame{m_frames[m_frame]};
// (no timestamps on a queue without valid bits; the device is chosen with them, this is for safety)
if ((frame.used < max_passes) && (m_mask != 0))
{
vkCmdWriteTimestamp2(m_commands, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, frame.pool, frame.used * 2);
frame.names.emplace_back(Name);
m_open = static_cast<int>(frame.used);
++frame.used;
}
else
{
m_open = static_cast<int>(max_passes);
}
}
void pass_list::end_pass()
{
if (m_open < 0)
{
return;
}
if (m_open < static_cast<int>(max_passes))
{
vkCmdWriteTimestamp2(m_commands, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, m_frames[m_frame].pool, static_cast<std::uint32_t>(m_open) * 2 + 1);
}
end_label(m_commands);
m_open = -1;
}
void pass_list::end_frame()
{
end_pass();
m_commands = VK_NULL_HANDLE;
}
void pass_list::collect(std::uint32_t Frame)
{
auto &frame{m_frames[Frame]};
if ((false == frame.recorded) || (frame.used == 0))
{
return;
}
frame.recorded = false;
std::vector<std::uint64_t> stamps(frame.used * 2);
auto const result{vkGetQueryPoolResults(m_device, frame.pool, 0, frame.used * 2, stamps.size() * sizeof(std::uint64_t), stamps.data(), sizeof(std::uint64_t), VK_QUERY_RESULT_64_BIT)};
if (result != VK_SUCCESS)
{
return;
}
// a running average, so the numbers can be read
auto const blend = [](double &Average, double const Value) { Average = (Average <= 0.0 ? Value : Average * 0.9 + Value * 0.1); };
double total{0.0};
for (std::uint32_t i = 0; i < frame.used; ++i)
{
auto const ticks{((stamps[i * 2 + 1] & m_mask) - (stamps[i * 2] & m_mask)) & m_mask};
auto const ms{static_cast<double>(ticks) * m_period / 1000000.0};
total += ms;
auto lookup{std::find_if(m_times.begin(), m_times.end(), [&](auto const &Entry) { return Entry.first == frame.names[i]; })};
if (lookup == m_times.end())
{
m_times.emplace_back(frame.names[i], 0.0);
lookup = std::prev(m_times.end());
}
blend(lookup->second, ms);
}
blend(m_framems, total);
}
std::string pass_list::passes_of(std::uint64_t Number) const
{
std::string result;
for (auto const &frame : m_frames)
{
if (frame.number != Number)
{
continue;
}
for (auto const &name : frame.names)
{
result += (result.empty() ? "" : ", ") + name;
}
}
return (result.empty() ? std::string("(not known)") : result);
}
std::string pass_list::info() const
{
std::string text;
char buffer[64] = {};
for (auto const &[name, ms] : m_times)
{
std::snprintf(buffer, sizeof(buffer), " %-14s %7.3f ms\n", name.c_str(), ms);
text += buffer;
}
std::snprintf(buffer, sizeof(buffer), " %-14s %7.3f ms\n", "total", m_framems);
text += buffer;
return text;
}
} // namespace vk14

View File

@@ -0,0 +1,96 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the passes of a frame (decision K12 of the plan): a list in the fixed order the frame makes them, each pass saying which images
// it uses and how. the barriers between the passes (sync2) are worked out from the state each image was left in, there's no
// graph. each pass is a label for the captures and the validation layer (debug utils), and is timed on the gpu: the times
// of the passes of the frames done go to the debug panel under the same names as the parts of the gpu frame of the opengl
// renderer. when the device is lost, the log tells which passes of which frame were on the gpu
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14memory.h"
namespace vk14
{
class device;
// an image a pass uses, and how
struct image_use
{
VkImage image{VK_NULL_HANDLE};
VkImageAspectFlags aspect{VK_IMAGE_ASPECT_COLOR_BIT};
// where the state of the image is kept: by the image itself, or by the swapchain for its images
image_state *state{nullptr};
VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED};
VkPipelineStageFlags2 stage{VK_PIPELINE_STAGE_2_NONE};
VkAccessFlags2 access{VK_ACCESS_2_NONE};
};
// the use of a vk14::image
image_use use_of(image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access);
// the barriers which bring the images from their states to the uses, the states updated
void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses);
class pass_list
{
public:
pass_list() = default;
pass_list(pass_list const &) = delete;
pass_list &operator=(pass_list const &) = delete;
~pass_list();
bool create(device const &Device, std::uint32_t Frames, std::string &Reason);
void destroy();
// the frame Frame (index of the frame in flight) is recorded to Commands from now on; Number counts the frames
void begin_frame(VkCommandBuffer Commands, std::uint32_t Frame, std::uint64_t Number);
// a pass: the barriers of its images, its label and the timestamp of its start
void begin_pass(char const *Name, std::vector<image_use> const &Uses = {});
void end_pass();
void end_frame();
// the gpu times of the frame Frame, once the gpu is done with it (after the wait for its value of the timeline)
void collect(std::uint32_t Frame);
// the passes recorded in the frame done last, and the ones of the frame Number, for the report of a lost device
std::string passes_of(std::uint64_t Number) const;
// the gpu times (ms) of the parts of the frame, averaged, as the opengl renderer gives them: "name: x.xx ms" lines
std::string info() const;
double frame_ms() const
{
return m_framems;
}
private:
struct frame_queries
{
VkQueryPool pool{VK_NULL_HANDLE};
std::vector<std::string> names;
std::uint32_t used{0};
std::uint64_t number{0};
bool recorded{false};
};
static constexpr std::uint32_t max_passes{32};
VkDevice m_device{VK_NULL_HANDLE};
float m_period{1.f}; // ns per tick
std::uint64_t m_mask{~0ull}; // the valid bits of the timestamps
std::vector<frame_queries> m_frames;
VkCommandBuffer m_commands{VK_NULL_HANDLE};
std::uint32_t m_frame{0};
int m_open{-1};
// averaged times of the parts, ms
std::vector<std::pair<std::string, double>> m_times;
double m_framems{0.0};
};
} // namespace vk14

View File

@@ -13,20 +13,33 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/vulkan14/vulkan14instance.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "rendering/vulkan14/vulkan14swapchain.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "rendering/vulkan14/vulkan14passes.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include "rendering/vulkan14/vulkan14imgui.h"
#include "rendering/vulkan14/vulkan14bindings.h"
#include "rendering/nullrenderer.h"
#include "application/application.h"
#include "simulation/simulation.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/Timer.h"
#include "utilities/crashreporter.h"
#include "utilities/utilities.h"
#include "imgui/imgui.h"
namespace
{
// the colour the windows are cleared to, the initial background of the opengl renderer (srgb)
// the colour the windows are cleared to once a scenery is loaded, until the scenery is drawn (stage D3 of the plan): the initial
// background of the opengl renderer (srgb). before that the windows are black, as with the opengl renderer
glm::vec3 const clear_colour{51.f / 255.f, 102.f / 255.f, 85.f / 255.f};
// the ring each frame in flight streams its data through (the user interface for now)
VkDeviceSize const ring_size{8 * 1024 * 1024};
float srgb_to_linear(float const Value)
{
return (Value <= 0.04045f ? Value / 12.92f : std::pow((Value + 0.055f) / 1.055f, 2.4f));
@@ -40,13 +53,31 @@ struct vulkan14_renderer::context
struct frame
{
VkCommandPool pool{VK_NULL_HANDLE};
// the uploads of the frame go to the queue first, then the commands of the frame
VkCommandBuffer uploads{VK_NULL_HANDLE};
VkCommandBuffer commands{VK_NULL_HANDLE};
// the value of the timeline semaphore the gpu reaches when it's done with the frame; 0: never submitted
std::uint64_t done{0};
// the number of the frame, for the report of a lost device
std::uint64_t number{0};
};
vk14::instance instance;
vk14::device device;
vk14::allocator memory;
vk14::deletion_queue deletions;
vk14::uploader uploads;
vk14::image_table images;
vk14::samplers sampling;
vk14::frame_ring ring;
vk14::shader_library shaders;
vk14::pipeline_library pipelines;
vk14::pass_list passes;
vk14::pending_textures pending;
vk14::resources resources;
vk14::imgui_backend imgui;
// the textures of the files; made after the objects above and gone before them
std::unique_ptr<texture_bank> textures;
// [0]: the main window, then the windows of the extra viewports
std::vector<std::unique_ptr<vk14::swapchain>> windows;
std::vector<frame> frames;
@@ -94,21 +125,71 @@ struct vulkan14_renderer::context
Reason = "no command pool";
return false;
}
VkCommandBuffer buffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
VkCommandBufferAllocateInfo bufferinfo{};
bufferinfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
bufferinfo.commandPool = frames[i].pool;
bufferinfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
bufferinfo.commandBufferCount = 1;
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, &frames[i].commands)))
bufferinfo.commandBufferCount = 2;
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, buffers)))
{
Reason = "no command buffer";
return false;
}
frames[i].uploads = buffers[0];
frames[i].commands = buffers[1];
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].uploads), "uploads " + std::to_string(i));
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].commands), "frame " + std::to_string(i));
}
return true;
}
// the parts of the renderer made once the device is there
bool create_core(std::uint32_t const Frames, std::string &Reason)
{
if (false == memory.create(instance, device, Reason))
{
return false;
}
uploads.init(memory, deletions, device.physical());
if (false == sampling.create(device, static_cast<float>(Global.AnisotropicFiltering), Reason))
{
return false;
}
auto const &limits{device.caps().limits};
(void)limits;
if (false == ring.create(memory, ring_size, Frames, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, "frame ring"))
{
Reason = "no ring for the data of the frames";
return false;
}
if ((false == shaders.init(vk14::binding_preamble(), Reason)) || (false == pipelines.init(device, Reason)) || (false == passes.create(device, Frames, Reason)))
{
return false;
}
resources.gpu = &device;
resources.memory = &memory;
resources.uploads = &uploads;
resources.deletions = &deletions;
resources.sampling = &sampling;
resources.images = &images;
resources.thread = std::this_thread::get_id();
resources.pending = &pending;
textures = std::make_unique<texture_bank>([this]() -> texture_source * { return new vk14::vulkan14_texture(resources); });
return imgui.create(resources, shaders, pipelines, ring, Reason);
}
// the value of the timeline the gpu reached, 0 when it can't be read
std::uint64_t completed() const
{
std::uint64_t value{0};
if ((timeline == VK_NULL_HANDLE) || (vkGetSemaphoreCounterValue(device.handle(), timeline, &value) != VK_SUCCESS))
{
return 0;
}
return value;
}
void destroy()
{
auto const handle{device.handle()};
@@ -119,8 +200,20 @@ struct vulkan14_renderer::context
}
acquired.clear();
windows.clear();
imgui.destroy();
// the textures give their images to the deletion queue, which goes now that the gpu is idle
textures.reset();
uploads.end_frame();
uploads = vk14::uploader{};
images.clear();
deletions.flush();
pipelines.shutdown();
shaders.shutdown();
passes.destroy();
ring.destroy();
if (handle != VK_NULL_HANDLE)
{
sampling.destroy(handle);
for (auto &frame : frames)
{
if (frame.pool != VK_NULL_HANDLE)
@@ -133,6 +226,7 @@ struct vulkan14_renderer::context
vkDestroySemaphore(handle, timeline, nullptr);
}
}
memory.destroy();
frames.clear();
timeline = VK_NULL_HANDLE;
device.destroy();
@@ -211,7 +305,8 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
config.debug_utils = (Global.gfx_vulkan_validation > 0);
auto const framesinflight{static_cast<std::uint32_t>(std::clamp(Global.gfx_vulkan_framesinflight, 2, 3))};
auto ok{ctx.instance.create(config, reason) && ctx.device.select(ctx.instance.handle(), VK_NULL_HANDLE, Global.gfx_vulkan_device, false, reason) && ctx.device.create(ctx.instance, reason) && ctx.create_frames(framesinflight, reason)};
auto ok{ctx.instance.create(config, reason) && ctx.device.select(ctx.instance.handle(), VK_NULL_HANDLE, Global.gfx_vulkan_device, false, reason) && ctx.device.create(ctx.instance, reason) && ctx.create_frames(framesinflight, reason) &&
ctx.create_core(framesinflight, reason)};
if (ok)
{
auto mainwindow{std::make_unique<vk14::swapchain>()};
@@ -227,7 +322,7 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
vk14::log_info(std::to_string(framesinflight) + " frames in flight");
// until the stages of the plan bring them, these parts of the simulator aren't there with vulkan
vk14::log_info("not available with vulkan yet: the scenery and the vehicles (D1-D7), the user interface (R5), picking, mouse position in the scene and screenshots (D6), "
vk14::log_info("not available with vulkan yet: the scenery and the vehicles (D1-D7), picking, mouse position in the scene and screenshots (D6), the map panel, "
"python screens in their own windows (N5), panels in their own windows (R5b), VR (X4)");
WriteLog("Gfx Renderer: setup complete");
update_info();
@@ -271,6 +366,27 @@ void vulkan14_renderer::Shutdown()
m_context.reset();
}
void vulkan14_renderer::report_device_lost(char const *Where)
{
auto &ctx{*m_context};
if (ctx.devicelost)
{
return;
}
ctx.devicelost = true;
auto const done{ctx.completed()};
std::string report{std::string("the device was lost (") + Where + "); the gpu was done with the timeline value " + std::to_string(done) + " of " + std::to_string(ctx.timelinevalue)};
for (auto const &frame : ctx.frames)
{
if ((frame.done > done) && (frame.done <= ctx.timelinevalue))
{
report += "; frame " + std::to_string(frame.number) + " (value " + std::to_string(frame.done) + ") wasn't done, its passes: " + ctx.passes.passes_of(frame.number);
}
}
vk14::log_error(report);
crashreport_add_info("vulkan_device_lost", report);
}
bool vulkan14_renderer::Render()
{
if (!m_context || m_context->devicelost)
@@ -294,11 +410,35 @@ bool vulkan14_renderer::Render()
wait.pValues = &frame.done;
if (!VK14_VERIFY(vkWaitSemaphores(device, &wait, UINT64_MAX)))
{
ctx.devicelost = true;
report_device_lost("waiting for a frame");
return false;
}
ctx.passes.collect(slot);
}
// what the frames done used last goes
ctx.deletions.collect(std::max(ctx.completed(), frame.done));
VK14_VERIFY(vkResetCommandPool(device, frame.pool, 0));
ctx.ring.begin_frame(slot);
VkCommandBufferBeginInfo begin{};
begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
VK14_VERIFY(vkBeginCommandBuffer(frame.uploads, &begin));
VK14_VERIFY(vkBeginCommandBuffer(frame.commands, &begin));
auto const value{ctx.timelinevalue + 1};
ctx.uploads.begin_frame(frame.uploads, value);
// the content other threads gave the textures (python)
{
std::vector<vk14::vulkan14_texture *> pending;
{
std::lock_guard<std::mutex> lock(ctx.pending.mutex);
pending.swap(ctx.pending.textures);
}
for (auto *texture : pending)
{
texture->apply_pending();
}
}
ctx.acquired.clear();
for (auto &window : ctx.windows)
@@ -311,26 +451,61 @@ bool vulkan14_renderer::Render()
case vk14::swapchain::acquire_result::skipped:
break;
default:
ctx.devicelost = true;
report_device_lost("acquiring an image");
return false;
}
}
// the frames of the user interface go on (its input works too), though nothing of it is drawn yet
// the user interface: imgui makes its frame, the images it asks for are uploaded in this frame
Timer::subsystem.gfx_gui.start();
Application.render_ui();
Timer::subsystem.gfx_gui.stop();
VkCommandBufferBeginInfo begin{};
begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
VK14_VERIFY(vkBeginCommandBuffer(frame.commands, &begin));
frame.number = ctx.framecount;
ctx.passes.begin_frame(frame.commands, slot, ctx.framecount);
auto const background{simulation::is_ready ? clear_colour : glm::vec3{0.f}};
bool uidrawn{false};
for (auto *window : ctx.acquired)
{
auto const colour{window->srgb() ? glm::vec3{srgb_to_linear(clear_colour.r), srgb_to_linear(clear_colour.g), srgb_to_linear(clear_colour.b)} : clear_colour};
window->record_clear(frame.commands, glm::vec4{colour, 1.f});
auto const colour{window->srgb() ? glm::vec3{srgb_to_linear(background.r), srgb_to_linear(background.g), srgb_to_linear(background.b)} : background};
if (window != ctx.windows.front().get())
{
// the extra viewports: their colour only, until the scenery is drawn into them (D6)
window->record_clear(frame.commands, glm::vec4{colour, 1.f});
continue;
}
// the main window: cleared, the user interface over it, shown
ctx.passes.begin_pass("ui", {window->use(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT)});
VkRenderingAttachmentInfo attachment{};
attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
attachment.imageView = window->view();
attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.clearValue.color = {{colour.r, colour.g, colour.b, 1.f}};
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, window->extent()};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &attachment;
vkCmdBeginRendering(frame.commands, &rendering);
ctx.imgui.record(frame.commands, window->extent(), window->format(), window->srgb());
vkCmdEndRendering(frame.commands);
ctx.passes.end_pass();
vk14::barriers(frame.commands, {window->use(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE)});
uidrawn = true;
}
if (false == uidrawn)
{
// the main window is minimized: the frame of imgui has nowhere to go
ctx.imgui.skip();
}
ctx.passes.end_frame();
auto const uploaded{ctx.uploads.end_frame()};
VK14_VERIFY(vkEndCommandBuffer(frame.uploads));
VK14_VERIFY(vkEndCommandBuffer(frame.commands));
ctx.ring.flush();
std::vector<VkSemaphoreSubmitInfo> waits;
std::vector<VkSemaphoreSubmitInfo> signals;
@@ -351,28 +526,40 @@ bool vulkan14_renderer::Render()
VkSemaphoreSubmitInfo timelinesignal{};
timelinesignal.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
timelinesignal.semaphore = ctx.timeline;
timelinesignal.value = ctx.timelinevalue + 1;
timelinesignal.value = value;
timelinesignal.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
signals.push_back(timelinesignal);
VkCommandBufferSubmitInfo commands{};
commands.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
commands.commandBuffer = frame.commands;
// the uploads first, in the same submit: their barriers make them done before the frame samples the images
std::vector<VkCommandBufferSubmitInfo> commands;
if (uploaded)
{
VkCommandBufferSubmitInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
info.commandBuffer = frame.uploads;
commands.push_back(info);
}
{
VkCommandBufferSubmitInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
info.commandBuffer = frame.commands;
commands.push_back(info);
}
VkSubmitInfo2 submit{};
submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
submit.waitSemaphoreInfoCount = static_cast<std::uint32_t>(waits.size());
submit.pWaitSemaphoreInfos = waits.data();
submit.commandBufferInfoCount = 1;
submit.pCommandBufferInfos = &commands;
submit.commandBufferInfoCount = static_cast<std::uint32_t>(commands.size());
submit.pCommandBufferInfos = commands.data();
submit.signalSemaphoreInfoCount = static_cast<std::uint32_t>(signals.size());
submit.pSignalSemaphoreInfos = signals.data();
if (!VK14_VERIFY(vkQueueSubmit2(ctx.device.graphics_queue(), 1, &submit, VK_NULL_HANDLE)))
{
ctx.devicelost = true;
report_device_lost("submitting a frame");
return false;
}
frame.done = ++ctx.timelinevalue;
frame.done = ctx.timelinevalue = value;
Timer::subsystem.gfx_total.stop();
return true;
@@ -415,7 +602,7 @@ void vulkan14_renderer::SwapBuffers()
if ((result == VK_ERROR_DEVICE_LOST) || (result == VK_ERROR_SURFACE_LOST_KHR))
{
vk14::log_error(std::string("vkQueuePresentKHR failed with ") + vk14::result_string(result));
ctx.devicelost = true;
report_device_lost("presenting");
}
ctx.acquired.clear();
}
@@ -475,7 +662,7 @@ gfx::userdata_array const &vulkan14_renderer::UserData(gfx::geometry_handle cons
return m_geometry.userdata(Geometry);
}
// materials, shaders, textures: none until D1 and D2
// materials and their shaders: none until D1
material_handle vulkan14_renderer::Fetch_Material(std::string const &Filename, bool const Loadnow)
{
@@ -489,28 +676,44 @@ IMaterial const *vulkan14_renderer::Material(material_handle const Material) con
return IMaterial::null_material();
}
std::shared_ptr<gl::program> vulkan14_renderer::Fetch_Shader(std::string const &name)
std::shared_ptr<gfx::shader_program> vulkan14_renderer::Fetch_Shader(std::string const &name)
{
return nullptr;
}
// textures: the files read and decoded as for the opengl renderer, made into images when they are first used (create(), e.g. by the
// launcher); the materials of the scene take them from D1
texture_handle vulkan14_renderer::Fetch_Texture(std::string const &Filename, bool const Loadnow, GLint format_hint)
{
return null_handle;
if (!m_context || !m_context->textures)
{
return null_handle;
}
return m_context->textures->create(Filename, Loadnow, format_hint);
}
// the shaders take the textures by the pushed descriptors (D1), there's nothing to bind
void vulkan14_renderer::Bind_Texture(texture_handle const Texture) {}
void vulkan14_renderer::Bind_Texture(std::size_t const Unit, texture_handle const Texture) {}
ITexture &vulkan14_renderer::Texture(texture_handle const Texture)
{
return *ITexture::null_texture();
if ((Texture == null_handle) || !m_context || !m_context->textures)
{
return *ITexture::null_texture();
}
return m_context->textures->mark_as_used(Texture);
}
ITexture const &vulkan14_renderer::Texture(texture_handle const Texture) const
{
return *ITexture::null_texture();
if ((Texture == null_handle) || !m_context || !m_context->textures)
{
return *ITexture::null_texture();
}
return m_context->textures->texture(Texture);
}
// picking: D6
@@ -538,6 +741,11 @@ glm::dvec3 vulkan14_renderer::Mouse_Position() const
void vulkan14_renderer::Update(double const Deltatime)
{
if (m_context && m_context->textures && (true == Global.ResourceSweep) && (true == simulation::is_ready))
{
// textures nothing used for a while give their images back
m_context->textures->update();
}
m_updateaccumulator += Deltatime;
if (m_updateaccumulator < 1.0)
{
@@ -559,7 +767,13 @@ void vulkan14_renderer::update_info()
auto const &ctx{*m_context};
auto const &caps{ctx.device.caps()};
m_infotimes = "vulkan " + vk14::version_string(caps.api_version) + ": " + caps.device_name + " (" + caps.driver_name + ")\n" + "frames: " + std::to_string(ctx.framecount) + ", in flight " + std::to_string(ctx.frames.size()) + "\n" +
"mainloop total: " + to_string(Timer::subsystem.mainloop_total.average(), 2) + " ms, frame cpu: " + to_string(Timer::subsystem.gfx_total.average(), 2) + " ms, present: " + to_string(Timer::subsystem.gfx_swap.average(), 2) + " ms\n";
"mainloop total: " + to_string(Timer::subsystem.mainloop_total.average(), 2) + " ms, frame cpu: " + to_string(Timer::subsystem.gfx_total.average(), 2) + " ms, ui: " + to_string(Timer::subsystem.gfx_gui.average(), 2) +
" ms, present: " + to_string(Timer::subsystem.gfx_swap.average(), 2) + " ms\n" + "gpu frame parts:\n" + ctx.passes.info();
VkDeviceSize usage{0}, budget{0};
ctx.memory.budget(usage, budget);
m_infostats = "device memory: " + vk14::memory_size_string(usage) + " of " + vk14::memory_size_string(budget) + (ctx.device.memory_budget_enabled() ? "" : " (no budget from the driver)") + "\n" + "images: " + std::to_string(ctx.images.size()) +
", uploaded " + vk14::memory_size_string(ctx.uploads.uploaded()) + ", waiting for the gpu to be deleted: " + std::to_string(ctx.deletions.size()) + "\n" + (ctx.textures ? ctx.textures->info() + "\n" : std::string{}) + ctx.shaders.info() +
"\n" + ctx.pipelines.info() + "\n" + "frame ring: " + vk14::memory_size_string(ctx.ring.used()) + " of " + vk14::memory_size_string(ctx.ring.capacity()) + "\n";
for (auto const &window : ctx.windows)
{
auto const extent{window->extent()};
@@ -597,32 +811,10 @@ std::string const &vulkan14_renderer::info_stats() const
imgui_renderer *vulkan14_renderer::GetImguiRenderer()
{
return &m_imgui;
return (m_context ? &m_context->imgui : nullptr);
}
void vulkan14_renderer::MakeScreenshot()
{
vk14::log_warning("screenshots aren't made by the vulkan renderer yet (D6)");
}
// imgui: the font atlas is built so its frames go on; nothing is drawn until R5
bool vulkan14_renderer::imgui_stub::Init()
{
auto &io{ImGui::GetIO()};
io.BackendRendererName = "vulkan14 (no drawing yet)";
unsigned char *pixels{nullptr};
int width{0}, height{0};
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
io.Fonts->SetTexID(static_cast<ImTextureID>(0));
return true;
}
void vulkan14_renderer::imgui_stub::Shutdown()
{
ImGui::GetIO().BackendRendererName = nullptr;
}
void vulkan14_renderer::imgui_stub::BeginFrame() {}
void vulkan14_renderer::imgui_stub::Render() {}

View File

@@ -7,10 +7,11 @@ obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the vulkan 1.4 renderer, picked with gfxrenderer vulkan. so far the frame of the renderer only: the gpu, the windows with
// their swapchains, the frames in flight; each window is cleared to a colour. the scenery, the materials and the textures
// come in the later stages of the plan (claude/vulkan14-renderer-plan.md); until then those methods keep the data the
// simulation reads back and draw nothing.
// the vulkan 1.4 renderer, picked with gfxrenderer vulkan. so far the core of the renderer (the track R of the plan,
// claude/vulkan14-renderer-plan.md): the gpu, the windows with their swapchains, the frames in flight, the memory and the
// uploads, the shaders and the pipelines, the pushed descriptors, the passes with their barriers and gpu times, the textures
// and the user interface (imgui) with the launcher. the scenery and the materials come in the later stages of the plan; until
// then those methods keep the data the simulation reads back and draw nothing.
//
// the vulkan headers stay in the files of rendering/vulkan14: the objects of the api live in vulkan14_renderer::context
@@ -46,7 +47,7 @@ class vulkan14_renderer : public gfx_renderer
void Bind_Material(material_handle const Material, TSubModel const *sm = nullptr, lighting_data const *lighting = nullptr) override;
IMaterial const *Material(material_handle const Material) const override;
// shaders
std::shared_ptr<gl::program> Fetch_Shader(std::string const &name) override;
std::shared_ptr<gfx::shader_program> Fetch_Shader(std::string const &name) override;
// textures
texture_handle Fetch_Texture(std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA) override;
void Bind_Texture(texture_handle const Texture) override;
@@ -80,22 +81,13 @@ class vulkan14_renderer : public gfx_renderer
// the objects of the vulkan api (vulkan14renderer.cpp)
struct context;
// imgui without drawing: the frames of the ui go on (its input works), nothing is drawn until the stage R5 of the plan
class imgui_stub : public imgui_renderer
{
public:
bool Init() override;
void Shutdown() override;
void BeginFrame() override;
void Render() override;
};
void update_info();
// the device was lost: what was on the gpu goes to the log
void report_device_lost(char const *Where);
std::unique_ptr<context> m_context;
// the data of the geometry, which the simulation reads back; nothing on the gpu yet
gfx::geometrybank_manager m_geometry;
imgui_stub m_imgui;
float m_framerate{0.f};
double m_updateaccumulator{0.0};
bool m_debug_ui_active{false};

View File

@@ -0,0 +1,705 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/utilities.h"
#include <glslang/Public/ResourceLimits.h>
#include <glslang/Public/ShaderLang.h>
#include <SPIRV/GlslangToSpv.h>
#include <chrono>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <sstream>
namespace vk14
{
namespace
{
EShLanguage language_of(gfx::shader_stage const Stage)
{
switch (Stage)
{
case gfx::shader_stage::vertex:
return EShLangVertex;
case gfx::shader_stage::fragment:
return EShLangFragment;
case gfx::shader_stage::geometry:
return EShLangGeometry;
case gfx::shader_stage::compute:
return EShLangCompute;
}
return EShLangVertex;
}
VkShaderStageFlagBits stage_bit(gfx::shader_stage const Stage)
{
switch (Stage)
{
case gfx::shader_stage::vertex:
return VK_SHADER_STAGE_VERTEX_BIT;
case gfx::shader_stage::fragment:
return VK_SHADER_STAGE_FRAGMENT_BIT;
case gfx::shader_stage::geometry:
return VK_SHADER_STAGE_GEOMETRY_BIT;
case gfx::shader_stage::compute:
return VK_SHADER_STAGE_COMPUTE_BIT;
}
return VK_SHADER_STAGE_VERTEX_BIT;
}
std::string files_of(gfx::shader_source const &Source)
{
std::string result;
for (std::size_t i = 0; i < Source.files.size(); ++i)
{
result += (i > 0 ? ", " : "") + std::to_string(i) + ": " + Source.files[i];
}
return result;
}
std::vector<std::uint32_t> read_spirv(std::string const &Path)
{
std::ifstream file(Path, std::ios::binary | std::ios::ate);
if (false == file.is_open())
{
return {};
}
auto const size{static_cast<std::size_t>(file.tellg())};
if ((size < 20) || (size % 4 != 0))
{
return {};
}
std::vector<std::uint32_t> words(size / 4);
file.seekg(0);
file.read(reinterpret_cast<char *>(words.data()), static_cast<std::streamsize>(size));
if ((false == static_cast<bool>(file)) || (words[0] != 0x07230203u))
{
return {};
}
return words;
}
// written beside and moved into place, so a program reading the cache at the same time doesn't get half of the file
void write_file(std::string const &Path, void const *Data, std::size_t const Size)
{
std::error_code error;
std::filesystem::create_directories(std::filesystem::path(Path).parent_path(), error);
auto const temporary{Path + ".tmp"};
{
std::ofstream file(temporary, std::ios::binary | std::ios::trunc);
if (false == file.is_open())
{
return;
}
file.write(static_cast<char const *>(Data), static_cast<std::streamsize>(Size));
if (false == static_cast<bool>(file))
{
return;
}
}
std::filesystem::rename(temporary, Path, error);
if (error)
{
std::filesystem::remove(temporary, error);
}
}
std::string hex(std::uint64_t const Value)
{
char buffer[17] = {};
std::snprintf(buffer, sizeof(buffer), "%016llx", static_cast<unsigned long long>(Value));
return buffer;
}
// fnv-1a 64 over more data
void hash_bytes(std::uint64_t &Hash, void const *Data, std::size_t const Size)
{
auto const *bytes{static_cast<std::uint8_t const *>(Data)};
for (std::size_t i = 0; i < Size; ++i)
{
Hash ^= bytes[i];
Hash *= 1099511628211ull;
}
}
template <typename Type_> void hash_value(std::uint64_t &Hash, Type_ const &Value)
{
hash_bytes(Hash, &Value, sizeof(Value));
}
std::string user_cache_dir(std::string const &Name)
{
// with a separator at the end
auto const path{user_config_path("cache")};
auto const name{Name.empty() ? std::string{} : Name + "/"};
if (path.empty())
{
return "cache/vulkan14/" + name;
}
return (path / "vulkan14").string() + "/" + name;
}
} // namespace
// shader library
shader_library::~shader_library()
{
shutdown();
}
bool shader_library::init(std::string const &Preamble, std::string &Reason)
{
m_preamble = Preamble;
if (false == glslang::InitializeProcess())
{
Reason = "the shader compiler (glslang) can't start";
return false;
}
m_glslang = true;
auto const version{glslang::GetVersion()};
m_compilerversion = "glslang " + std::to_string(version.major) + "." + std::to_string(version.minor) + "." + std::to_string(version.patch) + " vulkan1.4 spirv1.6";
m_cachedir = user_cache_dir("shaders");
m_binaries = Global.gfx_shader_binaries;
if (m_binaries)
{
// the binaries are compiled with the constants written in the files, the sources get those of the renderer
std::vector<std::string> differences;
if (false == gfx::shader_constants_match(gfx::shader_api::vulkan14, "common.glsl", Preamble, {}, differences))
{
std::string text;
for (auto const &difference : differences)
{
text += "\n " + difference;
}
ErrorLog("vulkan: the constants in " + gfx::shader_source_dir(gfx::shader_api::vulkan14) + "common.glsl aren't those of the renderer, so the SPIR-V binaries of " +
gfx::shader_binary_dir(gfx::shader_api::vulkan14) + " can't be used; the shaders are compiled from the sources:" + text,
logtype::shader);
m_binaries = false;
}
}
log_info("shaders: " + std::string(m_binaries ? "the SPIR-V binaries of " + gfx::shader_binary_dir(gfx::shader_api::vulkan14) + " first, then " : "") + "the cache in " + m_cachedir + ", then " + m_compilerversion);
return true;
}
void shader_library::shutdown()
{
m_shaders.clear();
if (m_glslang)
{
glslang::FinalizeProcess();
m_glslang = false;
}
}
std::shared_ptr<shader_code const> shader_library::get(std::string const &Name, std::string const &Defines)
{
auto const key{Name + "|" + Defines};
auto const lookup{m_shaders.find(key)};
if (lookup != m_shaders.end())
{
return lookup->second;
}
auto code{std::make_shared<shader_code>()};
code->source = gfx::preprocess_shader(gfx::shader_api::vulkan14, Name, Defines, m_preamble, true);
code->stage = stage_bit(code->source.stage);
auto const label{Name + (code->source.variant.empty() ? "" : " (" + code->source.variant + ")")};
if (m_binaries)
{
std::string reason;
code->spirv = gfx::load_shader_binary(code->source, reason);
if (false == reason.empty())
{
log_info("shader " + label + ": " + reason + ", compiled from the source");
}
if (false == code->spirv.empty())
{
code->origin = "binary";
++m_frombinary;
}
}
if (code->spirv.empty())
{
if (false == code->source.has_source)
{
throw gfx::shader_exception("cannot read shader: " + gfx::shader_source_dir(gfx::shader_api::vulkan14) + Name);
}
std::uint64_t hash{14695981039346656037ull};
hash_bytes(hash, code->source.text.data(), code->source.text.size());
hash_bytes(hash, m_compilerversion.data(), m_compilerversion.size());
auto const cachepath{m_cachedir + Name + (code->source.variant.empty() ? "" : "." + code->source.variant) + "." + hex(hash) + ".spv"};
code->spirv = read_spirv(cachepath);
if (false == code->spirv.empty())
{
code->origin = "cache";
++m_fromcache;
}
else
{
code->spirv = compile(code->source);
code->origin = "compiled";
++m_compiled;
write_file(cachepath, code->spirv.data(), code->spirv.size() * sizeof(std::uint32_t));
}
}
// the text isn't needed any more
code->source.text = std::string();
m_shaders.emplace(key, code);
return code;
}
std::vector<std::uint32_t> shader_library::compile(gfx::shader_source const &Source) const
{
auto const language{language_of(Source.stage)};
glslang::TShader shader(language);
char const *text{Source.text.c_str()};
char const *name{Source.name.c_str()};
shader.setStringsWithLengthsAndNames(&text, nullptr, &name, 1);
shader.setEnvInput(glslang::EShSourceGlsl, language, glslang::EShClientVulkan, 100);
shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_4);
shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_6);
auto const messages{static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules)};
if (false == shader.parse(GetDefaultResources(), 460, false, messages))
{
auto const info{std::string(shader.getInfoLog())};
ErrorLog("bad shader: " + Source.name + ": " + info + "(source strings: " + files_of(Source) + ")", logtype::shader);
throw gfx::shader_exception("failed to compile " + Source.name + ": " + info);
}
glslang::TProgram program;
program.addShader(&shader);
if (false == program.link(messages))
{
auto const info{std::string(program.getInfoLog())};
ErrorLog("bad shader: " + Source.name + ": " + info, logtype::shader);
throw gfx::shader_exception("failed to link " + Source.name + ": " + info);
}
std::vector<std::uint32_t> spirv;
glslang::SpvOptions options;
options.validate = false;
glslang::GlslangToSpv(*program.getIntermediate(language), spirv, &options);
if (spirv.empty())
{
throw gfx::shader_exception("no SPIR-V made of " + Source.name);
}
return spirv;
}
std::string shader_library::info() const
{
return "shaders: " + std::to_string(m_shaders.size()) + " (" + std::to_string(m_frombinary) + " binaries, " + std::to_string(m_fromcache) + " from the cache, " + std::to_string(m_compiled) + " compiled)";
}
// pipeline library
pipeline_library::~pipeline_library()
{
shutdown();
}
bool pipeline_library::init(device const &Device, std::string &Reason)
{
m_device = &Device;
auto const cachedir{user_cache_dir("")};
m_cachepath = cachedir + "pipelines.bin";
// the cache of another device or driver is no good; vulkan would ignore it, but it would be written over anyway
std::vector<char> data;
{
std::ifstream file(m_cachepath, std::ios::binary | std::ios::ate);
if (file.is_open())
{
data.resize(static_cast<std::size_t>(file.tellg()));
file.seekg(0);
file.read(data.data(), static_cast<std::streamsize>(data.size()));
if (false == static_cast<bool>(file))
{
data.clear();
}
}
}
if (false == data.empty())
{
VkPipelineCacheHeaderVersionOne header{};
auto const &caps{Device.caps()};
bool valid{data.size() >= sizeof(header)};
if (valid)
{
std::memcpy(&header, data.data(), sizeof(header));
valid = (header.headerSize >= sizeof(header)) && (header.headerVersion == VK_PIPELINE_CACHE_HEADER_VERSION_ONE) && (header.vendorID == caps.vendor_id) && (header.deviceID == caps.device_id) &&
(std::memcmp(header.pipelineCacheUUID, caps.pipeline_cache_uuid.data(), VK_UUID_SIZE) == 0);
}
if (false == valid)
{
log_info("the pipeline cache " + m_cachepath + " comes from another device or driver, made anew");
data.clear();
}
}
VkPipelineCacheCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
info.initialDataSize = data.size();
info.pInitialData = (data.empty() ? nullptr : data.data());
if (!VK14_VERIFY(vkCreatePipelineCache(Device.handle(), &info, nullptr, &m_cache)))
{
// without the old data, then
info.initialDataSize = 0;
info.pInitialData = nullptr;
if (!VK14_VERIFY(vkCreatePipelineCache(Device.handle(), &info, nullptr, &m_cache)))
{
m_cache = VK_NULL_HANDLE;
Reason = "no pipeline cache";
return false;
}
}
log_info("pipeline cache " + m_cachepath + (data.empty() ? " (empty)" : " (" + std::to_string(data.size() / 1024) + " KiB)"));
return true;
}
void pipeline_library::shutdown()
{
if (m_device == nullptr)
{
return;
}
auto const device{m_device->handle()};
if (device != VK_NULL_HANDLE)
{
for (auto const &[key, pipeline] : m_pipelines)
{
if (pipeline != VK_NULL_HANDLE)
{
vkDestroyPipeline(device, pipeline, nullptr);
}
}
if (m_cache != VK_NULL_HANDLE)
{
std::size_t size{0};
if (VK14_VERIFY(vkGetPipelineCacheData(device, m_cache, &size, nullptr)) && (size > 0))
{
std::vector<char> data(size);
if (VK14_VERIFY(vkGetPipelineCacheData(device, m_cache, &size, data.data())))
{
write_file(m_cachepath, data.data(), size);
}
}
vkDestroyPipelineCache(device, m_cache, nullptr);
}
}
m_pipelines.clear();
m_cache = VK_NULL_HANDLE;
m_device = nullptr;
}
std::uint64_t pipeline_library::key_of(pipeline_desc const &Desc) const
{
std::uint64_t hash{14695981039346656037ull};
for (auto const &stage : Desc.stages)
{
hash_value(hash, stage.get());
}
hash_value(hash, Desc.layout);
for (auto const &binding : Desc.vertices.bindings)
{
hash_value(hash, binding.binding);
hash_value(hash, binding.stride);
hash_value(hash, binding.inputRate);
}
for (auto const &attribute : Desc.vertices.attributes)
{
hash_value(hash, attribute.location);
hash_value(hash, attribute.binding);
hash_value(hash, attribute.format);
hash_value(hash, attribute.offset);
}
hash_value(hash, Desc.topology);
hash_value(hash, Desc.blending);
for (auto const format : Desc.colour_formats)
{
hash_value(hash, format);
}
hash_value(hash, Desc.depth_format);
hash_value(hash, Desc.samples);
for (auto const value : Desc.specialization)
{
hash_value(hash, value);
}
return hash;
}
namespace
{
VkPrimitiveTopology topology_class_head(VkPrimitiveTopology const Topology)
{
return Topology;
}
std::vector<VkDynamicState> const dynamic_states{VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
VK_DYNAMIC_STATE_CULL_MODE,
VK_DYNAMIC_STATE_FRONT_FACE,
VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY,
VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE,
VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE,
VK_DYNAMIC_STATE_DEPTH_COMPARE_OP,
VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE,
VK_DYNAMIC_STATE_DEPTH_BIAS,
VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE,
VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE};
} // namespace
VkPipeline pipeline_library::get(pipeline_desc const &Desc)
{
auto const key{key_of(Desc)};
auto const lookup{m_pipelines.find(key)};
if (lookup != m_pipelines.end())
{
return lookup->second;
}
auto const start{std::chrono::steady_clock::now()};
// the code goes straight to the stages (maintenance5), with the specialization of all of them
std::vector<VkSpecializationMapEntry> entries(Desc.specialization.size());
for (std::size_t i = 0; i < entries.size(); ++i)
{
entries[i] = {static_cast<std::uint32_t>(i), static_cast<std::uint32_t>(i * sizeof(std::uint32_t)), sizeof(std::uint32_t)};
}
VkSpecializationInfo specialization{};
specialization.mapEntryCount = static_cast<std::uint32_t>(entries.size());
specialization.pMapEntries = entries.data();
specialization.dataSize = Desc.specialization.size() * sizeof(std::uint32_t);
specialization.pData = Desc.specialization.data();
std::vector<VkShaderModuleCreateInfo> modules(Desc.stages.size());
std::vector<VkPipelineShaderStageCreateInfo> stages(Desc.stages.size());
for (std::size_t i = 0; i < Desc.stages.size(); ++i)
{
modules[i].sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
modules[i].codeSize = Desc.stages[i]->spirv.size() * sizeof(std::uint32_t);
modules[i].pCode = Desc.stages[i]->spirv.data();
stages[i].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stages[i].pNext = &modules[i];
stages[i].stage = Desc.stages[i]->stage;
stages[i].module = VK_NULL_HANDLE;
stages[i].pName = "main";
stages[i].pSpecializationInfo = (entries.empty() ? nullptr : &specialization);
}
VkPipelineVertexInputStateCreateInfo vertexinput{};
vertexinput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexinput.vertexBindingDescriptionCount = static_cast<std::uint32_t>(Desc.vertices.bindings.size());
vertexinput.pVertexBindingDescriptions = Desc.vertices.bindings.data();
vertexinput.vertexAttributeDescriptionCount = static_cast<std::uint32_t>(Desc.vertices.attributes.size());
vertexinput.pVertexAttributeDescriptions = Desc.vertices.attributes.data();
VkPipelineInputAssemblyStateCreateInfo inputassembly{};
inputassembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputassembly.topology = topology_class_head(Desc.topology);
VkPipelineViewportStateCreateInfo viewport{};
viewport.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewport.viewportCount = 1;
viewport.scissorCount = 1;
VkPipelineRasterizationStateCreateInfo rasterization{};
rasterization.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterization.polygonMode = VK_POLYGON_MODE_FILL;
rasterization.cullMode = VK_CULL_MODE_NONE;
rasterization.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterization.lineWidth = 1.f;
VkPipelineMultisampleStateCreateInfo multisample{};
multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisample.rasterizationSamples = Desc.samples;
VkPipelineDepthStencilStateCreateInfo depthstencil{};
depthstencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthstencil.depthCompareOp = VK_COMPARE_OP_GREATER_OR_EQUAL;
depthstencil.maxDepthBounds = 1.f;
std::vector<VkPipelineColorBlendAttachmentState> attachments(Desc.colour_formats.size());
for (auto &attachment : attachments)
{
attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
switch (Desc.blending)
{
case blend_mode::none:
break;
case blend_mode::alpha:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.colorBlendOp = VK_BLEND_OP_ADD;
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
case blend_mode::premultiplied:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.colorBlendOp = VK_BLEND_OP_ADD;
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
case blend_mode::additive:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.colorBlendOp = VK_BLEND_OP_ADD;
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
}
}
VkPipelineColorBlendStateCreateInfo blend{};
blend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
blend.attachmentCount = static_cast<std::uint32_t>(attachments.size());
blend.pAttachments = attachments.data();
VkPipelineDynamicStateCreateInfo dynamic{};
dynamic.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamic.dynamicStateCount = static_cast<std::uint32_t>(dynamic_states.size());
dynamic.pDynamicStates = dynamic_states.data();
// no render pass: the formats of the attachments only (dynamic rendering)
VkPipelineRenderingCreateInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
rendering.colorAttachmentCount = static_cast<std::uint32_t>(Desc.colour_formats.size());
rendering.pColorAttachmentFormats = Desc.colour_formats.data();
rendering.depthAttachmentFormat = Desc.depth_format;
VkGraphicsPipelineCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
info.pNext = &rendering;
info.stageCount = static_cast<std::uint32_t>(stages.size());
info.pStages = stages.data();
info.pVertexInputState = &vertexinput;
info.pInputAssemblyState = &inputassembly;
info.pViewportState = &viewport;
info.pRasterizationState = &rasterization;
info.pMultisampleState = &multisample;
info.pDepthStencilState = &depthstencil;
info.pColorBlendState = &blend;
info.pDynamicState = &dynamic;
info.layout = Desc.layout;
VkPipeline pipeline{VK_NULL_HANDLE};
if (!VK14_VERIFY(vkCreateGraphicsPipelines(m_device->handle(), m_cache, 1, &info, nullptr, &pipeline)))
{
log_error("the pipeline " + Desc.name + " can't be made");
pipeline = VK_NULL_HANDLE;
}
else
{
name_object(m_device->handle(), VK_OBJECT_TYPE_PIPELINE, handle_value(pipeline), Desc.name);
}
m_creationms += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
m_pipelines.emplace(key, pipeline);
return pipeline;
}
VkPipeline pipeline_library::compute(std::shared_ptr<shader_code const> const &Shader, VkPipelineLayout Layout, std::vector<std::uint32_t> const &Specialization, std::string const &Name)
{
std::uint64_t key{14695981039346656037ull};
hash_value(key, Shader.get());
hash_value(key, Layout);
for (auto const value : Specialization)
{
hash_value(key, value);
}
auto const lookup{m_pipelines.find(key)};
if (lookup != m_pipelines.end())
{
return lookup->second;
}
auto const start{std::chrono::steady_clock::now()};
std::vector<VkSpecializationMapEntry> entries(Specialization.size());
for (std::size_t i = 0; i < entries.size(); ++i)
{
entries[i] = {static_cast<std::uint32_t>(i), static_cast<std::uint32_t>(i * sizeof(std::uint32_t)), sizeof(std::uint32_t)};
}
VkSpecializationInfo specialization{};
specialization.mapEntryCount = static_cast<std::uint32_t>(entries.size());
specialization.pMapEntries = entries.data();
specialization.dataSize = Specialization.size() * sizeof(std::uint32_t);
specialization.pData = Specialization.data();
VkShaderModuleCreateInfo module{};
module.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
module.codeSize = Shader->spirv.size() * sizeof(std::uint32_t);
module.pCode = Shader->spirv.data();
VkComputePipelineCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
info.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
info.stage.pNext = &module;
info.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
info.stage.pName = "main";
info.stage.pSpecializationInfo = (entries.empty() ? nullptr : &specialization);
info.layout = Layout;
VkPipeline pipeline{VK_NULL_HANDLE};
if (!VK14_VERIFY(vkCreateComputePipelines(m_device->handle(), m_cache, 1, &info, nullptr, &pipeline)))
{
log_error("the compute pipeline " + Name + " can't be made");
pipeline = VK_NULL_HANDLE;
}
else
{
name_object(m_device->handle(), VK_OBJECT_TYPE_PIPELINE, handle_value(pipeline), Name);
}
m_creationms += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
m_pipelines.emplace(key, pipeline);
return pipeline;
}
std::string pipeline_library::info() const
{
char buffer[32] = {};
std::snprintf(buffer, sizeof(buffer), "%.1f", m_creationms);
return "pipelines: " + std::to_string(m_pipelines.size()) + " (made in " + buffer + " ms)";
}
void set_draw_state(VkCommandBuffer Commands, VkExtent2D Extent)
{
VkViewport viewport{0.f, 0.f, static_cast<float>(Extent.width), static_cast<float>(Extent.height), 0.f, 1.f};
vkCmdSetViewport(Commands, 0, 1, &viewport);
VkRect2D scissor{{0, 0}, Extent};
vkCmdSetScissor(Commands, 0, 1, &scissor);
vkCmdSetCullMode(Commands, VK_CULL_MODE_NONE);
vkCmdSetFrontFace(Commands, VK_FRONT_FACE_COUNTER_CLOCKWISE);
vkCmdSetPrimitiveTopology(Commands, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
vkCmdSetDepthTestEnable(Commands, VK_FALSE);
vkCmdSetDepthWriteEnable(Commands, VK_FALSE);
// reverse z, as the opengl renderer
vkCmdSetDepthCompareOp(Commands, VK_COMPARE_OP_GREATER_OR_EQUAL);
vkCmdSetDepthBiasEnable(Commands, VK_FALSE);
vkCmdSetDepthBias(Commands, 0.f, 0.f, 0.f);
vkCmdSetPrimitiveRestartEnable(Commands, VK_FALSE);
vkCmdSetStencilTestEnable(Commands, VK_FALSE);
}
} // namespace vk14

View File

@@ -0,0 +1,134 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the shaders and the pipelines of the vulkan renderer.
//
// a shader of shaders/vk14 is taken from the SPIR-V binary of shaders/vk14bin when there's an up to date one
// (gfx.shaders.binaries), from the cache of the shaders compiled before (cache/vulkan14/shaders in the directory of the user,
// keyed by the source with its includes, the variant and the version of glslang), or compiled now by glslang and put in the
// cache. the pipelines are made from a description of what isn't dynamic (the shaders, the layout of the vertices, the class
// of the primitives, the blending, the formats of the attachments, the samples); the rest of the state is dynamic. the
// VkPipelineCache is read from and written to the directory of the user, and thrown away when it comes from another device
// or driver. no VkShaderModule: the code goes to the stages of the pipelines (maintenance5)
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/common/shadersource.h"
#include <memory>
#include <unordered_map>
namespace vk14
{
class device;
// the SPIR-V of a shader, and what its source says (the material directives)
struct shader_code
{
gfx::shader_source source;
VkShaderStageFlagBits stage{VK_SHADER_STAGE_VERTEX_BIT};
std::vector<std::uint32_t> spirv;
// where it came from: "binary", "cache", "compiled"
char const *origin{""};
};
class shader_library
{
public:
shader_library() = default;
shader_library(shader_library const &) = delete;
shader_library &operator=(shader_library const &) = delete;
~shader_library();
// Preamble: what the renderer puts after #version (the constants of vulkan14bindings.h)
bool init(std::string const &Preamble, std::string &Reason);
void shutdown();
// the shader of shaders/vk14 (Defines: the variant). throws gfx::shader_exception
std::shared_ptr<shader_code const> get(std::string const &Name, std::string const &Defines = {});
// what was loaded from where, for the debug panel
std::string info() const;
private:
std::vector<std::uint32_t> compile(gfx::shader_source const &Source) const;
std::string m_preamble;
std::string m_cachedir;
std::string m_compilerversion;
bool m_glslang{false};
bool m_binaries{false};
std::unordered_map<std::string, std::shared_ptr<shader_code const>> m_shaders;
int m_frombinary{0};
int m_fromcache{0};
int m_compiled{0};
};
// how the colour of a pipeline is mixed with what is there
enum class blend_mode
{
none,
alpha, // the colour over what is there by its alpha, the alpha kept for what's behind (the user interface)
premultiplied,
additive
};
struct vertex_layout
{
std::vector<VkVertexInputBindingDescription> bindings;
std::vector<VkVertexInputAttributeDescription> attributes;
};
// what a pipeline is made of; the rest of the state is set while drawing (vk14::set_draw_state())
struct pipeline_desc
{
std::vector<std::shared_ptr<shader_code const>> stages;
VkPipelineLayout layout{VK_NULL_HANDLE};
vertex_layout vertices;
// the class of the primitives; the primitive itself is dynamic
VkPrimitiveTopology topology{VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST};
blend_mode blending{blend_mode::none};
std::vector<VkFormat> colour_formats;
VkFormat depth_format{VK_FORMAT_UNDEFINED};
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
// the values of the specialization constants 0, 1, 2 ... of all the stages
std::vector<std::uint32_t> specialization;
std::string name;
};
class pipeline_library
{
public:
pipeline_library() = default;
pipeline_library(pipeline_library const &) = delete;
pipeline_library &operator=(pipeline_library const &) = delete;
~pipeline_library();
bool init(device const &Device, std::string &Reason);
// writes the pipeline cache to the disk and destroys the pipelines
void shutdown();
// the pipeline of the description, made at the first call (from the cache on the disk, when it has it). null when it can't be made
VkPipeline get(pipeline_desc const &Desc);
VkPipeline compute(std::shared_ptr<shader_code const> const &Shader, VkPipelineLayout Layout, std::vector<std::uint32_t> const &Specialization = {}, std::string const &Name = {});
std::string info() const;
private:
std::uint64_t key_of(pipeline_desc const &Desc) const;
device const *m_device{nullptr};
VkPipelineCache m_cache{VK_NULL_HANDLE};
std::string m_cachepath;
std::unordered_map<std::uint64_t, VkPipeline> m_pipelines;
double m_creationms{0.0};
};
// the dynamic state of a pipeline in its plain form: the whole of Extent, no culling, no depth, triangles
void set_draw_state(VkCommandBuffer Commands, VkExtent2D Extent);
} // namespace vk14

View File

@@ -280,6 +280,7 @@ bool swapchain::build(std::string &Reason)
vkGetSwapchainImagesKHR(device, m_swapchain, &count, nullptr);
m_images.resize(count);
vkGetSwapchainImagesKHR(device, m_swapchain, &count, m_images.data());
m_states.assign(count, image_state{});
VkSemaphoreCreateInfo semaphoreinfo{};
semaphoreinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
@@ -324,6 +325,15 @@ swapchain::acquire_result swapchain::acquire(std::uint32_t const Frame)
}
auto const result{vkAcquireNextImageKHR(m_device->handle(), m_swapchain, UINT64_MAX, m_acquiresemaphores[Frame], VK_NULL_HANDLE, &m_image)};
if ((result == VK_SUCCESS) || (result == VK_SUBOPTIMAL_KHR))
{
// what the image held is of no use; its use waits for the acquire semaphore, which the submit waits for at the colour output
if (m_states.size() < m_images.size())
{
m_states.resize(m_images.size());
}
m_states[m_image] = image_state{VK_IMAGE_LAYOUT_UNDEFINED, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_NONE};
}
switch (result)
{
case VK_SUCCESS:
@@ -422,6 +432,7 @@ void swapchain::release_images()
m_views.clear();
m_presentsemaphores.clear();
m_images.clear();
m_states.clear();
}
void swapchain::destroy()

View File

@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14passes.h"
namespace vk14
{
@@ -44,6 +45,20 @@ class swapchain
acquire_result acquire(std::uint32_t const Frame);
// the image acquired, from undefined to cleared with Colour (linear) and ready to be shown
void record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour) const;
// the image acquired and its view
VkImage image() const
{
return m_images[m_image];
}
VkImageView view() const
{
return m_views[m_image];
}
// a use of the image acquired by a pass (vk14::pass_list); the image starts the frame undefined, after the acquire semaphore
image_use use(VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
{
return image_use{m_images[m_image], VK_IMAGE_ASPECT_COLOR_BIT, &m_states[m_image], Layout, Stage, Access};
}
// the semaphore signalled when the image of the frame Frame is acquired
VkSemaphore acquire_semaphore(std::uint32_t const Frame) const
{
@@ -111,6 +126,8 @@ class swapchain
bool m_rebuild{false};
std::vector<VkImage> m_images;
// the states of the images for the barriers of the passes
std::vector<image_state> m_states;
std::vector<VkImageView> m_views;
// one per image: a semaphore given to the presentation can be reused only once the image comes back
std::vector<VkSemaphore> m_presentsemaphores;

View File

@@ -0,0 +1,596 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include <cstring>
namespace vk14
{
// uploader
void uploader::init(allocator const &Allocator, deletion_queue &Deletions, VkPhysicalDevice Physical)
{
m_allocator = &Allocator;
m_deletions = &Deletions;
m_physical = Physical;
}
bool uploader::can_blit(VkFormat Format) const
{
auto const lookup{m_blittable.find(Format)};
if (lookup != m_blittable.end())
{
return lookup->second;
}
VkFormatProperties properties{};
vkGetPhysicalDeviceFormatProperties(m_physical, Format, &properties);
auto const needed{VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT};
auto const result{(properties.optimalTilingFeatures & needed) == needed};
m_blittable.emplace(Format, result);
return result;
}
void uploader::begin_frame(VkCommandBuffer Commands, std::uint64_t Value)
{
m_commands = Commands;
m_value = Value;
m_recorded = false;
auto pending{std::move(m_pending)};
m_pending.clear();
for (auto &job : pending)
{
record(job);
}
}
bool uploader::end_frame()
{
m_commands = VK_NULL_HANDLE;
return m_recorded;
}
bool uploader::upload(std::shared_ptr<image> const &Image, void const *Data, VkDeviceSize Size, std::vector<level> const &Levels, bool const Mipmaps)
{
job upload;
upload.target = Image;
upload.levels = Levels;
upload.mipmaps = Mipmaps;
upload.staging = std::make_shared<buffer>();
if ((false == upload.staging->create(*m_allocator, Size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, memory_kind::upload)) || (upload.staging->mapped() == nullptr))
{
log_error("no staging buffer of " + memory_size_string(Size) + " for an upload");
return false;
}
std::memcpy(upload.staging->mapped(), Data, static_cast<std::size_t>(Size));
upload.staging->flush(0, Size);
m_uploaded += Size;
if (m_commands != VK_NULL_HANDLE)
{
record(upload);
}
else
{
m_pending.emplace_back(std::move(upload));
}
return true;
}
void uploader::record(job &Job)
{
auto &target{*Job.target};
auto const commands{m_commands};
auto const aspect{target.aspect()};
auto const layers{target.desc().layers};
auto const mips{target.desc().mips};
transition(commands, target, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
std::vector<VkBufferImageCopy2> regions;
for (std::size_t i = 0; i < Job.levels.size(); ++i)
{
VkBufferImageCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_BUFFER_IMAGE_COPY_2;
region.bufferOffset = Job.levels[i].offset;
region.imageSubresource = {aspect, static_cast<std::uint32_t>(i), 0, layers};
region.imageExtent = Job.levels[i].extent;
regions.push_back(region);
}
VkCopyBufferToImageInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_TO_IMAGE_INFO_2;
copy.srcBuffer = Job.staging->handle();
copy.dstImage = target.handle();
copy.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
copy.regionCount = static_cast<std::uint32_t>(regions.size());
copy.pRegions = regions.data();
vkCmdCopyBufferToImage2(commands, &copy);
auto const given{static_cast<std::uint32_t>(Job.levels.size())};
if (Job.mipmaps && (mips > given) && (given > 0))
{
// the other levels made by blits, each from the one before it
auto barrier = [&](std::uint32_t Level, std::uint32_t Count, VkImageLayout From, VkImageLayout To, VkPipelineStageFlags2 SrcStage, VkAccessFlags2 SrcAccess, VkPipelineStageFlags2 DstStage, VkAccessFlags2 DstAccess) {
VkImageMemoryBarrier2 entry{};
entry.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
entry.srcStageMask = SrcStage;
entry.srcAccessMask = SrcAccess;
entry.dstStageMask = DstStage;
entry.dstAccessMask = DstAccess;
entry.oldLayout = From;
entry.newLayout = To;
entry.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
entry.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
entry.image = target.handle();
entry.subresourceRange = {aspect, Level, Count, 0, layers};
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = 1;
dependency.pImageMemoryBarriers = &entry;
vkCmdPipelineBarrier2(commands, &dependency);
};
auto const extent{target.desc().extent};
for (std::uint32_t level = given; level < mips; ++level)
{
barrier(level - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT,
VK_ACCESS_2_TRANSFER_READ_BIT);
VkImageBlit2 blit{};
blit.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2;
blit.srcSubresource = {aspect, level - 1, 0, layers};
blit.srcOffsets[1] = {static_cast<std::int32_t>(std::max(extent.width >> (level - 1), 1u)), static_cast<std::int32_t>(std::max(extent.height >> (level - 1), 1u)), 1};
blit.dstSubresource = {aspect, level, 0, layers};
blit.dstOffsets[1] = {static_cast<std::int32_t>(std::max(extent.width >> level, 1u)), static_cast<std::int32_t>(std::max(extent.height >> level, 1u)), 1};
VkBlitImageInfo2 info{};
info.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2;
info.srcImage = target.handle();
info.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
info.dstImage = target.handle();
info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
info.regionCount = 1;
info.pRegions = &blit;
info.filter = VK_FILTER_LINEAR;
vkCmdBlitImage2(commands, &info);
}
barrier(0, mips - 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
barrier(mips - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
target.state = image_state{VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT};
}
else
{
transition(commands, target, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
}
m_recorded = true;
// the staging buffer goes, and the image may, once the gpu is done with the frame
m_deletions->push(m_value, [staging = Job.staging, image = Job.target]() {});
}
// image table
std::uint64_t image_table::add(std::shared_ptr<image> const &Image, VkSampler Sampler)
{
std::uint64_t id;
if (false == m_free.empty())
{
id = m_free.back();
m_free.pop_back();
}
else
{
id = m_entries.size();
m_entries.emplace_back();
}
m_entries[id] = entry{Image, Sampler};
return id;
}
void image_table::replace(std::uint64_t Id, std::shared_ptr<image> const &Image, VkSampler Sampler)
{
if ((Id > 0) && (Id < m_entries.size()))
{
m_entries[Id] = entry{Image, Sampler};
}
}
void image_table::remove(std::uint64_t Id, deletion_queue &Deletions, std::uint64_t Value)
{
if ((Id == 0) || (Id >= m_entries.size()) || (m_entries[Id].target == nullptr))
{
return;
}
Deletions.push(Value, [image = std::move(m_entries[Id].target)]() {});
m_entries[Id] = entry{};
m_free.push_back(Id);
}
image_table::entry const *image_table::find(std::uint64_t Id) const
{
if ((Id == 0) || (Id >= m_entries.size()) || (m_entries[Id].target == nullptr))
{
return nullptr;
}
return &m_entries[Id];
}
void image_table::clear()
{
m_entries.assign(1, entry{});
m_free.clear();
}
// formats
texture_format texture_format_of(GLint Format, GLint Hint)
{
// (etc2 of the ktx files isn't mapped: the desktop gpus don't sample it, and the feature isn't enabled on the device; the
// opengl driver decodes it)
// the material wants srgb colours (the default), or values: the compressed formats keep their alpha either way, as in the
// opengl renderer (opengl_texture::mapping)
auto const srgb{(Hint == 0) || (Hint == GL_SRGB_ALPHA) || (Hint == GL_SRGB)};
texture_format result;
switch (Format)
{
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
result.format = ((srgb || (Format == GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT)) ? VK_FORMAT_BC1_RGBA_SRGB_BLOCK : VK_FORMAT_BC1_RGBA_UNORM_BLOCK);
result.compressed = true;
result.block_bytes = 8;
return result;
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
result.format = ((srgb || (Format == GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT)) ? VK_FORMAT_BC2_SRGB_BLOCK : VK_FORMAT_BC2_UNORM_BLOCK);
result.compressed = true;
result.block_bytes = 16;
return result;
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
result.format = ((srgb || (Format == GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT)) ? VK_FORMAT_BC3_SRGB_BLOCK : VK_FORMAT_BC3_UNORM_BLOCK);
result.compressed = true;
result.block_bytes = 16;
return result;
case GL_RGBA:
case GL_BGRA:
case GL_RGB:
result.format = (Format == GL_BGRA ? (srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM) : (srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM));
// rgb texels aren't sampled by every gpu, they are made rgba
result.expand_rgb = (Format == GL_RGB);
break;
default:
return result;
}
// what the material takes of the colour, as the internal formats of opengl give it (GL_SRGB8, GL_RG8, GL_R8: the missing
// channels read 0, the alpha 1)
switch (Hint)
{
case GL_SRGB:
case GL_RGB:
result.components.a = VK_COMPONENT_SWIZZLE_ONE;
break;
case GL_RG:
result.components.b = VK_COMPONENT_SWIZZLE_ZERO;
result.components.a = VK_COMPONENT_SWIZZLE_ONE;
break;
case GL_RED:
result.components.g = VK_COMPONENT_SWIZZLE_ZERO;
result.components.b = VK_COMPONENT_SWIZZLE_ZERO;
result.components.a = VK_COMPONENT_SWIZZLE_ONE;
break;
default:
break;
}
return result;
}
// texture
vulkan14_texture::~vulkan14_texture()
{
if (m_resources.pending != nullptr)
{
std::lock_guard<std::mutex> lock(m_resources.pending->mutex);
auto &list{m_resources.pending->textures};
list.erase(std::remove(list.begin(), list.end(), this), list.end());
}
}
bool vulkan14_texture::create(bool const Static)
{
if ((data_state == resource_state::none) && (true == reload_on_use))
{
// the data was dropped along with the gpu image when the texture was far away
reload_on_use = false;
load();
}
if ((data_state == resource_state::loading) && (request != nullptr))
{
complete_data();
}
if (m_made)
{
return true;
}
if (data_state != resource_state::good)
{
// don't bother until we have useful texture data
return false;
}
auto const format{texture_format_of(data_format, components_hint != 0 ? components_hint : GL_SRGB_ALPHA)};
VkFormatProperties properties{};
if (format.format != VK_FORMAT_UNDEFINED)
{
vkGetPhysicalDeviceFormatProperties(m_resources.gpu->physical(), format.format, &properties);
}
if ((format.format == VK_FORMAT_UNDEFINED) || ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) == 0))
{
ErrorLog("Bad texture: the format of \"" + name + "\" can't be sampled by the gpu", logtype::texture);
on_failed();
is_ready = true;
return true;
}
std::vector<uploader::level> levels;
std::vector<std::uint8_t> expanded;
std::uint8_t const *source{data.data()};
VkDeviceSize size{data.size()};
auto width{data_width}, height{data_height};
if (format.compressed)
{
VkDeviceSize offset{0};
for (unsigned int level = 0; level < data_mapcount; ++level)
{
auto const levelsize{static_cast<VkDeviceSize>((std::max(width, 4u) + 3) / 4) * ((std::max(height, 4u) + 3) / 4) * format.block_bytes};
if (offset + levelsize > data.size())
{
break;
}
levels.push_back({offset, {width, height, 1}});
offset += levelsize;
width = std::max(width / 2, 1u);
height = std::max(height / 2, 1u);
}
size = offset;
}
else
{
if (format.expand_rgb)
{
auto const pixels{static_cast<std::size_t>(data_width) * data_height};
expanded.resize(pixels * 4);
for (std::size_t i = 0; i < pixels; ++i)
{
expanded[i * 4 + 0] = data[i * 3 + 0];
expanded[i * 4 + 1] = data[i * 3 + 1];
expanded[i * 4 + 2] = data[i * 3 + 2];
expanded[i * 4 + 3] = 0xff;
}
source = expanded.data();
size = expanded.size();
}
else
{
size = static_cast<VkDeviceSize>(data_width) * data_height * 4;
}
levels.push_back({0, {data_width, data_height, 1}});
}
auto const available{expanded.empty() ? data.size() : expanded.size()};
if (levels.empty() || (size > available) || (data_width == 0) || (data_height == 0))
{
ErrorLog("Bad texture: \"" + name + "\" holds less data than its size needs", logtype::texture);
on_failed();
is_ready = true;
return true;
}
// a single level is made into the whole chain on the gpu, as glGenerateMipmap() does for the opengl renderer
auto const generate{(levels.size() == 1) && (false == format.compressed) && m_resources.uploads->can_blit(format.format)};
image_desc desc;
desc.format = format.format;
desc.extent = {data_width, data_height, 1};
desc.mips = (generate ? static_cast<std::uint32_t>(std::floor(std::log2(std::max(data_width, data_height)))) + 1 : static_cast<std::uint32_t>(levels.size()));
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (generate ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0);
desc.components = format.components;
auto image{std::make_shared<vk14::image>()};
if (false == image->create(*m_resources.memory, desc, name))
{
ErrorLog("Bad texture: no gpu image for \"" + name + "\"", logtype::texture);
on_failed();
is_ready = true;
return true;
}
if (false == m_resources.uploads->upload(image, source, size, levels, generate))
{
// never filled, so never sampled
ErrorLog("Bad texture: \"" + name + "\" couldn't be uploaded", logtype::texture);
on_failed();
is_ready = true;
return true;
}
// the traits of the file: clamped along s or t
auto const clamped{(traits.find('s') != std::string::npos) || (traits.find('t') != std::string::npos)};
m_image = image;
m_id = m_resources.images->add(image, clamped ? m_resources.sampling->clamp_aniso : m_resources.sampling->repeat_aniso);
m_made = true;
if ((true == Global.ResourceMove) || (false == Global.ResourceSweep) || (true == streamed()))
{
// if garbage collection is disabled we don't expect having to upload the texture more than once
data = std::vector<unsigned char>();
data_state = resource_state::none;
}
if (type == "make:")
{
// for generated textures send a request to have the actual content of the texture generated
make_request();
}
is_static = Static;
is_ready = true;
return true;
}
void vulkan14_texture::release()
{
if (false == m_made)
{
return;
}
if (is_static)
{
return;
}
if (true == streamed())
{
// no copy of the data is kept, it's read from the file again when the texture is needed (prefetch(), create())
data = std::vector<unsigned char>();
data_state = resource_state::none;
reload_on_use = true;
}
else if (data_state != resource_state::good)
{
if (type == "make:")
{
// auto generated textures only store a stub
make_stub();
}
else if (type != "internalsrc:")
{
// the data went after the upload (gfx.resource.move): read from the file again, instead of from the gpu
reload_on_use = true;
}
}
m_resources.images->remove(m_id, *m_resources.deletions, m_resources.uploads->current_value());
m_image.reset();
m_id = 0;
m_made = false;
is_ready = false;
}
void vulkan14_texture::destroy()
{
if (m_id != 0)
{
m_resources.images->remove(m_id, *m_resources.deletions, m_resources.uploads->current_value());
}
m_image.reset();
m_id = 0;
m_made = false;
}
void vulkan14_texture::on_failed()
{
// the texture counts as made, without an image (the id 0), as with the opengl renderer
if (m_id != 0)
{
m_resources.images->remove(m_id, *m_resources.deletions, m_resources.uploads->current_value());
}
m_image.reset();
m_id = 0;
m_made = true;
}
void vulkan14_texture::update_from_memory(size_t width, size_t height, const uint8_t *raw)
{
if (std::this_thread::get_id() != m_resources.thread)
{
// the renderer takes it over in its frame (apply_pending())
std::lock_guard<std::mutex> lock(m_pendingmutex);
m_pending.assign(raw, raw + width * height * 4);
m_pendingwidth = width;
m_pendingheight = height;
if (m_resources.pending != nullptr)
{
std::lock_guard<std::mutex> listlock(m_resources.pending->mutex);
auto &list{m_resources.pending->textures};
if (std::find(list.begin(), list.end(), this) == list.end())
{
list.push_back(this);
}
}
return;
}
upload_rgba(raw, width, height);
}
void vulkan14_texture::apply_pending()
{
std::vector<std::uint8_t> pending;
std::size_t width{0}, height{0};
{
std::lock_guard<std::mutex> lock(m_pendingmutex);
pending.swap(m_pending);
width = m_pendingwidth;
height = m_pendingheight;
}
if (false == pending.empty())
{
upload_rgba(pending.data(), width, height);
}
}
void vulkan14_texture::upload_rgba(std::uint8_t const *Data, std::size_t Width, std::size_t Height)
{
// as the opengl renderer: srgb rgba with mipmaps, the image made anew when the size changes
auto const width{static_cast<std::uint32_t>(Width)}, height{static_cast<std::uint32_t>(Height)};
auto const format{VK_FORMAT_R8G8B8A8_SRGB};
auto const generate{m_resources.uploads->can_blit(format)};
auto target{m_image};
auto const fresh{(m_image == nullptr) || (m_image->desc().extent.width != width) || (m_image->desc().extent.height != height) || (m_image->desc().format != format)};
if (fresh)
{
image_desc desc;
desc.format = format;
desc.extent = {width, height, 1};
desc.mips = (generate ? static_cast<std::uint32_t>(std::floor(std::log2(std::max(width, height)))) + 1 : 1);
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (generate ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0);
target = std::make_shared<vk14::image>();
if (false == target->create(*m_resources.memory, desc, name))
{
return;
}
}
// a new image goes to the table only once it has its content
if (false == m_resources.uploads->upload(target, Data, static_cast<VkDeviceSize>(width) * height * 4, {{0, {width, height, 1}}}, generate))
{
return;
}
if (fresh)
{
auto const sampler{m_resources.sampling->repeat_aniso};
if (m_id != 0)
{
// the old image goes once the frames which drew it are done
m_resources.deletions->push(m_resources.uploads->current_value(), [old = m_image]() {});
m_resources.images->replace(m_id, target, sampler);
}
else
{
m_id = m_resources.images->add(target, sampler);
}
m_image = target;
}
data_width = width;
data_height = height;
data_format = GL_SRGB8_ALPHA8;
data_components = GL_RGBA;
m_made = true;
// the content given straight to the gpu can't be read again: released by the garbage sweep, the texture would come back as
// the grey stub
if (type == "internalsrc:")
{
is_static = true;
is_ready = true;
}
}
} // namespace vk14

View File

@@ -0,0 +1,187 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the images the vulkan renderer samples: the textures of the files (texture_source, decoded as for the opengl renderer and
// laid out the same way in the memory, decision K8 of the plan), the images of the user interface, and their uploads.
//
// the uploads are copies through staging buffers recorded into the command buffer of uploads of the frame being made, which goes
// to the queue before the commands of the frame in the same submit; an upload asked for between the frames waits for the next
// one. every image has an id in one table (decision K10): ITexture::get_id() of the textures, the ids of the images of the user
// interface and the font of imgui are all ImTextureID, so the user interface draws any of them
#pragma once
#include "rendering/vulkan14/vulkan14memory.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "model/Texture.h"
#include <memory>
#include <mutex>
#include <thread>
namespace vk14
{
class device;
// the uploads of data to the images
class uploader
{
public:
struct level
{
VkDeviceSize offset{0}; // in the data
VkExtent3D extent{1, 1, 1};
};
void init(allocator const &Allocator, deletion_queue &Deletions, VkPhysicalDevice Physical);
// the commands of the uploads go to Commands until end_frame(); Value: the value of the timeline the gpu reaches when it's done
// with the frame. the uploads waiting from between the frames are recorded first
void begin_frame(VkCommandBuffer Commands, std::uint64_t Value);
// whether anything was recorded to the commands of the frame
bool end_frame();
// the last value of the timeline the gpu may use the objects with: the frame being made, or the last one sent
std::uint64_t current_value() const
{
return m_value;
}
// copies Data into Image (the levels given, the others made by blits of the first one when Mipmaps), the image left ready to be
// sampled. Data is copied away. false: no staging memory, nothing will be copied
bool upload(std::shared_ptr<image> const &Image, void const *Data, VkDeviceSize Size, std::vector<level> const &Levels, bool const Mipmaps);
// the bytes uploaded since the start, for the debug panel
std::uint64_t uploaded() const
{
return m_uploaded;
}
// whether the format can be filtered by blits (the mipmaps made on the gpu)
bool can_blit(VkFormat Format) const;
private:
struct job
{
std::shared_ptr<image> target;
std::shared_ptr<buffer> staging;
std::vector<level> levels;
bool mipmaps{false};
};
void record(job &Job);
allocator const *m_allocator{nullptr};
deletion_queue *m_deletions{nullptr};
VkPhysicalDevice m_physical{VK_NULL_HANDLE};
VkCommandBuffer m_commands{VK_NULL_HANDLE};
std::uint64_t m_value{0};
bool m_recorded{false};
std::vector<job> m_pending;
std::uint64_t m_uploaded{0};
mutable std::unordered_map<VkFormat, bool> m_blittable;
};
// the images the shaders can be given by an id
class image_table
{
public:
struct entry
{
std::shared_ptr<image> target;
VkSampler sampler{VK_NULL_HANDLE};
};
// a new id for the image (0 is no image)
std::uint64_t add(std::shared_ptr<image> const &Image, VkSampler Sampler);
void replace(std::uint64_t Id, std::shared_ptr<image> const &Image, VkSampler Sampler);
// the id is free again, the image goes once the gpu is done with it
void remove(std::uint64_t Id, deletion_queue &Deletions, std::uint64_t Value);
entry const *find(std::uint64_t Id) const;
void clear();
std::size_t size() const
{
return m_entries.size() - m_free.size();
}
private:
std::vector<entry> m_entries{entry{}};
std::vector<std::uint64_t> m_free;
};
struct vulkan14_texture;
// the textures given content by other threads, waiting for the thread of the renderer
struct pending_textures
{
std::mutex mutex;
std::vector<vulkan14_texture *> textures;
};
// what the images of the renderer need from it
struct resources
{
device const *gpu{nullptr};
allocator *memory{nullptr};
uploader *uploads{nullptr};
deletion_queue *deletions{nullptr};
samplers const *sampling{nullptr};
image_table *images{nullptr};
// the thread of the renderer: the content given by other threads waits for it (update_from_memory())
std::thread::id thread;
pending_textures *pending{nullptr};
};
// the vulkan format of the data of a texture and how the data has to be changed for it
struct texture_format
{
VkFormat format{VK_FORMAT_UNDEFINED};
bool expand_rgb{false}; // rgb texels made rgba
bool compressed{false};
int block_bytes{0}; // of a block of 4x4 texels, for the compressed formats
// the channels the material reads, as the internal format of opengl would leave them
VkComponentMapping components{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
};
// Format: data_format of a texture_source, Hint: the components the material wants (GL_SRGB_ALPHA ...)
texture_format texture_format_of(GLint Format, GLint Hint);
// a texture of the vulkan renderer
struct vulkan14_texture : public texture_source
{
explicit vulkan14_texture(resources const &Resources) : m_resources(Resources) {}
~vulkan14_texture() override;
bool create(bool const Static = false) override;
void release() override;
size_t get_id() const override
{
return static_cast<size_t>(m_id);
}
void update_from_memory(size_t width, size_t height, const uint8_t *data) override;
bool resident() const override
{
return m_made;
}
void destroy() override;
// the content given by update_from_memory() from another thread, uploaded by the thread of the renderer
void apply_pending();
protected:
void on_failed() override;
private:
void upload_rgba(std::uint8_t const *Data, std::size_t Width, std::size_t Height);
resources const &m_resources;
std::shared_ptr<image> m_image;
std::uint64_t m_id{0};
bool m_made{false};
// content from another thread, waiting for the thread of the renderer
std::mutex m_pendingmutex;
std::vector<std::uint8_t> m_pending;
std::size_t m_pendingwidth{0};
std::size_t m_pendingheight{0};
};
} // namespace vk14

View File

@@ -0,0 +1,19 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the implementation of the vulkan memory allocator (ref/VulkanMemoryAllocator). the functions of vulkan are given to it by
// vk14::allocator, as volk loaded them (vmaImportVulkanFunctionsFromVolk)
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14common.h"
#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0
#define VMA_IMPLEMENTATION
#include <vk_mem_alloc.h>