From 31b7be9ff22ed32596b95a0f99518e0d306cf37a Mon Sep 17 00:00:00 2001 From: nmzik Date: Tue, 13 Oct 2026 03:21:40 +0200 Subject: [PATCH] graphics: materialize fast clears before sampling reused targets Co-authored-by: Ali Almohaya --- .../host_gpu/renderer/colorRenderTarget.cpp | 3 +- .../host_gpu/renderer/image/imageInfo.h | 9 +- src/graphics/host_gpu/renderer/render.h | 2 + src/graphics/host_gpu/renderer/renderDraw.cpp | 25 ++-- tests/ShaderRecompilerComputeTests.cpp | 115 +++++++++++++----- 5 files changed, 113 insertions(+), 41 deletions(-) diff --git a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp index 1202db0e8..441ea30e5 100644 --- a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp +++ b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp @@ -236,7 +236,8 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor // DCC owns the clear when both planes are enabled; single-sample CMASK stays expanded. desc.info.metadata.kind = has_dcc ? ImageMetadataKind::Dcc : ImageMetadataKind::Cmask; desc.info.metadata.range = {has_dcc ? rt.dcc_addr.addr : rt.cmask.addr, metadata_size.size}; - desc.info.metadata.clear_word = rt.clear_word0.word0; + desc.info.metadata.clear_word = + rt.clear_word0.word0 | (uint64_t {rt.clear_word1.word1} << 32u); desc.info.metadata.clear_register_valid = true; } desc.view_info.format = target_format.format; diff --git a/src/graphics/host_gpu/renderer/image/imageInfo.h b/src/graphics/host_gpu/renderer/image/imageInfo.h index 71bedc341..db5db3d96 100644 --- a/src/graphics/host_gpu/renderer/image/imageInfo.h +++ b/src/graphics/host_gpu/renderer/image/imageInfo.h @@ -27,7 +27,7 @@ struct ImageMetadataInfo { GuestRange range; ImageMetadataKind kind = ImageMetadataKind::None; uint32_t control = 0; - uint32_t clear_word = 0; + uint64_t clear_word = 0; VideoOutCompression compression = VideoOutCompression::Uncompressed; bool stencil_compressed = false; bool clear_register_valid = false; @@ -414,9 +414,10 @@ inline constexpr std::array VIDEO_OUT_FORMAT_POLICIES { return false; } -[[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint32_t packed, +[[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint64_t clear_words, vk::ClearColorValue& clear) { vk::ClearColorValue next {}; + const auto packed = static_cast(clear_words); const auto unorm8 = [](uint32_t value) { return static_cast(value & 0xffu) / 255.0f; }; const auto srgb8 = [](uint32_t value) { const auto encoded = static_cast(value & 0xffu) / 255.0f; @@ -434,6 +435,10 @@ inline constexpr std::array VIDEO_OUT_FORMAT_POLICIES { (mantissa << 13u)); }; switch (format) { + case vk::Format::eR16G16B16A16Sfloat: + next.float32[2] = float16(static_cast(clear_words >> 32u)); + next.float32[3] = float16(static_cast(clear_words >> 48u)); + [[fallthrough]]; case vk::Format::eR16G16Sfloat: next.float32[1] = float16(packed >> 16u); [[fallthrough]]; diff --git a/src/graphics/host_gpu/renderer/render.h b/src/graphics/host_gpu/renderer/render.h index 9e9b01481..e0d206dd2 100644 --- a/src/graphics/host_gpu/renderer/render.h +++ b/src/graphics/host_gpu/renderer/render.h @@ -196,6 +196,8 @@ private: std::span stages = {}); [[nodiscard]] bool ResolveColorTargets(CommandBuffer& buffer, uint32_t render_target_slice_offset); + [[nodiscard]] bool ConsumeMetadataColorOperation(CommandBuffer& buffer, + uint32_t render_target_slice_offset); void BindImage(ImageId id, bool storage); void BindRenderTarget(ImageId id); void ResetBindings(); diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index 6f762b3a5..180194a20 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -608,16 +608,19 @@ enum class CbColorMode : uint8_t { DccDecompress = 6, }; -static bool ConsumeMetadataColorOperation(const CommandBuffer& buffer) { +bool RenderExecutor::ConsumeMetadataColorOperation(CommandBuffer& buffer, + uint32_t render_target_slice_offset) { const auto& ctx = buffer.GetRegisters(); const auto mode = ctx.GetColorControl().mode; - // These special modes run color-buffer metadata or decompression operations. The shader is a - // vehicle for that operation, and its exported color must not be applied as a normal draw. - // Kyty stores expanded Vulkan images rather than compressed guest surfaces, so no equivalent - // hardware pass is emitted. Tracked DCC clear state is materialized on attachment bind; - // future CMask/FMask support can consume its state through the same TextureCache path. - return mode == static_cast(CbColorMode::EliminateFastClear) || - mode == static_cast(CbColorMode::FmaskDecompress) || + if (mode == static_cast(CbColorMode::EliminateFastClear)) { + // FCE can precede any ordinary draw. Discover the target to materialize its native + // clear metadata before the expanded image is sampled. + RenderColorInfo target {}; + ResolveRenderColorTarget(buffer, target, render_target_slice_offset, 0, true); + return true; + } + // Metadata/decompression shaders must not export colors into the expanded image. + return mode == static_cast(CbColorMode::FmaskDecompress) || mode == static_cast(CbColorMode::DccDecompress); } @@ -1257,7 +1260,8 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer, return; } - if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) || + if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) || + DepthStencilCopy(buffer) || ResolveColorTargets(buffer, args.render_target_slice_offset)) { ResetBindings(); return; @@ -1370,7 +1374,8 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D return; } - if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) || + if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) || + DepthStencilCopy(buffer) || ResolveColorTargets(buffer, args.render_target_slice_offset)) { ResetBindings(); return; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index deeec598f..28adbc191 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -10865,12 +10865,14 @@ public: constexpr const char *name = "RenderExecutorColorMetadataClear"; struct PackedCase { vk::Format format; - uint32_t word; + uint64_t word; std::array channels; }; const std::array packed_cases{ PackedCase{vk::Format::eR16Sfloat, 0x7bff7bffu, {65504.0f}}, PackedCase{vk::Format::eR16G16Sfloat, 0xbc003c00u, {1.0f, -1.0f}}, + PackedCase{vk::Format::eR16G16B16A16Sfloat, 0x40003800bc003c00ull, + {1.0f, -1.0f, 0.5f, 2.0f}}, PackedCase{vk::Format::eR16Sfloat, 0xffff0001u, {0x1p-24f}}, PackedCase{vk::Format::eR16Sfloat, 0x00008001u, {-0x1p-24f}}, PackedCase{vk::Format::eR16Sfloat, 0x000003ffu, {1023.0f * 0x1p-24f}}, @@ -10904,7 +10906,7 @@ public: "alternating R16 pixels were treated as a uniform clear"); } constexpr uintptr_t base = 0x0000000204100000ull; - constexpr uint64_t allocation_size = 0x200000; + constexpr uint64_t allocation_size = 0x400000; constexpr uint64_t allocation_alignment = 0x10000; struct FillCase { uint32_t fill; @@ -10915,12 +10917,19 @@ public: Prospero::ChannelLayout layout = Prospero::ChannelLayout::k16_16_16_16; Prospero::ChannelType type = Prospero::ChannelType::kFloat; uint32_t clear_word = 0; + bool eliminate_fast_clear = false; }; constexpr std::array cases{ FillCase{0x40404040u, {0, 0x3c000000u}}, FillCase{0x80808080u, {0x3c003c00u, 0x00003c00u}}, FillCase{0x40404040u, {0, 0x3c000000u}, true}, - FillCase{0, {0x804020ffu, 0}, false, true}, + FillCase{.fill = 0, .texel = {0x804020ffu, 0}, .cmask = true, + .layout = Prospero::ChannelLayout::k8_8_8_8, + .type = Prospero::ChannelType::kUNorm}, + FillCase{.fill = 0, .texel = {0, 0}, .cmask = true, + .eliminate_fast_clear = true}, + FillCase{.fill = 0, .texel = {0, 0x3c000000u}, .cmask = true, + .eliminate_fast_clear = true}, FillCase{0x40404040u, {0, 0x3c000000u}, false, false, true}, FillCase{.fill = 0x20202020u, .texel = {0x7bff7bffu}, .layout = Prospero::ChannelLayout::k16, .clear_word = 0x7bff7bffu}, @@ -10985,15 +10994,16 @@ public: std::memset(mapped, 0, allocation_size); for (const auto &fill_case : cases) { - const auto layout = fill_case.cmask ? Prospero::ChannelLayout::k8_8_8_8 : fill_case.layout; - const auto type = fill_case.cmask ? Prospero::ChannelType::kUNorm : fill_case.type; + const auto layout = fill_case.layout; + const auto type = fill_case.type; const auto bytes_per_pixel = TextureGetRenderTargetFormat( layout, type, Prospero::ChannelOrder::kStandard).bytes_per_element; const uint32_t probe_width = bytes_per_pixel == 2 ? 2 : 1; TileSizeAlign dcc_size{}; (void)TileGetDccSize(512, 256, 1, bytes_per_pixel, 1, Prospero::TileMode::kRenderTarget, dcc_size, 0); - const uint64_t dcc_address = base + (fill_case.reuse_mips ? 0x160000 : 0x100000); + const uint64_t dcc_address = base + (fill_case.eliminate_fast_clear ? 0x200000 : + fill_case.reuse_mips ? 0x160000 : 0x100000); const uint32_t selected_layer = fill_case.cmask ? 1 : 0; const uint64_t metadata_size = fill_case.cmask ? 0x2000 : dcc_size.size; const std::vector expected( @@ -11023,7 +11033,7 @@ public: registers.SetColorDccAddr(0, {.addr = dcc_address}); registers.SetColorCmask(0, {.addr = dcc_address}); registers.SetColorClearWord0(0, {.word0 = fill_case.cmask ? expected[0] : fill_case.clear_word}); - registers.SetColorClearWord1(0, {.word1 = 0}); + registers.SetColorClearWord1(0, {.word1 = fill_case.eliminate_fast_clear ? expected[1] : 0}); registers.SetRenderTargetMask(0x0f); scheduler.Begin(registers, user_config, shaders); @@ -11032,7 +11042,8 @@ public: auto &executor = context.GetRenderExecutor(); resources.MapMemory(base, allocation_size); ImageId preexisting_id{}; - if (fill_case.reuse_unorm || fill_case.reuse_mips) { + RenderColorInfo color{}; + if (fill_case.reuse_unorm || fill_case.reuse_mips || fill_case.eliminate_fast_clear) { const auto float_info = registers.GetRenderTarget(0).info; const auto single_mip = registers.GetRenderTarget(0).attrib2; auto initial_info = float_info; @@ -11049,6 +11060,7 @@ public: RenderExecutorTestAccess::ResolveRenderColorTarget( executor, scheduler.Current(), preexisting, 0); preexisting_id = preexisting.image_id; + color = preexisting; (void)texture_cache.FindRenderTarget(preexisting_id, preexisting.desc); if (fill_case.reuse_mips) { Require(name, "mip chain excludes metadata", @@ -11061,6 +11073,13 @@ public: texture_cache, scheduler.Current(), preexisting_id, {vk::ImageAspectFlagBits::eColor, 0, 2, 0, 1}, clear); } + if (fill_case.eliminate_fast_clear) { + vk::ClearValue stale{}; + stale.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f}; + TextureCacheTestAccess::ClearImage( + texture_cache, scheduler.Current(), preexisting_id, + {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 2}, stale); + } RenderExecutorTestAccess::ResetBindings(executor); registers.SetColorInfo(0, float_info); registers.SetColorAttrib2(0, single_mip); @@ -11088,7 +11107,66 @@ public: !texture_cache.IsMeta(dcc_address), "the native fill guessed an unbound metadata allocation's type"); - RenderColorInfo color{}; + const auto check_expanded_metadata = [&] { + context.GetBufferCache().ReadMemory(dcc_address, metadata_size, false); + std::vector bytes(metadata_size); + Require(name, "expanded metadata readback", + LibKernel::Memory::TryReadBacking(dcc_address, bytes.data(), bytes.size()), + "native color metadata could not be read back"); + const auto selected = bytes.begin() + (fill_case.cmask ? 0x1000 : 0); + Require(name, "selected metadata slice expansion", + std::all_of(bytes.begin(), selected, [](uint8_t v) { return v == 0; }) && + std::all_of(selected, bytes.end(), [](uint8_t v) { return v == 0xff; }), + "materialization retained a clear key or changed an unselected slice"); + }; + + const auto paint = [&] { + vk::ClearValue clear{}; + clear.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f}; + TextureCacheTestAccess::ClearImage( + texture_cache, scheduler.Current(), color.image_id, + {vk::ImageAspectFlagBits::eColor, 0, 1, selected_layer, 1}, clear); + }; + + if (fill_case.eliminate_fast_clear) { + registers.SetRenderTargetMask(0); + registers.SetColorControl({.mode = 2}); + RenderExecutorTestAccess::DrawAuto(executor, scheduler.Current(), + {.vertex_count = 3, .instance_count = 1}); + auto sampled = color.desc; + sampled.type = BindingType::Texture; + sampled.view_info.usage = vk::ImageUsageFlagBits::eSampled; + color.image_id = texture_cache.FindImage(sampled); + Require(name, "FCE sampled image reuses stale target", + color.image_id == preexisting_id && + texture_cache.FindTexture(color.image_id, sampled) != nullptr, + "FCE failed to retain the sampled allocation"); + const std::vector stale{0x00003c00u, 0x3c003c00u}; + Require(name, "FCE before graphics shader or target binding", + ReadCachedTexel(name, context, color.image_id, {}, {1, 1, 1}, 1) == expected && + ReadCachedTexel(name, context, color.image_id, {511, 255, 0}, + {1, 1, 1}, 1) == expected && + ReadCachedTexel(name, context, color.image_id, {}, {1, 1, 1}, 0) == stale, + "FCE lost a clear register or changed the unselected layer"); + check_expanded_metadata(); + paint(); + RenderExecutorTestAccess::DrawIndex(executor, scheduler.Current(), + {.index_count = 3, .instance_count = 1}); + (void)texture_cache.FindTexture(color.image_id, sampled); + Require(name, "repeated indexed FCE preserves expanded color", + ReadCachedTexel(name, context, color.image_id, {}, {1, 1, 1}, 1) == stale, + "FCE reapplied the consumed clear after rendering"); + WriteMetadata(context, dcc_address, metadata_size, 0); + RenderExecutorTestAccess::DrawIndex(executor, scheduler.Current(), + {.index_count = 3, .instance_count = 1}); + (void)texture_cache.FindTexture(color.image_id, sampled); + Require(name, "indexed FCE consumes a new clear", + ReadCachedTexel(name, context, color.image_id, {}, {1, 1, 1}, 1) == expected, + "indexed FCE skipped a new metadata clear"); + resources.UnmapMemory(base, allocation_size); + scheduler.Finish(); + return; + } RenderExecutorTestAccess::ResolveRenderColorTarget( executor, scheduler.Current(), color, 0); RenderDepthInfo no_depth{}; @@ -11132,18 +11210,6 @@ public: std::vector{0x00003c00u, 0x00003c00u}, "clearing mip zero changed its higher mip"); } - const auto check_expanded_metadata = [&] { - context.GetBufferCache().ReadMemory(dcc_address, metadata_size, false); - std::vector bytes(metadata_size); - Require(name, "expanded metadata readback", - LibKernel::Memory::TryReadBacking(dcc_address, bytes.data(), bytes.size()), - "native color metadata could not be read back"); - const auto selected = bytes.begin() + (fill_case.cmask ? 0x1000 : 0); - Require(name, "selected metadata slice expansion", - std::all_of(bytes.begin(), selected, [](uint8_t v) { return v == 0; }) && - std::all_of(selected, bytes.end(), [](uint8_t v) { return v == 0xff; }), - "materialization retained a clear key or changed an unselected slice"); - }; check_expanded_metadata(); const auto bind = [&] { @@ -11151,13 +11217,6 @@ public: color.image_id = texture_cache.FindImage(color.desc); (void)texture_cache.FindRenderTarget(color.image_id, color.desc); }; - const auto paint = [&] { - vk::ClearValue clear{}; - clear.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f}; - TextureCacheTestAccess::ClearImage( - texture_cache, scheduler.Current(), color.image_id, - {vk::ImageAspectFlagBits::eColor, 0, 1, selected_layer, 1}, clear); - }; std::vector painted{0x00003c00u, 0x3c003c00u}; if (fill_case.reuse_unorm) { painted = {0x0000ffffu, 0xffffffffu};