From c1dfb309a13d10f612fb4126f9fadd2806db0e35 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 25 Oct 2026 20:56:32 +0100 Subject: [PATCH] graphics: drop image heap table snapshots superseded at the same base --- .../host_gpu/renderer/pipeline/imageHeap.cpp | 46 +++++++++++++------ .../host_gpu/renderer/pipeline/imageHeap.h | 6 ++- .../host_gpu/renderer/renderContext.cpp | 1 + tests/ShaderRecompilerComputeTests.cpp | 11 +++++ 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/graphics/host_gpu/renderer/pipeline/imageHeap.cpp b/src/graphics/host_gpu/renderer/pipeline/imageHeap.cpp index 0258f4256..7d4e5189e 100644 --- a/src/graphics/host_gpu/renderer/pipeline/imageHeap.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/imageHeap.cpp @@ -526,26 +526,46 @@ void ImageHeap::UnmapMemory(uint64_t address, uint64_t size) { } else { m_unmapped.push_back({address, size}); } - auto& scheduler = m_context.GetCommandScheduler(); std::scoped_lock lock {m_mutex}; std::erase_if(m_memories, [&](const std::unique_ptr& memory) { if (address >= memory->base + memory->size || memory->base >= address + size) { return false; } - Unwatch(*memory); - for (auto& view: memory->views) { - std::erase(m_active, view.get()); - for (const auto& [value, index]: view->elements) { - Unreference(m_elements[index]); - view->released.push_back(index); - } - Retire(*view); - if (scheduler.Active()) { - scheduler.DeferOperation([buffer = std::move(view->buffer)] {}); - } - } + Drop(*memory); return true; }); } +void ImageHeap::RunGarbageCollector() { + // A table whose descriptor grew keeps its old snapshot until a frame passes without it. + std::scoped_lock lock {m_mutex}; + std::vector acquired; + for (const auto& memory: m_memories) { + if (memory->draw > m_frame_draw) acquired.push_back(memory->base); + } + std::erase_if(m_memories, [&](const std::unique_ptr& memory) { + const bool superseded = memory->draw <= m_frame_draw && + std::ranges::find(acquired, memory->base) != acquired.end(); + if (superseded) Drop(*memory); + return superseded; + }); + m_frame_draw = m_draw; +} + +void ImageHeap::Drop(Memory& memory) { + auto& scheduler = m_context.GetCommandScheduler(); + Unwatch(memory); + for (auto& view: memory.views) { + std::erase(m_active, view.get()); + for (const auto& [value, index]: view->elements) { + Unreference(m_elements[index]); + view->released.push_back(index); + } + Retire(*view); + if (scheduler.Active()) { + scheduler.DeferOperation([buffer = std::move(view->buffer)] {}); + } + } +} + } // namespace Libs::Graphics diff --git a/src/graphics/host_gpu/renderer/pipeline/imageHeap.h b/src/graphics/host_gpu/renderer/pipeline/imageHeap.h index 41aef7333..2256404ee 100644 --- a/src/graphics/host_gpu/renderer/pipeline/imageHeap.h +++ b/src/graphics/host_gpu/renderer/pipeline/imageHeap.h @@ -46,6 +46,8 @@ public: // CPU write fault, on any thread. void InvalidateMemory(uint64_t address, uint64_t size); void UnmapMemory(uint64_t address, uint64_t size); + // Frame boundary: drops table snapshots that another at the same base replaced this frame. + void RunGarbageCollector(); private: using DescriptorValue = ShaderRecompiler::IR::DescriptorValue; @@ -113,6 +115,7 @@ private: Image* ImageOf(const Element& element) const; uint32_t Allocate(); void Retire(View& view); + void Drop(Memory& memory); void WriteDescriptors(); RenderContext& m_context; @@ -120,7 +123,8 @@ private: std::mutex m_mutex; // Fault-visible Memory state and m_memories. std::vector> m_memories; std::vector m_active; // Acquired since the last commit. - uint64_t m_draw = 1; + uint64_t m_draw = 1; + uint64_t m_frame_draw = 0; // Last draw before this frame. std::vector m_elements; std::vector m_free; std::deque> m_retired; // Tick, element. diff --git a/src/graphics/host_gpu/renderer/renderContext.cpp b/src/graphics/host_gpu/renderer/renderContext.cpp index 83db56606..bfabf608e 100644 --- a/src/graphics/host_gpu/renderer/renderContext.cpp +++ b/src/graphics/host_gpu/renderer/renderContext.cpp @@ -148,6 +148,7 @@ void RenderContext::RunGarbageCollector() { m_buffer_cache.ProcessFaultBuffer(); } m_texture_cache.ProcessDownloadImages(); + m_image_heap.RunGarbageCollector(); m_texture_cache.RunGarbageCollector(); m_buffer_cache.RunGarbageCollector(); } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index f79a53e22..6df80f7e5 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -5511,6 +5511,17 @@ public: ImageHeapTestAccess::ImageOf(heap, filtered.slots[240]), "a reactivated view kept an element whose image slot holds a new image"); + // A table whose descriptor grew keeps its old snapshot until a frame passes without it. + const IR::ImageHeapRequest grown{table, table_size * 2u, 32u, 0u, table_size / 16u, &sampled}; + for (uint32_t frame = 0; frame < 2u; ++frame) { + Require(name, "grown table", heap.Acquire(grown, view), "a grown table was declined"); + draw(); + heap.RunGarbageCollector(); + heap.BeginDraw(); + Require(name, "superseded table", ImageHeapTestAccess::MemoryCount(heap) == 2u - frame, + "a snapshot was dropped in a frame that acquired it or kept after one without it"); + } + heap.UnmapMemory(table, table_size); Require(name, "unmapped table", ImageHeapTestAccess::MemoryCount(heap) == 0u &&