From 7fcf715559ebb8faed69260ee62b49b3b6015a71 Mon Sep 17 00:00:00 2001 From: EdouardSence <103503538+EdouardSence@users.noreply.github.com> Date: Wed, 21 Oct 2026 12:24:43 +0200 Subject: [PATCH 1/2] perf(shader): reuse an evicted memo result's bindings for the next miss A result memo miss now takes the least recently used entry of its set before materializing when the memo holds the only reference to it, and Populate copy-assigns the new bindings into that result's binding elements, so their vectors keep their storage instead of being freed and allocated again on every miss. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAgcDriver/tests/ShaderDiskCache.cpp | 3 +++ .../Optimization/src/DescriptorBindingBuilder.cpp | 12 ++++++------ core/shader/recompiler/Recompiler.cpp | 15 +++++++++++++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index 8ef8dfde37b7..022c3c002fe4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -1375,6 +1375,9 @@ void verifyBindingPlanSelection() { expected.guestDescriptor[0] += 0x10000u; expected.guestDescriptor[6] = 128u; require(sameBinding(expected, populated.bindings[0]) && sameBinding(full.bindings[3], populated.bindings[1]), "selected plan changed binding order or reused stale buffer data"); + BindingAllocationResult reused = full; + builder.Populate(reused, compiled, selected, 0u, snapshot, {}); + require(sameBindings(reused.bindings, populated.bindings) && reused.pushConstants.empty(), "a reused allocation kept bindings or metadata of its previous plan"); bool rejected = false; try { static_cast(builder.Select(plan, std::array{UINT32_MAX})); } catch (const std::runtime_error&) { rejected = true; } diff --git a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp index 170b97d6b8d0..f3286274330e 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -566,12 +566,14 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con if (needsShaderData) { for (const auto reg : layout.userDataRegisters) allocation.shaderDataUserWords.push_back(reg - userDataBase); } - std::vector bindings; - bindings.reserve(plan.bindings.size()); + auto bindings = std::move(allocation.bindings); + bindings.resize(plan.bindings.size()); std::size_t writtenHere = 0; std::size_t readOnlyHere = 0; - for (const auto& entry : plan.bindings) { - auto physical = entry.descriptor; + for (std::size_t index = 0; index < plan.bindings.size(); ++index) { + const auto& entry = plan.bindings[index]; + auto& physical = bindings[index]; + physical = entry.descriptor; switch (physical.role) { case DescriptorRole::GuestBuffers: physical.guestDescriptor = GuestBuffersDescriptor(entry.resources, snapshot); @@ -654,8 +656,6 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con fail("DescriptorBindingBuilder::Populate guest descriptor size is not a multiple of the binding count"); } } - - bindings.push_back(std::move(physical)); } if (bufferWrittenTraceEnabled()) { diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index ad94823d644c..02baf1a9ff2a 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -979,7 +979,7 @@ std::shared_ptr specializeModule(const CompiledShaderAr return entry->module; } -RecompileResult materializeResult(const CompiledVariant& variant, const RecompileRequest& request, const ResourceSnapshot& snapshot, bool registrationDefaults = false) { +RecompileResult materializeResult(const CompiledVariant& variant, const RecompileRequest& request, const ResourceSnapshot& snapshot, bool registrationDefaults = false, std::vector reuse = {}) { RecompileResult result; const auto& artifact = variant.artifact; static_cast(result) = { @@ -1122,6 +1122,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil const auto resolved = ResourceMaterializer::ResolveImageTablesShared(variant.info.info, snapshot, artifact.variantId); const auto& tables = *resolved; BindingAllocationResult bindings; + bindings.bindings = std::move(reuse); DescriptorBindingBuilder{}.Populate(bindings, variant.bindings, *bindingPlan, variant.info.userDataBase, snapshot, partialThreads(request), &tables); result.workgroupMemoryDwords = WorkgroupMemoryStrideDwords(variant.info.info); result.bindings = std::move(bindings.bindings); @@ -1418,12 +1419,15 @@ std::shared_ptr materializePreparedMemoized(SourceEntry& const auto hash = snapshotHash(request, snapshot); std::uint64_t index = 0; auto& counters = resultMemoCounters(); + std::shared_ptr recycled; { std::lock_guard lock(source.mutex); index = (variant->artifact.variantId * 0x9e3779b97f4a7c15ull) ^ hash; if (auto* set = memoSet(source, index)) { + auto* victim = set; for (std::size_t way = 0; way < ResultMemoWays; ++way) { auto& entry = set[way]; + if (entry.used < victim->used) victim = &entry; if (entry.result == nullptr || entry.variantId != variant->artifact.variantId || entry.hash != hash) continue; entry.used = ++source.memoClock; counters.hits.fetch_add(1, std::memory_order_relaxed); @@ -1431,10 +1435,17 @@ std::shared_ptr materializePreparedMemoized(SourceEntry& reportResultMemo(); return entry.result; } + if (victim->result != nullptr && victim->result.use_count() == 1) { + recycled = std::move(victim->result); + *victim = {}; + counters.evictions.fetch_add(1, std::memory_order_relaxed); + } } } + std::vector reuse; + if (recycled != nullptr) reuse = std::move(const_cast(*recycled).bindings); const auto started = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; - auto result = std::make_shared(materializeResult(*variant, request, snapshot)); + auto result = std::make_shared(materializeResult(*variant, request, snapshot, false, std::move(reuse))); result->cacheHit = cacheHit; if (profile) counters.populateNanoseconds.fetch_add(static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - started).count()), std::memory_order_relaxed); counters.misses.fetch_add(1, std::memory_order_relaxed); From edc95643a1f40c5fa66f8d3327bea43a277a6d51 Mon Sep 17 00:00:00 2001 From: EdouardSence <103503538+EdouardSence@users.noreply.github.com> Date: Thu, 22 Oct 2026 09:53:13 +0200 Subject: [PATCH 2/2] perf(shader): fill guest buffer and sampler descriptors in the reused binding's storage Co-Authored-By: Claude Opus 5.5 --- .../Optimization/src/DescriptorBindingBuilder.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp index f3286274330e..040a2575285c 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -118,8 +118,8 @@ DescriptorImageShape ImageShapeForResource(const ImageResource& image) { fail("DescriptorBindingBuilder::Populate image resource dimension has no descriptor image shape"); } -std::vector GuestBuffersDescriptor(const std::vector& resources, const ResourceSnapshot& snapshot) { - std::vector result; +void GuestBuffersDescriptor(std::vector& result, const std::vector& resources, const ResourceSnapshot& snapshot) { + result.clear(); result.reserve(resources.size() * 4u); for (const std::uint32_t r : resources) { if (r >= snapshot.buffers.size()) { @@ -133,11 +133,10 @@ std::vector GuestBuffersDescriptor(const std::vector GuestSamplersDescriptor(const std::vector& resources, const ResourceSnapshot& snapshot) { - std::vector result; +void GuestSamplersDescriptor(std::vector& result, const std::vector& resources, const ResourceSnapshot& snapshot) { + result.clear(); std::uint32_t dwordCount = 0; for (std::size_t i = 0; i < resources.size(); i++) { const std::uint32_t r = resources[i]; @@ -157,7 +156,6 @@ std::vector GuestSamplersDescriptor(const std::vector