From ad25c9ee1156cbad1a6591f2bbb74c53cf171695 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Wed, 21 Oct 2026 05:59:34 +0200 Subject: [PATCH] perf(agc): prefer a pooled buffer of the requested exact size A pooled buffer is retained by size class, and a hit whose VkBuffer was created for another exact size destroys it and binds a new one to the same memory (BindExactBuffer). Copying draw inputs on NVIDIA's Windows driver takes one such buffer per index or vertex range per draw, so most hits in that path pay a vkDestroyBuffer, vkCreateBuffer and bind. Take now scans the newest 64 slots of the class for one whose buffer has the requested size and takes the newest slot as before when none has. The memory class, usage and properties are the same, so only the choice among equivalent slots changes. AI-assisted: yes --- .../Graphics/include/BufferPool.hpp | 1 + .../Graphics/src/BufferPool.cpp | 12 ++++++--- .../prx/libSceAgcDriver/tests/BufferPool.cpp | 27 +++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp index 657c5351c4..22b8c85f69 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp @@ -178,6 +178,7 @@ private: static constexpr std::size_t classLimit = std::size_t{1} << 20u; static constexpr unsigned deviceClassBits = 3; static constexpr std::size_t defaultSlots = 4096; + static constexpr std::size_t exactSizeScan = 64; }; std::shared_ptr GetBufferPool(const Context& context); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp index 128970a898..eef879399f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace AgcDriver::Graphics { @@ -409,9 +410,14 @@ std::optional BufferPool::Take(std::size_t bytes, VkBufferUsag ++tier.misses; return std::nullopt; } - auto result = found->second.back().allocation; - found->second.pop_back(); - if (found->second.empty()) tier.free.erase(found); + auto& slots = found->second; + const auto newest = std::prev(slots.end()); + auto chosen = newest; + for (std::size_t scanned = 1; chosen->allocation.bufferBytes != bytes && scanned < exactSizeScan && chosen != slots.begin(); ++scanned) --chosen; + if (chosen->allocation.bufferBytes != bytes) chosen = newest; + auto result = chosen->allocation; + slots.erase(chosen); + if (slots.empty()) tier.free.erase(found); --tier.slots; tier.retainedBytes -= result.allocationBytes; ++tier.hits; diff --git a/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp b/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp index abdf8f0166..4d63ee7125 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp @@ -349,6 +349,32 @@ void LargerClass() { Expect(MemoryOf(best.Handle()) != twelve && ClassOf(best.Handle()) == 5 * MiB, "a 4 MiB + 1 request did not take the smallest retained class that fits (5 MiB)"); } +void ExactSizeReuse() { + mock = MockDevice{}; + { + auto context = mockContext(); + VkBuffer first = VK_NULL_HANDLE; + VkBuffer second = VK_NULL_HANDLE; + { + auto a = std::make_unique(context, 3000, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, HostVisible); + auto b = std::make_unique(context, 4000, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, HostVisible); + first = a->Handle(); + second = b->Handle(); + a.reset(); + b.reset(); + } + const auto destroyed = mock.destroyedBuffers; + { + Buffer again(context, 3000, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, HostVisible); + Expect(again.Handle() == first && mock.destroyedBuffers == destroyed, "a pooled buffer of the requested exact size was passed over for a newer one that had to be rebuilt"); + Expect(mock.sizes.at(again.Handle()) == 3000 && again.Bytes().size() == 3000, "a reused buffer does not have the requested size"); + } + Buffer other(context, 4000, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, HostVisible); + Expect(other.Handle() == second && mock.sizes.at(other.Handle()) == 4000, "the other exact size was not kept for its own request"); + } + Expect(mock.liveBytes == 0 && mock.liveBuffers.empty(), "exact-size reuse leaked Vulkan objects"); +} + void KeptUntilFence() { mock = MockDevice{}; auto context = mockContext(); @@ -1019,6 +1045,7 @@ int main(int argc, char** argv) { Expect(mock.liveBytes == 0, "slab return test leaked memory"); SizeClassesAndDirections(); LargerClass(); + ExactSizeReuse(); KeptUntilFence(); SmallDeviceRetention(); Budget();