vulkan/slang: Allow a cache for spv.

This commit is contained in:
BearOso 2023-02-21 16:16:34 -06:00
parent 1c787cd309
commit 8907541f28
9 changed files with 112 additions and 31 deletions

View File

@ -22,6 +22,26 @@
namespace fs = std::filesystem;
std::string get_cache_dir()
{
char *env_home = getenv("HOME");
char *env_cache = getenv("XDG_CACHE_HOME");
std::string dir;
if (env_cache)
dir = std::string(env_cache) + "/snes9x";
else if (env_home)
dir = std::string(env_home) + "/.cache/snes9x";
if (dir.empty())
return dir;
fs::create_directories(dir);
return dir;
}
std::string get_config_dir()
{
// Find config directory

View File

@ -159,6 +159,7 @@ class Snes9xConfig
int joystick_threshold;
};
std::string get_cache_dir();
std::string get_config_dir();
std::string get_config_file_name();

View File

@ -84,7 +84,7 @@ int S9xVulkanDisplayDriver::init()
if (!gui_config->shader_filename.empty() && gui_config->use_shaders)
{
shaderchain = std::make_unique<Vulkan::ShaderChain>(context.get());
if (!shaderchain->load_shader_preset(gui_config->shader_filename))
if (!shaderchain->load_shader_preset(gui_config->shader_filename, get_cache_dir()))
{
fmt::print("Couldn't load shader preset file\n");
shaderchain = nullptr;
@ -94,6 +94,8 @@ int S9xVulkanDisplayDriver::init()
window->enable_widget("shader_parameters_item", true);
return 0;
}
}
simple_output = std::make_unique<Vulkan::SimpleOutput>(context.get(), vk::Format::eR5G6B5UnormPack16);

View File

@ -107,6 +107,10 @@ bool SlangPreset::load_preset_file(string filename)
}
}
if (!cache_directory.empty())
for (auto &p : passes)
p.set_cache_directory(cache_directory);
std::vector<std::future<bool>> futures;
for (size_t i = 0; i < passes.size(); i++)
{
@ -135,6 +139,11 @@ bool SlangPreset::load_preset_file(string filename)
return true;
}
void SlangPreset::set_cache_directory(std::string filename)
{
cache_directory = filename;
}
/*
Aggregates the parameters from individual stages and separate shader files,
resolving duplicates.

View File

@ -18,6 +18,7 @@ struct SlangPreset
bool match_sampler_semantic(const std::string &name, int pass, SlangShader::Sampler::Type &type, int &specifier);
void gather_parameters();
bool save_to_file(std::string filename);
void set_cache_directory(std::string filename);
struct Texture
{
@ -34,4 +35,5 @@ struct SlangPreset
int oldest_previous_frame;
bool uses_feedback;
bool last_pass_uses_feedback;
std::string cache_directory;
};

View File

@ -10,6 +10,8 @@
#include "../external/glslang/glslang/Public/ShaderLang.h"
#include "../external/glslang/SPIRV/GlslangToSpv.h"
#include "../external/glslang/StandAlone/ResourceLimits.h"
#include "../external/xxh64/xxh64.hpp"
#include <filesystem>
using std::string;
using std::vector;
@ -23,6 +25,11 @@ SlangShader::~SlangShader()
{
}
void SlangShader::set_cache_directory(std::string dir)
{
cache_directory = dir;
}
/*
Recursively load shader file and included files into memory, applying
#include and #pragma directives. Will strip all directives except
@ -231,44 +238,76 @@ std::vector<uint32_t> SlangShader::generate_spirv(std::string shader_string, std
}
std::string SlangShader::get_hash_filename(const std::string &shader_string)
{
uint64_t hash = xxh64::hash(shader_string.c_str(), fragment_shader_string.size(), 56);
char hash_hex_string[32];
snprintf(hash_hex_string, 32, "%016lx.spv", hash);
auto cache_path = std::filesystem::path(cache_directory);
cache_path /= hash_hex_string;
return cache_path.string();
}
static const char *header = "SPVCACHE1";
static size_t header_size = 9;
bool SlangShader::load_cache(std::string hash_filename, std::vector<uint32_t> &output)
{
if (cache_directory.empty())
return false;
if (!std::filesystem::exists(hash_filename))
return false;
auto size = std::filesystem::file_size(hash_filename);
size -= header_size;
if ((size & 3) != 0)
return false;
std::ifstream file(hash_filename, std::ios::binary);
char headerblock[header_size];
file.read(headerblock, header_size);
if (memcmp(headerblock, header, header_size))
{
printf("Invalid cache file format %s.\n", headerblock);
return false;
}
output.resize(size / 4);
file.read((char *)output.data(), size);
return true;
}
void SlangShader::save_cache(std::string hash_filename, std::vector<uint32_t> &spv)
{
if (cache_directory.empty())
return;
std::ofstream file(hash_filename, std::ios::binary);
file.write(header, header_size);
file.write((char *)spv.data(), spv.size() * 4);
}
/*
Generate SPIRV from separate preprocessed fragment and vertex shaders.
Must have called divide_into_stages beforehand. Returns true on success.
*/
bool SlangShader::generate_spirv()
{
Initializeglslang();
fragment_shader_spirv.clear();
vertex_shader_spirv.clear();
const EShMessages messages = (EShMessages)(EShMsgDefault | EShMsgVulkanRules | EShMsgSpvRules | EShMsgDebugInfo | EShMsgAST | EShMsgEnhanced);
auto forbid_includer = glslang::TShader::ForbidIncluder();
auto vertex_cache_filename = get_hash_filename(vertex_shader_string);
auto fragment_cache_filename = get_hash_filename(fragment_shader_string);
glslang::TShader vertexTShader(EShLangVertex);
glslang::TShader fragmentTShader(EShLangFragment);
if (load_cache(vertex_cache_filename, vertex_shader_spirv))
if (load_cache(fragment_cache_filename, fragment_shader_spirv))
return true;
auto compile = [&](glslang::TShader &shader, string &shader_string, std::vector<uint32_t> &spirv) -> bool {
const char *source = shader_string.c_str();
shader.setStrings(&source, 1);
if (!shader.parse(&glslang::DefaultTBuiltInResource, 450, false, messages, forbid_includer))
return false;
vertex_shader_spirv = generate_spirv(vertex_shader_string, "vertex");
fragment_shader_spirv = generate_spirv(fragment_shader_string, "fragment");
glslang::TProgram program;
program.addShader(&shader);
if (!program.link(messages))
return false;
save_cache(vertex_cache_filename, vertex_shader_spirv);
save_cache(fragment_cache_filename, fragment_shader_spirv);
glslang::GlslangToSpv(*program.getIntermediate(shader.getStage()), spirv);
return true;
};
if (!compile(vertexTShader, vertex_shader_string, vertex_shader_spirv))
{
printf("%s\n%s\n", vertexTShader.getInfoLog(), vertexTShader.getInfoDebugLog());
return false;
}
if (!compile(fragmentTShader, fragment_shader_string, fragment_shader_spirv))
{
printf("%s\n%s\n", fragmentTShader.getInfoLog(), fragmentTShader.getInfoDebugLog());
return false;
}
return true;
}

