From b8a459e83d840b56c5fbdff0a9a89a7b48f05cc7 Mon Sep 17 00:00:00 2001 From: Brandon Strong Date: Mon, 31 Aug 2026 18:48:06 -0700 Subject: [PATCH] graphics: do not arm a fast clear from a read-modify-write dispatch (#428) * graphics: do not arm a fast clear from a read-modify-write dispatch * tests: cover compute metadata clear classification --------- Co-authored-by: nmzik --- CMakeLists.txt | 2 + .../host_gpu/renderer/renderCompute.cpp | 4 +- tests/ShaderRecompilerComputeTests.cpp | 104 ++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1347d463d..855202ff9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -574,6 +574,8 @@ if(BUILD_TESTING) COMMAND $ --storage-sampled-only) add_test(NAME texture_cache_depth_readback COMMAND $ --depth-readback-only) + add_test(NAME compute_meta_clear_classification + COMMAND $ --compute-meta-clear-only) add_test(NAME buffer_cache_dirty_gc COMMAND $ --buffer-cache-gc-only) diff --git a/src/graphics/host_gpu/renderer/renderCompute.cpp b/src/graphics/host_gpu/renderer/renderCompute.cpp index f53514299..fba45ba13 100644 --- a/src/graphics/host_gpu/renderer/renderCompute.cpp +++ b/src/graphics/host_gpu/renderer/renderCompute.cpp @@ -54,7 +54,9 @@ bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& in for (uint32_t i = 0; i < program.info.buffers.size(); i++) { const auto& resource = program.info.buffers[i]; const auto descriptor = DecodeNativeDescriptor(resources.buffers[i]); - if (!resource.written && cache.IsMeta(descriptor.Base48())) { + // A metadata resource that is also read is not proven to be a full overwrite. Execute it + // conservatively instead of replacing the dispatch with a coarse full-surface clear. + if (cache.IsMeta(descriptor.Base48()) && (!resource.written || resource.read)) { return false; } } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 599d28cea..b74c1dce5 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -357,10 +357,23 @@ struct TextureCacheTestAccess { return cache.TryDownloadImage(id); } + static void RegisterHtileMeta(TextureCache &cache, uint64_t address) { + std::lock_guard lock(cache.m_lock); + auto &metadata = cache.m_surface_metas[address]; + metadata.type = TextureCache::MetaDataInfo::Type::HTile; + metadata.clear_mask = 0; + } + static TileManager &Tiler(TextureCache &cache) { return cache.m_tiler; } }; struct RenderExecutorTestAccess { + static bool TryConsumeComputeMetaClear(RenderExecutor &executor, + const ShaderComputeInputInfo &input, + const CommandBuffer &buffer) { + return executor.TryConsumeComputeMetaClear(input, buffer); + } + static TextureBinding ResolveTexture(RenderExecutor &executor, const ShaderRecompiler::IR::ImageResource &resource, @@ -3778,6 +3791,92 @@ public: std::printf("[host] %-32s ok\n", name); } + void CheckComputeMetaClearClassification() { + constexpr const char *name = "ComputeMetaClearClassification"; + constexpr uint64_t read_only_meta = 0x0000000204201f00ull; + constexpr uint64_t read_write_meta = 0x0000000204202000ull; + constexpr uint64_t write_only_meta = 0x0000000204202100ull; + EnsureRuntimeContext(); + RenderContext context(m_runtime_context); + auto &scheduler = context.GetCommandScheduler(); + HW::Context registers{}; + HW::UserConfig user_config{}; + HW::Shader shaders{}; + scheduler.Begin(registers, user_config, shaders); + auto &texture_cache = context.GetTextureCache(); + TextureCacheTestAccess::RegisterHtileMeta(texture_cache, read_only_meta); + TextureCacheTestAccess::RegisterHtileMeta(texture_cache, read_write_meta); + TextureCacheTestAccess::RegisterHtileMeta(texture_cache, write_only_meta); + Require(name, "HTile fixture", + texture_cache.IsMeta(read_only_meta) && + texture_cache.IsMeta(read_write_meta) && + texture_cache.IsMeta(write_only_meta) && + !texture_cache.IsMetaCleared(read_only_meta, 0) && + !texture_cache.IsMetaCleared(read_write_meta, 0) && + !texture_cache.IsMetaCleared(write_only_meta, 0), + "test HTile entries were not registered in their initial state"); + + const auto MakeInput = [](uint64_t address, bool read, bool written) { + auto program = std::make_shared(); + program->stage = ShaderType::Compute; + ShaderRecompiler::IR::BufferResource resource{}; + resource.read = read; + resource.written = written; + resource.formatted = true; + program->info.buffers.push_back(resource); + + ShaderBufferResource descriptor{}; + descriptor.UpdateAddress48(address); + descriptor.fields[1] |= 16u << 16u; + descriptor.fields[2] = 8; + descriptor.fields[3] = + DstSel(4, 5, 6, 7) | + (static_cast(Prospero::BufferFormat::k32_32_32_32UInt) + << 12u); + ShaderRecompiler::IR::DescriptorValue value{}; + value.dword_count = 4; + std::copy_n(descriptor.fields, value.dword_count, value.dwords.begin()); + auto snapshot = + std::make_shared(); + snapshot->buffers.push_back(value); + + ShaderComputeInputInfo input{}; + input.stage.program = std::move(program); + input.stage.resources = std::move(snapshot); + return input; + }; + auto &executor = context.GetRenderExecutor(); + auto &command = scheduler.Current(); + const auto read_only_input = MakeInput(read_only_meta, true, false); + const bool read_only_consumed = + RenderExecutorTestAccess::TryConsumeComputeMetaClear( + executor, read_only_input, command); + Require(name, "read-only dispatch preserved", + !read_only_consumed && + !texture_cache.IsMetaCleared(read_only_meta, 0), + "a metadata read-only dispatch changed logical clear state"); + + const auto read_write_input = MakeInput(read_write_meta, true, true); + const bool read_write_consumed = + RenderExecutorTestAccess::TryConsumeComputeMetaClear( + executor, read_write_input, command); + Require(name, "read/write dispatch preserved", + !read_write_consumed && + !texture_cache.IsMetaCleared(read_write_meta, 0), + "a metadata read/modify/write dispatch was replaced by a clear"); + + const auto write_only_input = MakeInput(write_only_meta, false, true); + const bool write_only_consumed = + RenderExecutorTestAccess::TryConsumeComputeMetaClear( + executor, write_only_input, command); + Require(name, "write-only fill consumed", + write_only_consumed && + texture_cache.IsMetaCleared(write_only_meta, 0), + "a metadata write-only fill was not consumed as a clear"); + scheduler.Finish(); + std::printf("[host] %-32s ok\n", name); + } + void CheckUnifiedTextureCacheFlow() { constexpr const char *name = "UnifiedTextureCacheFlow"; constexpr uintptr_t base = 0x0000000200600000ull; @@ -25443,6 +25542,11 @@ int main(int argc, char **argv) { vulkan.CheckUnifiedTextureCacheFlow(); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--compute-meta-clear-only") == 0) { + VulkanHarness vulkan; + vulkan.CheckComputeMetaClearClassification(); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--layered-image-only") == 0) { VulkanHarness vulkan; vulkan.CheckUnifiedImageViewCache();