From 57c97ebd861defd379addb124ba19be4b6ff130c Mon Sep 17 00:00:00 2001 From: TheCruZ Date: Mon, 5 Oct 2026 21:52:10 +0400 Subject: [PATCH] Decode 16-bit color clears (TheCruZ/KytyPS5-GTA@b8dcd826) --- .../host_gpu/renderer/image/imageInfo.h | 25 ++++++ tests/ShaderRecompilerComputeTests.cpp | 85 ++++++++++++++++--- 2 files changed, 97 insertions(+), 13 deletions(-) diff --git a/src/graphics/host_gpu/renderer/image/imageInfo.h b/src/graphics/host_gpu/renderer/image/imageInfo.h index f13efc68c..e9239ae37 100644 --- a/src/graphics/host_gpu/renderer/image/imageInfo.h +++ b/src/graphics/host_gpu/renderer/image/imageInfo.h @@ -390,7 +390,28 @@ inline constexpr std::array VIDEO_OUT_FORMAT_POLICIES { const auto encoded = static_cast(value & 0xffu) / 255.0f; return encoded <= 0.04045f ? encoded / 12.92f : std::pow((encoded + 0.055f) / 1.055f, 2.4f); }; + const auto float16 = [](uint32_t value) { + const auto sign = (value & 0x8000u) << 16u; + const auto exponent = (value >> 10u) & 0x1fu; + const auto mantissa = value & 0x3ffu; + if (exponent == 0) { + return std::copysign(static_cast(mantissa) * 0x1p-24f, + std::bit_cast(sign)); + } + return std::bit_cast(sign | ((exponent == 31 ? 255u : exponent + 112u) << 23u) | + (mantissa << 13u)); + }; switch (format) { + case vk::Format::eR16G16Sfloat: + next.float32[1] = float16(packed >> 16u); + [[fallthrough]]; + case vk::Format::eR16Sfloat: next.float32[0] = float16(packed); break; + case vk::Format::eR16G16Unorm: + next.float32[1] = static_cast(packed >> 16u) / 65535.0f; + [[fallthrough]]; + case vk::Format::eR16Unorm: + next.float32[0] = static_cast(packed & 0xffffu) / 65535.0f; + break; // A single-plane float target carries its clear as raw float bits, the same encoding the // depth decoder below uses. Without this the clear is discarded and the target keeps stale // contents. @@ -443,6 +464,10 @@ 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) && + (packed & 0xffffu) != (packed >> 16u)) { + return false; + } if (format == vk::Format::eR16G16B16A16Sfloat) { if (packed != 0) { return false; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 5e2471301..ea5f16600 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -10161,6 +10161,46 @@ public: void CheckRenderExecutorColorMetadataClear() { constexpr const char *name = "RenderExecutorColorMetadataClear"; + struct PackedCase { + vk::Format format; + uint32_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::eR16Sfloat, 0xffff0001u, {0x1p-24f}}, + PackedCase{vk::Format::eR16Sfloat, 0x00008001u, {-0x1p-24f}}, + PackedCase{vk::Format::eR16Sfloat, 0x000003ffu, {1023.0f * 0x1p-24f}}, + PackedCase{vk::Format::eR16Sfloat, 0x00000400u, {0x1p-14f}}, + PackedCase{vk::Format::eR16G16Sfloat, 0x80000000u, {0.0f, -0.0f}}, + PackedCase{vk::Format::eR16G16Sfloat, 0xfc007c00u, + {std::numeric_limits::infinity(), + -std::numeric_limits::infinity()}}, + PackedCase{vk::Format::eR16Unorm, 0x0000ffffu, {1.0f}}, + PackedCase{vk::Format::eR16G16Unorm, 0x0000ffffu, {1.0f, 0.0f}}, + PackedCase{vk::Format::eR16G16Unorm, 0x80000001u, + {1.0f / 65535.0f, 32768.0f / 65535.0f}}, + }; + for (const auto &test : packed_cases) { + vk::ClearColorValue clear{}; + Require(name, "packed 16-bit clear", + DecodePackedColorClear(test.format, test.word, clear) && + std::bit_cast>(clear.float32) == + std::bit_cast>(test.channels), + "16-bit clear lost channel order, sign, range or unused-channel zeros"); + } + vk::ClearColorValue clear{}; + Require(name, "half NaN clear", + DecodePackedColorClear(vk::Format::eR16Sfloat, 0x7e00u, clear) && + std::isnan(clear.float32[0]), + "half NaN did not remain NaN"); + for (const auto format : {vk::Format::eR16Sfloat, vk::Format::eR16Unorm}) { + Require(name, "R16 DWORD fill", + !DecodeColorDwordFill(format, 0xbc003c00u, clear) && + DecodeColorDwordFill(format, 0x3c003c00u, clear), + "alternating R16 pixels were treated as a uniform clear"); + } constexpr uintptr_t base = 0x0000000204100000ull; constexpr uint64_t allocation_size = 0x200000; constexpr uint64_t allocation_alignment = 0x10000; @@ -10170,6 +10210,9 @@ public: bool reuse_unorm = false; bool cmask = false; bool reuse_mips = false; + Prospero::ChannelLayout layout = Prospero::ChannelLayout::k16_16_16_16; + Prospero::ChannelType type = Prospero::ChannelType::kFloat; + uint32_t clear_word = 0; }; constexpr std::array cases{ FillCase{0x40404040u, {0, 0x3c000000u}}, @@ -10177,6 +10220,16 @@ public: FillCase{0x40404040u, {0, 0x3c000000u}, true}, FillCase{0, {0x804020ffu, 0}, false, true}, FillCase{0x40404040u, {0, 0x3c000000u}, false, false, true}, + FillCase{.fill = 0x20202020u, .texel = {0x7bff7bffu}, + .layout = Prospero::ChannelLayout::k16, .clear_word = 0x7bff7bffu}, + FillCase{.fill = 0x20202020u, .texel = {0xbc003c00u}, + .layout = Prospero::ChannelLayout::k16_16, .clear_word = 0xbc003c00u}, + FillCase{.fill = 0x20202020u, .texel = {0x80008000u}, + .layout = Prospero::ChannelLayout::k16, + .type = Prospero::ChannelType::kUNorm, .clear_word = 0x80008000u}, + FillCase{.fill = 0x20202020u, .texel = {0x0000ffffu}, + .layout = Prospero::ChannelLayout::k16_16, + .type = Prospero::ChannelType::kUNorm, .clear_word = 0x0000ffffu}, }; EnsureRuntimeContext(); // Astro's generic metadata fill, through S_ENDPGM; trailing debug data is omitted. @@ -10214,10 +10267,6 @@ public: {.type = Prospero::ShaderBinaryType::kCs, .code_size_bytes = sizeof(native_fill)}); } - TileSizeAlign dcc_size{}; - (void)TileGetDccSize(512, 256, 1, 8, 1, - Prospero::TileMode::kRenderTarget, dcc_size, 0); - int64_t direct_offset = -1; Require(name, "direct allocation", Libs::LibKernel::Memory::KernelAllocateDirectMemory( @@ -10234,11 +10283,19 @@ 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 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 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( - fill_case.texel.begin(), fill_case.texel.begin() + (fill_case.cmask ? 1 : 2)); + fill_case.texel.begin(), fill_case.texel.begin() + std::max(bytes_per_pixel / 4u, 1u)); RenderContext context(m_runtime_context); HW::Context registers{}; HW::UserConfig user_config{}; @@ -10251,10 +10308,8 @@ public: registers.SetColorInfo( 0, {.cmask_fast_clear_enable = !fill_case.reuse_unorm, .dcc_compression_enable = !fill_case.cmask, - .format = fill_case.cmask ? Prospero::ChannelLayout::k8_8_8_8 - : Prospero::ChannelLayout::k16_16_16_16, - .channel_type = fill_case.cmask ? Prospero::ChannelType::kUNorm - : Prospero::ChannelType::kFloat, + .format = layout, + .channel_type = type, .channel_order = Prospero::ChannelOrder::kStandard}); registers.SetColorView(0, {.base_array_slice_index = selected_layer, .last_array_slice_index = selected_layer}); @@ -10265,7 +10320,7 @@ public: .metadata_pipe_aligned = true}); registers.SetColorDccAddr(0, {.addr = dcc_address}); registers.SetColorCmask(0, {.addr = dcc_address}); - registers.SetColorClearWord0(0, {.word0 = fill_case.cmask ? expected[0] : 0}); + registers.SetColorClearWord0(0, {.word0 = fill_case.cmask ? expected[0] : fill_case.clear_word}); registers.SetColorClearWord1(0, {.word1 = 0}); registers.SetRenderTargetMask(0x0f); scheduler.Begin(registers, user_config, shaders); @@ -10354,12 +10409,13 @@ public: !texture_cache.IsMeta(dcc_address), "color metadata did not retain its type and full native allocation"); const auto read_texel = [&] { - return ReadCachedTexel(name, context, color.image_id, {}, {1, 1, 1}, selected_layer); + return ReadCachedTexel(name, context, color.image_id, {}, {probe_width, 1, 1}, selected_layer); }; Require(name, "GPU color clear value", read_texel() == expected && - ReadCachedTexel(name, context, color.image_id, {511, 255, 0}, - {1, 1, 1}, selected_layer) == expected, + ReadCachedTexel(name, context, color.image_id, + {static_cast(512 - probe_width), 255, 0}, + {probe_width, 1, 1}, selected_layer) == expected, "native metadata did not materialize the expected color"); if (fill_case.reuse_mips) { const auto &image = texture_cache.GetImage(color.image_id); @@ -10405,6 +10461,9 @@ public: painted = {0x0000ffffu, 0xffffffffu}; } else if (fill_case.cmask) { painted = {0xffff00ffu}; + } else if (bytes_per_pixel <= 4) { + const uint32_t one = type == Prospero::ChannelType::kFloat ? 0x3c00u : 0xffffu; + painted = {bytes_per_pixel == 2 ? one | (one << 16u) : one}; } const auto retains_painted_texel = [&] { const auto texels = ReadCachedTexel(name, context, color.image_id, {},