diff --git a/gtk/src/gtk_config.cpp b/gtk/src/gtk_config.cpp index 9d8ceddb..968d9aec 100644 --- a/gtk/src/gtk_config.cpp +++ b/gtk/src/gtk_config.cpp @@ -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 diff --git a/gtk/src/gtk_config.h b/gtk/src/gtk_config.h index ff953c12..3572119f 100644 --- a/gtk/src/gtk_config.h +++ b/gtk/src/gtk_config.h @@ -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(); diff --git a/gtk/src/gtk_display_driver_vulkan.cpp b/gtk/src/gtk_display_driver_vulkan.cpp index 79811d70..26ace9f3 100644 --- a/gtk/src/gtk_display_driver_vulkan.cpp +++ b/gtk/src/gtk_display_driver_vulkan.cpp @@ -84,7 +84,7 @@ int S9xVulkanDisplayDriver::init() if (!gui_config->shader_filename.empty() && gui_config->use_shaders) { shaderchain = std::make_unique(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(context.get(), vk::Format::eR5G6B5UnormPack16); diff --git a/vulkan/slang_preset.cpp b/vulkan/slang_preset.cpp index 6ee21516..0cda4e3c 100644 --- a/vulkan/slang_preset.cpp +++ b/vulkan/slang_preset.cpp @@ -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> 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. diff --git a/vulkan/slang_preset.hpp b/vulkan/slang_preset.hpp index caa32520..cb28f4db 100644 --- a/vulkan/slang_preset.hpp +++ b/vulkan/slang_preset.hpp @@ -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; }; \ No newline at end of file diff --git a/vulkan/slang_shader.cpp b/vulkan/slang_shader.cpp index da91de52..ea705687 100644 --- a/vulkan/slang_shader.cpp +++ b/vulkan/slang_shader.cpp @@ -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 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 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 &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 &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 &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; } \ No newline at end of file diff --git a/vulkan/slang_shader.hpp b/vulkan/slang_shader.hpp index abfa0fc0..51ebaa59 100644 --- a/vulkan/slang_shader.hpp +++ b/vulkan/slang_shader.hpp @@ -72,6 +72,10 @@ struct SlangShader bool load_file(std::string new_filename = ""); void divide_into_stages(const std::vector &lines); bool generate_spirv(); + std::string get_hash_filename(const std::string &shader_string); + bool load_cache(std::string hash_filename, std::vector &output); + void save_cache(std::string hash_filename, std::vector &spv); + void set_cache_directory(std::string dir); static std::vector generate_spirv(std::string shader_string, std::string stage); std::string filename; @@ -100,4 +104,5 @@ struct SlangShader int ubo_binding; std::vector uniforms; std::vector samplers; + std::string cache_directory; }; \ No newline at end of file diff --git a/vulkan/vulkan_shader_chain.cpp b/vulkan/vulkan_shader_chain.cpp index 7ea08a02..ecfad6a6 100644 --- a/vulkan/vulkan_shader_chain.cpp +++ b/vulkan/vulkan_shader_chain.cpp @@ -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(); + 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()); diff --git a/vulkan/vulkan_shader_chain.hpp b/vulkan/vulkan_shader_chain.hpp index ca835211..2087534f 100644 --- a/vulkan/vulkan_shader_chain.hpp +++ b/vulkan/vulkan_shader_chain.hpp @@ -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);