From 4f8c8b127f2cb39f2fea6e1a97dc7929ea6cdb90 Mon Sep 17 00:00:00 2001 From: Scott Schuster Date: Thu, 15 Oct 2026 15:18:39 -0500 Subject: [PATCH] perf(agc): fewer heap allocations per recorded draw The committer built a recorded draw's resource key through two temporaries a shader, took the in-place and device read lists by value, and in PrepareDrawBindings built two layout-binding vectors only to test them for emptiness, a std::map of pool sizes a set, and copies of the descriptor buffer and write lists, all on every draw. The key is now built in place with its size reserved, the read lists fill a caller's vector, and the per-draw bookkeeping lives in the thread's scratch (ThreadOwned), as the selected-slot list already did. The queue worker's program list and memory snapshots reserve their size up front. Co-Authored-By: Claude Fable 5.1 --- .../Graphics/include/GuestBufferMemory.hpp | 2 + .../Graphics/include/ShaderResources.hpp | 3 ++ .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 17 ++++-- .../Graphics/src/GuestBufferMemory.cpp | 22 +++++--- .../Graphics/src/ShaderResources.cpp | 54 ++++++++++++------- 5 files changed, 67 insertions(+), 31 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp index 7f932aad60..009a5f1d05 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp @@ -302,12 +302,14 @@ public: // the GPU copies out of an import (gpuCopy) notes its read itself when the copy is recorded. // For the recorder's read tracking (ShaderResources::MarkGpuWrites); nothing once committed. std::vector> InPlaceReads() const; + void InPlaceReads(std::vector>& out) const; struct SplitReads { std::shared_ptr>> space; std::vector> own; }; std::optional SplitInPlaceReads() const; std::vector> DeviceReads() const; + void DeviceReads(std::vector>& out) const; private: struct Region { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index efefda3a7f..0f8cf6c5f1 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -192,8 +192,10 @@ public: // ranges, the regions read in place, and the storage images with whether each is written. const std::vector>& GpuWrites() const { return guestMemory.Writes(); } std::vector> InPlaceReads() const { return guestMemory.InPlaceReads(); } + void InPlaceReads(std::vector>& out) const { guestMemory.InPlaceReads(out); } std::vector> StorageImages() const; std::vector> DeviceReads() const { return guestMemory.DeviceReads(); } + void DeviceReads(std::vector>& out) const { guestMemory.DeviceReads(out); } bool ReadsImage(const StorageTexture* image) const; bool HasCachedStorageImages() const; std::uint32_t TableElements() const { return tableElements[0] + tableElements[1]; } @@ -214,6 +216,7 @@ public: // (their count and size remain): a compute template then serves dispatches whose constants // differ, and the hit refreshes its data buffers with the dispatch's words (RefreshData). static std::vector ContentKey(const CompiledShader& shader, bool dataWords = true, bool movableBuffers = false); + static void AppendContentKey(const CompiledShader& shader, bool dataWords, bool movableBuffers, std::vector& key); static bool DiffContentKeys(std::span previous, std::span key, bool draw, ContentKeyDiff& diff); // Records the shader's ShaderData and FlattenedSrt words into this object's data buffers // (vkCmdUpdateBuffer, a transfer write the caller's pre-dispatch barrier makes visible; a diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 7fb4593c6e..717f37eb6b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -17,6 +17,7 @@ #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "Optimization/include/Optimization/ShaderStageInputInfo.hpp" +#include "ThreadOwned.hpp" #include #include #include @@ -737,7 +738,9 @@ bool MovableBuffers() { } ResourceCache::Key DrawResourceKey(const Context& context, std::span shaders, const ColorTarget& target, std::uint32_t colorAttachments, std::uint64_t indexAddress, std::uint64_t indexBytes, bool ranges) { - ResourceCache::Key key{0xffffffffu}; + ResourceCache::Key key; + key.reserve(256); + key.push_back(0xffffffffu); const auto append64 = [&](std::uint64_t value) { key.push_back(static_cast(value)); key.push_back(static_cast(value >> 32u)); @@ -745,9 +748,10 @@ ResourceCache::Key DrawResourceKey(const Context& context, std::span(context.device)); key.push_back(static_cast(shaders.size())); for (const auto& shader : shaders) { - const auto part = ShaderResources::ContentKey(shader, true, MovableBuffers()); - key.push_back(static_cast(part.size())); - key.insert(key.end(), part.begin(), part.end()); + const auto at = key.size(); + key.push_back(0); + ShaderResources::AppendContentKey(shader, true, MovableBuffers(), key); + key[at] = static_cast(key.size() - at - 1); } key.push_back(colorAttachments); if (ranges) { @@ -1737,7 +1741,10 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam const bool capture = CaptureInputsEnabled(); const bool meshIndirect = state.stages.mesh && args != nullptr; const bool addressBased = resources.HoldsLease(); - auto passReads = addressBased ? std::vector>{} : resources.DeviceReads(); + thread_local std::vector>* passReadsSlot = nullptr; + auto& passReads = ShaderRecompiler::ThreadOwned(passReadsSlot); + passReads.clear(); + if (!addressBased) resources.DeviceReads(passReads); if (gpuIndirect) { passReads.emplace_back(args->arguments, args->arguments + args->RangeBytes()); if (args->countIndirect) passReads.emplace_back(args->countAddress, args->countAddress + 4); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index fcfb60616a..eac62eab58 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -4486,9 +4486,9 @@ std::uint64_t GuestBufferMemory::inPlaceEnd(const Region& region) { return region.direct != nullptr && region.runCut ? std::min(region.end, region.direct->base + region.direct->bytes) : region.end; } -std::vector> GuestBufferMemory::InPlaceReads() const { - std::vector> result; - if (!uploaded || committed) return result; +void GuestBufferMemory::InPlaceReads(std::vector>& result) const { + result.clear(); + if (!uploaded || committed) return; if (space != nullptr) { for (const auto& region : space->base) { if (region.direct != nullptr) result.emplace_back(region.begin, region.end); @@ -4497,6 +4497,11 @@ std::vector> GuestBufferMemory::InPlaceR for (const auto& region : regions) { if (region.direct != nullptr) result.emplace_back(region.begin, inPlaceEnd(region)); } +} + +std::vector> GuestBufferMemory::InPlaceReads() const { + std::vector> result; + InPlaceReads(result); return result; } @@ -4509,9 +4514,9 @@ std::optional GuestBufferMemory::SplitInPlaceRead return split; } -std::vector> GuestBufferMemory::DeviceReads() const { - std::vector> result; - if (!uploaded || committed) return result; +void GuestBufferMemory::DeviceReads(std::vector>& result) const { + result.clear(); + if (!uploaded || committed) return; if (space != nullptr) { for (const auto& region : space->base) { if (region.direct != nullptr || region.mirror != nullptr) result.emplace_back(region.begin, region.end); @@ -4520,6 +4525,11 @@ std::vector> GuestBufferMemory::DeviceRe for (const auto& region : regions) { if (region.direct != nullptr || region.mirror != nullptr) result.emplace_back(region.begin, region.end); } +} + +std::vector> GuestBufferMemory::DeviceReads() const { + std::vector> result; + DeviceReads(result); return result; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index e31084e0c4..866f1ace2a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -2176,9 +2176,14 @@ void VisitContentKey(const CompiledShader& shader, bool dataWords, bool movableB } std::vector ShaderResources::ContentKey(const CompiledShader& shader, bool dataWords, bool movableBuffers) { - Require(shader.program != nullptr, "missing compiled shader"); std::vector key; - std::size_t words = 5; + AppendContentKey(shader, dataWords, movableBuffers, key); + return key; +} + +void ShaderResources::AppendContentKey(const CompiledShader& shader, bool dataWords, bool movableBuffers, std::vector& key) { + Require(shader.program != nullptr, "missing compiled shader"); + std::size_t words = key.size() + 5; const auto addWords = [&](std::size_t count) { if (count > key.max_size() - words) throw std::length_error("resource content key exceeds vector limits"); words += count; @@ -2192,7 +2197,6 @@ std::vector ShaderResources::ContentKey(const CompiledShader& sha } key.reserve(words); VisitContentKey(shader, dataWords, movableBuffers, [&](std::uint32_t word) { key.push_back(word); }); - return key; } namespace { @@ -4502,7 +4506,9 @@ std::size_t ShaderResources::MaxDrawSnapshotBytes() { std::shared_ptr ShaderResources::PrepareDrawBindings(Recorder& recorder, std::span moved) const { if ((_set == VK_NULL_HANDLE && fragment.set == VK_NULL_HANDLE) || usesBda) return {}; - const auto reads = guestMemory.InPlaceReads(); + thread_local std::vector>* readsSlot = nullptr; + auto& reads = ShaderRecompiler::ThreadOwned(readsSlot); + guestMemory.InPlaceReads(reads); const auto maxSnapshotBytes = maxDrawSnapshotBytes; std::size_t totalSnapshotBytes = 0; auto result = std::make_shared(); @@ -4579,35 +4585,43 @@ std::shared_ptr ShaderResources::PrepareDrawBindi } if (selected.empty()) return {}; Require(context.descriptorCache != nullptr, "draw snapshots require a descriptor cache"); - std::array, 2> descriptions; - std::array, 2> counts; + std::array described{}; + thread_local std::array, 2>* countsSlot = nullptr; + auto& counts = ShaderRecompiler::ThreadOwned(countsSlot); + for (auto& sizes : counts) sizes.clear(); for (const auto& binding : bindings) { const auto set = setOf(binding); - descriptions[set].push_back(binding.layout); - counts[set][binding.layout.descriptorType] += (binding.flags & VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT) != 0 ? std::max(1u, static_cast(binding.imageAllocations.size())) : binding.layout.descriptorCount; + described[set] = true; + const auto count = (binding.flags & VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT) != 0 ? std::max(1u, static_cast(binding.imageAllocations.size())) : binding.layout.descriptorCount; + if (count == 0) continue; + auto& sizes = counts[set]; + const auto held = std::find_if(sizes.begin(), sizes.end(), [&](const VkDescriptorPoolSize& entry) { return entry.type == binding.layout.descriptorType; }); + if (held != sizes.end()) held->descriptorCount += count; + else sizes.push_back({binding.layout.descriptorType, count}); } - const auto sizesOf = [&](std::size_t set) { - std::vector sizes; - for (const auto& [type, count] : counts[set]) { - if (count != 0) sizes.push_back({type, count}); - } - return sizes; - }; + for (auto& sizes : counts) std::sort(sizes.begin(), sizes.end(), [](const VkDescriptorPoolSize& a, const VkDescriptorPoolSize& b) { return a.type < b.type; }); + const auto sizesOf = [&](std::size_t set) { return std::span(counts[set]); }; const auto variableOf = [&](std::size_t set) { return tableBindings[set] != std::numeric_limits::max() ? std::optional(std::max(1u, static_cast(bindings[tableBindings[set]].imageAllocations.size()))) : std::nullopt; }; result->cache = context.descriptorCache; - if (!descriptions[0].empty() || splitSets) { + if (described[0] || splitSets) { result->allocation = result->cache->AllocateRewritten(_layout, sizesOf(0), updateAfterBind, variableOf(0)); result->layout = _layout; Require(result->allocation.set != VK_NULL_HANDLE, "draw snapshot descriptor allocation failed"); } - if (!descriptions[1].empty() || splitSets) { + if (described[1] || splitSets) { result->fragmentAllocation = result->cache->AllocateRewritten(fragment.layout, sizesOf(1), updateAfterBind, variableOf(1)); result->fragmentLayout = fragment.layout; Require(result->fragmentAllocation.set != VK_NULL_HANDLE, "draw snapshot descriptor allocation failed"); } - auto buffers = descriptorBuffers; - auto writes = descriptorWrites; - std::vector snapshotIndices(allocations.size(), selected.size()); + thread_local std::vector* buffersSlot = nullptr; + auto& buffers = ShaderRecompiler::ThreadOwned(buffersSlot); + buffers = descriptorBuffers; + thread_local std::vector* writesSlot = nullptr; + auto& writes = ShaderRecompiler::ThreadOwned(writesSlot); + writes = descriptorWrites; + thread_local std::vector* indicesSlot = nullptr; + auto& snapshotIndices = ShaderRecompiler::ThreadOwned(indicesSlot); + snapshotIndices.assign(allocations.size(), selected.size()); for (std::size_t index = 0; index < selected.size(); ++index) snapshotIndices[selected[index]] = index; auto nextWrite = writes.begin(); for (const auto& binding : bindings) {