From e801dcd9e9e81fb0f2295de19f9ab106db897590 Mon Sep 17 00:00:00 2001 From: ionut27001 <194206737+ionut27001@users.noreply.github.com> Date: Mon, 19 Oct 2026 11:09:56 +0300 Subject: [PATCH 1/2] fix(agc): let a kept descriptor set's table record hold its textures weakly A freed dedicated set kept for reuse recorded every texture of the resources that freed it and kept them alive, so the kept handles could not be reused while the set referred to them. Up to eight kept sets held textures the caches had already evicted: their memory stayed in use outside the caches' budgets, and a title with large image tables ran out of device memory. The record now holds the textures weakly. A build compares the kept table's views only while every recorded texture is still alive, so a matching handle is still the same view; when one has died the table is written in full, as for any other change. --- .../Graphics/include/ShaderResources.hpp | 4 ++-- .../Graphics/src/ShaderResources.cpp | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 33918639b7..ea7e85ea54 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -72,7 +72,7 @@ struct TableDescriptors { std::uint32_t binding = 0; std::vector> runs; std::vector images; - std::shared_ptr>> textures; + std::shared_ptr>> textures; }; class DescriptorCache { @@ -569,7 +569,7 @@ private: std::vector> runs; }; std::array tableWrites; - std::shared_ptr keptTable(std::size_t set, std::shared_ptr>>& held) noexcept; + std::shared_ptr keptTable(std::size_t set, std::shared_ptr>>& held) noexcept; std::uint32_t setOf(const Binding& binding) const; std::vector allocations; std::vector descriptorBuffers; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 7d3a64775f..16e1e904c9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -2226,7 +2226,8 @@ void ShaderResources::buildComplete() { if (!recorded) continue; const auto& held = allocation.table; auto& counters = TextureCounts(); - if (held != nullptr && held->binding == binding.layout.binding && held->runs == runs && std::equal(held->images.begin(), held->images.end(), images.begin() + static_cast(firstImage), images.end(), [](const VkDescriptorImageInfo& a, const VkDescriptorImageInfo& b) { return a.sampler == b.sampler && a.imageView == b.imageView && a.imageLayout == b.imageLayout; })) { + const bool heldAlive = held != nullptr && held->textures != nullptr && std::none_of(held->textures->begin(), held->textures->end(), [](const std::weak_ptr& texture) { return texture.expired(); }); + if (heldAlive && held->binding == binding.layout.binding && held->runs == runs && std::equal(held->images.begin(), held->images.end(), images.begin() + static_cast(firstImage), images.end(), [](const VkDescriptorImageInfo& a, const VkDescriptorImageInfo& b) { return a.sampler == b.sampler && a.imageView == b.imageView && a.imageLayout == b.imageLayout; })) { keptWrites.emplace_back(firstWrite, writes.size()); counters.tableWritesKept.fetch_add(1, std::memory_order_relaxed); } else { @@ -4960,11 +4961,18 @@ ShaderResources::~ShaderResources() { release(); } -std::shared_ptr ShaderResources::keptTable(std::size_t set, std::shared_ptr>>& held) noexcept { +std::shared_ptr ShaderResources::keptTable(std::size_t set, std::shared_ptr>>& held) noexcept { const auto& table = tableWrites[set]; if (!table.written || KeptSets() == 0u || table.firstImage + table.images > descriptorImages.size()) return nullptr; try { - if (held == nullptr) held = std::make_shared>>(std::move(textures)); + if (held == nullptr) { + std::vector> weak; + weak.reserve(textures.size()); + for (const auto& texture : textures) { + if (texture != nullptr) weak.push_back(texture); + } + held = std::make_shared>>(std::move(weak)); + } auto kept = std::make_shared(); kept->binding = table.binding; kept->runs = table.runs; @@ -4978,7 +4986,7 @@ std::shared_ptr ShaderResources::keptTable(std::size_t s void ShaderResources::release() noexcept { // A set from the cache's pool chain goes back to it; a dedicated pool dies with its set. - std::shared_ptr>> held; + std::shared_ptr>> held; if (cacheAllocation.pool && context.descriptorCache != nullptr) context.descriptorCache->Free(cacheAllocation, keptTable(0, held)); if (pool) context.Function("vkDestroyDescriptorPool")(context.device, pool, nullptr); if (_layout && ownsLayout) context.Function("vkDestroyDescriptorSetLayout")(context.device, _layout, nullptr); From 15c6c9ff157bfc78a26be552f53698abeeb53003 Mon Sep 17 00:00:00 2001 From: ionut27001 <194206737+ionut27001@users.noreply.github.com> Date: Mon, 19 Oct 2026 12:43:16 +0300 Subject: [PATCH 2/2] test(agc): check that kept table records free their textures and that a kept table whose textures died is written again --- .../tests/execution/ImageTableReuse.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableReuse.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableReuse.cpp index d5a6b46847..a46aeb00be 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableReuse.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableReuse.cpp @@ -330,6 +330,20 @@ void RunTests(AgcDriver::VulkanDevice& device, const GuestBlock& block, const Gu Require(Dispatch(device, result, 15) == Row(block.Texels(15)), "image table reuse: the table after a dispatch wrote an entry sampled the wrong entry"); RunImageStore(device, stores); + + AgcDriver::Graphics::ReleaseTextureCaches(~std::uint64_t{0}); + Require(AgcDriver::Graphics::SampledTextureMemory() == 0u, "image table reuse: " + std::to_string(AgcDriver::Graphics::SampledTextureMemory()) + " bytes of sampled textures outlived a full release of the texture caches after the kept-table steps"); + + AgcDriver::GuestMemory::BumpCollectEpoch(); + Require(Dispatch(device, result, 29) == Row(block.Texels(29)), "image table reuse: the table before its textures were destroyed sampled the wrong entry"); + before = AgcDriver::Graphics::ImageTableReuses(); + Require(Dispatch(device, result, 29) == Row(block.Texels(29)), "image table reuse: the table built again before its textures were destroyed sampled the wrong entry"); + ExpectWrites(before, 1, 0, "a kept table built again before its textures were destroyed"); + AgcDriver::Graphics::ReleaseTextureCaches(~std::uint64_t{0}); + Require(AgcDriver::Graphics::SampledTextureMemory() == 0u, "image table reuse: " + std::to_string(AgcDriver::Graphics::SampledTextureMemory()) + " bytes of sampled textures outlived a full release of the texture caches after a table's set was kept"); + before = AgcDriver::Graphics::ImageTableReuses(); + Require(Dispatch(device, result, 29) == Row(block.Texels(29)), "image table reuse: a kept table whose textures were destroyed sampled the wrong entry"); + ExpectWrites(before, 0, 1, "a kept table whose textures were destroyed"); } }