From 463a096249bcf3f39414d6986f9e1456fd322f22 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 25 Oct 2026 03:50:55 +0100 Subject: [PATCH] graphics: unwatch image heap pages on writes next to a table --- src/graphics/host_gpu/renderer/pipeline/imageHeap.cpp | 8 +++++--- tests/ShaderRecompilerComputeTests.cpp | 11 +++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/graphics/host_gpu/renderer/pipeline/imageHeap.cpp b/src/graphics/host_gpu/renderer/pipeline/imageHeap.cpp index 7c1c50e8c..0258f4256 100644 --- a/src/graphics/host_gpu/renderer/pipeline/imageHeap.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/imageHeap.cpp @@ -254,10 +254,12 @@ void ImageHeap::Unwatch(const Memory& memory) { void ImageHeap::InvalidateMemory(uint64_t address, uint64_t size) { std::scoped_lock lock {m_mutex}; for (auto& memory: m_memories) { - const auto begin = std::max(address, memory->base); - const auto end = std::min(address + size, memory->base + memory->size); - if (begin >= end) continue; + // Watches cover whole pages, so a write next to the table must unwatch its page too. const auto origin = Common::AlignDown(memory->base, TRACKER_PAGE_SIZE); + const auto begin = std::max(address, origin); + const auto end = std::min(address + size, + origin + memory->dirty_pages.size() * TRACKER_PAGE_SIZE); + if (begin >= end) continue; MarkDirty(*memory, (begin - origin) / TRACKER_PAGE_SIZE, (end - 1u - origin) / TRACKER_PAGE_SIZE + 1u); } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 90aa7beb3..cdc8210b7 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -242,6 +242,10 @@ struct ImageHeapTestAccess { static ImageId ImageOf(const ImageHeap &heap, uint32_t entry) { return Element(heap, entry).binding.image_id; } + // Whether the newest table no longer watches its last page. + static bool TailPageDirty(const ImageHeap &heap) { + return heap.m_memories.back()->dirty_pages.back() != 0u; + } }; struct TextureCacheTestAccess { @@ -5518,6 +5522,13 @@ public: acquire(sampled, idle_table) && view.kinds.empty() && ImageHeapTestAccess::MemoryCount(heap) == 1u, "an all-null table was not kept resident"); + // A write past a table's end, in its watched tail page, must unwatch the page. + constexpr uint64_t unaligned_table = base + 0x1a0800; + Require(name, "tail page fault", + acquire(sampled, unaligned_table) && + context.HandleFault(PageFaultAccess::Write, unaligned_table + table_size) && + ImageHeapTestAccess::TailPageDirty(heap), + "a write fault next to a table left its page watched"); draw(); context.UnmapMemory(base, idle_table - base); scheduler.Finish();