From be5915c248ebc7785ee2ad4652fd0669c3f8beb0 Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 14 Oct 2026 03:36:33 +0200 Subject: [PATCH] graphics: skip GPU-clean empty indirect dispatches before shader preparation Co-authored-by: fxpw --- .../host_gpu/renderer/renderCompute.cpp | 7 +++++ tests/ShaderRecompilerComputeTests.cpp | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/graphics/host_gpu/renderer/renderCompute.cpp b/src/graphics/host_gpu/renderer/renderCompute.cpp index 2f25adaf1..08f19478e 100644 --- a/src/graphics/host_gpu/renderer/renderCompute.cpp +++ b/src/graphics/host_gpu/renderer/renderCompute.cpp @@ -22,6 +22,7 @@ #include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h" #include "graphics/shader/shader.h" #include "kernel/eventQueue.h" +#include "kernel/memory.h" #include "kernel/pthread.h" #include "libs/errno.h" @@ -444,6 +445,12 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, static_cast(args_addr), static_cast(args_addr >> 32u), 0, mode, buffer.GetShaders().GetCs().cs_regs.data_addr); Common::LockGuard lock(m_context.GetMutex()); + vk::DispatchIndirectCommand args {}; + if (!m_context.GetBufferCache().HasGpuDirtyBytes(args_addr, sizeof(args)) && + LibKernel::Memory::TryReadBacking(args_addr, &args, sizeof(args)) && + (args.x == 0 || args.y == 0 || args.z == 0)) { + return; + } const auto& cs_regs = buffer.GetShaders().GetCs(); if (cs_regs.cs_regs.data_addr == 0) { return; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index ffa8f75d4..45b521d13 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -10566,6 +10566,34 @@ public: auto &cache = context.GetBufferCache(); auto &shaders = processor.GetShCtx(); context.MapMemory(base, allocation_size); + // Empty CPU-owned arguments must not inspect the shader, even on a GPU-dirty page. + constexpr auto empty_args = base + allocation_size - case_size; + constexpr auto neighbor = empty_args + 0x100u; + constexpr std::array, 3> empty_dimensions{{ + {0, 1, 1}, {1, 0, 1}, {1, 1, 0}}}; + Require(name, "empty arguments", + LibKernel::Memory::TryWriteBacking(empty_args, empty_dimensions.data(), + sizeof(empty_dimensions)), + "empty indirect arguments could not be written"); + shaders.SetCsShader({.data_addr = 1}); + for (const bool dirty_neighbor : {false, true}) { + if (dirty_neighbor) { + auto [buffer, offset] = cache.ObtainBuffer(neighbor, sizeof(sentinel), true); + buffer->Fill(offset, sizeof(sentinel), sentinel + 1u); + } + Require(name, "exact argument ownership", + !cache.HasGpuDirtyBytes(empty_args, sizeof(empty_dimensions)) && + cache.IsRegionGpuModified(empty_args, sizeof(empty_dimensions)) == dirty_neighbor, + "empty arguments did not retain independent byte ownership"); + const auto tick = scheduler.CurrentTick(); + for (size_t i = 0; i < empty_dimensions.size(); ++i) { + processor.DispatchIndirect(empty_args + i * sizeof(empty_dimensions[0]), 0x41u); + } + Require(name, "empty dispatch avoids readback", + scheduler.CurrentTick() == tick && + cache.HasGpuDirtyBytes(neighbor, sizeof(sentinel)) == dirty_neighbor, + "empty dispatch submitted work or read back neighboring GPU data"); + } const auto set_buffer = [&](u32 sgpr, uint64_t address, u32 records, u32 stride = 0) { ShaderBufferResource descriptor{}; descriptor.UpdateAddress48(address);