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:
689
rendering/common/shadersource.cpp
Normal file
689
rendering/common/shadersource.cpp
Normal 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
|
||||
173
rendering/common/shadersource.h
Normal file
173
rendering/common/shadersource.h
Normal 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
|
||||
@@ -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"); }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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) {}
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
54
rendering/vulkan14/vulkan14bindings.h
Normal file
54
rendering/vulkan14/vulkan14bindings.h
Normal 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
|
||||
@@ -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());
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
168
rendering/vulkan14/vulkan14descriptors.cpp
Normal file
168
rendering/vulkan14/vulkan14descriptors.cpp
Normal 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
|
||||
61
rendering/vulkan14/vulkan14descriptors.h
Normal file
61
rendering/vulkan14/vulkan14descriptors.h
Normal 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
|
||||
@@ -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{};
|
||||
|
||||
@@ -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};
|
||||
|
||||
341
rendering/vulkan14/vulkan14imgui.cpp
Normal file
341
rendering/vulkan14/vulkan14imgui.cpp
Normal 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
|
||||
75
rendering/vulkan14/vulkan14imgui.h
Normal file
75
rendering/vulkan14/vulkan14imgui.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
380
rendering/vulkan14/vulkan14memory.cpp
Normal file
380
rendering/vulkan14/vulkan14memory.cpp
Normal 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
|
||||
241
rendering/vulkan14/vulkan14memory.h
Normal file
241
rendering/vulkan14/vulkan14memory.h
Normal 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
|
||||
237
rendering/vulkan14/vulkan14passes.cpp
Normal file
237
rendering/vulkan14/vulkan14passes.cpp
Normal 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
|
||||
96
rendering/vulkan14/vulkan14passes.h
Normal file
96
rendering/vulkan14/vulkan14passes.h
Normal 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
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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};
|
||||
|
||||
705
rendering/vulkan14/vulkan14shaders.cpp
Normal file
705
rendering/vulkan14/vulkan14shaders.cpp
Normal 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
|
||||
134
rendering/vulkan14/vulkan14shaders.h
Normal file
134
rendering/vulkan14/vulkan14shaders.h
Normal 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
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
596
rendering/vulkan14/vulkan14texture.cpp
Normal file
596
rendering/vulkan14/vulkan14texture.cpp
Normal 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, ©);
|
||||
|
||||
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
|
||||
187
rendering/vulkan14/vulkan14texture.h
Normal file
187
rendering/vulkan14/vulkan14texture.h
Normal 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
|
||||
19
rendering/vulkan14/vulkan14vma.cpp
Normal file
19
rendering/vulkan14/vulkan14vma.cpp
Normal 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>
|
||||
Reference in New Issue
Block a user