From 437e69ef1b7690046b18e2565720c784755da5d2 Mon Sep 17 00:00:00 2001 From: nmzik Date: Mon, 14 Sep 2026 16:40:46 +0400 Subject: [PATCH] Remove extra DCC tracking --- .../host_gpu/renderer/cache/textureCache.cpp | 239 +++++------------- .../host_gpu/renderer/cache/textureCache.h | 23 +- .../renderer/pipeline/descriptors.cpp | 2 + tests/ShaderRecompilerComputeTests.cpp | 207 ++++++++++++--- 4 files changed, 247 insertions(+), 224 deletions(-) diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index 2ce02c90e..aee3cdc17 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -33,23 +33,16 @@ namespace { constexpr uint64_t NumFramesBeforeRemoval = 32; -[[nodiscard]] bool IsDccClearCode(uint32_t fill) { - switch (fill) { - case 0x00000000u: - case 0x20202020u: - case 0x40404040u: - case 0x80808080u: - case 0xc0c0c0c0u: return true; +[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, vk::Format format, + uint8_t code, vk::ClearColorValue& clear) { + switch (code) { + case 0x00: + case 0x20: + case 0x40: + case 0x80: + case 0xc0: break; default: return false; } -} - -[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, vk::Format format, - uint32_t fill, vk::ClearColorValue& clear) { - if (!IsDccClearCode(fill)) { - return false; - } - const auto code = static_cast(fill); const auto& metadata = desc.info.metadata; if (code == 0x20) { // Clear-to-register is a color-buffer operation; the texture pipe cannot decode it. @@ -196,9 +189,6 @@ TextureCache::TextureCache(GraphicContext& graphics, CommandScheduler& scheduler } TextureCache::~TextureCache() { - for (auto& [address, metadata]: m_surface_metas) { - UntrackMetadata(address, metadata); - } m_slot_images.ForEach([&](ImageId id, const Image& image) { if (image.registered) { UnregisterImage(id); @@ -1133,139 +1123,71 @@ void TextureCache::InitializeImage(ImageId id) { } } -void TextureCache::UntrackMetadata(uint64_t address, MetaDataInfo& metadata) { - if (metadata.tracked) { - m_page_manager.UpdatePageWatchers(address, metadata.size); - metadata.tracked = false; - } -} - -void TextureCache::InvalidateDccMetadata(uint64_t address, uint64_t size) { - for (auto& [base, metadata]: m_surface_metas) { - if (metadata.type != MetaDataInfo::Type::Dcc || - metadata.size == 0 || base >= address + size || address >= base + metadata.size) { - continue; - } - UntrackMetadata(base, metadata); - metadata.dirty = true; - metadata.clear_mask = UINT32_MAX; - metadata.revision = ++m_metadata_revision; - } -} - -void TextureCache::RefreshDccMetadata(const ImageDesc& desc) { - const auto range = desc.info.metadata.range; - if (desc.info.metadata.kind != ImageMetadataKind::Dcc || range.size == 0 || - desc.info.resources.levels != 1) { - return; - } - // DCC allocations use complete 4 KiB metadata blocks. Besides validating - // the descriptor, this ensures that a CPU page fault identifies the metadata it modifies. - if (!range.Valid() || range.address % TRACKER_PAGE_SIZE != 0 || - range.size % TRACKER_PAGE_SIZE != 0) { - EXIT("TextureCache: DCC allocation must contain aligned 4 KiB blocks\n"); - } - for (;;) { - uint64_t revision = 0; - { - std::scoped_lock lock {m_lock}; - auto [entry, inserted] = m_surface_metas.try_emplace(range.address); - auto& metadata = entry->second; - if (inserted || metadata.type != MetaDataInfo::Type::Dcc) { - UntrackMetadata(range.address, metadata); - metadata = {.type = MetaDataInfo::Type::Dcc, .clear_mask = UINT32_MAX}; - } - if (metadata.size != range.size) { - UntrackMetadata(range.address, metadata); - metadata.size = range.size; - metadata.dirty = true; - metadata.revision = ++m_metadata_revision; - } - if (!metadata.dirty) { - return; - } - if (!metadata.tracked) { - m_page_manager.UpdatePageWatchers(range.address, range.size); - metadata.tracked = true; - } - revision = metadata.revision; - } - // Discovery precedes descriptor/vertex uploads. Drain metadata writes before taking - // the texture lock; completion can retire buffers and run memory-tracking callbacks. - m_buffer_cache.ReadMemory(range.address, range.size, false); - std::vector bytes(range.size); - std::scoped_lock lock {m_lock}; - const auto entry = m_surface_metas.find(range.address); - if (entry == m_surface_metas.end() || entry->second.revision != revision) { - continue; - } - if (!LibKernel::Memory::TryReadBacking(range.address, bytes.data(), range.size)) { - EXIT("TextureCache: failed to read DCC metadata backing\n"); - } - auto& metadata = entry->second; - metadata.fill_value = UINT32_MAX; - if (std::all_of(bytes.begin(), bytes.end(), [&](uint8_t byte) { return byte == bytes[0]; })) { - metadata.fill_value = static_cast(bytes[0]) * 0x01010101u; - } - metadata.dirty = false; - return; - } -} - -void TextureCache::PrepareDccClear(ImageId id, const ImageDesc& desc, - uint32_t metadata_base_layer) { +void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc, + uint32_t metadata_base_layer) { if (desc.info.metadata.kind != ImageMetadataKind::Dcc) { return; } - auto& image = m_slot_images[id]; - image.info.metadata = desc.info.metadata; - const auto entry = m_surface_metas.find(desc.info.metadata.range.address); - if (entry == m_surface_metas.end() || entry->second.type != MetaDataInfo::Type::Dcc) { - return; + const auto range = desc.info.metadata.range; + vk::Format format; + { + 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. + m_surface_metas.erase(range.address); + if (range.size == 0 || desc.info.resources.levels != 1 || image.info.resources.levels != 1) { + return; + } + format = image.backing.format; } - auto& metadata = entry->second; - if (metadata.dirty || metadata.clear_mask == 0 || image.info.resources.levels != 1 || - metadata.size != desc.info.metadata.range.size) { - return; - } - vk::ClearValue clear {}; - if (!DecodeDccClear(desc, image.backing.format, metadata.fill_value, clear.color)) { - return; + const auto layers = desc.info.TransferLayers(); + // These one-mip surfaces use complete 4 KiB DCC 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"); } const auto& view = desc.view_info; - const bool volume_texture = image.info.IsVolume() && view.type == vk::ImageViewType::e3D; + const bool volume_texture = desc.info.IsVolume() && view.type == vk::ImageViewType::e3D; const auto first = volume_texture ? 0u : metadata_base_layer; const auto image_first = volume_texture ? 0u : view.base_layer; - const auto count = volume_texture ? std::max(desc.info.extent.depth >> view.base_level, 1u) - : view.layer_count; - if (first >= 32 || count > 32 - first) { - return; + 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"); } - const auto metadata_slice_size = metadata.size / desc.info.TransferLayers(); - // 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++; + // 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. + if (m_buffer_cache.IsRegionGpuModified(range.address, range.size)) { + m_buffer_cache.ReadMemory(range.address, range.size, false); + } + const auto slice_size = range.size / layers; + for (uint32_t slice = 0; slice < count; slice++) { + 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"); + } + vk::ClearValue clear {}; + if (!DecodeDccClear(desc, format, code, clear.color)) { 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, - image_first + start - first, - layer - start}, - clear); - // The host now owns expanded color data. Publish uncompressed (0xff) - // keys for these slices, so a later partial write cannot reuse stale clear codes. - const auto address = desc.info.metadata.range.address + metadata_slice_size * start; - const auto size = metadata_slice_size * (layer - start); - auto [buffer, offset] = m_buffer_cache.ObtainBuffer(address, size, true, false); - buffer->Fill(offset, size, UINT32_MAX); - metadata.clear_mask &= ~mask; + std::vector bytes(slice_size); + if (!LibKernel::Memory::TryReadBacking(address, bytes.data(), bytes.size())) { + EXIT("TextureCache: failed to read DCC metadata slice\n"); + } + if (!std::all_of(bytes.begin(), bytes.end(), [code](uint8_t byte) { return byte == code; })) { + continue; + } + { + std::scoped_lock lock {m_lock}; + ClearImage(m_scheduler.Current(), id, + {vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count, + image_first + slice, 1}, clear); + } + // Native expanded keys own consumption. Existing buffer tracking publishes this CPU + // write to future GPU readers; FillBuffer can fault and must run outside the texture lock. + m_buffer_cache.FillBuffer(address, slice_size, UINT32_MAX, false); } } @@ -1330,7 +1252,6 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) { std::scoped_lock lock {m_lock}; return GetNullImage(desc); } - RefreshDccMetadata(desc); const auto metadata_base_layer = desc.view_info.base_layer; ImageId result {}; @@ -1396,10 +1317,10 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) { if (view_layer >= 0) { desc.view_info.base_layer = static_cast(view_layer); } - PrepareDccClear(result, desc, metadata_base_layer); image.tick_accessed_last = m_scheduler.CurrentTick(); TouchImage(image); } + MaterializeDccClear(result, desc, metadata_base_layer); return result; } @@ -1515,17 +1436,9 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) { RefreshImage(id); if (desc.info.HasMetadata()) { image.info.metadata = desc.info.metadata; - 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::HTile) { - // PS5 allocations can reuse DCC storage as HTile while the old color image is cached. - // The depth binding defines the new type; incompatible fill state cannot carry over. - UntrackMetadata(metadata->first, metadata->second); - metadata->second = {.type = MetaDataInfo::Type::HTile, - .clear_mask = image.info.htile_clear_mask}; - } + m_surface_metas.emplace(desc.info.metadata.range.address, + MetaDataInfo {.type = MetaDataInfo::Type::HTile, + .clear_mask = image.info.htile_clear_mask}); } CommitGpuWrite(image); if (desc.info.HasStencil()) { @@ -1696,7 +1609,6 @@ void TextureCache::InvalidateMemory(uint64_t address, uint64_t size) { EXIT("TextureCache: invalid memory-invalidation range\n"); } std::scoped_lock lock {m_lock}; - InvalidateDccMetadata(address, size); InvalidateCpuAliases(address, size); } @@ -1905,7 +1817,6 @@ void TextureCache::InvalidateMemoryFromGPU(uint64_t address, uint64_t size) { return; } std::scoped_lock lock {m_lock}; - InvalidateDccMetadata(address, size); for (const auto id: FindImagesInRegion(address, size, true)) { auto& image = m_slot_images[id]; if (image.depth_id || !image.Overlaps(address, size)) { @@ -1963,28 +1874,19 @@ bool TextureCache::IsMeta(uint64_t address) { return found != m_surface_metas.end(); } -bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice, uint32_t* fill_value) { +bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice) { std::scoped_lock lock {m_lock}; const auto found = m_surface_metas.find(address); if (found == m_surface_metas.end() || slice >= 32) { return false; } - if (fill_value != nullptr) { - *fill_value = found->second.fill_value; - } - if (found->second.type == MetaDataInfo::Type::Dcc && - (found->second.dirty || !IsDccClearCode(found->second.fill_value))) { - return false; - } return (found->second.clear_mask & (1u << slice)) != 0; } bool TextureCache::ClearMeta(uint64_t address) { std::scoped_lock lock {m_lock}; const auto found = m_surface_metas.find(address); - if (found == m_surface_metas.end() || 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. + if (found == m_surface_metas.end()) { return false; } found->second.clear_mask = UINT32_MAX; @@ -2012,10 +1914,7 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) { std::scoped_lock lock {m_lock}; for (auto metadata = m_surface_metas.begin(); metadata != m_surface_metas.end();) { const auto base = metadata->first; - if ((base >= address && base < address + size) || - (metadata->second.size != 0 && base < address + size && - address < base + metadata->second.size)) { - UntrackMetadata(base, metadata->second); + if (base >= address && base < address + size) { metadata = m_surface_metas.erase(metadata); } else { ++metadata; diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.h b/src/graphics/host_gpu/renderer/cache/textureCache.h index d2aabe464..addce65d2 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.h +++ b/src/graphics/host_gpu/renderer/cache/textureCache.h @@ -64,8 +64,7 @@ public: [[nodiscard]] bool IsRegionGpuModified(uint64_t address, uint64_t size); [[nodiscard]] bool IsMeta(uint64_t address); - [[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice, - uint32_t* fill_value = nullptr); + [[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice); [[nodiscard]] bool ClearMeta(uint64_t address); [[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear); @@ -79,16 +78,10 @@ private: struct ImageDownload; struct MetaDataInfo { - enum class Type : uint8_t { CMask, FMask, HTile, Dcc }; + enum class Type : uint8_t { CMask, FMask, HTile }; - Type type = Type::Dcc; - uint32_t clear_mask = 0; - uint32_t fill_value = 0xffffffffu; - // PS5 DCC bytes and layer consumption belong to the metadata allocation, not an image. - uint64_t size = 0; - uint64_t revision = 0; - bool dirty = true; - bool tracked = false; + Type type; + uint32_t clear_mask = UINT32_MAX; }; struct OverlapResult { @@ -146,11 +139,8 @@ private: ImageId cached); [[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source); void RefreshImage(ImageId id); - void RefreshDccMetadata(const ImageDesc& desc); - void PrepareDccClear(ImageId id, const ImageDesc& desc, - uint32_t metadata_base_layer); - void UntrackMetadata(uint64_t address, MetaDataInfo& metadata); - void InvalidateDccMetadata(uint64_t address, uint64_t size); + void MaterializeDccClear(ImageId id, const ImageDesc& desc, + uint32_t metadata_base_layer); void InitializeImage(ImageId id); [[nodiscard]] TextureTransfer BuildTextureTransfer(const Image& image, BindingType binding, TransferDirection direction) const; @@ -187,7 +177,6 @@ private: Common::LeastRecentlyUsedCache m_lru_cache; std::unordered_set m_download_images; std::map m_surface_metas; - uint64_t m_metadata_revision = 0; uint64_t m_total_used_memory = 0; uint64_t m_trigger_gc_memory = 0; uint64_t m_pressure_gc_memory = 1536ull * 1024 * 1024; diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index c20631868..d23f6edd7 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -873,6 +873,8 @@ void RenderExecutor::PrepareGraphicsBindings(std::span if (old_image != nullptr) { old_image->binding = {}; } + target.desc.view_info.base_level = target.guest_mip_level; + target.desc.view_info.base_layer = target.guest_array_layer; target.image_id = cache.FindImage(target.desc); BindRenderTarget(target.image_id); } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 52b7ae231..383041392 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -4521,20 +4521,6 @@ public: { auto &resources = context; - namespace Exception = Common::HostException; - Require(name, "guest fault handler", - Exception::InstallHandler([](const Exception::ExceptionInfo &info) { - if (info.type != Exception::ExceptionType::AccessViolation || - (info.access_violation_type != Exception::AccessViolationType::Read && - info.access_violation_type != Exception::AccessViolationType::Write)) { - return false; - } - const auto access = - info.access_violation_type == Exception::AccessViolationType::Write - ? PageFaultAccess::Write : PageFaultAccess::Read; - return LibKernel::Memory::HandleGpuFault(access, info.access_violation_vaddr); - }), - "failed to install the production guest-memory fault route"); LibKernel::Memory::InstallGpuResources(&resources); auto &texture_cache = resources.GetTextureCache(); const auto [narrow_download, narrow_download_offset] = @@ -6186,6 +6172,39 @@ public: [](uint8_t value) { return value == 0x5a; }), "remapped host layer changed metadata outside the guest alias allocation"); + RenderColorInfo retired_alias{}; + retired_alias.desc = dcc_alias; + retired_alias.image_id = dcc_array_id; + retired_alias.guest_mip_level = 0; + retired_alias.guest_array_layer = 0; + texture_cache.UnmapMemory(dcc_array.info.data.address, dcc_array.info.data.size); + WriteMetadata(context, dcc_alias.info.metadata.range.address, 0x1000, 0x40404040u); + RenderExecutorTestAccess::PrepareGraphicsBindings( + context.GetRenderExecutor(), std::span{}, + std::span{&retired_alias, 1u}); + RenderDepthInfo alias_depth{}; + const auto alias_rendering = RenderExecutorTestAccess::AcquireRenderTargets( + context.GetRenderExecutor(), scheduler.Current(), &retired_alias, 1, alias_depth); + Require(name, "retired DCC alias native coordinates", + retired_alias.image_id != dcc_array_id && + retired_alias.desc.view_info.base_level == 0 && + retired_alias.desc.view_info.base_layer == 0 && + retired_alias.desc.info.resources.layers == 1 && + alias_rendering.color_attachments[0].image_view != nullptr && + ReadCachedTexel(name, context, retired_alias.image_id) == + std::vector{0, 0x3c000000u}, + "rediscovery treated a retired host layer as the native DCC slice"); + resources.GetBufferCache().ReadMemory(alias_dcc_address, 0x3000); + Require(name, "retired DCC alias metadata bounds", + LibKernel::Memory::TryReadBacking(alias_dcc_address, alias_metadata.data(), + alias_metadata.size()) && + std::all_of(alias_metadata.begin(), alias_metadata.begin() + 0x2000, + [](uint8_t byte) { return byte == 0xff; }) && + std::all_of(alias_metadata.begin() + 0x2000, alias_metadata.end(), + [](uint8_t byte) { return byte == 0x5a; }), + "retired alias rediscovery overwrote metadata outside its native slice"); + RenderExecutorTestAccess::ResetBindings(context.GetRenderExecutor()); + constexpr uint64_t metadata_data_a = 0x12000; constexpr uint64_t metadata_data_b = 0x12100; constexpr uint64_t metadata_a = 0x13000; @@ -6284,7 +6303,7 @@ public: reused_color.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment; const auto reused_color_id = texture_cache.FindImage(reused_color); (void)texture_cache.FindRenderTarget(reused_color_id, reused_color); - // Exact repeated CPU stores must re-arm DCC metadata write protection. + // Repeated CPU stores must remain visible through ordinary buffer ownership. for (uint32_t repeat = 0; repeat < 2; repeat++) { vk::ClearValue painted{}; painted.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f}; @@ -6305,14 +6324,12 @@ public: reused_depth.info.stencil.size); const auto reused_depth_id = texture_cache.FindImage(reused_depth); (void)texture_cache.FindDepthTarget(reused_depth_id, reused_depth); - uint32_t reused_fill = 0; Require( name, "DCC allocation reused as HTile", TextureCacheTestAccess::Contains(texture_cache, reused_color_id) && texture_cache.GetImage(reused_color_id).IsBufferModified() && !texture_cache.GetImage(reused_color_id).IsGpuModified() && - !texture_cache.IsMetaCleared(base + 0x28000, 0, &reused_fill) && - reused_fill == UINT32_MAX && + !texture_cache.IsMetaCleared(base + 0x28000, 0) && texture_cache.ClearMeta(base + 0x28000) && texture_cache.TouchMeta(base + 0x28000, 0, false), "depth binding retained incompatible DCC clear state"); @@ -8250,14 +8267,13 @@ public: allocation_alignment) == 0 && mapped == reinterpret_cast(base), "color-volume fixed mapping failed"); - std::memset(mapped, 0, allocation_size); + std::memset(mapped, 0x5a, allocation_size); constexpr uint64_t slice_size = 0x10000; - std::memset(static_cast(mapped) + 31 * slice_size, 0x5a, - slice_size); { RenderContext context(m_runtime_context); context.InitializeGpu(nullptr); + LibKernel::Memory::InstallGpuResources(&context); auto &scheduler = context.GetCommandScheduler(); HW::Context registers{}; HW::UserConfig user_config{}; @@ -8296,7 +8312,6 @@ 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 && @@ -8308,8 +8323,7 @@ public: color.desc.info.mip_layout[0].size == 0x10000 && color.desc.info.metadata.kind == ImageMetadataKind::Dcc && color.desc.info.metadata.range.address == dcc_address && - texture_cache.IsMetaCleared(dcc_address, 7, &dcc_clear_value) && - dcc_clear_value == 0 && !texture_cache.ClearMeta(dcc_address) && + !texture_cache.IsMeta(dcc_address) && !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 && @@ -8319,6 +8333,18 @@ public: "dimension=2/depth=31 did not create the expected 32x32x32 " "backing and 2D " "attachment slice"); + constexpr uint64_t metadata_slice_size = metadata_size / 32; + std::vector metadata_bytes(metadata_size); + resources.GetBufferCache().ReadMemory(dcc_address, metadata_size); + Require(name, "first volume slice expanded metadata", + LibKernel::Memory::TryReadBacking(dcc_address, metadata_bytes.data(), + metadata_bytes.size()) && + std::all_of(metadata_bytes.begin(), + metadata_bytes.begin() + metadata_slice_size, + [](uint8_t byte) { return byte == 0xff; }) && + std::all_of(metadata_bytes.begin() + metadata_slice_size, + metadata_bytes.end(), [](uint8_t byte) { return byte == 0; }), + "consuming volume slice 0 changed unrequested native metadata slices"); RenderExecutorTestAccess::ResetBindings(executor); registers.SetColorView( @@ -8336,9 +8362,21 @@ public: sliced_rendering.color_attachments[0].image_view != nullptr && sliced_color.desc.view_info.base_layer == 7 && sliced_rendering.num_color_attachments == 1 && - sliced_rendering.num_layers == 1 && - !texture_cache.IsMetaCleared(dcc_address, 7), + sliced_rendering.num_layers == 1, "a nonzero 3D attachment slice was treated as a Vulkan array layer"); + resources.GetBufferCache().ReadMemory(dcc_address, metadata_size); + Require(name, "mixed volume metadata readback", + LibKernel::Memory::TryReadBacking(dcc_address, metadata_bytes.data(), + metadata_bytes.size()), + "mixed volume metadata is unavailable"); + for (uint32_t slice = 0; slice < 32; slice++) { + const uint8_t expected = slice == 0 || slice == 7 ? 0xff : 0; + const auto first = metadata_bytes.begin() + slice * metadata_slice_size; + Require(name, "independent volume metadata consumption", + std::all_of(first, first + metadata_slice_size, + [expected](uint8_t byte) { return byte == expected; }), + "mixed DCC metadata lost a pending slice or consumed an unrequested slice"); + } Require(name, "3D slice clear and preservation", ReadCachedTexel(name, context, color.image_id, {0, 0, 7}) == std::vector{0} && @@ -8394,20 +8432,37 @@ public: "render-target upload/readback lost the final Z slice"); DestroyBuffer(&slice_probe); - dcc_clear_value = 0; + vk::ClearValue painted{}; + painted.color.float32 = std::array{1.0f, 1.0f, 1.0f, 0.0f}; + TextureCacheTestAccess::ClearImage( + texture_cache, scheduler.Current(), color.image_id, + {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, painted); WriteMetadata(context, dcc_address, metadata_size, 0xffffffffu); color.image_id = texture_cache.FindImage(color.desc); (void)texture_cache.FindRenderTarget(color.image_id, color.desc); - const bool uncompressed = - !texture_cache.IsMetaCleared(dcc_address, 7, &dcc_clear_value) && - dcc_clear_value == 0xffffffffu; + resources.GetBufferCache().ReadMemory(dcc_address, metadata_size); + Require(name, "expanded volume metadata preserves rendering", + ReadCachedTexel(name, context, color.image_id) == + std::vector{0x3fffffffu} && + LibKernel::Memory::TryReadBacking(dcc_address, metadata_bytes.data(), + metadata_bytes.size()) && + std::all_of(metadata_bytes.begin(), metadata_bytes.end(), + [](uint8_t byte) { return byte == 0xff; }), + "uncompressed native keys reapplied an old volume clear"); WriteMetadata(context, dcc_address, metadata_size, 0); color.image_id = texture_cache.FindImage(color.desc); (void)texture_cache.FindRenderTarget(color.image_id, color.desc); - Require( - name, "registered DCC code state", - uncompressed && texture_cache.IsMetaCleared(dcc_address, 7), - "DCC fill codes were not classified as clear or uncompressed"); + resources.GetBufferCache().ReadMemory(dcc_address, metadata_size); + Require(name, "native volume re-clear", + ReadCachedTexel(name, context, color.image_id) == std::vector{0} && + LibKernel::Memory::TryReadBacking(dcc_address, metadata_bytes.data(), + metadata_bytes.size()) && + std::all_of(metadata_bytes.begin(), + metadata_bytes.begin() + metadata_slice_size, + [](uint8_t byte) { return byte == 0xff; }) && + std::all_of(metadata_bytes.begin() + metadata_slice_size, + metadata_bytes.end(), [](uint8_t byte) { return byte == 0; }), + "a native metadata overwrite did not clear only its requested volume slice"); for (const uint32_t base_slice : {0u, 7u}) { RenderExecutorTestAccess::ResetBindings(executor); @@ -8432,10 +8487,19 @@ public: scheduler.Current().BeginRendering(volume_rendering); scheduler.Current().EndRendering(); } + resources.GetBufferCache().ReadMemory(dcc_address, metadata_size); + Require(name, "complete volume expansion", + LibKernel::Memory::TryReadBacking(dcc_address, metadata_bytes.data(), + metadata_bytes.size()) && + std::all_of(metadata_bytes.begin(), metadata_bytes.end(), + [](uint8_t byte) { return byte == 0xff; }), + "full volume acquisition left an unconsumed native clear slice"); RenderExecutorTestAccess::ResetBindings(executor); resources.UnmapMemory(base, allocation_size); scheduler.Finish(); + context.ShutdownGpu(); + LibKernel::Memory::InstallGpuResources(nullptr); } Require(name, "unmap direct backing", @@ -8570,6 +8634,7 @@ public: HW::UserConfig user_config{}; HW::Shader shaders{}; context.InitializeGpu(nullptr); + LibKernel::Memory::InstallGpuResources(&context); context.GetGpu().SendCommandSync([&] { auto &scheduler = context.GetCommandScheduler(); registers.SetColorBase(0, {.addr = base}); @@ -8627,7 +8692,7 @@ public: color.desc.info.metadata.kind == ImageMetadataKind::Dcc && color.desc.info.metadata.range.address == dcc_address && rendering.num_color_attachments == 1 && - !texture_cache.IsMetaCleared(dcc_address, 0), + !texture_cache.IsMeta(dcc_address), "a DCC fixed clear code was not materialised on an RGBA16F " "target"); Require(name, "GPU float clear value", @@ -8694,11 +8759,55 @@ public: ReadCachedTexel(name, context, color.image_id) == std::vector(fill_case.texel.begin(), fill_case.texel.end()), "completing the metadata overwrite did not restore its clear"); + + ImageDesc previous_depth{}; + previous_depth.type = BindingType::DepthTarget; + previous_depth.info.data = {base + 0x180000, 0x10000}; + previous_depth.info.pixel_format = vk::Format::eD32Sfloat; + previous_depth.info.guest_format = Prospero::BufferFormat::k32Float; + previous_depth.info.type = Prospero::ImageType::kColor2D; + previous_depth.info.extent = {128, 128, 1}; + previous_depth.info.resources = {1, 1}; + previous_depth.info.pitch = 128; + previous_depth.info.bytes_per_block = 4; + previous_depth.info.samples = 1; + 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.htile_clear_mask = 0; + previous_depth.view_info.format = vk::Format::eD32Sfloat; + previous_depth.view_info.type = vk::ImageViewType::e2D; + previous_depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth; + previous_depth.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment; + const auto previous_depth_id = texture_cache.FindImage(previous_depth); + Require(name, "old HTile interpretation", + texture_cache.FindDepthTarget(previous_depth_id, previous_depth) != nullptr && + texture_cache.IsMeta(dcc_address), + "the old depth target did not register its metadata allocation"); + paint(); + bind(); + Require(name, "HTile allocation reused as expanded DCC", + !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"); + 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"); RenderExecutorTestAccess::ResetBindings(executor); resources.UnmapMemory(base, allocation_size); scheduler.Finish(); }); context.ShutdownGpu(); + LibKernel::Memory::InstallGpuResources(nullptr); } Require(name, "unmap direct backing", @@ -8737,6 +8846,7 @@ public: std::memset(reinterpret_cast(dcc_address), 0xff, metadata_size); RenderContext context(m_runtime_context); context.InitializeGpu(nullptr); + LibKernel::Memory::InstallGpuResources(&context); auto &scheduler = context.GetCommandScheduler(); HW::Context registers{}; HW::UserConfig user_config{}; @@ -8785,9 +8895,16 @@ public: } 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), + std::vector{0, 0x3c000000u}, "sampling read stale color bytes instead of the DCC opaque black clear"); + context.GetBufferCache().ReadMemory(dcc_address, metadata_size); + std::vector expanded_metadata(metadata_size); + Require(name, "sampled clear expands native metadata", + 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 sampled clear retained native clear keys after materialization"); WriteMetadata(context, dcc_address, metadata_size, 0x80808080u); binding.image_id = cache.FindImage(binding.desc); (void)cache.FindTexture(binding.image_id, binding.desc); @@ -8797,6 +8914,8 @@ public: "an existing sampled image retained the previous DCC clear"); resources.UnmapMemory(base, allocation_size); scheduler.Finish(); + context.ShutdownGpu(); + LibKernel::Memory::InstallGpuResources(nullptr); } Require(name, "unmap", Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0, @@ -14243,6 +14362,20 @@ private: if (m_runtime_context.allocator != nullptr) { return; } + namespace Exception = Common::HostException; + Require("VulkanHarness", "guest fault handler", + Exception::InstallHandler([](const Exception::ExceptionInfo &info) { + if (info.type != Exception::ExceptionType::AccessViolation || + (info.access_violation_type != Exception::AccessViolationType::Read && + info.access_violation_type != Exception::AccessViolationType::Write)) { + return false; + } + const auto access = + info.access_violation_type == Exception::AccessViolationType::Write + ? PageFaultAccess::Write : PageFaultAccess::Read; + return LibKernel::Memory::HandleGpuFault(access, info.access_violation_vaddr); + }), + "failed to install the production guest-memory fault route"); m_runtime_context.instance = m_instance; m_runtime_context.physical_device = m_physical_device; m_runtime_context.device = m_device;