From 279bc9594109245e36115b5e24ba9fa1f8e4bd09 Mon Sep 17 00:00:00 2001 From: nmzik Date: Thu, 15 Oct 2026 00:45:03 +0200 Subject: [PATCH] graphics: track HTile clears across PS5 depth array slices Co-authored-by: fxpw --- .../host_gpu/renderer/cache/textureCache.cpp | 26 ++-- .../host_gpu/renderer/cache/textureCache.h | 6 +- .../host_gpu/renderer/depthRenderTarget.cpp | 3 - .../host_gpu/renderer/image/imageInfo.h | 2 +- tests/ShaderRecompilerComputeTests.cpp | 124 ++++++++++++++++-- 5 files changed, 128 insertions(+), 33 deletions(-) diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index 640baa8ff..2f591439e 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -692,7 +692,7 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp info.linear_slice_stride = cached.info.linear_slice_stride; info.first_tail_level = cached.info.first_tail_level; } - info.htile_clear_mask = 0; + info.htile_initial_clear = false; const auto replacement_id = InsertImage(info); auto& replacement = m_slot_images[replacement_id]; replacement.usage = cached.usage; @@ -1416,9 +1416,13 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) { image.info.stencil = desc.info.stencil; image.info.metadata = desc.info.metadata; if (desc.info.HasMetadata()) { - m_surface_metas.emplace(desc.info.metadata.range.address, - MetaDataInfo {.type = MetaDataInfo::Type::HTile, - .clear_mask = image.info.htile_clear_mask}); + const auto [metadata, inserted] = m_surface_metas.try_emplace(desc.info.metadata.range.address); + if (inserted) { + metadata->second.type = MetaDataInfo::Type::HTile; + if (image.info.htile_initial_clear) { + metadata->second.clear_mask.set(); + } + } } RefreshImage(id); CommitGpuWrite(image); @@ -1841,10 +1845,10 @@ bool TextureCache::IsMeta(uint64_t address) { 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) { + if (found == m_surface_metas.end() || slice >= found->second.clear_mask.size()) { return false; } - return (found->second.clear_mask & (1u << slice)) != 0; + return found->second.clear_mask.test(slice); } bool TextureCache::ClearMeta(uint64_t address) { @@ -1853,21 +1857,17 @@ bool TextureCache::ClearMeta(uint64_t address) { if (found == m_surface_metas.end()) { return false; } - found->second.clear_mask = UINT32_MAX; + found->second.clear_mask.set(); return true; } bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) { std::scoped_lock lock {m_lock}; const auto found = m_surface_metas.find(address); - if (found == m_surface_metas.end() || slice >= 32) { + if (found == m_surface_metas.end() || slice >= found->second.clear_mask.size()) { return false; } - if (is_clear) { - found->second.clear_mask |= 1u << slice; - } else { - found->second.clear_mask &= ~(1u << slice); - } + found->second.clear_mask.set(slice, is_clear); return true; } diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.h b/src/graphics/host_gpu/renderer/cache/textureCache.h index 2a9974a43..e91928f6c 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.h +++ b/src/graphics/host_gpu/renderer/cache/textureCache.h @@ -12,6 +12,7 @@ #include "graphics/host_gpu/renderer/image/image.h" #include "graphics/host_gpu/renderer/image/tiler.h" +#include #include #include #include @@ -82,8 +83,9 @@ private: struct MetaDataInfo { enum class Type : uint8_t { CMask, FMask, HTile }; - Type type; - uint32_t clear_mask = UINT32_MAX; + Type type; + // PS5 DB_DEPTH_VIEW encodes 13-bit base and last array-slice indices. + std::bitset<8192> clear_mask; }; struct OverlapResult { diff --git a/src/graphics/host_gpu/renderer/depthRenderTarget.cpp b/src/graphics/host_gpu/renderer/depthRenderTarget.cpp index 9a14b6742..c27b1c8fe 100644 --- a/src/graphics/host_gpu/renderer/depthRenderTarget.cpp +++ b/src/graphics/host_gpu/renderer/depthRenderTarget.cpp @@ -177,9 +177,6 @@ static TextureCache::ImageDesc MakeDepthTargetDesc(const CommandBuffer& buffer, if (z.htile_data_base_addr == 0 || (z.htile_data_base_addr & 0x7fffu) != 0) { DepthFatal("invalid HTile metadata address"); } - if (z.depth_view.slice_max >= 32) { - DepthFatal("HTile clear tracking supports at most 32 slices"); - } } if (!z.size.valid) { DepthFatal("missing depth extent"); diff --git a/src/graphics/host_gpu/renderer/image/imageInfo.h b/src/graphics/host_gpu/renderer/image/imageInfo.h index 44c5d7e3d..e7270652d 100644 --- a/src/graphics/host_gpu/renderer/image/imageInfo.h +++ b/src/graphics/host_gpu/renderer/image/imageInfo.h @@ -69,7 +69,7 @@ struct ImageInfo { GuestRange data; GuestRange stencil; ImageMetadataInfo metadata; - uint32_t htile_clear_mask = UINT32_MAX; + bool htile_initial_clear = true; vk::Format pixel_format = vk::Format::eUndefined; Prospero::BufferFormat guest_format = Prospero::BufferFormat::kInvalid; Prospero::ImageType type = Prospero::ImageType::kColor2D; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 3191d3b8b..8e6342f95 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -391,7 +391,7 @@ struct TextureCacheTestAccess { std::lock_guard lock(cache.m_lock); auto &metadata = cache.m_surface_metas[address]; metadata.type = TextureCache::MetaDataInfo::Type::HTile; - metadata.clear_mask = 0; + metadata.clear_mask.reset(); } static TileManager &Tiler(TextureCache &cache) { return cache.m_tiler; } @@ -5747,14 +5747,23 @@ public: "non-exact lookup did not retain the last compatible " "registration"); - auto null_desc = sampled; - null_desc.info.data = {}; - auto null_repeat_desc = null_desc; - const auto null_image = texture_cache.FindImage(null_desc); - const auto null_repeat = texture_cache.FindImage(null_repeat_desc); - Require(name, "null image", - null_image && null_repeat == null_image && null_image != exact, - "typed null-image lookup was not stable"); + ShaderRecompiler::IR::ImageResource null_resource{}; + null_resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Sampled; + null_resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D; + null_resource.read = true; + ShaderRecompiler::IR::DescriptorValue null_descriptor{}; + null_descriptor.dword_count = 8; + for (u32 attempt = 0; attempt < 2; ++attempt) { + const auto binding = RenderExecutorTestAccess::ResolveTexture( + context.GetRenderExecutor(), null_resource, null_descriptor); + const auto native = MakeImageInfo(binding); + Require(name, "null image binding", + !binding.image_id && binding.image_view == nullptr && + native.imageView == nullptr && + native.imageLayout == vk::ImageLayout::eGeneral && + TextureCacheTestAccess::Contains(texture_cache, exact), + "a null descriptor acquired a cache image or disturbed a real image"); + } constexpr uint64_t extent_alias_offset = 0x2400000; auto narrow_target = sampled; @@ -6750,8 +6759,7 @@ public: texture_cache.GetImage(ms_depth_image).backing.state.layout == vk::ImageLayout::eDepthStencilAttachmentOptimal && texture_cache.GetImage(ms_depth_image).IsGpuModified() && - texture_cache.GetImage(ms_depth_image).info.htile_clear_mask == - 0 && + !texture_cache.GetImage(ms_depth_image).info.htile_initial_clear && texture_cache.GetImage(ms_depth_image).info.metadata.range == ms_depth_desc.info.metadata.range && texture_cache.IsMeta(ms_htile_address) && @@ -7659,7 +7667,7 @@ 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; + metadata_depth_a.info.htile_initial_clear = false; const auto metadata_depth_a_id = texture_cache.FindImage(metadata_depth_a); const auto metadata_depth_b_id = @@ -7677,6 +7685,8 @@ public: metadata_depth_a_view != nullptr && metadata_depth_b_view != nullptr && !texture_cache.IsMetaCleared(base + metadata_a, 0) && + !texture_cache.IsMetaCleared(base + metadata_a, 86) && + !texture_cache.IsMetaCleared(base + metadata_a, 8191) && texture_cache.ClearMeta(base + metadata_a) && texture_cache.ClearMeta(base + metadata_b), "shared-page metadata did not retain logical clear state"); @@ -7713,6 +7723,32 @@ public: texture_cache.TouchMeta(base + metadata_b, 0, false) && !texture_cache.IsMetaCleared(base + metadata_b, 0), "per-slice metadata update incorrectly required prior GPU ownership"); + constexpr std::array htile_slices{31u, 32u, 63u, 64u, 86u, 8191u}; + Require(name, "HTile clear across slice words", + texture_cache.ClearMeta(base + metadata_b), + "registered metadata did not accept a full clear"); + for (const auto slice : htile_slices) { + Require(name, "independent high HTile slice", + texture_cache.IsMetaCleared(base + metadata_b, slice) && + texture_cache.TouchMeta(base + metadata_b, slice, false) && + !texture_cache.IsMetaCleared(base + metadata_b, slice) && + texture_cache.IsMetaCleared(base + metadata_b, 0), + "high slice clear state was missing or aliased a lower slice"); + } + (void)texture_cache.FindDepthTarget(metadata_depth_b_id, metadata_depth_b); + Require(name, "HTile registration retains consumed slices", + std::ranges::none_of(htile_slices, [&](uint32_t slice) { + return texture_cache.IsMetaCleared(base + metadata_b, slice); + }) && texture_cache.TouchMeta(base + metadata_b, 86, true) && + texture_cache.IsMetaCleared(base + metadata_b, 86) && + !texture_cache.IsMetaCleared(base + metadata_b, 64) && + !texture_cache.IsMetaCleared(base + metadata_b, 8192) && + !texture_cache.TouchMeta(base + metadata_b, 8192, true) && + texture_cache.ClearMeta(base + metadata_b) && + std::ranges::all_of(htile_slices, [&](uint32_t slice) { + return texture_cache.IsMetaCleared(base + metadata_b, slice); + }), + "registration reset consumed slices or full clear failed to rearm them"); resources.GetBufferCache().FillBuffer(base + metadata_b, sizeof(uint32_t), 0, false); Require( @@ -7725,7 +7761,7 @@ public: auto reused_depth = MakeMetadataDepth(0x12300, 0x28000); reused_depth.info.pixel_format = vk::Format::eD32SfloatS8Uint; reused_depth.info.stencil = {base + 0x12200, 0x80}; - reused_depth.info.htile_clear_mask = 0; + reused_depth.info.htile_initial_clear = false; reused_depth.view_info.format = vk::Format::eD32SfloatS8Uint; reused_depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil; @@ -11818,7 +11854,7 @@ public: previous_depth.info.UpdateSize(); previous_depth.info.metadata.kind = ImageMetadataKind::Htile; previous_depth.info.metadata.range = {dcc_address, metadata_size}; - previous_depth.info.htile_clear_mask = 0; + previous_depth.info.htile_initial_clear = false; previous_depth.view_info.format = vk::Format::eD32Sfloat; previous_depth.view_info.type = vk::ImageViewType::e2D; previous_depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth; @@ -14772,6 +14808,66 @@ public: [](uint32_t value) { return value == std::bit_cast(0.625f); }), "pending clear did not use DB_DEPTH_CLEAR or cleared the next acquisition again"); DestroyBuffer(&bounds_readback); + + // Captured DB_DEPTH_VIEW selects one layer of an 87-layer HTile target. + auto high_slice_target = depth_only_target; + high_slice_target.z_read_base_addr = base + 0x800000; + high_slice_target.z_write_base_addr = high_slice_target.z_read_base_addr; + high_slice_target.htile_data_base_addr = base + 0x500000; + high_slice_target.depth_view.slice_start = 86; + high_slice_target.depth_view.slice_max = 86; + high_slice_target.size = {127, 127, true}; + registers.SetDepthRenderTarget(high_slice_target); + registers.SetDepthControl(HW::DepthControl{.depth_bounds_enable = true}); + registers.SetRenderControl({}); + RenderDepthInfo high_slice{}; + RenderExecutorTestAccess::ResolveRenderDepthTarget( + executor, scheduler.Current(), high_slice); + (void)texture_cache.FindDepthTarget(high_slice.image_id, high_slice.desc); + Require(name, "captured high HTile slice layout", + high_slice.desc.info.resources.layers == 87 && + high_slice.desc.view_info.base_layer == 86 && + high_slice.desc.view_info.layer_count == 1 && + high_slice.desc.info.data.End() <= base + 0xe00000 && + high_slice.desc.info.metadata.range.End() <= base + 0x800000 && + texture_cache.ClearMeta(high_slice_target.htile_data_base_addr), + "single high slice was rejected or changed the backing layer layout"); + auto high_slice_readback = CreateHostBuffer( + name, 4 * sizeof(uint32_t), vk::BufferUsageFlagBits::eTransferDst, {}); + for (uint32_t pass = 0; pass < 2; ++pass) { + vk::ClearValue prior_depth{}; + prior_depth.depthStencil.depth = 0.625f; + TextureCacheTestAccess::ClearImage(texture_cache, scheduler.Current(), + high_slice.image_id, {vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 87}, + prior_depth); + RenderDepthInfo acquired_high_slice{}; + RenderExecutorTestAccess::ResolveRenderDepthTarget( + executor, scheduler.Current(), acquired_high_slice); + const auto rendering = RenderExecutorTestAccess::AcquireRenderTargets( + executor, scheduler.Current(), &no_color, 0, acquired_high_slice); + Require(name, "high HTile slice clear consumption", + acquired_high_slice.image_id == high_slice.image_id && + acquired_high_slice.depth_load_clear_enable == (pass == 0) && + rendering.depth_stencil_attachment.depth_clear == (pass == 0) && + !texture_cache.IsMetaCleared(high_slice_target.htile_data_base_addr, 86) && + texture_cache.IsMetaCleared(high_slice_target.htile_data_base_addr, 85), + "high slice was cleared repeatedly or consumed its neighbor's state"); + scheduler.BeginRendering(rendering); + scheduler.EndRendering(); + RenderExecutorTestAccess::ResetBindings(executor); + const vk::BufferImageCopy copy{pass * 2 * sizeof(uint32_t), 0, 0, + {vk::ImageAspectFlagBits::eDepth, 0, 85, 2}, {}, {1, 1, 1}}; + texture_cache.GetImage(high_slice.image_id).Download( + std::span{©, 1}, high_slice_readback.buffer, 0, high_slice_readback.size); + } + scheduler.Current().Handle().pipelineBarrier2(bounds_dependency); + scheduler.Finish(); + Require(name, "high HTile slice contents and neighbor", + ReadBuffer(name, high_slice_readback, 4) == std::vector{ + std::bit_cast(0.625f), std::bit_cast(0.375f), + std::bit_cast(0.625f), std::bit_cast(0.625f)}, + "layer86 clear missed, repeated, or changed adjacent layer85"); + DestroyBuffer(&high_slice_readback); registers.SetDepthClearValue(0.0f); // DB override copies run with depth and stencil tests disabled.