diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp index 01a9c04191..ea21ef4638 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp @@ -184,6 +184,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 9b22e6a5b5..416b9d29e4 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 { @@ -411,9 +412,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 66ec1aa305..62ce062d8c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp @@ -350,6 +350,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(); @@ -1168,6 +1194,7 @@ int main(int argc, char** argv) { Expect(mock.liveBytes == 0, "slab return test leaked memory"); SizeClassesAndDirections(); LargerClass(); + ExactSizeReuse(); KeptUntilFence(); SmallDeviceRetention(); Budget();