From 44f982bca7421e1326ad7fae5f036e1c9879d1ac Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 6 Sep 2026 08:00:59 +0400 Subject: [PATCH] render: apply DCC clears before binding images Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com> Co-authored-by: Brandon Strong --- src/graphics/guest_gpu/tile.cpp | 25 ++ src/graphics/guest_gpu/tile.h | 3 + .../host_gpu/renderer/cache/textureCache.cpp | 228 ++++++++++++++---- .../host_gpu/renderer/cache/textureCache.h | 10 +- .../host_gpu/renderer/colorRenderTarget.cpp | 61 ++--- .../host_gpu/renderer/colorRenderTarget.h | 3 - .../host_gpu/renderer/image/imageInfo.h | 3 + .../renderer/pipeline/descriptors.cpp | 11 + .../host_gpu/renderer/renderCompute.cpp | 14 +- src/graphics/host_gpu/renderer/renderDraw.cpp | 77 +----- tests/ShaderRecompilerComputeTests.cpp | 211 +++++++++++++--- 11 files changed, 435 insertions(+), 211 deletions(-) diff --git a/src/graphics/guest_gpu/tile.cpp b/src/graphics/guest_gpu/tile.cpp index 87f325f00..9498bc33d 100644 --- a/src/graphics/guest_gpu/tile.cpp +++ b/src/graphics/guest_gpu/tile.cpp @@ -1221,6 +1221,31 @@ 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)) { + 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; + if (blocks > UINT32_MAX / 4096u / slices) { + return false; + } + total_size.size = static_cast(blocks * slices * 4096u); + total_size.align = 4096; + return true; +} + bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch, uint32_t bytes_per_element, uint32_t levels, TileSizeAlign& total_size, TileSizeOffset* level_sizes, diff --git a/src/graphics/guest_gpu/tile.h b/src/graphics/guest_gpu/tile.h index 311df761f..5cbbba8f2 100644 --- a/src/graphics/guest_gpu/tile.h +++ b/src/graphics/guest_gpu/tile.h @@ -122,6 +122,9 @@ uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element, bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t bytes_per_element, TileSizeAlign& total_size, uint32_t num_fragments_log2 = 0); +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 TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch, uint32_t bytes_per_element, uint32_t levels, TileSizeAlign& total_size, TileSizeOffset* level_sizes, diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index 65c8f8e21..fd8d2777c 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -32,6 +32,78 @@ namespace { constexpr uint64_t NumFramesBeforeRemoval = 32; +[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, vk::Format format, + uint32_t fill, vk::ClearColorValue& clear) { + const auto code = static_cast(fill); + if (fill != static_cast(code) * 0x01010101u) { + return false; + } + const auto& metadata = desc.info.metadata; + 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); + } + if (code != 0x00 && code != 0x40 && code != 0x80 && code != 0xc0) { + return false; + } + clear = {}; + if (code == 0x00) { + return true; + } + switch (format) { + case vk::Format::eR8Unorm: + case vk::Format::eR8G8Unorm: + case vk::Format::eR8G8B8A8Unorm: + case vk::Format::eR8G8B8A8Srgb: + case vk::Format::eB8G8R8A8Unorm: + case vk::Format::eB8G8R8A8Srgb: + case vk::Format::eA2B10G10R10UnormPack32: + case vk::Format::eA2R10G10B10UnormPack32: + case vk::Format::eR5G6B5UnormPack16: + case vk::Format::eA1R5G5B5UnormPack16: + case vk::Format::eR4G4B4A4UnormPack16: + case vk::Format::eR16Unorm: + case vk::Format::eR16G16Unorm: + case vk::Format::eR16G16B16A16Unorm: + case vk::Format::eR16Sfloat: + case vk::Format::eR16G16Sfloat: + case vk::Format::eR16G16B16A16Sfloat: + case vk::Format::eR32Sfloat: + case vk::Format::eR32G32Sfloat: + case vk::Format::eR32G32B32A32Sfloat: + case vk::Format::eB10G11R11UfloatPack32: break; + default: return false; + } + const float rgb = (code & 0x80u) != 0 ? 1.0f : 0.0f; + const float alpha = (code & 0x40u) != 0 ? 1.0f : 0.0f; + std::array channels {rgb, rgb, rgb, alpha}; + if (!metadata.dcc_alpha_msb) { + std::swap(channels[0], channels[3]); + } + // DCC clear decoding clamps missing lanes before applying the format swizzle. + const auto components = vk::componentCount(format); + if (components == 1) { + channels[0] = channels[3]; + } else if (components == 2) { + channels[1] = channels[3]; + } + switch (format) { + case vk::Format::eB8G8R8A8Unorm: + case vk::Format::eB8G8R8A8Srgb: + case vk::Format::eA2R10G10B10UnormPack32: + case vk::Format::eA1R5G5B5UnormPack16: + case vk::Format::eR5G6B5UnormPack16: std::swap(channels[0], channels[2]); break; + case vk::Format::eR4G4B4A4UnormPack16: + std::reverse(channels.begin(), channels.end()); + break; + default: break; + } + clear.float32 = channels; + return true; +} + [[nodiscard]] const char* BindingTypeName(TextureCache::BindingType type) { switch (type) { case TextureCache::BindingType::Texture: return "Texture"; @@ -1062,6 +1134,56 @@ void TextureCache::InitializeImage(ImageId id) { } } +void TextureCache::PrepareDccClear(ImageId id, const ImageDesc& desc) { + if (desc.info.metadata.kind != ImageMetadataKind::Dcc) { + return; + } + auto& image = m_slot_images[id]; + image.info.metadata = desc.info.metadata; + auto [entry, inserted] = m_surface_metas.try_emplace( + desc.info.metadata.range.address, MetaDataInfo {.type = MetaDataInfo::Type::Dcc}); + auto& metadata = entry->second; + if (!inserted && metadata.type == MetaDataInfo::Type::PendingDcc) { + metadata.type = MetaDataInfo::Type::Dcc; + } else if (metadata.type != MetaDataInfo::Type::Dcc) { + EXIT("TextureCache: image reuses non-DCC metadata\n"); + } + if (metadata.clear_mask == 0 || image.info.resources.levels != 1 || + desc.info.metadata.range.size == 0 || metadata.fill_size < desc.info.metadata.range.size) { + return; + } + vk::ClearValue clear {}; + if (!DecodeDccClear(desc, image.backing.format, metadata.fill_value, clear.color)) { + return; + } + const auto& view = desc.view_info; + const bool volume_texture = image.info.IsVolume() && view.type == vk::ImageViewType::e3D; + const auto first = volume_texture ? 0u : view.base_layer; + const auto count = volume_texture ? std::max(image.info.extent.depth >> view.base_level, 1u) + : view.layer_count; + if (first >= 32 || count > 32 - first) { + return; + } + // The metadata fill covers the complete allocation. Consume each layer only after its + // native image contents exist; already materialized layers may have been rendered since. + for (uint32_t layer = first; layer < first + count;) { + if ((metadata.clear_mask & (1u << layer)) == 0) { + layer++; + continue; + } + const auto start = layer; + uint32_t mask = 0; + do { + mask |= 1u << layer++; + } while (layer < first + count && (metadata.clear_mask & (1u << layer)) != 0); + ClearImage(m_scheduler.Current(), id, + {vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count, start, + layer - start}, + clear); + metadata.clear_mask &= ~mask; + } +} + void TextureCache::RefreshImage(ImageId id) { TrackImage(id); auto& image = m_slot_images[id]; @@ -1253,6 +1375,7 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) { image.MarkGpuModified(); } if (!image.info.data.Empty()) { + PrepareDccClear(id, desc); RefreshImage(id); } switch (desc.type) { @@ -1285,20 +1408,8 @@ vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc) TouchImage(image); image.MarkGpuModified(); image.usage.render_target = true; + PrepareDccClear(id, desc); RefreshImage(id); - // 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); TrackImageDownload(id, image); const auto view = image.FindView(desc.view_info); @@ -1422,8 +1533,8 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address } clear.depthStencil.stencil = stencil_clear; } - ClearImage(command, selected, {aspect, 0, image.info.resources.levels, 0, image.backing.layers}, - clear); + ClearImage(command, selected, + {aspect, 0, image.info.resources.levels, 0, image.info.TransferLayers()}, clear); return true; } @@ -1432,27 +1543,70 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id, auto& image = m_slot_images[id]; const auto aspects = image.info.IsDepth() ? ImageViewOps::DepthAspectMask(image.backing.format) : vk::ImageAspectFlagBits::eColor; - EXIT_IF(command.IsInvalid() || image.depth_id || !range.aspectMask || - range.levelCount == 0 || range.baseMipLevel >= image.info.resources.levels || + EXIT_IF(range.baseMipLevel >= image.info.resources.levels); + const auto layers = image.info.IsVolume() + ? std::max(image.info.extent.depth >> range.baseMipLevel, 1u) + : image.backing.layers; + EXIT_IF(command.IsInvalid() || image.depth_id || !range.aspectMask || range.levelCount == 0 || range.levelCount > image.info.resources.levels - range.baseMipLevel || - range.layerCount == 0 || range.baseArrayLayer >= image.backing.layers || - range.layerCount > image.backing.layers - range.baseArrayLayer || + range.layerCount == 0 || range.baseArrayLayer >= layers || + range.layerCount > layers - range.baseArrayLayer || (range.aspectMask & aspects) != range.aspectMask); - if (image.IsBufferModified() || image.IsCpuDirty()) { + const bool full_image = range.aspectMask == aspects && range.baseMipLevel == 0 && + range.levelCount == image.info.resources.levels && + range.baseArrayLayer == 0 && range.layerCount == layers; + TrackImage(id); + if (!full_image && (image.IsBufferModified() || image.IsCpuDirty())) { InitializeImage(id); if (image.info.samples == 1 && (image.IsBufferModified() || image.IsCpuDirty())) { EXIT("TextureCache: image clear retained guest ownership\n"); } } command.EndRendering(); + if (image.info.IsVolume() && !full_image) { + EXIT_NOT_IMPLEMENTED(range.aspectMask != vk::ImageAspectFlagBits::eColor || + range.levelCount != 1); + ImageViewInfo view {}; + view.format = image.backing.format; + view.type = range.layerCount == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray; + view.base_level = range.baseMipLevel; + view.base_layer = range.baseArrayLayer; + view.layer_count = range.layerCount; + view.usage = vk::ImageUsageFlagBits::eColorAttachment; + image.Transit(vk::ImageLayout::eColorAttachmentOptimal, + vk::AccessFlagBits2::eColorAttachmentWrite, {}, command.Handle()); + vk::RenderingAttachmentInfo attachment {}; + attachment.imageView = image.FindView(view); + attachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal; + attachment.loadOp = vk::AttachmentLoadOp::eClear; + attachment.storeOp = vk::AttachmentStoreOp::eStore; + attachment.clearValue = clear; + vk::RenderingInfo rendering {}; + rendering.renderArea.extent = { + std::max(image.info.extent.width >> range.baseMipLevel, 1u), + std::max(image.info.extent.height >> range.baseMipLevel, 1u)}; + rendering.layerCount = range.layerCount; + rendering.colorAttachmentCount = 1; + rendering.pColorAttachments = &attachment; + command.Handle().beginRendering(&rendering); + command.Handle().endRendering(); + CommitGpuWrite(image); + return; + } image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command.Handle()); + auto native_range = range; + if (image.info.IsVolume()) { + native_range.baseArrayLayer = 0; + native_range.layerCount = 1; + } if (range.aspectMask == vk::ImageAspectFlagBits::eColor) { command.Handle().clearColorImage(image.backing.image, vk::ImageLayout::eTransferDstOptimal, - &clear.color, 1, &range); + &clear.color, 1, &native_range); } else { - command.Handle().clearDepthStencilImage( - image.backing.image, vk::ImageLayout::eTransferDstOptimal, &clear.depthStencil, 1, &range); + command.Handle().clearDepthStencilImage(image.backing.image, + vk::ImageLayout::eTransferDstOptimal, + &clear.depthStencil, 1, &native_range); } CommitGpuWrite(image); } @@ -1757,7 +1911,7 @@ bool TextureCache::ClearMeta(uint64_t address) { return true; } -bool TextureCache::TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t fill_value) { +void TextureCache::TrackDccFill(uint64_t address, uint64_t size, uint32_t fill_value) { if (!GuestRange {address, size}.Valid()) { EXIT("TextureCache: invalid DCC fill range\n"); } @@ -1778,31 +1932,15 @@ bool TextureCache::TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t f } }(); std::scoped_lock lock {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) { + // The guest dispatch still writes metadata. An unknown address remains PendingDcc until an + // image descriptor confirms its role; never reinterpret CMask/FMask/HTile as DCC. + const auto found = m_surface_metas.try_emplace(address).first; + if (found->second.type == MetaDataInfo::Type::PendingDcc || + 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 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) { diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.h b/src/graphics/host_gpu/renderer/cache/textureCache.h index 44ee6283e..9d4a1e1a0 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.h +++ b/src/graphics/host_gpu/renderer/cache/textureCache.h @@ -72,9 +72,8 @@ public: [[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); + // Record deferred DCC state while the original guest dispatch writes the metadata. + void TrackDccFill(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 +87,7 @@ 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, + // PendingDcc retains that exact fill until an image binding classifies the address, // without exposing an unconfirmed buffer address to the normal metadata heuristics. // Keep all surface metadata in one entry so CMask/FMask can be // registered beside HTile and DCC without introducing parallel tracking paths. @@ -138,6 +137,7 @@ private: ImageId cached); [[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source); void RefreshImage(ImageId id); + void PrepareDccClear(ImageId id, const ImageDesc& desc); void InitializeImage(ImageId id); [[nodiscard]] TextureTransferPlan BuildTextureTransfer(const Image& image, BindingType binding, TransferDirection direction) const; @@ -147,7 +147,7 @@ private: uint64_t destination_size, DownloadPlan plan); void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset); void CommitGpuWrite(Image& image); - // Caller holds m_lock. Clears only the selected aspects and subresources. + // Caller holds m_lock. Volume layer ranges select depth slices. void ClearImage(CommandBuffer& command, ImageId id, const vk::ImageSubresourceRange& range, const vk::ClearValue& clear); void PrepareImageCopy(Image& image); diff --git a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp index 9fd0e66c3..53dc45e4b 100644 --- a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp +++ b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp @@ -4,6 +4,7 @@ #include "common/logging/log.h" #include "common/profiler.h" #include "graphics/guest_gpu/gpu_defs.h" +#include "graphics/guest_gpu/gpu_format.h" #include "graphics/guest_gpu/hardwareContext.h" #include "graphics/guest_gpu/tile.h" #include "graphics/host_gpu/graphicContext.h" @@ -21,41 +22,22 @@ 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::eR8Unorm: - case vk::Format::eR8G8Unorm: - case vk::Format::eR8G8B8A8Unorm: - case vk::Format::eR8G8B8A8Srgb: - case vk::Format::eB8G8R8A8Unorm: - case vk::Format::eB8G8R8A8Srgb: - case vk::Format::eA2B10G10R10UnormPack32: - case vk::Format::eA2R10G10B10UnormPack32: - case vk::Format::eR5G6B5UnormPack16: - case vk::Format::eA1R5G5B5UnormPack16: - case vk::Format::eR4G4B4A4UnormPack16: - case vk::Format::eR16Unorm: - case vk::Format::eR16G16Unorm: - case vk::Format::eR16G16B16A16Unorm: - case vk::Format::eR16Sfloat: - case vk::Format::eR16G16Sfloat: - case vk::Format::eR16G16B16A16Sfloat: - case vk::Format::eR32Sfloat: - case vk::Format::eR32G32Sfloat: - case vk::Format::eR32G32B32A32Sfloat: - case vk::Format::eB10G11R11UfloatPack32: - info.metadata_fixed_clear_supported = has_dcc; - break; - default: info.metadata_fixed_clear_supported = false; break; +static bool DccAlphaOnMsb(const HW::ColorInfo& info) { + switch (info.format) { + case Prospero::ChannelLayout::k10_10_10_2: + case Prospero::ChannelLayout::k10_10_10_2Float: + case Prospero::ChannelLayout::k5_5_5_1: return true; + case Prospero::ChannelLayout::k2_10_10_10: + case Prospero::ChannelLayout::k1_5_5_5: return false; + default: break; } - if (!info.metadata_clear_supported) { - info.color_clear_value = {}; + const auto components = + Prospero::ResolveRenderTargetFormat(info.format, info.channel_type).components; + if (components == 1) { + return info.channel_order != Prospero::ChannelOrder::kStandard; } + return components == 3 || info.channel_order == Prospero::ChannelOrder::kStandard || + info.channel_order == Prospero::ChannelOrder::kAlt; } // NOLINTNEXTLINE(readability-function-cognitive-complexity) @@ -329,10 +311,14 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& 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; + 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); } for (uint32_t level = 0; level < levels; level++) { if (volume) { @@ -378,7 +364,6 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& r.guest_array_layer = view.base_layer; r.image_id = texture_cache.FindImage(r.desc, exact_format); r.export_mapping = target_format.export_mapping; - 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 6a02634d9..d78c83259 100644 --- a/src/graphics/host_gpu/renderer/colorRenderTarget.h +++ b/src/graphics/host_gpu/renderer/colorRenderTarget.h @@ -19,9 +19,6 @@ struct RenderColorInfo { uint32_t guest_mip_level = 0; uint32_t guest_array_layer = 0; Prospero::ColorComponentMapping export_mapping; - bool metadata_clear_supported = false; - bool metadata_fixed_clear_supported = false; - vk::ClearColorValue color_clear_value {}; [[nodiscard]] vk::Extent2D Extent() const { return {std::max(desc.info.extent.width >> guest_mip_level, 1u), diff --git a/src/graphics/host_gpu/renderer/image/imageInfo.h b/src/graphics/host_gpu/renderer/image/imageInfo.h index a9ab0fc80..5ad5d9072 100644 --- a/src/graphics/host_gpu/renderer/image/imageInfo.h +++ b/src/graphics/host_gpu/renderer/image/imageInfo.h @@ -23,8 +23,11 @@ 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; }; struct ImageSubresources { diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index b2667132c..8a58120db 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -758,6 +759,16 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR block_bytes != 0 ? block_bytes : Prospero::NumBytesPerElement(format); desc.info.samples = samples; desc.info.tile_mode = tile; + if (!resource.r128 && descriptor.MetaCompress() && tile != Prospero::TileMode::kDepth && + !desc.info.IsDepth()) { + TileSizeAlign metadata_size {}; + (void)TileGetDccSize(width, height, volume ? depth : image_layers, + desc.info.bytes_per_block, levels, tile, metadata_size, + std::countr_zero(samples)); + desc.info.metadata.kind = ImageMetadataKind::Dcc; + desc.info.metadata.range = {descriptor.MetaAddr() << 8u, metadata_size.size}; + desc.info.metadata.dcc_alpha_msb = descriptor.DccAlphaPos(); + } if (samples > 1) { desc.info.mip_layout[0] = {0, size.size, pitch, height}; } else { diff --git a/src/graphics/host_gpu/renderer/renderCompute.cpp b/src/graphics/host_gpu/renderer/renderCompute.cpp index 6550feda3..f0be459e7 100644 --- a/src/graphics/host_gpu/renderer/renderCompute.cpp +++ b/src/graphics/host_gpu/renderer/renderCompute.cpp @@ -189,19 +189,15 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i return false; } 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); + // Track deferred DCC state while the original dispatch writes the metadata allocation. + cache.TrackDccFill(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 + LOGF("GraphicsRenderDispatchDirect: metadata fill 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); + input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear); } - return registered_metadata; + return false; } static std::atomic logged_clears {0}; if (logged_clears.fetch_add(1, std::memory_order_relaxed) < 32) { diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index 17d0e3231..623211640 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -215,15 +215,14 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col LOGF( "DrawTargetState[%u]: frame=%d %s target=%s addr=0x%010" PRIx64 " extent=%ux%u prim=%u index_count=%u flags=0x%08" PRIx32 " color_mask=0x%08" PRIx32 - " clear=%s clear_rgba=(%.3f,%.3f,%.3f,%.3f) cc_mode=%u cc_op=0x%02x" + " cc_mode=%u cc_op=0x%02x" " blend=%s src=%u dst=%u comb=%u ps_tex=%d sampled=%d storage=%d ps_kill=%s target_mode0=%u" " depth_test=%s depth_write=%s depth_func=%u depth_clear=%s viewport=(%.1f,%.1f %.1fx%.1f) " "scissor=(%d,%d)-(%d,%d)\n", log_id, buffer.GetContext().GetGpu().GetFrameNum(), draw_name, RenderColorTypeName(color), color.desc.info.data.address, extent.width, extent.height, static_cast(ucfg.GetPrimType()), index_count, flags, ctx.GetRenderTargetMask(), - "false", color.color_clear_value.float32[0], color.color_clear_value.float32[1], - color.color_clear_value.float32[2], color.color_clear_value.float32[3], cc.mode, cc.op, + cc.mode, cc.op, bc.enable ? "true" : "false", bc.color_srcblend, bc.color_destblend, bc.color_comb_fcn, static_cast(ps_resources.images.size()), static_cast(sampled_images), static_cast(ps_resources.images.size() - sampled_images), @@ -460,70 +459,6 @@ 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); @@ -581,14 +516,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo auto& attachment = state.color_attachments[i]; attachment.image_view = 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 = metadata_clear; } if (depth.image_id) { const auto owner = cache.m_slot_images.try_get(depth.image_id); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index c1b36dd4a..b1ac03d1f 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -5796,10 +5796,10 @@ public: texture_cache.FindImage(metadata_depth_a); const auto metadata_depth_b_id = texture_cache.FindImage(metadata_depth_b); + texture_cache.TrackDccFill(base + metadata_a, 0x80, 0); Require( name, "pending DCC/HTile separation", - !texture_cache.TryConsumeDccFill(base + metadata_a, 0x80, 0) && - !texture_cache.IsMeta(base + metadata_a), + !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); @@ -5832,11 +5832,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"); + const bool touched_metadata = texture_cache.TouchMeta(base + metadata_a, 0, false); + texture_cache.TrackDccFill(base + metadata_a, 0x80, 0); 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), + touched_metadata && !texture_cache.IsMetaCleared(base + metadata_a, 0), "a PS5 DCC fill changed registered HTile state"); Require( name, "metadata first-touch state", @@ -7649,7 +7649,9 @@ public: void CheckRenderExecutorColorVolumeDiscovery() { constexpr const char *name = "RenderExecutorColorVolumeDiscovery"; constexpr uintptr_t base = 0x0000000203e00000ull; - constexpr uint64_t allocation_size = 0x200000; + constexpr uint64_t color_size = 0x200000; + constexpr uint64_t metadata_size = 0x20000; + constexpr uint64_t allocation_size = color_size + metadata_size; constexpr uint64_t allocation_alignment = 0x10000; EnsureRuntimeContext(); @@ -7689,7 +7691,7 @@ public: .tile_mode = Prospero::TileMode::kRenderTarget, .dimension = 2, .metadata_pipe_aligned = true}); - constexpr uint64_t dcc_address = base + allocation_size - 0x1000; + constexpr uint64_t dcc_address = base + color_size; registers.SetColorDccAddr(0, {.addr = dcc_address}); registers.SetColorClearWord0(0, {.word0 = 0}); registers.SetColorClearWord1(0, {.word1 = 0}); @@ -7700,9 +7702,9 @@ public: auto &texture_cache = resources.GetTextureCache(); auto &executor = context.GetRenderExecutor(); resources.MapMemory(base, allocation_size); + texture_cache.TrackDccFill(dcc_address, metadata_size, 0); Require(name, "deferred DCC clear", - !texture_cache.TryConsumeDccFill(dcc_address, 0x1000, 0) && - !texture_cache.IsMeta(dcc_address), + !texture_cache.IsMeta(dcc_address), "a pre-registration DCC clear was not deferred"); RenderColorInfo color{}; @@ -7719,11 +7721,10 @@ public: color.desc.info.extent == vk::Extent3D{32, 32, 32} && color.desc.info.resources == ImageSubresources{1, 1} && color.desc.info.pitch == 128 && - color.desc.info.data.size == allocation_size && + color.desc.info.data.size == color_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 && @@ -7753,23 +7754,17 @@ public: sliced_color.desc.view_info.base_layer == 7 && sliced_rendering.num_color_attachments == 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"); - + Require(name, "3D slice clear and preservation", + ReadCachedTexel(name, context, color.image_id, {0, 0, 7}) == + std::vector{0} && + ReadCachedTexel(name, context, color.image_id, {0, 0, 31}) == + std::vector{0x5a5a5a5a}, + "clearing one volume slice changed untouched slice contents"); auto storage_desc = color.desc; storage_desc.type = BindingType::Storage; + storage_desc.info.metadata = {}; storage_desc.info.guest_format = Prospero::BufferFormat::k10_10_10_2UNorm; storage_desc.view_info.type = vk::ImageViewType::e3D; storage_desc.view_info.usage = vk::ImageUsageFlagBits::eStorage; @@ -7789,7 +7784,7 @@ public: TextureCacheTestAccess::TryDownload(texture_cache, storage_id), "the 3D render target could not be queued for guest-layout readback"); auto mirror = resources.GetBufferCache().ObtainBuffer( - base, allocation_size, false, true); + base, color_size, false, true); Require(name, "volume mirror", mirror.first != nullptr, "the 3D render-target readback has no BufferCache owner"); auto slice_probe = @@ -7816,6 +7811,17 @@ public: "render-target upload/readback lost the final Z slice"); DestroyBuffer(&slice_probe); + dcc_clear_value = 0; + texture_cache.TrackDccFill(dcc_address, metadata_size, 0xffffffffu); + const bool uncompressed = + !texture_cache.IsMetaCleared(dcc_address, 7, &dcc_clear_value) && + dcc_clear_value == 0xffffffffu; + texture_cache.TrackDccFill(dcc_address, metadata_size, 0); + Require( + name, "registered DCC code state", + uncompressed && texture_cache.IsMetaCleared(dcc_address, 7), + "DCC fill codes were not classified as clear or uncompressed"); + for (const uint32_t base_slice : {0u, 7u}) { RenderExecutorTestAccess::ResetBindings(executor); registers.SetColorView(0, {.base_array_slice_index = base_slice, @@ -7829,7 +7835,7 @@ public: Require(name, "3D export bound exceeds depth", volume_color.image_id == color.image_id && volume_color.desc.info.extent == vk::Extent3D{32, 32, 32} && - volume_color.desc.info.data.size == allocation_size && + volume_color.desc.info.data.size == color_size && volume_color.desc.view_info.type == vk::ImageViewType::e2DArray && volume_color.desc.view_info.base_layer == base_slice && volume_color.desc.view_info.layer_count == 32 - base_slice && @@ -7855,6 +7861,40 @@ public: std::printf("[gpu] %-32s ok\n", name); } + std::vector ReadCachedTexel(const char *name, RenderContext &context, + ImageId id, vk::Offset3D offset = {}) { + auto &scheduler = context.GetCommandScheduler(); + auto &image = context.GetTextureCache().GetImage(id); + auto probe = CreateHostBuffer(name, image.info.bytes_per_block, + vk::BufferUsageFlagBits::eTransferDst, {}); + scheduler.Current().EndRendering(); + image.Transit(vk::ImageLayout::eTransferSrcOptimal, + vk::AccessFlagBits2::eTransferRead, {}, + scheduler.Current().Handle()); + vk::BufferImageCopy copy{}; + copy.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}; + copy.imageOffset = offset; + copy.imageExtent = vk::Extent3D{1, 1, 1}; + scheduler.Current().Handle().copyImageToBuffer( + image.backing.image, vk::ImageLayout::eTransferSrcOptimal, + probe.buffer, 1, ©); + vk::BufferMemoryBarrier barrier{}; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eHostRead; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.buffer = probe.buffer; + barrier.size = probe.size; + scheduler.Current().Handle().pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1, &barrier, 0, + nullptr); + scheduler.Finish(); + auto result = ReadBuffer(name, probe, image.info.bytes_per_block / 4); + DestroyBuffer(&probe); + return result; + } + void CheckRenderExecutorDccFixedClearFloat() { constexpr const char *name = "RenderExecutorDccFixedClearFloat"; constexpr uintptr_t base = 0x0000000204100000ull; @@ -7863,11 +7903,11 @@ public: constexpr uint64_t dcc_address = base + 0x100000; struct FillCase { uint32_t fill; - std::array clear; + std::array texel; }; constexpr std::array cases{ - FillCase{0x40404040u, {0, 0, 0, 0x3f800000u}}, - FillCase{0x80808080u, {0x3f800000u, 0x3f800000u, 0x3f800000u, 0}}, + FillCase{0x40404040u, {0, 0x3c000000u}}, + FillCase{0x80808080u, {0x3c003c00u, 0x00003c00u}}, }; EnsureRuntimeContext(); @@ -7913,9 +7953,9 @@ public: auto &texture_cache = resources.GetTextureCache(); auto &executor = context.GetRenderExecutor(); resources.MapMemory(base, allocation_size); + texture_cache.TrackDccFill(dcc_address, 0x1000, fill_case.fill); Require(name, "deferred fixed-code fill", - !texture_cache.TryConsumeDccFill(dcc_address, 0x1000, - fill_case.fill), + !texture_cache.IsMeta(dcc_address), "a pre-registration DCC fill was not deferred"); RenderColorInfo color{}; @@ -7928,14 +7968,14 @@ public: color.image_id && color.desc.info.metadata.kind == ImageMetadataKind::Dcc && color.desc.info.metadata.range.address == dcc_address && - color.metadata_fixed_clear_supported && rendering.num_color_attachments == 1 && - rendering.color_attachments[0].is_clear && - rendering.color_attachments[0].clear_value == - fill_case.clear && !texture_cache.IsMetaCleared(dcc_address, 0), "a DCC fixed clear code was not materialised on an RGBA16F " "target"); + Require(name, "GPU float clear value", + ReadCachedTexel(name, context, color.image_id) == + std::vector(fill_case.texel.begin(), fill_case.texel.end()), + "RGBA16F native image did not receive the fixed clear value"); RenderExecutorTestAccess::ResetBindings(executor); resources.UnmapMemory(base, allocation_size); @@ -7952,6 +7992,100 @@ public: std::printf("[gpu] %-32s ok\n", name); } + void CheckSampledDccClear() { + constexpr const char *name = "SampledDccClear"; + constexpr uintptr_t base = 0x0000000205000000ull; + constexpr uint64_t color_size = 0x3fc0000; + constexpr uint64_t metadata_size = 0x48000; + constexpr uint64_t dcc_address = base + color_size; + constexpr uint64_t allocation_size = 0x4020000; + constexpr uint64_t alignment = 0x10000; + EnsureRuntimeContext(); + int64_t direct_offset = -1; + Require(name, "direct allocation", + Libs::LibKernel::Memory::KernelAllocateDirectMemory( + 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), + allocation_size, alignment, 0, &direct_offset) == 0, + "captured scene allocation failed"); + void *mapped = reinterpret_cast(base); + Require(name, "direct mapping", + Libs::LibKernel::Memory::KernelMapDirectMemory( + &mapped, allocation_size, 0x3, 0x10, direct_offset, alignment) == 0 && + mapped == reinterpret_cast(base), + "captured scene mapping failed"); + for (const auto initial_size : {metadata_size - 0x1000, metadata_size}) { + std::memset(mapped, 0x5a, color_size); + std::memset(reinterpret_cast(dcc_address), 0x40, metadata_size); + RenderContext context(m_runtime_context); + auto &scheduler = context.GetCommandScheduler(); + HW::Context registers{}; + HW::UserConfig user_config{}; + HW::Shader shaders{}; + scheduler.Begin(registers, user_config, shaders); + auto &resources = context.GetGpuResources(); + auto &cache = resources.GetTextureCache(); + auto &executor = context.GetRenderExecutor(); + resources.MapMemory(base, allocation_size); + cache.TrackDccFill(dcc_address, initial_size, 0x40404040u); + Require(name, "deferred metadata fill", + !cache.IsMeta(dcc_address), + "unknown metadata was consumed before descriptor discovery"); + + // PPSA19244's first sampled scene descriptor, with relocated allocations. + ShaderTextureResource descriptor{{static_cast(base >> 8u), + 0xc4700000u, 0x021bc3bfu, 0x91b00facu, 0, 0x00700000u, + 0x006b0000u | (static_cast(dcc_address >> 8u) << 24u), + static_cast(dcc_address >> 16u)}}; + ShaderRecompiler::IR::DescriptorValue value{}; + value.dword_count = 8; + std::copy_n(descriptor.fields, 8, value.dwords.begin()); + ShaderRecompiler::IR::ImageResource resource{}; + resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Sampled; + resource.numeric_class = Prospero::TextureNumericClass::Float; + resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D; + resource.read = true; + auto binding = RenderExecutorTestAccess::ResolveTexture(executor, resource, value); + Require(name, "sampled metadata discovery", + binding.desc.info.extent == vk::Extent3D{3840, 2160, 1} && + binding.desc.info.data.size == color_size && + binding.desc.info.metadata.kind == ImageMetadataKind::Dcc && + binding.desc.info.metadata.range.address == dcc_address && + binding.desc.info.metadata.range.size == metadata_size, + "captured sampled descriptor lost its DCC allocation"); + Require(name, "sampled view", cache.FindTexture(binding.image_id, binding.desc) != nullptr, + "captured scene did not produce a sampled view"); + if (initial_size != metadata_size) { + Require(name, "partial metadata coverage", + ReadCachedTexel(name, context, binding.image_id) == + std::vector{0x5a5a5a5au, 0x5a5a5a5au}, + "an incomplete metadata fill cleared the whole scene"); + cache.TrackDccFill(dcc_address, metadata_size, 0x40404040u); + (void)cache.FindTexture(binding.image_id, binding.desc); + } + Require(name, "opaque black before first sample", + ReadCachedTexel(name, context, binding.image_id, {3839, 2159, 0}) == + std::vector{0, 0x3c000000u} && + !cache.IsMetaCleared(dcc_address, 0), + "sampling read stale color bytes instead of the DCC opaque black clear"); + cache.TrackDccFill(dcc_address, metadata_size, 0x80808080u); + (void)cache.FindTexture(binding.image_id, binding.desc); + Require(name, "later sampled clear", + ReadCachedTexel(name, context, binding.image_id) == + std::vector{0x3c003c00u, 0x00003c00u}, + "an existing sampled image retained the previous DCC clear"); + resources.UnmapMemory(base, allocation_size); + scheduler.Finish(); + } + Require(name, "unmap", + Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0, + "captured scene mapping release failed"); + Require(name, "release", + Libs::LibKernel::Memory::KernelReleaseDirectMemory( + direct_offset, allocation_size) == 0, + "captured scene allocation release failed"); + std::printf("[gpu] %-32s ok\n", name); + } + void CheckComparisonDepthTexture() { constexpr const char *name = "ComparisonDepthTexture"; constexpr uintptr_t base = 0x0000000204200000ull; @@ -10379,7 +10513,7 @@ public: static_cast(image->height) * image->layers * image->dwords_per_pixel; auto staging = CreateHostBuffer(shader_name, dword_count * sizeof(u32), - vk::BufferUsageFlagBits::eTransferDst, {}); + vk::BufferUsageFlagBits::eTransferDst, {}); vk::CommandBuffer cmd = BeginCommands(shader_name, "readback"); AddImageBarrier( @@ -27472,6 +27606,7 @@ int main(int argc, char **argv) { vulkan.CheckRenderExecutorColor1DDiscovery(); vulkan.CheckRenderExecutorColorVolumeDiscovery(); vulkan.CheckRenderExecutorDccFixedClearFloat(); + vulkan.CheckSampledDccClear(); vulkan.CheckRenderExecutorColorDepthTileDiscovery(); vulkan.CheckRenderExecutorStencilBindingDiscovery(); vulkan.CheckUnifiedTextureCacheFlow(); @@ -27487,6 +27622,9 @@ int main(int argc, char **argv) { CheckDynamicRenderingState(); VulkanHarness vulkan; vulkan.CheckComputeMetaClearClassification(); + vulkan.CheckRenderExecutorColorVolumeDiscovery(); + vulkan.CheckRenderExecutorDccFixedClearFloat(); + vulkan.CheckSampledDccClear(); vulkan.CheckRenderExecutorStencilBindingDiscovery(); return 0; } @@ -27664,6 +27802,7 @@ int main(int argc, char **argv) { vulkan.CheckRenderExecutorColor1DDiscovery(); vulkan.CheckRenderExecutorColorVolumeDiscovery(); vulkan.CheckRenderExecutorDccFixedClearFloat(); + vulkan.CheckSampledDccClear(); vulkan.CheckRenderExecutorColorStandardTileDiscovery(); vulkan.CheckRenderExecutorColorDepthTileDiscovery(); vulkan.CheckRenderExecutorStencilBindingDiscovery();