From b2c9ec76094460ed26a993ff8f483779ee4556bf Mon Sep 17 00:00:00 2001 From: 4wgycvn84p-afk <4wgycvn84p@privaterelay.appleid.com> Date: Sun, 25 Oct 2026 18:47:45 +0100 Subject: [PATCH] renderer: decode R8, R8G8 and R16_UINT color register clears (#1893) DecodePackedColorClear had no case for R8_UNORM/UINT, R8G8_UNORM/UINT and R16_UINT, so a CMASK fast clear of such a render target was dropped and the target kept its old contents. UFC 6 (PPSA23566) fast-clears an R8_UINT region map this way every frame and then writes only part of it; a capture showed the map never cleared. DWORD fills of these formats are decoded only when every pixel in the word holds the same value. Co-authored-by: 4wgycvn84p-afk <346802885+4wgycvn84p-afk@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- .../host_gpu/renderer/image/imageInfo.h | 22 +++++++++++++++- tests/ShaderRecompilerComputeTests.cpp | 25 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/graphics/host_gpu/renderer/image/imageInfo.h b/src/graphics/host_gpu/renderer/image/imageInfo.h index e7270652d..41351267c 100644 --- a/src/graphics/host_gpu/renderer/image/imageInfo.h +++ b/src/graphics/host_gpu/renderer/image/imageInfo.h @@ -416,6 +416,20 @@ inline constexpr std::array VIDEO_OUT_FORMAT_POLICIES { case vk::Format::eR32Sfloat: next.float32[0] = std::bit_cast(packed); break; case vk::Format::eR32Uint: next.uint32[0] = packed; break; case vk::Format::eR32Sint: next.int32[0] = static_cast(packed); break; + // Narrow targets hold their channels in the low bits of the first word. UFC 6 (PPSA23566) + // fast-clears an R8_UINT map through CMASK every frame and then writes only part of it; + // without these the clear was dropped and old contents piled up. + case vk::Format::eR8Uint: next.uint32[0] = packed & 0xffu; break; + case vk::Format::eR8Unorm: next.float32[0] = unorm8(packed); break; + case vk::Format::eR8G8Uint: + next.uint32[0] = packed & 0xffu; + next.uint32[1] = (packed >> 8u) & 0xffu; + break; + case vk::Format::eR8G8Unorm: + next.float32[0] = unorm8(packed); + next.float32[1] = unorm8(packed >> 8u); + break; + case vk::Format::eR16Uint: next.uint32[0] = packed & 0xffffu; break; case vk::Format::eR8G8B8A8Srgb: next.float32[0] = srgb8(packed); next.float32[1] = srgb8(packed >> 8u); @@ -462,10 +476,16 @@ inline constexpr std::array VIDEO_OUT_FORMAT_POLICIES { // Keep this separate: the first register word alone cannot describe a 64-bit color. [[nodiscard]] inline bool DecodeColorDwordFill(vk::Format format, uint32_t packed, vk::ClearColorValue& clear) { - if ((format == vk::Format::eR16Sfloat || format == vk::Format::eR16Unorm) && + if ((format == vk::Format::eR16Sfloat || format == vk::Format::eR16Unorm || + format == vk::Format::eR16Uint || format == vk::Format::eR8G8Unorm || + format == vk::Format::eR8G8Uint) && (packed & 0xffffu) != (packed >> 16u)) { return false; } + if ((format == vk::Format::eR8Unorm || format == vk::Format::eR8Uint) && + packed != (packed & 0xffu) * 0x01010101u) { + return false; + } if (format == vk::Format::eR16G16B16A16Sfloat) { if (packed != 0) { return false; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index ce4f6eb1b..90aa7beb3 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -12274,6 +12274,8 @@ public: PackedCase{vk::Format::eR16G16Unorm, 0x0000ffffu, {1.0f, 0.0f}}, PackedCase{vk::Format::eR16G16Unorm, 0x80000001u, {1.0f / 65535.0f, 32768.0f / 65535.0f}}, + PackedCase{vk::Format::eR8Unorm, 0xffffff80u, {128.0f / 255.0f}}, + PackedCase{vk::Format::eR8G8Unorm, 0xffff40ffu, {1.0f, 64.0f / 255.0f}}, }; for (const auto &test : packed_cases) { vk::ClearColorValue clear{}; @@ -12300,6 +12302,29 @@ public: DecodeColorDwordFill(format, 0x3c003c00u, clear), "alternating R16 pixels were treated as a uniform clear"); } + struct UintCase { + vk::Format format; + uint64_t word; + std::array channels; + }; + for (const auto &test : {UintCase{vk::Format::eR8Uint, 0xffffff05u, {5u}}, + UintCase{vk::Format::eR8G8Uint, 0xffff0a05u, {5u, 10u}}, + UintCase{vk::Format::eR16Uint, 0xffff1234u, {0x1234u}}}) { + Require(name, "packed unsigned color clear", + DecodePackedColorClear(test.format, test.word, clear) && + std::bit_cast>(clear.uint32) == test.channels, + "narrow unsigned clear lost a channel or kept bits of the next one"); + } + Require(name, "R8 DWORD fill", + !DecodeColorDwordFill(vk::Format::eR8Uint, 0x01020304u, clear) && + DecodeColorDwordFill(vk::Format::eR8Uint, 0x07070707u, clear) && + clear.uint32[0] == 7u, + "differing R8 pixels were treated as a uniform clear"); + Require(name, "RG8 DWORD fill", + !DecodeColorDwordFill(vk::Format::eR8G8Unorm, 0x00ff00feu, clear) && + DecodeColorDwordFill(vk::Format::eR8G8Unorm, 0x00ff00ffu, clear) && + clear.float32[0] == 1.0f && clear.float32[1] == 0.0f, + "alternating RG8 pixels were treated as a uniform clear"); Require(name, "RG32 DWORD fill", DecodeColorDwordFill(vk::Format::eR32G32Sfloat, 0x3f800000u, clear) && clear.float32[0] == 1.0f && clear.float32[1] == 1.0f,