From 5d83ce82af3a36b0c9aa99cbf879663c391799ab Mon Sep 17 00:00:00 2001 From: nmzik Date: Tue, 18 Aug 2026 19:09:12 +0200 Subject: [PATCH] renderer: track and materialize deferred DCC clears Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com> --- .../host_gpu/renderer/cache/textureCache.cpp | 113 ++++++++++++++++-- .../host_gpu/renderer/cache/textureCache.h | 16 ++- .../host_gpu/renderer/colorRenderTarget.cpp | 67 ++++++++--- .../host_gpu/renderer/colorRenderTarget.h | 4 +- .../host_gpu/renderer/renderCompute.cpp | 15 +++ src/graphics/host_gpu/renderer/renderDraw.cpp | 102 +++++++++++++--- tests/ShaderRecompilerComputeTests.cpp | 45 ++++++- 7 files changed, 312 insertions(+), 50 deletions(-) diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index 6ec85157d..b83afca2a 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -252,7 +252,7 @@ void TextureCache::DeleteImage(ImageId id) { EXIT("TextureCache: deleting a GPU-modified image without resolving its contents\n"); } m_download_images.erase(id); - if (owner->info.metadata.kind == ImageMetadataKind::Htile) { + if (owner->info.HasMetadata()) { m_surface_metas.erase(owner->info.metadata.range.address); } UnregisterImage(id); @@ -1327,6 +1327,19 @@ vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc) image.MarkGpuModified(); image.usage.render_target = true; RefreshImage(id, desc); + // DCC uses a separate metadata allocation. Register it when the color target is bound, + // matching the point where CMask/FMask will be registered. Preserve a PendingDcc entry + // because the metadata fill may have run before this bind. + if (desc.info.metadata.kind == ImageMetadataKind::Dcc) { + image.info.metadata = desc.info.metadata; + auto [metadata, inserted] = m_surface_metas.try_emplace( + desc.info.metadata.range.address, MetaDataInfo {.type = MetaDataInfo::Type::Dcc}); + if (!inserted && metadata->second.type == MetaDataInfo::Type::PendingDcc) { + metadata->second.type = MetaDataInfo::Type::Dcc; + } else if (!inserted && metadata->second.type != MetaDataInfo::Type::Dcc) { + EXIT("TextureCache: color target reuses non-DCC metadata\n"); + } + } CommitGpuWrite(image); TrackImageDownloadLocked(id, image); const auto view = image.FindView(desc.view_info); @@ -1349,8 +1362,20 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) { RefreshImage(id, desc); if (desc.info.HasMetadata()) { image.info.metadata = desc.info.metadata; - m_surface_metas.try_emplace(desc.info.metadata.range.address, - MetaDataInfo {.clear_mask = image.info.htile_clear_mask}); + auto [metadata, inserted] = + m_surface_metas.try_emplace(desc.info.metadata.range.address, + MetaDataInfo {.type = MetaDataInfo::Type::HTile, + .clear_mask = image.info.htile_clear_mask}); + if (!inserted && metadata->second.type == MetaDataInfo::Type::PendingDcc) { + // PendingDcc fills use DCC-specific encoding. Do not reinterpret a speculative + // DCC/buffer fill as HTile state if the address is later classified as depth metadata. + metadata->second.type = MetaDataInfo::Type::HTile; + metadata->second.clear_mask = image.info.htile_clear_mask; + metadata->second.fill_value = 0xffffffffu; + metadata->second.fill_size = 0; + } else if (!inserted && metadata->second.type != MetaDataInfo::Type::HTile) { + EXIT("TextureCache: depth target reuses non-HTile metadata\n"); + } } CommitGpuWrite(image); if (desc.info.HasStencil()) { @@ -1826,33 +1851,90 @@ void TextureCache::ClearGpuModified(ImageId id) { } bool TextureCache::IsMeta(uint64_t address) { - CacheLock lock(*this, m_lock); - return m_surface_metas.contains(address); -} - -bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice) { CacheLock lock(*this, m_lock); const auto found = m_surface_metas.find(address); - if (found == m_surface_metas.end()) { + return found != m_surface_metas.end() && found->second.type != MetaDataInfo::Type::PendingDcc; +} + +bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice, uint32_t* fill_value) { + CacheLock lock(*this, m_lock); + const auto found = m_surface_metas.find(address); + if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc || + slice >= 32) { return false; } + if (fill_value != nullptr) { + *fill_value = found->second.fill_value; + } return (found->second.clear_mask & (1u << slice)) != 0; } bool TextureCache::ClearMeta(uint64_t address) { CacheLock lock(*this, m_lock); const auto found = m_surface_metas.find(address); - if (found == m_surface_metas.end()) { + if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc || + found->second.type == MetaDataInfo::Type::Dcc) { + // Preserve the broad metadata-clear operation for CMask/FMask/HTile. DCC requires a + // validated fill value, so an arbitrary compute write must not clear it. return false; } found->second.clear_mask = UINT32_MAX; return true; } +bool TextureCache::TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t fill_value) { + if (!GuestRange {address, size}.Valid()) { + EXIT("TextureCache: invalid DCC fill range\n"); + } + // DCC fills use a repeated byte code. Require all four bytes of the detected dword to agree, + // and mark only recognized deferred-clear encodings as logically clear. + const auto dcc_clear_mask = [fill_value] { + const auto code = static_cast(fill_value); + if (fill_value != static_cast(code) * 0x01010101u) { + return 0u; + } + switch (code) { + case 0x00: + case 0x20: + case 0x40: + case 0x80: + case 0xc0: return UINT32_MAX; + default: return 0u; + } + }(); + CacheLock lock(*this, m_lock); + auto found = m_surface_metas.find(address); + if (found == m_surface_metas.end()) { + // This dispatch may precede color-target discovery. Retain it in the shared + // metadata map, but PendingDcc remains invisible to IsMeta until registration. Returning + // false lets the guest dispatch execute and initialize memory while the type is uncertain. + m_surface_metas.emplace(address, MetaDataInfo {.type = MetaDataInfo::Type::PendingDcc, + .clear_mask = dcc_clear_mask, + .fill_value = fill_value, + .fill_size = size}); + return false; + } + if (found->second.type == MetaDataInfo::Type::PendingDcc) { + found->second.clear_mask = dcc_clear_mask; + found->second.fill_value = fill_value; + found->second.fill_size = size; + return false; + } + if (found->second.type == MetaDataInfo::Type::Dcc) { + found->second.clear_mask = dcc_clear_mask; + found->second.fill_value = fill_value; + found->second.fill_size = size; + return true; + } + // The strict DCC path must not reinterpret a CMask/FMask/HTile allocation as DCC. + return false; +} + bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) { CacheLock lock(*this, m_lock); const auto found = m_surface_metas.find(address); - if (found == m_surface_metas.end()) { + if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc || + slice >= 32) { return false; } if (is_clear) { @@ -1867,8 +1949,13 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) { if (!GuestRange {address, size}.Valid()) { EXIT("TextureCache: invalid unmap range\n"); } - CacheLock lock(*this, m_lock); - auto images = FindImagesInRegion(address, size, false); + CacheLock lock(*this, m_lock); + const auto end = address + size; + for (auto metadata = m_surface_metas.lower_bound(address); + metadata != m_surface_metas.end() && metadata->first < end;) { + metadata = m_surface_metas.erase(metadata); + } + auto images = FindImagesInRegion(address, size, false); if (!images.empty()) { m_scheduler.Finish(); } diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.h b/src/graphics/host_gpu/renderer/cache/textureCache.h index 33042e03b..945226afc 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.h +++ b/src/graphics/host_gpu/renderer/cache/textureCache.h @@ -69,8 +69,12 @@ public: [[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size); [[nodiscard]] bool IsMeta(uint64_t address); - [[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice); + [[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice, + uint32_t* fill_value = nullptr); [[nodiscard]] bool ClearMeta(uint64_t address); + // Returns true when registered DCC absorbed the fill and the caller may skip the dispatch. + // False may still record PendingDcc state, but the guest dispatch must execute. + [[nodiscard]] bool TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t fill_value); [[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear); void UnmapMemory(uint64_t address, uint64_t size); @@ -88,7 +92,17 @@ private: }; struct MetaDataInfo { + // A guest metadata-fill dispatch may initialize DCC before its render target is bound. + // PendingDcc retains that exact fill until FindRenderTarget classifies the address, + // without exposing an unconfirmed buffer address to the normal metadata heuristics. + // Keep all surface metadata in this one shadPS4-style entry so CMask/FMask can be + // registered beside HTile and DCC without introducing parallel tracking paths. + enum class Type : uint8_t { PendingDcc, CMask, FMask, HTile, Dcc }; + + Type type = Type::PendingDcc; uint32_t clear_mask = 0; + uint32_t fill_value = 0xffffffffu; + uint64_t fill_size = 0; }; struct OverlapResult { diff --git a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp index 1ce206272..8cfeddfc7 100644 --- a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp +++ b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp @@ -21,6 +21,28 @@ namespace Libs::Graphics { static std::atomic g_render_color_log_count = 0; +static void ResolveDccClearInfo(RenderColorInfo& info, vk::Format format, bool has_dcc, + uint32_t packed_clear) { + // Register-backed DCC clears use the target's packed clear value. Decode one-word guest + // formats here; unsupported encodings remain tracked without unsafe materialization. + info.metadata_clear_supported = + has_dcc && DecodePackedColorClear(format, packed_clear, info.color_clear_value); + switch (format) { + case vk::Format::eR8G8B8A8Unorm: + case vk::Format::eR8G8B8A8Srgb: + case vk::Format::eB8G8R8A8Unorm: + case vk::Format::eB8G8R8A8Srgb: + case vk::Format::eA2B10G10R10UnormPack32: + case vk::Format::eA2R10G10B10UnormPack32: + info.metadata_fixed_clear_supported = has_dcc; + break; + default: info.metadata_fixed_clear_supported = false; break; + } + if (!info.metadata_clear_supported) { + info.color_clear_value = {}; + } +} + // NOLINTNEXTLINE(readability-function-cognitive-complexity) void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, RenderColorInfo& r, @@ -55,20 +77,22 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB } // No color output - r.type = RenderColorType::NoColorOutput; - r.desc = {}; - r.base_addr = 0; - r.image_id = {}; - r.image_view = nullptr; - r.format = vk::Format::eUndefined; - r.extent = {}; - r.base_mip_level = 0; - r.base_array_layer = 0; - r.buffer_size = 0; - r.samples = 1; - r.export_mapping = {}; - r.color_clear_enable = false; - r.color_clear_value = {}; + r.type = RenderColorType::NoColorOutput; + r.desc = {}; + r.base_addr = 0; + r.image_id = {}; + r.image_view = nullptr; + r.format = vk::Format::eUndefined; + r.extent = {}; + r.base_mip_level = 0; + r.base_array_layer = 0; + r.buffer_size = 0; + r.samples = 1; + r.export_mapping = {}; + r.color_clear_enable = false; + r.metadata_clear_supported = false; + r.metadata_fixed_clear_supported = false; + r.color_clear_value = {}; return; } const auto samples = render_sample_count(rt.attrib.num_fragments); @@ -107,9 +131,9 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB } } - // CB_COLOR_CONTROL describes the color-buffer operation / ROP3 logic op. - // ROP3 Copy is the normal color write path, not a render-target clear. - // SRGB clear words are still encoded as normalized component values. + // 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. r.color_clear_enable = false; @@ -309,6 +333,13 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB desc.info.bytes_per_block = bytes_per_element; 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) { + // DCC uses a separate metadata allocation. Carry its address through ImageInfo so + // TextureCache can associate metadata fills observed before target registration. + desc.info.metadata.kind = ImageMetadataKind::Dcc; + desc.info.metadata.range.address = rt.dcc_addr.addr; + } for (uint32_t level = 0; level < levels; level++) { if (volume) { const auto& mip = volume_layout.mips[level]; @@ -355,7 +386,7 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB r.samples = samples; r.export_mapping = target_format.export_mapping; r.color_clear_enable = false; - r.color_clear_value = {}; + ResolveDccClearInfo(r, target_format.format, has_dcc, rt.clear_word0.word0); BindRenderTarget(r.image_id); } diff --git a/src/graphics/host_gpu/renderer/colorRenderTarget.h b/src/graphics/host_gpu/renderer/colorRenderTarget.h index b2abfc7ad..3b3d17493 100644 --- a/src/graphics/host_gpu/renderer/colorRenderTarget.h +++ b/src/graphics/host_gpu/renderer/colorRenderTarget.h @@ -31,7 +31,9 @@ struct RenderColorInfo { uint32_t target_slot = 0; uint32_t samples = 1; Prospero::ColorComponentMapping export_mapping; - bool color_clear_enable = false; + bool color_clear_enable = false; + bool metadata_clear_supported = false; + bool metadata_fixed_clear_supported = false; vk::ClearColorValue color_clear_value {}; }; diff --git a/src/graphics/host_gpu/renderer/renderCompute.cpp b/src/graphics/host_gpu/renderer/renderCompute.cpp index 7e10cfd9f..977931523 100644 --- a/src/graphics/host_gpu/renderer/renderCompute.cpp +++ b/src/graphics/host_gpu/renderer/renderCompute.cpp @@ -139,6 +139,21 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com } auto& cache = command.GetContext().GetTextureCache(); if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) { + // Recognized metadata-fill shaders access DCC as an ordinary storage buffer and may run + // before the render target is bound. TryConsumeDccFill either consumes registered state + // or retains a PendingDcc fill while allowing the dispatch to run. + const bool registered_metadata = + cache.TryConsumeDccFill(descriptor.Base48(), size, packed_clear); + static std::atomic logged_metadata_clears {0}; + if (logged_metadata_clears.fetch_add(1, std::memory_order_relaxed) < 32) { + LOGF("GraphicsRenderDispatchDirect: %s metadata clear shader=0x%016" PRIx64 + " addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n", + registered_metadata ? "tracked" : "deferred", input.stage.program->shader_hash, + descriptor.Base48(), size, packed_clear); + } + if (registered_metadata) { + return true; + } return false; } static std::atomic logged_clears {0}; diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index cb88c4c4a..24dfc091b 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -377,8 +377,8 @@ static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandB (use_front ? poly_offset.front_scale : poly_offset.back_scale) / 16.0f; } - // CB_COLOR_CONTROL.operation controls special CB operations, not the normal color component - // write mask. Use CB_TARGET_MASK here so scanout passes with mode=Disable do not go black. + // Color-control operation selects special color-buffer paths, not the normal component write + // mask. Attachment availability therefore follows the target write mask. for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) { ret.color_write_enable[i] = (i < color_count && @@ -552,6 +552,71 @@ struct DrawCallInfo { uint32_t first_instance = 0; }; +static bool ResolveDccAttachmentClear(TextureCache& cache, const RenderColorInfo& target, + const ImageViewInfo& view, + vk::ClearColorValue& clear_value) { + if (target.desc.info.metadata.kind != ImageMetadataKind::Dcc) { + return false; + } + + // A DCC fast clear may update metadata only and leave the color allocation stale. Vulkan has + // no guest DCC state, so materialize the deferred value when the surface is next bound. + const auto metadata_address = target.desc.info.metadata.range.address; + uint32_t metadata_value = 0xffffffffu; + if (!cache.IsMetaCleared(metadata_address, view.base_layer, &metadata_value)) { + return false; + } + + // Translate recognized constant and register-backed DCC states into a host clear value. + clear_value = {}; + switch (static_cast(metadata_value)) { + case 0x00: break; + case 0x20: + if (!target.metadata_clear_supported) { + return false; + } + clear_value = target.color_clear_value; + break; + case 0x40: + if (!target.metadata_fixed_clear_supported) { + return false; + } + clear_value.float32[3] = 1.0f; + break; + case 0x80: + if (!target.metadata_fixed_clear_supported) { + return false; + } + clear_value.float32[0] = 1.0f; + clear_value.float32[1] = 1.0f; + clear_value.float32[2] = 1.0f; + break; + case 0xc0: + if (!target.metadata_fixed_clear_supported) { + return false; + } + clear_value.float32[0] = 1.0f; + clear_value.float32[1] = 1.0f; + clear_value.float32[2] = 1.0f; + clear_value.float32[3] = 1.0f; + break; + default: return false; + } + + for (uint32_t layer = 1; layer < view.layer_count; layer++) { + if (!cache.IsMetaCleared(metadata_address, view.base_layer + layer)) { + return false; + } + } + // Consume only after every layer in this Vulkan view can be materialized together. + for (uint32_t layer = 0; layer < view.layer_count; layer++) { + if (!cache.TouchMeta(metadata_address, view.base_layer + layer, false)) { + EXIT("failed to consume DCC clear state\n"); + } + } + return true; +} + RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth) { EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX); @@ -592,14 +657,20 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo ImageSubresourceRange {view.base_level, view.level_count, view.base_layer, view.layer_count}, buffer.Handle()); - state.width = std::min(state.width, target.extent.width); - state.height = std::min(state.height, target.extent.height); - state.num_layers = std::min(state.num_layers, view.layer_count); - auto& attachment = state.color_attachments[i]; - attachment.image_view = target.image_view; - attachment.image_layout = layout; - attachment.clear_value = target.color_clear_value.uint32; - attachment.is_clear = target.color_clear_enable; + state.width = std::min(state.width, target.extent.width); + state.height = std::min(state.height, target.extent.height); + state.num_layers = std::min(state.num_layers, view.layer_count); + auto& attachment = state.color_attachments[i]; + attachment.image_view = target.image_view; + attachment.image_layout = layout; + attachment.clear_value = target.color_clear_value.uint32; + vk::ClearColorValue metadata_clear_value {}; + const bool metadata_clear = + ResolveDccAttachmentClear(cache, target, view, metadata_clear_value); + if (metadata_clear) { + attachment.clear_value = metadata_clear_value.uint32; + } + attachment.is_clear = target.color_clear_enable || metadata_clear; } if (depth.image_id) { const auto owner = cache.ResolveOwner(depth.image_id); @@ -687,10 +758,11 @@ enum class CbColorMode : uint8_t { static bool ConsumeMetadataColorOperation(const RenderCommandBuffer& buffer) { const auto& ctx = buffer.GetRegisters(); const auto mode = ctx.GetColorControl().mode; - // These AGC CB modes run color-buffer metadata/decompression operations. The shader is a - // dummy vehicle for the CB, and its exported color must not be applied as a normal draw. - // Kyty currently stores host images as expanded Vulkan images and does not track CMASK/DCC - // metadata state, so the matching host operation is a no-op. + // 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) || mode == static_cast(CbColorMode::DccDecompress); @@ -948,7 +1020,7 @@ bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuf } ResolveRenderDepthTarget(submit_id, buffer, state.depth_info); - state.ps_active = DrawHasActivePixelShader(buffer); + state.ps_active = DrawHasActivePixelShader(buffer); const bool with_depth = (state.depth_info.format != vk::Format::eUndefined && static_cast(state.depth_info.image_id)); if (state.color_count == 0 && !with_depth && !state.ps_active) { diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 50dfbd794..3e0422987 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -4542,10 +4542,15 @@ public: }; auto metadata_depth_a = MakeMetadataDepth(metadata_data_a, metadata_a); auto metadata_depth_b = MakeMetadataDepth(metadata_data_b, metadata_b); + metadata_depth_a.info.htile_clear_mask = 0; const auto metadata_depth_a_id = texture_cache.FindImage(metadata_depth_a); const auto metadata_depth_b_id = texture_cache.FindImage(metadata_depth_b); + Require(name, "pending DCC/HTile separation", + !texture_cache.TryConsumeDccFill(base + metadata_a, 0x80, 0) && + !texture_cache.IsMeta(base + metadata_a), + "an unclassified PS5 DCC fill was exposed as registered metadata"); const auto metadata_depth_a_view = texture_cache.FindDepthTarget(metadata_depth_a_id, metadata_depth_a); const auto metadata_depth_b_view = @@ -4553,6 +4558,7 @@ public: Require(name, "shared-page metadata state", metadata_depth_a_view != nullptr && metadata_depth_b_view != nullptr && + !texture_cache.IsMetaCleared(base + metadata_a, 0) && texture_cache.ClearMeta(base + metadata_a) && texture_cache.ClearMeta(base + metadata_b), "shared-page metadata did not retain logical clear state"); @@ -4576,6 +4582,11 @@ public: texture_cache.IsMeta(base + metadata_b) && texture_cache.IsMetaCleared(base + metadata_b, 0), "image discovery retired aliased metadata or its live depth image"); + Require(name, "registered DCC/HTile separation", + texture_cache.TouchMeta(base + metadata_a, 0, false) && + !texture_cache.TryConsumeDccFill(base + metadata_a, 0x80, 0) && + !texture_cache.IsMetaCleared(base + metadata_a, 0), + "a PS5 DCC fill changed registered HTile state"); Require( name, "metadata first-touch state", texture_cache.TouchMeta(base + metadata_b, 0, true) && @@ -6313,7 +6324,8 @@ public: HW::Shader shaders{}; registers.SetColorBase(0, {.addr = base}); registers.SetColorInfo( - 0, {.format = Prospero::ChannelLayout::k10_10_10_2, + 0, {.dcc_compression_enable = true, + .format = Prospero::ChannelLayout::k10_10_10_2, .channel_type = Prospero::ChannelType::kUNorm, .channel_order = Prospero::ChannelOrder::kStandard}); registers.SetColorAttrib2(0, {.height = 31, .width = 31}); @@ -6323,6 +6335,10 @@ public: .dimension = 2, .cmask_pipe_aligned = true, .dcc_pipe_aligned = true}); + constexpr uint64_t dcc_address = base + allocation_size - 0x1000; + registers.SetColorDccAddr(0, {.addr = dcc_address}); + registers.SetColorClearWord0(0, {.word0 = 0}); + registers.SetColorClearWord1(0, {.word1 = 0}); registers.SetRenderTargetMask(0x0f); scheduler.Begin(registers, user_config, shaders); @@ -6330,6 +6346,10 @@ public: auto &texture_cache = resources.GetTextureCache(); auto &executor = context.GetRenderExecutor(); resources.MapMemory(base, allocation_size); + Require(name, "deferred DCC clear", + !texture_cache.TryConsumeDccFill(dcc_address, 0x1000, 0) && + !texture_cache.IsMeta(dcc_address), + "a pre-registration DCC clear was not deferred"); RenderColorInfo color{}; RenderExecutorTestAccess::ResolveRenderColorTarget( @@ -6337,6 +6357,7 @@ public: const auto attachment = texture_cache.FindRenderTarget(color.image_id, color.desc); const auto &image = texture_cache.GetImage(color.image_id); + uint32_t dcc_clear_value = 0xffffffffu; Require( name, "captured 3D target", color.image_id && attachment != nullptr && @@ -6346,6 +6367,13 @@ public: color.desc.info.pitch == 128 && color.desc.info.data.size == allocation_size && color.desc.info.mip_layout[0].size == 0x10000 && + color.desc.info.metadata.kind == ImageMetadataKind::Dcc && + color.desc.info.metadata.range.address == dcc_address && + color.metadata_clear_supported && + texture_cache.IsMetaCleared(dcc_address, 7, + &dcc_clear_value) && + dcc_clear_value == 0 && + !texture_cache.ClearMeta(dcc_address) && color.desc.view_info.type == vk::ImageViewType::e2D && color.desc.view_info.layer_count == 1 && image.backing.image_type == vk::ImageType::e3D && @@ -6372,8 +6400,21 @@ public: sliced_color.image_view != nullptr && sliced_color.desc.view_info.base_layer == 7 && sliced_rendering.num_color_attachments == 1 && - sliced_rendering.num_layers == 1, + sliced_rendering.num_layers == 1 && + sliced_rendering.color_attachments[0].is_clear && + sliced_rendering.color_attachments[0].clear_value == + vk::ClearColorValue {}.uint32 && + !texture_cache.IsMetaCleared(dcc_address, 7), "a nonzero 3D attachment slice was treated as a Vulkan array layer"); + dcc_clear_value = 0; + Require(name, "registered DCC code state", + texture_cache.TryConsumeDccFill(dcc_address, 0x1000, 0xffffffffu) && + !texture_cache.IsMetaCleared(dcc_address, 7, + &dcc_clear_value) && + dcc_clear_value == 0xffffffffu && + texture_cache.TryConsumeDccFill(dcc_address, 0x1000, 0) && + texture_cache.IsMetaCleared(dcc_address, 7), + "DCC fill codes were not classified as clear or uncompressed"); auto storage_desc = color.desc; storage_desc.type = BindingType::Storage;