From 1816004357ac358b3715ad547191ecc183fdb358 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sat, 24 Oct 2026 23:01:59 +0200 Subject: [PATCH] shader: serialize resource plans for the shader cache --- .../renderer/pipeline/pipelineCache.cpp | 46 +---- src/graphics/shader/recompiler/ir/Serialize.h | 174 ++++++++++++++++++ src/graphics/shader/recompiler/ir/ShaderIR.h | 1 - .../ir/passes/ResourceMaterialization.cpp | 103 ++++++++++- .../ir/passes/ResourceMaterialization.h | 5 + tests/ResourceMaterializationTests.cpp | 102 +++++++--- tests/ShaderRecompilerComputeTests.cpp | 17 +- 7 files changed, 378 insertions(+), 70 deletions(-) create mode 100644 src/graphics/shader/recompiler/ir/Serialize.h diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index c5cafce05..5548fb396 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -17,6 +17,7 @@ #include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/shader/recompiler/BufferFormat.h" #include "graphics/shader/recompiler/ShaderRecompiler.h" +#include "graphics/shader/recompiler/ir/Serialize.h" #include "graphics/shader/shaderCompiler.h" #include "kernel/memory.h" #include "loader/systemContent.h" @@ -168,37 +169,6 @@ ShaderRecompiler::Spirv::Profile SpirvProfile(const GraphicContext& graphics) { .device_clock_period_ps = graphics.device_clock_period_ps}; } -static_assert(sizeof(ShaderRecompiler::IR::ResourceSpecialization::Buffer) == 28 && - sizeof(ShaderRecompiler::IR::ResourceSpecialization::Image) == 36 && - sizeof(ShaderRecompiler::IR::ResourceSpecialization::Sampler) == 12, - "PackSpecialization must list every field"); - -// Appends every field as one word, each list preceded by its size. -void PackSpecialization(const ShaderRecompiler::IR::ResourceSpecialization& specialization, - std::vector& words) { - const auto word = [](auto value) { return static_cast(value); }; - words.push_back(word(specialization.buffers.size())); - for (const auto& buffer: specialization.buffers) { - words.insert(words.end(), {buffer.packed_stride, word(buffer.descriptor_format), - buffer.descriptor_swizzle, word(buffer.zero_stride_oob), - word(buffer.scalar_empty), buffer.indirect_root, - buffer.indirect_mapping_offset, buffer.indirect_search_iterations}); - } - words.push_back(word(specialization.images.size())); - for (const auto& image: specialization.images) { - words.insert(words.end(), {word(image.numeric_class), word(image.dimension), image.mip_count, - word(image.conversion_format), image.shader_swizzle, - image.indirect_root, image.indirect_mapping_offset, - image.indirect_search_iterations, word(image.cube), - word(image.fmask), word(image.heap)}); - } - words.push_back(word(specialization.samplers.size())); - for (const auto& sampler: specialization.samplers) { - words.insert(words.end(), {sampler.indirect_root, sampler.indirect_mapping_offset, - sampler.indirect_search_iterations}); - } -} - // Formats with a narrower host variant drop the components the shader does not read. vk::Format NarrowInputFormat(vk::Format format, uint32_t used_components) { switch (format) { @@ -326,15 +296,11 @@ ShaderModule& PipelineCache::CompileModule(Program& program, const ProgramKey& k // The same inputs give the same module id across runs. const auto function_code = program.FunctionCode(); - std::vector words {static_cast(key.stage), static_cast(key.hash), - static_cast(key.hash >> 32u), key.user_data_count, - static_cast(key.static_state.size())}; - words.insert(words.end(), key.static_state.begin(), key.static_state.end()); - words.push_back(info.bindings.push_data_start_dword); - words.push_back(static_cast(function_code.size())); - words.insert(words.end(), function_code.begin(), function_code.end()); - PackSpecialization(program.specialization, words); - const auto id = XXH3_64bits(words.data(), words.size() * sizeof(uint32_t)); + ShaderRecompiler::IR::Writer inputs; + ShaderRecompiler::IR::IoAll(inputs, key.stage, key.hash, key.user_data_count, key.static_state, + info.bindings.push_data_start_dword, program.specialization); + inputs.Raw(function_code.data(), function_code.size_bytes()); // Last, so it needs no size. + const auto id = XXH3_64bits(inputs.bytes.data(), inputs.bytes.size()); const auto* stage = ShaderRecompiler::StageName(key.stage); DumpShader(result.spirv, fmt::format("{}_{:016x}_{:016x}.spv", stage, key.hash, id)); diff --git a/src/graphics/shader/recompiler/ir/Serialize.h b/src/graphics/shader/recompiler/ir/Serialize.h new file mode 100644 index 000000000..1dfaf13b9 --- /dev/null +++ b/src/graphics/shader/recompiler/ir/Serialize.h @@ -0,0 +1,174 @@ +#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SERIALIZE_H_ +#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SERIALIZE_H_ + +#include "common/assert.h" +#include "graphics/shader/recompiler/ir/ShaderIR.h" +#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Shader cache records. Io(io, value) appends value to a Writer or reads it back from a Reader +// field by field, so padding never reaches the bytes; instructions travel as plan-local indices. +namespace Libs::Graphics::ShaderRecompiler::IR { + +struct Writer { + std::vector bytes; + std::unordered_map index; // Instructions that values may refer to. + + void Raw(const void* data, size_t size) { + const auto* begin = static_cast(data); + bytes.insert(bytes.end(), begin, begin + size); + } +}; + +struct Reader { + std::span bytes; + std::vector insts; // Instructions by index. + bool ok = true; // False once a read ran past the end or was invalid. + + void Raw(void* data, size_t size) { + ok = ok && size <= bytes.size(); + if (!ok) return; + std::copy_n(bytes.data(), size, static_cast(data)); + bytes = bytes.subspan(size); + } + // Every element takes at least one byte, which bounds counts read from damaged records. + uint32_t Count(uint32_t count) { + ok = ok && count <= bytes.size(); + return ok ? count : 0u; + } +}; + +template constexpr bool Reading = std::is_same_v; +template class Of> constexpr bool IsA = false; +template