diff --git a/src/graphics/host_gpu/renderer/render.h b/src/graphics/host_gpu/renderer/render.h index 9febff946..897d7935b 100644 --- a/src/graphics/host_gpu/renderer/render.h +++ b/src/graphics/host_gpu/renderer/render.h @@ -203,9 +203,9 @@ private: void ResetBindings(); void UpdateComputeMetaClears(const ShaderComputeInputInfo& input, const CommandBuffer& buffer); - [[nodiscard]] bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, - CommandBuffer& command, uint32_t group_x, - uint32_t group_y, uint32_t group_z, uint32_t mode); + [[nodiscard]] bool TryConsumeComputeFill(const ShaderComputeInputInfo& input, + CommandBuffer& command, uint32_t group_x, + uint32_t group_y, uint32_t group_z, uint32_t mode); RenderContext& m_context; GraphicsBindings m_graphics_bindings; diff --git a/src/graphics/host_gpu/renderer/renderCompute.cpp b/src/graphics/host_gpu/renderer/renderCompute.cpp index ae7fa68bf..efb2a9887 100644 --- a/src/graphics/host_gpu/renderer/renderCompute.cpp +++ b/src/graphics/host_gpu/renderer/renderCompute.cpp @@ -128,9 +128,9 @@ bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t grou return true; } -bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, - CommandBuffer& command, uint32_t group_x, - uint32_t group_y, uint32_t group_z, uint32_t mode) { +bool RenderExecutor::TryConsumeComputeFill(const ShaderComputeInputInfo& input, + CommandBuffer& command, uint32_t group_x, + uint32_t group_y, uint32_t group_z, uint32_t mode) { const auto& program = *input.stage.program; const auto& resources = *input.stage.resources; const auto& fill = resources.uniform_fill; @@ -186,14 +186,26 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i size)) { return false; } - if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) { - return false; + const bool image_clear = + cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear); + if (!image_clear) { + // CPU fills invalidate overlapping cached images. Preserve the native dispatch for + // partial or ambiguous image backings, including pooled metadata aliases. + if ((descriptor.Base48() & 3u) != 0 || + !m_context.IsMapped(descriptor.Base48(), size) || + cache.IsRegionRegistered(descriptor.Base48(), size)) { + return false; + } + // Metadata need not have an image backing yet. Keep proven fills in guest memory + // when CPU-owned, so color-clear discovery can read them without a GPU download. + m_context.GetBufferCache().FillBuffer(descriptor.Base48(), size, packed_clear, false); } static std::atomic logged_clears {0}; if (logged_clears.fetch_add(1, std::memory_order_relaxed) < 32) { - LOGF("GraphicsRenderDispatchDirect: compute image clear shader=0x%016" PRIx64 + LOGF("GraphicsRenderDispatchDirect: compute %s shader=0x%016" PRIx64 " addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n", - input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear); + image_clear ? "image clear" : "buffer fill", input.stage.program->shader_hash, + descriptor.Base48(), size, packed_clear); } return true; } @@ -308,8 +320,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, if (resources.specialization_reads.empty()) { // Cached metadata addresses can be reused as ordinary buffers; preserve their guest writes. UpdateComputeMetaClears(input_info, buffer); - if (TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y, - thread_group_z, mode)) { + if (TryConsumeComputeFill(input_info, buffer, thread_group_x, thread_group_y, + thread_group_z, mode)) { ResetBindings(); return; } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 9a7e505aa..decf78eb4 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -407,10 +407,10 @@ struct RenderExecutorTestAccess { executor.DrawAuto(0, command, args); } - static bool TryConsumeComputeImageClear(RenderExecutor &executor, + static bool TryConsumeComputeFill(RenderExecutor &executor, const ShaderComputeInputInfo &input, CommandBuffer &command, uint32_t x, uint32_t y, uint32_t z, uint32_t mode) { - return executor.TryConsumeComputeImageClear(input, command, x, y, z, mode); + return executor.TryConsumeComputeFill(input, command, x, y, z, mode); } static void UpdateComputeMetaClears(RenderExecutor &executor, @@ -11533,6 +11533,7 @@ public: uint32_t clear_word = 0; bool eliminate_fast_clear = false; uint32_t samples_log2 = 0; + bool goat = false; }; constexpr std::array cases{ FillCase{0x40404040u, {0, 0x3c000000u}}, @@ -11540,7 +11541,7 @@ public: FillCase{0x40404040u, {0, 0x3c000000u}, true}, FillCase{.fill = 0, .texel = {0x804020ffu, 0}, .cmask = true, .layout = Prospero::ChannelLayout::k8_8_8_8, - .type = Prospero::ChannelType::kUNorm}, + .type = Prospero::ChannelType::kUNorm, .goat = true}, FillCase{.fill = 0, .texel = {0, 0}, .cmask = true, .eliminate_fast_clear = true}, FillCase{.fill = 0, .texel = {0, 0x3c000000u}, .cmask = true, @@ -11605,11 +11606,18 @@ public: } return words; }(); + // Goat Simulator 3's immediate-zero fill (b0a34a6ed90ebace). + static constexpr std::array goat_fill{ + 0xd7460000u, 0x04010c04u, 0x7e020280u, + 0xe0102000u, 0x80000100u, 0xbf810000u}; for (const auto *shader : {&native_fill, &raw_fill}) { ShaderMapUserData(reinterpret_cast(shader->data()), {.type = Prospero::ShaderBinaryType::kCs, .code_size_bytes = sizeof(native_fill)}); } + ShaderMapUserData(reinterpret_cast(goat_fill.data()), + {.type = Prospero::ShaderBinaryType::kCs, + .code_size_bytes = sizeof(goat_fill)}); int64_t direct_offset = -1; Require(name, "direct allocation", Libs::LibKernel::Memory::KernelAllocateDirectMemory( @@ -11722,27 +11730,39 @@ public: registers.SetColorAttrib2(0, single_mip); } const auto fill_metadata = [&](uint32_t count, bool raw = false) { - const auto *shader = &native_fill; - if (raw) { - shader = &raw_fill; - } - shaders.SetCsShader({.data_addr = reinterpret_cast(shader->data()), + const bool goat = fill_case.goat && !raw; + const auto *shader = goat ? goat_fill.data() + : raw ? raw_fill.data() : native_fill.data(); + shaders.SetCsShader({.data_addr = reinterpret_cast(shader), .num_thread_x = 64, .num_thread_y = 1, .num_thread_z = 1, - .wave_size = 64, .user_sgpr = 10, .tgid_x_en = true}); + .wave_size = 64, .user_sgpr = goat ? uint8_t{4} : uint8_t{10}, + .tgid_x_en = true}); const std::array user_data{ static_cast(dcc_address), static_cast(dcc_address >> 32u) | (4u << 16u), count, 0x00014004u, fill_case.fill, 0, 0x200, 0, count, 1}; - for (uint32_t i = 0; i < user_data.size(); i++) { + for (uint32_t i = 0; i < (goat ? 4u : user_data.size()); i++) { shaders.SetCsUserSgpr(i, user_data[i], HW::UserSgprType::Unknown); } executor.DispatchDirect(0, scheduler.Current(), (count + 63) / 64, 1, 1, 0x41u, false); }; const auto metadata_words = static_cast(metadata_size / 4); + if (fill_case.goat) { + WriteMetadata(context, dcc_address, metadata_size, 0xa5a5a5a5u); + } fill_metadata(metadata_words); Require(name, "metadata before descriptor", !texture_cache.IsMeta(dcc_address), "the native fill guessed an unbound metadata allocation's type"); + if (fill_case.goat) { + std::vector filled_metadata(metadata_words); + Require(name, "Goat metadata fill stays CPU-owned", + !resources.GetBufferCache().IsRegionGpuModified(dcc_address, metadata_size) && + LibKernel::Memory::TryReadBacking(dcc_address, filled_metadata.data(), metadata_size) && + std::ranges::all_of(filled_metadata, + [&](uint32_t word) { return word == fill_case.fill; }), + "Goat's proven metadata fill required a GPU readback or lost its constant value"); + } const auto check_expanded_metadata = [&] { context.GetBufferCache().ReadMemory(dcc_address, metadata_size, false); @@ -11899,6 +11919,10 @@ public: retains_painted_texel(), "a partial same-value write reused stale clear bytes after rendering"); fill_metadata(metadata_words, true); + fill_metadata(metadata_words); + Require(name, "already GPU-owned metadata stays ordered", + resources.GetBufferCache().HasGpuDirtyBytes(dcc_address, metadata_size), + "a fill discarded pending native writes to its metadata pages"); bind(); Require(name, "same-value raw native re-clear", read_texel() == expected, @@ -11911,6 +11935,11 @@ public: retains_painted_texel(), "a nonuniform metadata allocation cleared the whole target"); fill_metadata(metadata_words); + if (fill_case.goat) { + Require(name, "repeated Goat fill stays CPU-owned", + !resources.GetBufferCache().IsRegionGpuModified(dcc_address, metadata_size), + "re-clearing expanded metadata required another GPU readback"); + } bind(); Require(name, "complete fill after partial fill", read_texel() == expected, @@ -16194,9 +16223,9 @@ public: {vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1}, stencil_initial); Require(name, "stencil fill coverage", - !RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute, + !RenderExecutorTestAccess::TryConsumeComputeFill(executor, stencil_compute, scheduler.Current(), 0, 1, 1, 0x41u) && - !RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute, + !RenderExecutorTestAccess::TryConsumeComputeFill(executor, stencil_compute, scheduler.Current(), 2, 1, 1, 0x41u), "incomplete or excessive stencil dispatch was consumed"); auto &stencil_source = stencil_snapshot.buffers[0]; @@ -16204,12 +16233,12 @@ public: stencil_source.dwords[0] = static_cast(stencil_address); stencil_source.dwords[1] = static_cast(stencil_address >> 32) | (4u << 16); Require(name, "stencil fill source alias", - !RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute, + !RenderExecutorTestAccess::TryConsumeComputeFill(executor, stencil_compute, scheduler.Current(), 1, 1, 1, 0x41u), "an aliased scalar load was consumed as a uniform image clear"); stencil_source = clean_source; Require(name, "stencil IMAGE_STORE clear", - RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute, + RenderExecutorTestAccess::TryConsumeComputeFill(executor, stencil_compute, scheduler.Current(), 1, 1, 1, 0x41u), "captured clear attempted an unsupported depth/stencil storage binding"); auto stencil_readback = CreateHostBuffer(name, 8,