From bf43995e8eb689c34bb240c533f2c7633ab010ca Mon Sep 17 00:00:00 2001 From: boykopovar Date: Thu, 24 Sep 2026 20:59:34 +0300 Subject: [PATCH] feat(shader): implement enable cache field in recompile request --- .../libSceAgcDriver/Execution/src/Driver.cpp | 4 +- .../Execution/src/ShaderMemory.cpp | 5 +- .../libSceAgcDriver/tests/ShaderMemory.cpp | 56 +++++++ core/shader/recompiler/CacheKey.hpp | 145 +++++++++++++++++ .../ControlFlow/src/RequestSerializer.cpp | 8 +- .../Optimization/DescriptorBindingBuilder.hpp | 1 + .../include/Optimization/ResourceProgram.hpp | 1 + .../src/DescriptorBindingBuilder.cpp | 17 +- core/shader/recompiler/Recompiler.cpp | 152 ++++++++++++++++-- core/shader/recompiler/Recompiler.hpp | 2 + .../src/ShaderInputInfoBuilder.cpp | 1 + 11 files changed, 365 insertions(+), 27 deletions(-) create mode 100644 core/shader/recompiler/CacheKey.hpp diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp index 28af0a23e..a4d060f9a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp @@ -355,7 +355,7 @@ private: request.context.memory = captured; timing.Mark("request_memory"); const auto compiled = ShaderRecompiler::Recompile(request); - timing.Mark("shader_compile"); + timing.Mark(compiled.cacheHit ? "shader_cache_hit" : "shader_compile"); std::vector snapshots; for (const auto& region : captured) snapshots.push_back({region.guestAddress, region.bytes}); timing.Mark("snapshots"); @@ -481,7 +481,7 @@ private: request.context.memory = memory; shaderTiming.Mark("request_memory"); results.push_back(ShaderRecompiler::Recompile(request)); - shaderTiming.Mark("compile"); + shaderTiming.Mark(results.back().cacheHit ? "cache_hit" : "compile"); const auto& result = results.back(); if (!drawParameters.indexed && i == 0) { const auto offsetValue = [&](std::int32_t sgpr) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp index 25be3e8a6..50d913bc1 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp @@ -46,9 +46,8 @@ bool ShaderMemory::read(void* context, std::uint64_t address, std::uint32_t* val } void ShaderMemory::Capture(const ShaderRecompiler::RecompileRequest& request) { - auto program = ShaderRecompiler::PrepareResourceProgram(request); + const auto plan = ShaderRecompiler::GetResourcePlan(request); constexpr ShaderRecompiler::ResourceMaterializer materializer; - const auto plan = materializer.ExtractPlan(program); ShaderRecompiler::SrtRuntime runtime; runtime.userData = request.context.userData; runtime.shaderBase = request.shader.codeAddress; @@ -57,7 +56,7 @@ void ShaderMemory::Capture(const ShaderRecompiler::RecompileRequest& request) { runtime.readSpecializationMemory = &read; ShaderRecompiler::ResourceSnapshot snapshot; ShaderRecompiler::ResourceSpecialization specialization; - materializer.Materialize(plan, runtime, snapshot, specialization); + materializer.Materialize(*plan, runtime, snapshot, specialization); } std::vector ShaderMemory::Regions() const { diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 69def35d0..0cae0fd69 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -7,6 +7,7 @@ #endif #include #include +#include #include #include @@ -115,6 +116,38 @@ int main() { request.context.memory = regions; const auto first = Recompile(request); require(!first.spirv.empty(), "empty compiled shader"); + require(!first.cacheHit, "first shader compilation unexpectedly hit the cache"); + const auto plan = GetResourcePlan(request); + require(plan == GetResourcePlan(request), "resource plan was rebuilt"); + const auto cached = Recompile(request); + require(cached.cacheHit, "unchanged shader did not hit the cache"); + verifyResult(first, cached); + auto relocated = request; + relocated.shader.codeAddress += 0x1000; + require(Recompile(relocated).cacheHit, "shader relocation caused recompilation"); + auto changedTarget = request; + changedTarget.target.subgroupSize = 32; + require(GetResourcePlan(changedTarget) != plan, "different target reused the source entry"); + std::vector changedCode(code.begin(), code.end()); + changedCode.insert(changedCode.begin(), 0xbf800000u); + auto changedSource = request; + changedSource.shader.code = changedCode; + require(GetResourcePlan(changedSource) != plan, "changed code reused the source entry"); + auto uncached = request; + uncached.useCache = false; + require(GetResourcePlan(uncached) != plan, "disabled cache reused the resource plan"); + const auto fresh = Recompile(uncached); + require(!fresh.cacheHit, "disabled cache reused the compiled variant"); + verifyResult(first, fresh); + require(!RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(uncached)).request.useCache, "cache policy was lost in serialization"); + auto changedLayout = request; + changedLayout.layout.pushConstantSizeBytes = 64; + require(!Recompile(changedLayout).cacheHit, "binding layout change reused an incompatible variant"); + require(Recompile(changedLayout).cacheHit, "new binding layout variant was not cached"); + require(Recompile(request).cacheHit, "compiling a new variant evicted the original"); + auto missingMemory = request; + missingMemory.context.memory = {}; + expectFailure([&] { static_cast(Recompile(missingMemory)); }, "SrtWalker::EvaluateRuntimeSources", "cache hit bypassed resource validation"); #if ANYPS5_ENABLE_SPIRV_TOOLS auto invalidSpirv = first.spirv; invalidSpirv[0] = 0; @@ -122,6 +155,29 @@ int main() { expectFailure([&] { static_cast(ValidateAndOptimizeSpirv(first.spirv, 0x00400000u, 0x00010600u)); }, "unsupported Vulkan/SPIR-V target", "incompatible target accepted"); expectFailure([&] { static_cast(ValidateAndOptimizeSpirv(first.spirv, 0x00405000u, 0x00010600u)); }, "unsupported Vulkan target", "unknown Vulkan target accepted"); #endif + payload = 0x40000000u; + AgcDriver::ShaderMemory updatedMemory({}); + updatedMemory.Capture(request); + const auto updatedRegions = updatedMemory.Regions(); + auto updated = request; + updated.context.memory = updatedRegions; + const auto updatedCached = Recompile(updated); + require(updatedCached.cacheHit, "dynamic shader data caused recompilation"); + updated.useCache = false; + verifyResult(updatedCached, Recompile(updated)); + bool changedData = updatedCached.pushConstants != first.pushConstants; + for (std::size_t i = 0; i < first.bindings.size(); ++i) changedData = changedData || updatedCached.bindings.at(i).guestDescriptor != first.bindings[i].guestDescriptor; + require(changedData, "cache hit retained stale shader data"); + auto concurrent = request; + concurrent.layout.pushConstantSizeBytes = 60; + std::array, 4> concurrentResults; + for (auto& future : concurrentResults) future = std::async(std::launch::async, [concurrent] { return Recompile(concurrent); }); + std::uint32_t compilations = 0; + for (auto& future : concurrentResults) { + const auto result = future.get(); + if (!result.cacheHit) ++compilations; + } + require(compilations == 1, "concurrent requests compiled the same variant repeatedly"); const auto serialized = RequestSerializer{}.Serialize(request); table = 0; payload = 0xdeadbeefu; diff --git a/core/shader/recompiler/CacheKey.hpp b/core/shader/recompiler/CacheKey.hpp new file mode 100644 index 000000000..df7af4478 --- /dev/null +++ b/core/shader/recompiler/CacheKey.hpp @@ -0,0 +1,145 @@ +#ifndef CORE_SHADER_RECOMPILER_CACHEKEY_HPP +#define CORE_SHADER_RECOMPILER_CACHEKEY_HPP + +#include "Recompiler.hpp" +#include +#include + +namespace ShaderRecompiler { + +class RecompileCacheKey { +public: + static void Build(const RecompileRequest& request, std::vector& key) { + key.clear(); + append(key, request.shader.stage); + append(key, request.shader.code); + append(key, request.context.waveSize); + append(key, request.context.userDataBaseRegister); + append(key, request.context.userData.size()); + append(key, request.context.compute); + append(key, request.context.pixel); + append(key, request.context.vertex); + append(key, request.target); + } + +private: + template + static void append(std::vector& key, TValue value) requires (std::is_integral_v || std::is_enum_v) { + key.push_back(static_cast(value)); + } + + template + static void append(std::vector& key, const std::array& values) { + for (const auto value : values) append(key, value); + } + + template + static void append(std::vector& key, std::span values) { + append(key, values.size()); + for (const auto value : values) append(key, value); + } + + template + static void append(std::vector& key, const std::optional& value) { + append(key, value.has_value()); + if (value) append(key, *value); + } + + static void append(std::vector& key, std::string_view value) { + append(key, value.size()); + for (const unsigned char byte : value) append(key, byte); + } + + static void append(std::vector& key, const ShaderComputeStageInfo& value) { + append(key, value.numThreads); + append(key, value.ldsSizeDwords); + append(key, value.groupIdEnable); + append(key, value.tgSizeEnable); + append(key, value.threadIdComponentCount); + } + + static void append(std::vector& key, const ShaderPixelStageInfo& value) { + append(key, value.interpolatorCount); + if (value.interpolatorCount > value.interpolatorSettings.size()) throw std::runtime_error("Shader cache: invalid interpolator count"); + for (std::uint32_t i = 0; i < value.interpolatorCount; ++i) append(key, value.interpolatorSettings[i]); + append(key, value.wave32); + append(key, value.perspectiveCenterVgpr); + append(key, value.hasPerspectiveCenterVgpr); + append(key, value.posX); + append(key, value.posY); + append(key, value.posZ); + append(key, value.posW); + append(key, value.frontFace); + append(key, value.ancillary); + append(key, value.sampleShading); + append(key, value.noPerspective); + append(key, value.pixelKillEnable); + append(key, value.depthExportEnable); + append(key, value.sampleMaskExportEnable); + append(key, value.earlyZ); + append(key, value.executeOnNoop); + append(key, value.targetOutputMode); + append(key, value.targetExportMapping); + } + + static void append(std::vector& key, const ShaderVertexResourceDestination& value) { + append(key, value.registerStart); + append(key, value.registersNum); + append(key, value.attrId); + append(key, value.fetchIndex); + } + + static void append(std::vector& key, const ShaderVertexStageInfo& value) { + append(key, value.resourcesNum); + append(key, value.fetchAttribReg); + append(key, value.fetchBufferReg); + append(key, value.fetchEmbedded); + if (value.resourcesNum > value.resources.size()) throw std::runtime_error("Shader cache: invalid vertex resource count"); + for (std::uint32_t i = 0; i < value.resourcesNum; ++i) { + append(key, value.resources[i].fields[1] & 0xffff0000u); + append(key, value.resources[i].fields[3]); + append(key, value.resourcesDst[i]); + } + } + + static void append(std::vector& key, const MeshTargetLimits& value) { + append(key, value.maxWorkgroupSize); + append(key, value.maxWorkgroupInvocations); + append(key, value.maxSharedMemoryBytes); + append(key, value.maxOutputVertices); + append(key, value.maxOutputPrimitives); + append(key, value.maxOutputComponents); + append(key, value.maxOutputMemoryBytes); + append(key, value.outputPerVertexGranularity); + append(key, value.outputPerPrimitiveGranularity); + } + + static void append(std::vector& key, const TessellationTargetLimits& value) { + append(key, value.maxPatchSize); + append(key, value.maxControlPerVertexInputComponents); + append(key, value.maxControlPerVertexOutputComponents); + append(key, value.maxControlPerPatchOutputComponents); + append(key, value.maxControlTotalOutputComponents); + append(key, value.maxEvaluationInputComponents); + append(key, value.maxEvaluationOutputComponents); + } + + static void append(std::vector& key, const SpirvTarget& value) { + append(key, value.vulkanVersion); + append(key, value.spirvVersion); + append(key, value.subgroupSize); + append(key, value.bdaAbiVersion); + append(key, value.supportedCapabilities); + append(key, value.supportedExtensions); + append(key, value.fragmentShaderBarycentricEnabled); + append(key, value.maxWorkgroupSize); + append(key, value.maxWorkgroupInvocations); + append(key, value.maxWorkgroupSharedMemoryBytes); + append(key, value.mesh); + append(key, value.tessellation); + } +}; + +} + +#endif diff --git a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp index 855d4c99b..be8e4e2f7 100644 --- a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp +++ b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp @@ -660,7 +660,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const std::string buffer; Writer writer(buffer); writer.WriteU32(0x41505335u); - writer.WriteU32(1u); + writer.WriteU32(2u); writeShaderBinary(writer, request.shader); writeGuestContext(writer, request.context); writeSpirvTarget(writer, request.target); @@ -669,13 +669,16 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const if (request.graphics.has_value()) { writeGraphicsCompileContext(writer, *request.graphics); } + writer.WriteBool(request.useCache); return base64Encode(buffer); } DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const { const std::string decoded = base64Decode(text); Reader reader(decoded); - if (reader.ReadU32() != 0x41505335u || reader.ReadU32() != 1u) throw std::runtime_error("unsupported recompile request serialization version"); + if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature"); + const auto version = reader.ReadU32(); + if (version != 1u && version != 2u) throw std::runtime_error("unsupported recompile request serialization version"); DeserializedRequest result{}; result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader); result.request.context = readGuestContext(reader, result); @@ -685,6 +688,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const result.graphicsStorage = std::make_unique(); result.request.graphics = readGraphicsCompileContext(reader, *result.graphicsStorage); } + if (version == 2u) result.request.useCache = reader.ReadBool(); return result; } diff --git a/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp b/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp index 43747d3af..f417cbbdf 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp @@ -10,6 +10,7 @@ namespace ShaderRecompiler { class DescriptorBindingBuilder { public: void Populate(BindingAllocationResult& allocation, const IrProgram& program, const ResourceSnapshot& snapshot) const; + void Populate(BindingAllocationResult& allocation, const ShaderInfo& info, IrShaderStage stage, std::uint32_t userDataBase, const ResourceSnapshot& snapshot) const; }; } diff --git a/core/shader/recompiler/Optimization/include/Optimization/ResourceProgram.hpp b/core/shader/recompiler/Optimization/include/Optimization/ResourceProgram.hpp index e14df52dd..7f02d33b8 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ResourceProgram.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ResourceProgram.hpp @@ -7,6 +7,7 @@ namespace ShaderRecompiler { [[nodiscard]] IrProgram PrepareResourceProgram(const RecompileRequest& request); +[[nodiscard]] std::shared_ptr GetResourcePlan(const RecompileRequest& request); } diff --git a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp index d163f230f..ba7c3fe4f 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -163,14 +163,14 @@ std::vector GuestSamplersDescriptor(const std::vector ShaderDataDwordsFor(const IrBindingLayout& layout, const IrResourcePlan& resources, const ResourceSnapshot& snapshot) { +std::vector ShaderDataDwordsFor(const IrBindingLayout& layout, std::uint32_t userDataBase, const ResourceSnapshot& snapshot) { std::vector result(layout.ShaderDataDwords(), 0u); for (std::size_t i = 0; i < layout.userDataRegisters.size(); i++) { const std::uint32_t reg = layout.userDataRegisters[i]; - if (reg < resources.userDataBase || reg - resources.userDataBase >= snapshot.userData.size()) { + if (reg < userDataBase || reg - userDataBase >= snapshot.userData.size()) { fail("DescriptorBindingBuilder::Populate user-data register is out of range"); } - result[i] = snapshot.userData[reg - resources.userDataBase]; + result[i] = snapshot.userData[reg - userDataBase]; } return result; } @@ -178,9 +178,12 @@ std::vector ShaderDataDwordsFor(const IrBindingLayout& layout, co } void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, const IrProgram& program, const ResourceSnapshot& snapshot) const { + Populate(allocation, program.Info(), program.Resources().stage, program.Resources().userDataBase, snapshot); +} + +void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, const ShaderInfo& info, IrShaderStage stage, std::uint32_t userDataBase, const ResourceSnapshot& snapshot) const { const IrBindingLayout& layout = allocation.layout; - const IrShaderStage stage = program.Resources().stage; - const std::vector shaderData = ShaderDataDwordsFor(layout, program.Resources(), snapshot); + const std::vector shaderData = ShaderDataDwordsFor(layout, userDataBase, snapshot); std::vector bindings; bindings.reserve(layout.descriptors.size()); @@ -199,12 +202,12 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con break; case DescriptorRole::GuestImages: physical.guestDescriptor = GuestImagesDescriptor(logical.resources, snapshot); - physical.imageShape = ImageShapeFor(program.Info().images, logical.resources); + physical.imageShape = ImageShapeFor(info.images, logical.resources); break; case DescriptorRole::GuestSamplers: physical.guestDescriptor = GuestSamplersDescriptor(logical.resources, snapshot); for (std::size_t element = 0; element < logical.resources.size(); ++element) { - const auto& sampler = program.Info().samplers.at(logical.resources[element]); + const auto& sampler = info.samplers.at(logical.resources[element]); physical.samplerDepthCompare.push_back(sampler.depthCompare); if (sampler.forcePointFiltering) { auto& filter = physical.guestDescriptor.at(element * 4u + 2u); diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index 7b4364931..2b9ef46a5 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -1,4 +1,8 @@ #include "Recompiler.hpp" +#include "CacheKey.hpp" +#include +#include +#include #include "ControlFlow/include/ControlFlow/GraphBuilder.hpp" #include "ControlFlow/include/ControlFlow/Structurizer.hpp" #include "RdnaDecoder/include/RdnaDecoder/RdnaInstructionDecoder.hpp" @@ -121,21 +125,76 @@ IrProgram PrepareResourceProgram(const RecompileRequest& request) { namespace { -RecompileResult RecompileImpl(const RecompileRequest& request) { +struct CompiledVariant { + ResourceSpecialization specialization; + BindingLayout layout; + CompiledShaderInfo info; + BindingAllocationResult bindings; + RecompileResult result; +}; - auto program = PrepareResourceProgram(request); +struct ResourceProgram { + explicit ResourceProgram(const RecompileRequest& request) : program(PrepareResourceProgram(request)), plan(ResourceMaterializer{}.ExtractPlan(program)) {} + + IrProgram program; + IrResourcePlan plan; +}; + +std::shared_ptr makeResourcePlan(const RecompileRequest& request) { + const auto resource = std::make_shared(request); + return std::shared_ptr(resource, &resource->plan); +} + +struct SourceEntry { + std::mutex mutex; + std::shared_ptr plan; + std::vector> variants; +}; + +struct SourceKeyHash { + std::size_t operator()(const std::vector& key) const { + std::size_t hash = 0; + for (const auto value : key) { + hash ^= static_cast(value) + static_cast(0x9e3779b97f4a7c15ull) + (hash << 6u) + (hash >> 2u); + if constexpr (sizeof(std::size_t) < sizeof(value)) hash ^= static_cast(value >> 32u); + } + return hash; + } +}; + +std::shared_ptr getSource(const RecompileRequest& request) { + static std::shared_mutex mutex; + static std::unordered_map, std::shared_ptr, SourceKeyHash> sources; + thread_local std::vector key; + RecompileCacheKey::Build(request, key); + std::shared_ptr source; + { + std::shared_lock lock(mutex); + const auto found = sources.find(key); + if (found != sources.end()) source = found->second; + } + if (source == nullptr) { + std::unique_lock lock(mutex); + const auto found = sources.find(key); + if (found != sources.end()) source = found->second; + else { + source = std::make_shared(); + sources.emplace(key, source); + } + } + { + std::lock_guard lock(source->mutex); + if (source->plan == nullptr) { + source->plan = makeResourcePlan(request); + } + } + return source; +} + +CompiledVariant compileVariant(const RecompileRequest& request, IrProgram program, const ResourceSnapshot& resourceSnapshot, const ResourceSpecialization& resourceSpecialization) { const auto inputInfo = BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context); constexpr DeadCodeEliminator deadCodeEliminator; - constexpr ResourceMaterializer resourceMaterializer; - const auto resourcePlan = resourceMaterializer.ExtractPlan(program); - - RequestMemoryView requestMemoryView(request.context.memory); - const auto srtRuntime = requestMemoryView.MakeRuntime(request.context.userData, request.shader.codeAddress); - - ResourceSnapshot resourceSnapshot; - ResourceSpecialization resourceSpecialization; - resourceMaterializer.Materialize(resourcePlan, srtRuntime, resourceSnapshot, resourceSpecialization); resourceMaterializer.Apply(program, resourceSpecialization); deadCodeEliminator.RemoveIdentities(program); @@ -167,8 +226,6 @@ RecompileResult RecompileImpl(const RecompileRequest& request) { #endif result.bdaAbiVersion = program.Info().usesDma ? request.target.bdaAbiVersion : 0u; - result.bindings = bindings.bindings; - result.pushConstants = bindings.pushConstants; result.vertexOffsetSgpr = program.Info().vertexOffsetSgpr; result.instanceOffsetSgpr = program.Info().instanceOffsetSgpr; for (const auto& output : program.Info().outputs) { @@ -184,9 +241,78 @@ RecompileResult RecompileImpl(const RecompileRequest& request) { } } + result.bindings.clear(); + result.pushConstants.clear(); + for (auto& attribute : result.vertexAttributes) attribute.resource = {}; + bindings.bindings.clear(); + bindings.pushConstants.clear(); + return {resourceSpecialization, request.layout, std::move(program).TakeCompiledInfo(), std::move(bindings), std::move(result)}; +} + +RecompileResult materializeResult(const CompiledVariant& variant, const RecompileRequest& request, const ResourceSnapshot& snapshot) { + auto result = variant.result; + BindingAllocationResult bindings; + bindings.layout = variant.bindings.layout; + bindings.pushConstantOffsetBytes = variant.bindings.pushConstantOffsetBytes; + bindings.pushConstantSizeBytes = variant.bindings.pushConstantSizeBytes; + DescriptorBindingBuilder{}.Populate(bindings, variant.info.info, variant.info.stage, variant.info.userDataBase, snapshot); + result.bindings = std::move(bindings.bindings); + result.pushConstants = std::move(bindings.pushConstants); + for (auto& attribute : result.vertexAttributes) { + if (!request.context.vertex || attribute.location >= request.context.vertex->resourcesNum) throw std::runtime_error("Shader cache: invalid vertex attribute metadata"); + attribute.resource = request.context.vertex->resources[attribute.location]; + } return result; } +bool sameLayout(const BindingLayout& left, const BindingLayout& right) { + return left.descriptorSet == right.descriptorSet && left.firstBinding == right.firstBinding && left.pushConstantOffsetBytes == right.pushConstantOffsetBytes && left.pushConstantSizeBytes == right.pushConstantSizeBytes; +} + +RecompileResult RecompileImpl(const RecompileRequest& request) { + static_cast(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context)); + RequestMemoryView memory(request.context.memory); + const auto runtime = memory.MakeRuntime(request.context.userData, request.shader.codeAddress); + ResourceSnapshot snapshot; + ResourceSpecialization specialization; + constexpr ResourceMaterializer materializer; + if (!request.useCache) { + auto program = PrepareResourceProgram(request); + const auto plan = materializer.ExtractPlan(program); + materializer.Materialize(plan, runtime, snapshot, specialization); + const auto variant = compileVariant(request, std::move(program), snapshot, specialization); + return materializeResult(variant, request, snapshot); + } + const auto source = getSource(request); + materializer.Materialize(*source->plan, runtime, snapshot, specialization); + std::shared_ptr variant; + bool cacheHit = false; + { + std::lock_guard lock(source->mutex); + for (const auto& candidate : source->variants) { + if (sameLayout(candidate->layout, request.layout) && candidate->specialization == specialization) { + variant = candidate; + cacheHit = true; + break; + } + } + if (variant == nullptr) { + auto program = PrepareResourceProgram(request); + variant = std::make_shared(compileVariant(request, std::move(program), snapshot, specialization)); + source->variants.push_back(variant); + } + } + auto result = materializeResult(*variant, request, snapshot); + result.cacheHit = cacheHit; + return result; +} + +} + +std::shared_ptr GetResourcePlan(const RecompileRequest& request) { + static_cast(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context)); + if (request.useCache) return getSource(request)->plan; + return makeResourcePlan(request); } RecompileResult Recompile(const RecompileRequest& request) { diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index d343a39fc..e160db406 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -198,6 +198,7 @@ struct RecompileRequest { SpirvTarget target; BindingLayout layout; std::optional graphics; + bool useCache = true; }; enum class DescriptorKind { @@ -265,6 +266,7 @@ struct RecompileResult { std::int32_t instanceOffsetSgpr = -1; std::vector parameterExports; std::vector fragmentParameters; + bool cacheHit = false; }; [[nodiscard]] RecompileResult Recompile(const RecompileRequest& request); diff --git a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp index eb9ef6cc5..1aaf5e1ef 100644 --- a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp +++ b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp @@ -132,6 +132,7 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues throw std::runtime_error("ShaderInputInfoBuilder: GuestContext.vertex is not set"); } const auto& vertex = *context.vertex; + if (vertex.resourcesNum > vertex.resources.size()) throw std::runtime_error("ShaderInputInfoBuilder: invalid vertex resource count"); vertexStorage = ShaderVertexInputInfo{}; vertexStorage.logicalStage = _toIrShaderStage(stage); vertexStorage.fetchEmbedded = vertex.fetchEmbedded;