View File

@ -72,6 +72,10 @@ struct SlangShader
bool load_file(std::string new_filename = "");
void divide_into_stages(const std::vector<std::string> &lines);
bool generate_spirv();
std::string get_hash_filename(const std::string &shader_string);
bool load_cache(std::string hash_filename, std::vector<uint32_t> &output);
void save_cache(std::string hash_filename, std::vector<uint32_t> &spv);
void set_cache_directory(std::string dir);
static std::vector<uint32_t> generate_spirv(std::string shader_string, std::string stage);
std::string filename;
@ -100,4 +104,5 @@ struct SlangShader
int ubo_binding;
std::vector<Uniform> uniforms;
std::vector<Sampler> samplers;
std::string cache_directory;
};

View File

@ -187,13 +187,16 @@ void ShaderChain::update_and_propagate_sizes(int original_width_new, int origina
}
}
bool ShaderChain::load_shader_preset(std::string filename)
bool ShaderChain::load_shader_preset(std::string filename, std::string cache_dir)
{
if (!ends_with(filename, ".slangp"))
printf("Warning: loading preset without .slangp extension\n");
preset = std::make_unique<SlangPreset>();
if (!cache_dir.empty())
preset->set_cache_directory(cache_dir);
if (!preset->load_preset_file(filename))
{
printf("Couldn't load preset file: %s\n", filename.c_str());

View File

@ -16,7 +16,7 @@ class ShaderChain
ShaderChain(Context *context_);
~ShaderChain();
bool load_shader_preset(std::string filename);
bool load_shader_preset(std::string filename, std::string cache_dir = "");
void update_and_propagate_sizes(int original_width_, int original_height_, int viewport_width_, int viewport_height_);
bool load_lookup_textures();
void do_frame(uint8_t *data, int width, int height, int stride, vk::Format format, int viewport_x, int viewport_y, int viewport_width, int viewport_height);