From 0f1ff37f2328b1ea7c8268157fcf7be62fdb2276 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 27 Sep 2026 13:48:11 +0400 Subject: [PATCH] Graphics: materialize CMASK clears before publishing expanded metadata --- src/graphics/guest_gpu/tile.cpp | 39 +++-- src/graphics/guest_gpu/tile.h | 2 + .../host_gpu/renderer/cache/textureCache.cpp | 44 +++--- .../host_gpu/renderer/cache/textureCache.h | 2 +- .../host_gpu/renderer/colorRenderTarget.cpp | 29 ++-- src/graphics/host_gpu/renderer/debug.cpp | 11 -- .../host_gpu/renderer/image/image.cpp | 3 +- .../host_gpu/renderer/image/imageInfo.h | 16 +-- tests/ShaderRecompilerComputeTests.cpp | 136 +++++++++++------- 9 files changed, 164 insertions(+), 118 deletions(-) diff --git a/src/graphics/guest_gpu/tile.cpp b/src/graphics/guest_gpu/tile.cpp index 39da3a09..3d07ae06 100644 --- a/src/graphics/guest_gpu/tile.cpp +++ b/src/graphics/guest_gpu/tile.cpp @@ -1213,20 +1213,12 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch, return true; } -bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices, - uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile, - TileSizeAlign& total_size, uint32_t num_fragments_log2) { - total_size = {}; - if (width == 0 || height == 0 || slices == 0 || levels != 1 || num_fragments_log2 != 0 || - !std::has_single_bit(bytes_per_element) || bytes_per_element > 16 || - (tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth)) { +static bool TileGetColorMetadataSize(uint32_t width, uint32_t height, uint32_t slices, + uint32_t block_width, uint32_t block_height, + TileSizeAlign& total_size) { + if (width == 0 || height == 0 || slices == 0) { return false; } - // Gen5 color metadata uses 4 KiB blocks, with one byte per 256 bytes of color data. - // Metadata stays thin for volume surfaces, so every depth slice has its own block raster. - const uint32_t coverage_bits = 20u - std::countr_zero(bytes_per_element); - const uint32_t block_width = 1u << ((coverage_bits + 1u) / 2u); - const uint32_t block_height = 1u << (coverage_bits / 2u); const uint64_t blocks_x = (static_cast(width) + block_width - 1u) / block_width; const uint64_t blocks_y = (static_cast(height) + block_height - 1u) / block_height; const uint64_t blocks = blocks_x * blocks_y; @@ -1238,6 +1230,29 @@ bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices, return true; } +bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices, + uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile, + TileSizeAlign& total_size, uint32_t num_fragments_log2) { + total_size = {}; + if (levels != 1 || num_fragments_log2 != 0 || + !std::has_single_bit(bytes_per_element) || bytes_per_element > 16 || + (tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth)) { + return false; + } + // Gen5 DCC uses 4 KiB blocks, with one byte per 256 bytes of color data. + // Metadata stays thin for volume surfaces, so every depth slice has its own block raster. + const uint32_t coverage_bits = 20u - std::countr_zero(bytes_per_element); + return TileGetColorMetadataSize(width, height, slices, 1u << ((coverage_bits + 1u) / 2u), + 1u << (coverage_bits / 2u), total_size); +} + +bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels, + TileSizeAlign& total_size) { + total_size = {}; + // Gen5 CMASK stores four bits per 8x8 tile in 4 KiB blocks covering 1024x512 pixels. + return levels == 1 && TileGetColorMetadataSize(width, height, slices, 1024, 512, total_size); +} + void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height, uint32_t levels, Prospero::TileMode tile, TileSizeAlign* total_size, TileSizeOffset* level_sizes, TilePaddedSize* padded_size) { diff --git a/src/graphics/guest_gpu/tile.h b/src/graphics/guest_gpu/tile.h index cac2f5e7..15148e44 100644 --- a/src/graphics/guest_gpu/tile.h +++ b/src/graphics/guest_gpu/tile.h @@ -126,6 +126,8 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile, TileSizeAlign& total_size, uint32_t num_fragments_log2 = 0); +bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels, + TileSizeAlign& total_size); void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height, uint32_t levels, Prospero::TileMode tile, TileSizeAlign* total_size, TileSizeOffset* level_sizes, TilePaddedSize* padded_size); diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index 2e9d6b6d..b93e70e6 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -33,24 +33,27 @@ namespace { constexpr uint64_t NumFramesBeforeRemoval = 32; -[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, uint8_t code, +[[nodiscard]] bool DecodeColorClear(const TextureCache::ImageDesc& desc, uint8_t code, vk::ClearColorValue& clear) { + const auto& metadata = desc.info.metadata; + const auto format = desc.view_info.format; + const bool cmask = metadata.kind == ImageMetadataKind::Cmask; + if (cmask ? code == 0 : code == 0x20) { + // Register clears belong to the color buffer; the texture pipe cannot decode them. + return desc.type == TextureCache::BindingType::RenderTarget && + metadata.clear_register_valid && + DecodePackedColorClear(format, metadata.clear_word, clear); + } + if (cmask) { + return false; + } switch (code) { case 0x00: - case 0x20: case 0x40: case 0x80: case 0xc0: break; default: return false; } - const auto& metadata = desc.info.metadata; - const auto format = desc.view_info.format; - if (code == 0x20) { - // Clear-to-register is a color-buffer operation; the texture pipe cannot decode it. - return desc.type == TextureCache::BindingType::RenderTarget && - metadata.dcc_clear_register_valid && - DecodePackedColorClear(format, metadata.dcc_clear_word, clear); - } clear = {}; if (code == 0x00) { return true; @@ -1125,9 +1128,10 @@ void TextureCache::InitializeImage(ImageId id) { } } -void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc, +void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc, uint32_t metadata_base_layer) { - if (desc.info.metadata.kind != ImageMetadataKind::Dcc) { + if (desc.info.metadata.kind != ImageMetadataKind::Dcc && + desc.info.metadata.kind != ImageMetadataKind::Cmask) { return; } const auto range = desc.info.metadata.range; @@ -1135,18 +1139,18 @@ void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc, std::scoped_lock lock {m_lock}; auto& image = m_slot_images[id]; image.info.metadata = desc.info.metadata; - // A native DCC allocation must not retain a reused HTile/CMask/FMask interpretation. + // Native color metadata must not retain a reused HTile/CMask/FMask clear flag. m_surface_metas.erase(range.address); if (range.size == 0 || desc.info.resources.levels != 1 || image.info.resources.levels != 1) { return; } } const auto layers = desc.info.TransferLayers(); - // These one-mip surfaces use complete 4 KiB DCC metadata blocks. + // These one-mip surfaces use complete 4 KiB color metadata blocks. constexpr uint64_t MetadataBlockSize = 0x1000; if (!range.Valid() || range.address % MetadataBlockSize != 0 || layers == 0 || range.size % layers != 0 || (range.size / layers) % MetadataBlockSize != 0) { - EXIT("TextureCache: DCC slices must contain aligned 4 KiB blocks\n"); + EXIT("TextureCache: color metadata slices must contain aligned 4 KiB blocks\n"); } const auto& view = desc.view_info; const bool volume_texture = desc.info.IsVolume() && view.type == vk::ImageViewType::e3D; @@ -1154,7 +1158,7 @@ void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc, const auto image_first = volume_texture ? 0u : view.base_layer; const auto count = volume_texture ? desc.info.extent.depth : view.layer_count; if (first >= layers || count > layers - first) { - EXIT("TextureCache: DCC view exceeds its native metadata slices\n"); + EXIT("TextureCache: color view exceeds its native metadata slices\n"); } // Finish native metadata writes before reading backing bytes. This can submit the scheduler, // so discovery runs before final draw uploads and never holds the texture lock across it. @@ -1166,15 +1170,15 @@ void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc, const auto address = range.address + slice_size * (first + slice); uint8_t code = 0; if (!LibKernel::Memory::TryReadBacking(address, &code, sizeof(code))) { - EXIT("TextureCache: failed to read DCC metadata backing\n"); + EXIT("TextureCache: failed to read color metadata backing\n"); } vk::ClearValue clear {}; - if (!DecodeDccClear(desc, code, clear.color)) { + if (!DecodeColorClear(desc, code, clear.color)) { continue; } std::vector bytes(slice_size); if (!LibKernel::Memory::TryReadBacking(address, bytes.data(), bytes.size())) { - EXIT("TextureCache: failed to read DCC metadata slice\n"); + EXIT("TextureCache: failed to read color metadata slice\n"); } if (!std::all_of(bytes.begin(), bytes.end(), [code](uint8_t byte) { return byte == code; })) { continue; @@ -1319,7 +1323,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) { image.tick_accessed_last = m_scheduler.CurrentTick(); TouchImage(image); } - MaterializeDccClear(result, desc, metadata_base_layer); + MaterializeColorClear(result, desc, metadata_base_layer); if (desc.type == BindingType::VideoOut && desc.info.metadata.compression != VideoOutCompression::Uncompressed) { std::scoped_lock lock {m_lock}; diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.h b/src/graphics/host_gpu/renderer/cache/textureCache.h index 9f70abf9..1eecd6af 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.h +++ b/src/graphics/host_gpu/renderer/cache/textureCache.h @@ -139,7 +139,7 @@ private: ImageId cached); [[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source); void RefreshImage(ImageId id); - void MaterializeDccClear(ImageId id, const ImageDesc& desc, + void MaterializeColorClear(ImageId id, const ImageDesc& desc, uint32_t metadata_base_layer); void InitializeImage(ImageId id); [[nodiscard]] TextureTransfer diff --git a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp index 4d7c3736..4ea5a69d 100644 --- a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp +++ b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp @@ -139,8 +139,7 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor // Color-control state selects the color-buffer operation and logical blend operation. // The normal copy operation is a regular color write, not an attachment clear. // Nonlinear clear values are still stored as normalized components. - // Fast color clears are metadata driven and must be handled explicitly when - // that metadata path is implemented; render-pass load must preserve contents. + // Metadata clears are materialized during image discovery; render-pass loads preserve contents. uint32_t width = 0; uint32_t height = 0; uint32_t pitch = 0; @@ -290,15 +289,25 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor desc.info.samples = samples; desc.info.tile_mode = rt.attrib3.tile_mode; const bool has_dcc = rt.info.dcc_compression_enable && rt.dcc_addr.addr != 0; - if (has_dcc) { + const bool has_cmask = !rt.info.dcc_compression_enable && rt.info.cmask_fast_clear_enable && + rt.cmask.addr != 0 && samples == 1 && + !rt.info.fmask_compression_enable && + !rt.attrib3.write_vrs_rate_hint_to_cmask; + if (has_dcc || has_cmask) { TileSizeAlign metadata_size {}; - (void)TileGetDccSize(width, height, volume ? depth : view.image_layers, bytes_per_element, - levels, rt.attrib3.tile_mode, metadata_size, rt.attrib.num_fragments); - desc.info.metadata.kind = ImageMetadataKind::Dcc; - desc.info.metadata.range = {rt.dcc_addr.addr, metadata_size.size}; - desc.info.metadata.dcc_clear_word = rt.clear_word0.word0; - desc.info.metadata.dcc_clear_register_valid = true; - desc.info.metadata.dcc_alpha_msb = DccAlphaOnMsb(rt.info); + const auto layers = volume ? depth : view.image_layers; + if (has_dcc) { + (void)TileGetDccSize(width, height, layers, bytes_per_element, levels, + rt.attrib3.tile_mode, metadata_size, rt.attrib.num_fragments); + desc.info.metadata.dcc_alpha_msb = DccAlphaOnMsb(rt.info); + } else { + (void)TileGetCmaskSize(width, height, layers, levels, metadata_size); + } + // 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_register_valid = true; } for (uint32_t level = 0; level < levels; level++) { if (volume) { diff --git a/src/graphics/host_gpu/renderer/debug.cpp b/src/graphics/host_gpu/renderer/debug.cpp index 97c63171..6d8cb374 100644 --- a/src/graphics/host_gpu/renderer/debug.cpp +++ b/src/graphics/host_gpu/renderer/debug.cpp @@ -219,17 +219,6 @@ static void RtCheck(const HW::RenderTarget& rt) { } } - if (rt.info.cmask_fast_clear_enable || rt.info.dcc_compression_enable) { - static bool logged = false; - if (!logged) { - LOGF("RenderTarget: temporary: ignoring PS5 color metadata fast_clear=%s dcc=%s " - "cmask=0x%016" PRIx64 " dcc_addr=0x%016" PRIx64 "\n", - rt.info.cmask_fast_clear_enable ? "true" : "false", - rt.info.dcc_compression_enable ? "true" : "false", rt.cmask.addr, - rt.dcc_addr.addr); - logged = true; - } - } if (rt.info.blend_bypass) { static bool logged = false; if (!logged) { diff --git a/src/graphics/host_gpu/renderer/image/image.cpp b/src/graphics/host_gpu/renderer/image/image.cpp index e1db0de2..062552d0 100644 --- a/src/graphics/host_gpu/renderer/image/image.cpp +++ b/src/graphics/host_gpu/renderer/image/image.cpp @@ -627,12 +627,13 @@ void Validate(const ImageInfo& info) { } break; case ImageMetadataKind::Dcc: + case ImageMetadataKind::Cmask: if (info.metadata.range.address == 0 || info.metadata.range.address >= TRACKER_ADDRESS_SIZE || (info.metadata.range.size != 0 && info.metadata.range.size > TRACKER_ADDRESS_SIZE - info.metadata.range.address) || info.metadata.compression == VideoOutCompression::Unsupported) { - EXIT("invalid DCC metadata\n"); + EXIT("invalid color metadata\n"); } break; } diff --git a/src/graphics/host_gpu/renderer/image/imageInfo.h b/src/graphics/host_gpu/renderer/image/imageInfo.h index fd1cfad8..8f5b948b 100644 --- a/src/graphics/host_gpu/renderer/image/imageInfo.h +++ b/src/graphics/host_gpu/renderer/image/imageInfo.h @@ -17,17 +17,17 @@ namespace Libs::Graphics { enum class VideoOutCompression : uint8_t { Uncompressed, Dcc256_256_0, Dcc256_64_64, Unsupported }; -enum class ImageMetadataKind : uint8_t { None, Htile, Dcc }; +enum class ImageMetadataKind : uint8_t { None, Htile, Dcc, Cmask }; struct ImageMetadataInfo { GuestRange range; - ImageMetadataKind kind = ImageMetadataKind::None; - uint32_t control = 0; - uint32_t dcc_clear_word = 0; - VideoOutCompression compression = VideoOutCompression::Uncompressed; - bool stencil_compressed = false; - bool dcc_clear_register_valid = false; - bool dcc_alpha_msb = true; + ImageMetadataKind kind = ImageMetadataKind::None; + uint32_t control = 0; + uint32_t clear_word = 0; + VideoOutCompression compression = VideoOutCompression::Uncompressed; + bool stencil_compressed = false; + bool clear_register_valid = false; + bool dcc_alpha_msb = true; }; struct ImageSubresources { diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index b39d8aba..92efd048 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -9029,8 +9029,8 @@ public: std::printf("[gpu] %-32s ok\n", name); } - void CheckRenderExecutorDccFixedClearFloat() { - constexpr const char *name = "RenderExecutorDccFixedClearFloat"; + void CheckRenderExecutorColorMetadataClear() { + constexpr const char *name = "RenderExecutorColorMetadataClear"; constexpr uintptr_t base = 0x0000000204100000ull; constexpr uint64_t allocation_size = 0x200000; constexpr uint64_t allocation_alignment = 0x10000; @@ -9039,11 +9039,13 @@ public: uint32_t fill; std::array texel; bool reuse_unorm = false; + bool cmask = false; }; constexpr std::array cases{ FillCase{0x40404040u, {0, 0x3c000000u}}, FillCase{0x80808080u, {0x3c003c00u, 0x00003c00u}}, FillCase{0x40404040u, {0, 0x3c000000u}, true}, + FillCase{0, {0x804020ffu, 0}, false, true}, }; EnsureRuntimeContext(); // Astro's generic metadata fill, through S_ENDPGM; trailing debug data is omitted. @@ -9081,9 +9083,9 @@ public: {.type = Prospero::ShaderBinaryType::kCs, .code_size_bytes = sizeof(native_fill)}); } - TileSizeAlign metadata_size{}; + TileSizeAlign dcc_size{}; (void)TileGetDccSize(512, 256, 1, 8, 1, - Prospero::TileMode::kRenderTarget, metadata_size, 0); + Prospero::TileMode::kRenderTarget, dcc_size, 0); int64_t direct_offset = -1; Require(name, "direct allocation", @@ -9101,6 +9103,10 @@ public: std::memset(mapped, 0, allocation_size); for (const auto &fill_case : cases) { + 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)); RenderContext context(m_runtime_context); HW::Context registers{}; HW::UserConfig user_config{}; @@ -9111,17 +9117,23 @@ public: auto &scheduler = context.GetCommandScheduler(); registers.SetColorBase(0, {.addr = base}); registers.SetColorInfo( - 0, {.dcc_compression_enable = true, - .format = Prospero::ChannelLayout::k16_16_16_16, - .channel_type = Prospero::ChannelType::kFloat, + 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, .channel_order = Prospero::ChannelOrder::kStandard}); + registers.SetColorView(0, {.base_array_slice_index = selected_layer, + .last_array_slice_index = selected_layer}); registers.SetColorAttrib2(0, {.height = 255, .width = 511}); registers.SetColorAttrib3(0, {.tile_mode = Prospero::TileMode::kRenderTarget, .dimension = 1, .metadata_pipe_aligned = true}); registers.SetColorDccAddr(0, {.addr = dcc_address}); - registers.SetColorClearWord0(0, {.word0 = 0}); + registers.SetColorCmask(0, {.addr = dcc_address}); + registers.SetColorClearWord0(0, {.word0 = fill_case.cmask ? expected[0] : 0}); registers.SetColorClearWord1(0, {.word1 = 0}); registers.SetRenderTargetMask(0x0f); scheduler.Begin(registers, user_config, shaders); @@ -9162,7 +9174,7 @@ public: } executor.DispatchDirect(0, scheduler.Current(), (count + 63) / 64, 1, 1, 0x41u); }; - const auto metadata_words = static_cast(metadata_size.size / 4); + const auto metadata_words = static_cast(metadata_size / 4); fill_metadata(metadata_words); Require(name, "metadata before descriptor", !texture_cache.IsMeta(dcc_address), @@ -9181,28 +9193,36 @@ public: vk::Format::eR16G16B16A16Unorm, "the aliased clear did not reuse its existing UNORM allocation"); } - Require(name, "fixed clear on a float target", + Require(name, "color clear metadata discovery", color.image_id && - color.desc.info.metadata.kind == ImageMetadataKind::Dcc && + color.desc.info.metadata.kind == + (fill_case.cmask ? ImageMetadataKind::Cmask : ImageMetadataKind::Dcc) && color.desc.info.metadata.range.address == dcc_address && + color.desc.info.metadata.range.size == metadata_size && rendering.num_color_attachments == 1 && !texture_cache.IsMeta(dcc_address), - "a DCC fixed clear code was not materialised on an RGBA16F " - "target"); - const auto cleared = ReadCachedTexel(name, context, color.image_id); - Require(name, "GPU float clear value", - cleared == - std::vector(fill_case.texel.begin(), fill_case.texel.end()), - "expected " + Hex(fill_case.texel[0]) + ", " + Hex(fill_case.texel[1]) + - "; got " + Hex(cleared[0]) + ", " + Hex(cleared[1])); - context.GetBufferCache().ReadMemory(dcc_address, metadata_size.size, false); - std::vector expanded_metadata(metadata_size.size); - Require(name, "expanded native metadata", - LibKernel::Memory::TryReadBacking(dcc_address, expanded_metadata.data(), - expanded_metadata.size()) && - std::all_of(expanded_metadata.begin(), expanded_metadata.end(), - [](uint8_t value) { return value == 0xff; }), - "materialized color retained stale native DCC clear codes"); + "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); + }; + Require(name, "GPU color clear value", + read_texel() == expected && + ReadCachedTexel(name, context, color.image_id, {511, 255, 0}, + {1, 1, 1}, selected_layer) == expected, + "native metadata did not materialize the expected 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"); + }; + check_expanded_metadata(); const auto bind = [&] { RenderExecutorTestAccess::ResetBindings(executor); @@ -9214,16 +9234,19 @@ public: 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, 0, 1}, clear); + {vk::ImageAspectFlagBits::eColor, 0, 1, selected_layer, 1}, clear); }; std::vector painted{0x00003c00u, 0x3c003c00u}; if (fill_case.reuse_unorm) { painted = {0x0000ffffu, 0xffffffffu}; + } else if (fill_case.cmask) { + painted = {0xffff00ffu}; } const auto retains_painted_texel = [&] { - const auto texels = ReadCachedTexel(name, context, color.image_id, {}, {512, 256, 1}); - for (size_t i = 0; i < texels.size(); i += 2) { - if (texels[i] == painted[0] && texels[i + 1] == painted[1]) { + const auto texels = ReadCachedTexel(name, context, color.image_id, {}, + {512, 256, 1}, selected_layer); + for (size_t i = 0; i < texels.size(); i += painted.size()) { + if (std::equal(painted.begin(), painted.end(), texels.begin() + i)) { return true; } } @@ -9232,8 +9255,19 @@ public: paint(); bind(); Require(name, "unchanged metadata preserves rendering", - ReadCachedTexel(name, context, color.image_id) == painted, - "rebinding an unchanged DCC allocation reapplied an old clear"); + read_texel() == painted, + "rebinding unchanged color metadata reapplied an old clear"); + if (fill_case.cmask) { + WriteMetadata(context, dcc_address, metadata_size, 0x40404040u); + bind(); + Require(name, "nonzero CMASK is not a DCC clear", read_texel() == painted, + "CMASK bytes were decoded as a DCC fixed-color key"); + } + WriteMetadata(context, dcc_address, metadata_size, fill_case.fill); + bind(); + Require(name, "CPU metadata re-clear", read_texel() == expected, + "CPU metadata writes did not clear an already drawn target"); + paint(); fill_metadata(metadata_words - 1); bind(); Require(name, "partial same-value metadata fill", @@ -9242,11 +9276,10 @@ public: fill_metadata(metadata_words, true); bind(); Require(name, "same-value raw native re-clear", - ReadCachedTexel(name, context, color.image_id) == - std::vector(fill_case.texel.begin(), fill_case.texel.end()), + read_texel() == expected, "a repeated metadata fill did not clear an already drawn target"); paint(); - WriteMetadata(context, dcc_address, metadata_size.size, UINT32_MAX); + WriteMetadata(context, dcc_address, metadata_size, UINT32_MAX); fill_metadata(metadata_words - 1); bind(); Require(name, "partial native metadata fill", @@ -9255,8 +9288,7 @@ public: fill_metadata(metadata_words); bind(); Require(name, "complete fill after partial fill", - ReadCachedTexel(name, context, color.image_id) == - std::vector(fill_case.texel.begin(), fill_case.texel.end()), + read_texel() == expected, "completing the metadata overwrite did not restore its clear"); ImageDesc previous_depth{}; @@ -9273,7 +9305,7 @@ public: previous_depth.info.tile_mode = Prospero::TileMode::kLinear; previous_depth.info.mip_layout[0] = {0, 0x10000, 128, 128}; previous_depth.info.metadata.kind = ImageMetadataKind::Htile; - previous_depth.info.metadata.range = {dcc_address, metadata_size.size}; + previous_depth.info.metadata.range = {dcc_address, metadata_size}; previous_depth.info.htile_clear_mask = 0; previous_depth.view_info.format = vk::Format::eD32Sfloat; previous_depth.view_info.type = vk::ImageViewType::e2D; @@ -9286,21 +9318,15 @@ public: "the old depth target did not register its metadata allocation"); paint(); bind(); - Require(name, "HTile allocation reused as expanded DCC", + Require(name, "HTile allocation reused as expanded color metadata", !texture_cache.IsMeta(dcc_address) && !texture_cache.ClearMeta(dcc_address) && - ReadCachedTexel(name, context, color.image_id) == painted, - "DCC discovery retained the old HTile classification or erased rendered color"); + read_texel() == painted, + "color discovery retained the old HTile classification or erased rendering"); fill_metadata(metadata_words); bind(); - context.GetBufferCache().ReadMemory(dcc_address, metadata_size.size); - Require(name, "native DCC dispatch after HTile reuse", - ReadCachedTexel(name, context, color.image_id) == - std::vector(fill_case.texel.begin(), fill_case.texel.end()) && - LibKernel::Memory::TryReadBacking(dcc_address, expanded_metadata.data(), - expanded_metadata.size()) && - std::all_of(expanded_metadata.begin(), expanded_metadata.end(), - [](uint8_t byte) { return byte == 0xff; }), - "the former HTile entry swallowed the native DCC fill dispatch"); + Require(name, "native color clear after HTile reuse", read_texel() == expected, + "the former HTile entry swallowed the native color metadata fill"); + check_expanded_metadata(); RenderExecutorTestAccess::ResetBindings(executor); resources.UnmapMemory(base, allocation_size); scheduler.Finish(); @@ -34602,7 +34628,7 @@ int main(int argc, char **argv) { vulkan.CheckRasterization(false); vulkan.CheckRenderExecutorColorDiscovery(); vulkan.CheckRenderExecutorColorVolumeDiscovery(); - vulkan.CheckRenderExecutorDccFixedClearFloat(); + vulkan.CheckRenderExecutorColorMetadataClear(); vulkan.CheckSampledDccClear(); vulkan.CheckRenderExecutorColorDepthTileDiscovery(); vulkan.CheckRenderExecutorStencilBindingDiscovery(); @@ -34633,7 +34659,7 @@ int main(int argc, char **argv) { } if (argc == 2 && std::strcmp(argv[1], "--dcc-clear-only") == 0) { VulkanHarness vulkan; - vulkan.CheckRenderExecutorDccFixedClearFloat(); + vulkan.CheckRenderExecutorColorMetadataClear(); return 0; } if (argc == 2 && std::strcmp(argv[1], "--compute-meta-clear-only") == 0) { @@ -34642,7 +34668,7 @@ int main(int argc, char **argv) { vulkan.CheckComputeMetaClearClassification(); vulkan.CheckNativeIndirectDispatch(); vulkan.CheckRenderExecutorColorVolumeDiscovery(); - vulkan.CheckRenderExecutorDccFixedClearFloat(); + vulkan.CheckRenderExecutorColorMetadataClear(); vulkan.CheckSampledDccClear(); vulkan.CheckRenderExecutorStencilBindingDiscovery(); return 0; @@ -34838,7 +34864,7 @@ int main(int argc, char **argv) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS vulkan.CheckRenderExecutorColorDiscovery(); vulkan.CheckRenderExecutorColorVolumeDiscovery(); - vulkan.CheckRenderExecutorDccFixedClearFloat(); + vulkan.CheckRenderExecutorColorMetadataClear(); vulkan.CheckSampledDccClear(); vulkan.CheckRenderExecutorColorStandardTileDiscovery(); vulkan.CheckRenderExecutorColorDepthTileDiscovery();