#include "slang_preset.hpp" #include "../external/SPIRV-Cross/spirv.hpp" #include "slang_helpers.hpp" #include "slang_preset_ini.hpp" #include #include #include #include #include #include #include #include #include #include "../external/SPIRV-Cross/spirv_cross.hpp" #include "../external/SPIRV-Cross/spirv_glsl.hpp" #include "slang_shader.hpp" using std::string; using std::to_string; SlangPreset::SlangPreset() { } SlangPreset::~SlangPreset() { } bool SlangPreset::load_preset_file(string filename) { if (!ends_with(filename, ".slangp")) return false; IniFile conf; if (!conf.load_file(filename)) return false; int num_passes = conf.get_int("shaders", 0); if (num_passes <= 0) return false; passes.resize(num_passes); int index; auto key = [&](string s) -> string { return s + to_string(index); }; auto iGetBool = [&](string s, bool def = false) -> bool { return conf.get_bool(key(s), def); }; auto iGetString = [&](string s, string def = "") -> string { return conf.get_string(key(s), def); }; auto iGetFloat = [&](string s, float def = 1.0f) -> float { return conf.get_float(key(s), def); }; auto iGetInt = [&](string s, int def = 0) -> int { return conf.get_int(key(s), def); }; for (index = 0; index < num_passes; index++) { auto &shader = passes[index]; shader.filename = iGetString("shader", ""); canonicalize(shader.filename, conf.get_source(key("shader"))); shader.alias = iGetString("alias", ""); shader.filter_linear = iGetBool("filter_linear"); shader.mipmap_input = iGetBool("mipmap_input"); shader.float_framebuffer = iGetBool("float_framebuffer"); shader.srgb_framebuffer = iGetBool("srgb_framebuffer"); shader.frame_count_mod = iGetInt("frame_count_mod", 0); shader.wrap_mode = iGetString("wrap_mode"); // Is this correct? It gives priority to _x and _y scale types. string scale_type = iGetString("scale_type", "undefined"); shader.scale_type_x = iGetString("scale_type_x", scale_type); shader.scale_type_y = iGetString("scale_type_y", scale_type); shader.scale_x = iGetFloat("scale_x", 1.0f); shader.scale_y = iGetFloat("scale_y", 1.0f); if (conf.exists(key("scale"))) { float scale = iGetFloat("scale"); shader.scale_x = scale; shader.scale_y = scale; } } string texture_string = conf.get_string("textures", ""); if (!texture_string.empty()) { auto texture_list = split_string(texture_string, ';'); for (auto &id : texture_list) { Texture texture; texture.id = trim(id); textures.push_back(texture); } for (auto &t : textures) { t.wrap_mode = conf.get_string(t.id + "_wrap_mode", ""); t.mipmap = conf.get_bool(t.id + "_mipmap", false); t.linear = conf.get_bool(t.id + "_linear", false); t.filename = conf.get_string(t.id, ""); canonicalize(t.filename, conf.get_source(t.id)); } } SlangShader::initialize_glslang(); std::vector> futures; for (size_t i = 0; i < passes.size(); i++) { futures.push_back(std::async(std::launch::async, [this, i]() -> bool { return passes[i].load_file(); })); } if (!std::all_of(futures.begin(), futures.end(), [](auto &f) { return f.get(); })) return false; gather_parameters(); for (auto &p : parameters) { auto value_str = conf.get_string(p.id, ""); if (!value_str.empty()) { p.val = atof(value_str.c_str()); if (p.val < p.min) p.val = p.min; else if (p.val > p.max) p.val = p.max; } } return true; } /* Aggregates the parameters from individual stages and separate shader files, resolving duplicates. */ void SlangPreset::gather_parameters() { parameters.clear(); for (auto &s : passes) { for (auto &p : s.parameters) { auto it = std::find_if(parameters.begin(), parameters.end(), [&p](SlangShader::Parameter &needle) { return (needle.id == p.id); }); if (it == parameters.end()) parameters.push_back(p); } } } /* Print to stdout the entire shader preset chain and parameters, minus source and SPIRV output */ void SlangPreset::print() { printf("Number of Shaders: %zu\n", passes.size()); for (size_t i = 0; i < passes.size(); i++) { auto &s = passes[i]; printf(" Shader \n"); printf(" filename: %s\n", s.filename.c_str()); printf(" alias: %s\n", s.alias.c_str()); printf(" filter_linear: %d\n", s.filter_linear);; printf(" mipmap_input: %d\n", s.mipmap_input); printf(" float_framebuffer: %d\n", s.float_framebuffer); printf(" srgb_framebuffer: %d\n", s.srgb_framebuffer); printf(" frame_count_mod: %d\n", s.frame_count_mod); printf(" wrap_mode: %s\n", s.wrap_mode.c_str()); printf(" scale_type_x: %s\n", s.scale_type_x.c_str()); printf(" scale_type_y: %s\n", s.scale_type_y.c_str()); printf(" scale_x: %f\n", s.scale_x); printf(" scale_y: %f\n", s.scale_y); printf(" pragma lines: "); for (auto &p : s.pragma_stage_lines) printf("%zu ", p); printf("\n"); printf(" Number of parameters: %zu\n", s.parameters.size()); for (auto &p : s.parameters) { printf(" %s \"%s\" min: %f max: %f val: %f step: %f digits: %d\n", p.id.c_str(), p.name.c_str(), p.min, p.max, p.val, p.step, p.significant_digits); } printf(" Uniforms: %zu\n", s.uniforms.size()); printf(" UBO size: %zu, binding: %d\n", s.ubo_size, s.ubo_binding); printf(" Push Constant block size: %zu\n", s.push_constant_block_size); for (auto &u : s.uniforms) { const char *strings[] = { "Output Size", "Previous Frame Size", "Pass Size", "Pass Feedback Size", "Lut Size", "MVP Matrix", "Frame Count", "Parameter" }; const char *block = u.block == SlangShader::Uniform::UBO ? "UBO" : "Push Constants"; const char *type = strings[u.type]; printf(" "); switch (u.type) { case SlangShader::Uniform::Type::ViewportSize: printf("%s at offset %zu in %s\n", type, u.offset, block); break; case SlangShader::Uniform::Type::PreviousFrameSize: case SlangShader::Uniform::Type::PassSize: case SlangShader::Uniform::Type::PassFeedbackSize: case SlangShader::Uniform::Type::LutSize: printf("%s %d at offset %zu in %s\n", type, u.specifier, u.offset, block); break; case SlangShader::Uniform::Type::MVP: case SlangShader::Uniform::Type::FrameCount: printf("%s at offset %zu in %s\n", type, u.offset, block); break; case SlangShader::Uniform::Type::Parameter: printf("%s #%d named \"%s\" at offset %zu in %s\n", type, u.specifier, parameters[u.specifier].id.c_str(), u.offset, block); default: break; } } printf(" Samplers: %zu\n", s.samplers.size()); for (auto &sampler : s.samplers) { const char *strings[] = { "Previous Frame", "Pass", "Pass Feedback", "Lut", }; const char *type = strings[sampler.type]; printf(" "); switch (sampler.type) { case SlangShader::Sampler::Type::PreviousFrame: case SlangShader::Sampler::Type::Pass: case SlangShader::Sampler::Type::PassFeedback: printf("%s %d at binding %d\n", type, sampler.specifier, sampler.binding); break; case SlangShader::Sampler::Type::Lut: printf("%s %d \"%s\" at binding %d\n", type, sampler.specifier, textures[sampler.specifier].id.c_str(), sampler.binding); break; default: break; } } } printf("Num textures: %zu\n", textures.size()); for (size_t i = 0; i < textures.size(); i++) { auto &t = textures[i]; printf(" Texture %zu\n", i); printf(" id: %s\n", t.id.c_str()); printf(" filename: %s\n", t.filename.c_str()); printf(" wrap_mode: %s\n", t.wrap_mode.c_str()); printf(" mipmap: %d\n", t.mipmap); printf(" linear: %d\n", t.linear); } printf("Parameters: %zu count\n", parameters.size()); for (size_t i = 0; i < parameters.size(); i++) { auto &p = parameters[i]; printf(" Parameter: %zu\n", i); printf(" id: %s\n", p.id.c_str()); printf(" name: %s\n", p.name.c_str()); printf(" min: %f\n", p.min); printf(" max: %f\n", p.max); printf(" step: %f\n", p.step); printf(" sigdigits: %d\n", p.significant_digits); printf(" value: %f\n", p.val); } printf("Oldest previous frame used: %d\n", oldest_previous_frame); } bool SlangPreset::match_sampler_semantic(const string &name, int pass, SlangShader::Sampler::Type &type, int &specifier) { auto match_with_specifier = [&name, &specifier](string prefix) -> bool { if (name.compare(0, prefix.length(), prefix) != 0) return false; if (name.length() <= prefix.length()) return false; for (auto iter = name.begin() + prefix.length(); iter < name.end(); iter++) { if (!std::isdigit(*iter)) return false; } specifier = std::stoi(name.substr(prefix.length())); return true; }; if (name == "Original") { type = SlangShader::Sampler::Type::Pass; specifier = -1; return true; } else if (name == "Source") { type = SlangShader::Sampler::Type::Pass; specifier = pass - 1; return true; } else if (match_with_specifier("OriginalHistory")) { type = SlangShader::Sampler::Type::PreviousFrame; return true; } else if (match_with_specifier("PassOutput")) { type = SlangShader::Sampler::Type::Pass; return true; } else if (match_with_specifier("PassFeedback")) { type = SlangShader::Sampler::Type::PassFeedback; return true; } else if (match_with_specifier("User")) { type = SlangShader::Sampler::Type::Lut; return true; } else { for (size_t i = 0; i < passes.size(); i++) { if (passes[i].alias == name) { type = SlangShader::Sampler::Type::Pass; specifier = i; return true; } else if (passes[i].alias + "Feedback" == name) { type = SlangShader::Sampler::Type::PassFeedback; specifier = i; return true; } } } for (size_t i = 0; i < textures.size(); i++) { if (name == textures[i].id) { type = SlangShader::Sampler::Type::Lut; specifier = i; return true; } } return false; } bool SlangPreset::match_buffer_semantic(const string &name, int pass, SlangShader::Uniform::Type &type, int &specifier) { if (name == "MVP") { type = SlangShader::Uniform::Type::MVP; return true; } if (name == "FrameCount") { type = SlangShader::Uniform::Type::FrameCount; return true; } if (name == "FinalViewportSize") { type = SlangShader::Uniform::Type::ViewportSize; return true; } if (name == "FrameDirection") { type = SlangShader::Uniform::Type::FrameDirection; return true; } if (name.find("Size") != string::npos) { auto match = [&name, &specifier](string prefix) -> bool { if (name.compare(0, prefix.length(), prefix) != 0) return false; if (name.compare(prefix.length(), 4, "Size") != 0) return false; if (prefix.length() + 4 < name.length()) specifier = std::stoi(name.substr(prefix.length() + 4)); return true; }; if (match("Original")) { type = SlangShader::Uniform::Type::PassSize; specifier = -1; return true; } else if (match("Source")) { type = SlangShader::Uniform::Type::PassSize; specifier = pass - 1; return true; } else if (match("Output")) { type = SlangShader::Uniform::Type::PassSize; specifier = pass; return true; } else if (match("OriginalHistory")) { type = SlangShader::Uniform::Type::PreviousFrameSize; return true; } else if (match("PassOutput")) { type = SlangShader::Uniform::Type::PassSize; return true; } else if (match("PassFeedback")) { type = SlangShader::Uniform::Type::PassFeedbackSize; return true; } else if (match("User")) { type = SlangShader::Uniform::Type::LutSize; return true; } for (size_t i = 0; i < passes.size(); i++) { if (match(passes[i].alias)) { type = SlangShader::Uniform::Type::PassSize; specifier = i; return true; } } for (size_t i = 0; i < textures.size(); i++) { if (match(textures[i].id)) { type = SlangShader::Uniform::Type::LutSize; return true; } } } for (size_t i = 0; i < parameters.size(); i++) { if (name == parameters[i].id) { type = SlangShader::Uniform::Type::Parameter; specifier = i; return true; } } return false; } /* Introspect an individual shader pass, collecting external resource info in order to build uniform blocks. */ bool SlangPreset::introspect_shader(SlangShader &shader, int pass, SlangShader::Stage stage) { spirv_cross::CompilerGLSL cross(stage == SlangShader::Stage::Vertex ? shader.vertex_shader_spirv : shader.fragment_shader_spirv); auto res = cross.get_shader_resources(); if (res.push_constant_buffers.size() > 1) { printf("%s: Too many push constant buffers.\n", shader.filename.c_str()); return false; } else if (res.uniform_buffers.size() > 1) { printf("%s: Too many uniform buffers.\n", shader.filename.c_str()); return false; } auto exists = [&shader](const SlangShader::Uniform &uniform) -> bool { for (const auto &u : shader.uniforms) { if (u.block == uniform.block && u.offset == uniform.offset && u.specifier == uniform.specifier && u.type == uniform.type) { return true; } } return false; }; if (res.push_constant_buffers.size() == 0) { shader.push_constant_block_size = 0; } else { auto &pcb = res.push_constant_buffers[0]; auto &pcb_type = cross.get_type(pcb.base_type_id); shader.push_constant_block_size = cross.get_declared_struct_size(pcb_type); for (size_t i = 0; i < pcb_type.member_types.size(); i++) { auto name = cross.get_member_name(pcb.base_type_id, i); auto offset = cross.get_member_decoration(pcb.base_type_id, i, spv::DecorationOffset); SlangShader::Uniform::Type semantic_type; int specifier; if (match_buffer_semantic(name, pass, semantic_type, specifier)) { SlangShader::Uniform uniform{ SlangShader::Uniform::Block::PushConstant, offset, semantic_type, specifier }; if (!exists(uniform)) shader.uniforms.push_back(uniform); } else { printf("%s: Failed to match push constant semantic: \"%s\"\n", shader.filename.c_str(), name.c_str()); } } } if (res.uniform_buffers.size() == 0) { shader.ubo_size = 0; } else { auto &ubo = res.uniform_buffers[0]; auto &ubo_type = cross.get_type(ubo.base_type_id); shader.ubo_size = cross.get_declared_struct_size(ubo_type); shader.ubo_binding = cross.get_decoration(ubo.base_type_id, spv::DecorationBinding); for (size_t i = 0; i < ubo_type.member_types.size(); i++) { auto name = cross.get_member_name(ubo.base_type_id, i); auto offset = cross.get_member_decoration(ubo.base_type_id, i, spv::DecorationOffset); SlangShader::Uniform::Type semantic_type; int specifier; if (match_buffer_semantic(name, pass, semantic_type, specifier)) { SlangShader::Uniform uniform{ SlangShader::Uniform::Block::UBO, offset, semantic_type, specifier }; if (!exists(uniform)) shader.uniforms.push_back(uniform); } else { printf("%s: Failed to match uniform buffer semantic: \"%s\"\n", shader.filename.c_str(), name.c_str()); } } } if (res.sampled_images.size() == 0 && stage == SlangShader::Stage::Fragment) { printf("No sampled images found in fragment shader.\n"); return false; } if (res.sampled_images.size() > 0 && stage == SlangShader::Stage::Vertex) { printf("Sampled image found in vertex shader.\n"); return false; } if (stage == SlangShader::Stage::Fragment) { for (auto &image : res.sampled_images) { SlangShader::Sampler::Type semantic_type; int specifier; if (match_sampler_semantic(image.name, pass, semantic_type, specifier)) { int binding = cross.get_decoration(image.id, spv::DecorationBinding); shader.samplers.push_back({ semantic_type, specifier, binding }); } else { printf("%s: Failed to match sampler semantic: \"%s\"\n", shader.filename.c_str(), image.name.c_str()); return false; } } } return true; } /* Introspect all of preset's shaders. */ bool SlangPreset::introspect() { for (size_t i = 0; i < passes.size(); i++) { if (!introspect_shader(passes[i], i, SlangShader::Stage::Vertex)) return false; if (!introspect_shader(passes[i], i, SlangShader::Stage::Fragment)) return false; } oldest_previous_frame = 0; uses_feedback = false; last_pass_uses_feedback = false; for (auto &p : passes) { for (auto &s : p.samplers) { if (s.type == SlangShader::Sampler::PreviousFrame && s.specifier > oldest_previous_frame) oldest_previous_frame = s.specifier; if (s.type == SlangShader::Sampler::PassFeedback) { uses_feedback = true; if (s.specifier == (int)passes.size() - 1) last_pass_uses_feedback = true; } } } return true; } bool SlangPreset::save_to_file(std::string filename) { std::ofstream out(filename); if (!out.is_open()) return false; auto outs = [&](std::string key, std::string value) { out << key << " = \"" << value << "\"\n"; }; auto outb = [&](std::string key, bool value) { outs(key, value ? "true" : "false"); }; auto outa = [&](std::string key, auto value) { outs(key, to_string(value)); }; outa("shaders", passes.size()); for (size_t i = 0; i < passes.size(); i++) { auto &pass = passes[i]; auto indexed = [i](std::string str) { return str + to_string(i); }; outs(indexed("shader"), pass.filename); outb(indexed("filter_linear"), pass.filter_linear); outs(indexed("wrap_mode"), pass.wrap_mode); outs(indexed("alias"), pass.alias); outb(indexed("float_framebuffer"), pass.float_framebuffer); outb(indexed("srgb_framebuffer"), pass.srgb_framebuffer); outb(indexed("mipmap_input"), pass.mipmap_input); outs(indexed("scale_type_x"), pass.scale_type_x); outs(indexed("scale_type_y"), pass.scale_type_y); outa(indexed("scale_x"), pass.scale_x); outa(indexed("scale_y"), pass.scale_y); outa(indexed("frame_count_mod"), pass.frame_count_mod); } if (parameters.size() > 0) { std::string parameter_list = ""; for (size_t i = 0; i < parameters.size(); i++) { parameter_list += parameters[i].id; if (i < parameters.size() - 1) parameter_list += ";"; } outs("parameters", parameter_list); } for (auto &item : parameters) outa(item.id, item.val); if (textures.size() > 0) { std::string texture_list = ""; for (size_t i = 0; i < textures.size(); i++) { texture_list += textures[i].id; if (i < textures.size() - 1) texture_list += ";"; } outs("textures", texture_list); } for (auto &item : textures) { outs(item.id, item.filename); outb(item.id + "_linear", item.linear); outs(item.id + "_wrap_mode", item.wrap_mode); outb(item.id + "_mipmap", item.mipmap); } out.close(); return true; }