diff --git a/src/graphics/host_gpu/renderer/bufferCache.cpp b/src/graphics/host_gpu/renderer/bufferCache.cpp index fa06f76ce..08b5607ec 100644 --- a/src/graphics/host_gpu/renderer/bufferCache.cpp +++ b/src/graphics/host_gpu/renderer/bufferCache.cpp @@ -702,9 +702,13 @@ std::pair BufferCache::ObtainBuffer(CommandBuffer* com ? texture_region : m_texture_cache->QueryRegion(begin, end - begin); if (texture_pages.metadata_pages) { - EXIT("BufferCache: buffer aliases metadata pages, addr=0x%016" PRIx64 " size=0x%016" PRIx64 - "\n", - begin, end - begin); + EXIT("BufferCache: buffer aliases metadata pages, request=0x%016" PRIx64 "+0x%016" PRIx64 + " aligned=0x%016" PRIx64 "+0x%016" PRIx64 " read=%d written=%d formatted=%d" + " request_meta=%d/%d/%d aligned_meta=%d/%d/%d\n", + vaddr, size, begin, end - begin, is_read, is_written, is_formatted, + texture_region.metadata_pages, texture_region.metadata_bytes, + texture_region.gpu_metadata_bytes, texture_pages.metadata_pages, + texture_pages.metadata_bytes, texture_pages.gpu_metadata_bytes); } // Cache allocations are tracker-page aligned, but byte-disjoint buffers and images may share // an edge page. Clean read-only buffer and image views may coexist; Kyty retains a hard failure diff --git a/src/graphics/host_gpu/renderer/descriptors.cpp b/src/graphics/host_gpu/renderer/descriptors.cpp index 447ec8d70..4dae9c0ef 100644 --- a/src/graphics/host_gpu/renderer/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/descriptors.cpp @@ -321,27 +321,33 @@ bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor, const auto height = static_cast(descriptor.Height5()) + 1u; const auto pitch = TileGetTexturePitch(descriptor.Format(), width, 1, descriptor.TileMode()); const auto type = static_cast(descriptor.Type()); - const bool supported_type = - type == Prospero::ImageType::kColor2D || type == Prospero::ImageType::kColor2DArray; - return image.type == VulkanImageType::DepthStencil && image.layers == 1 && - width == image.extent.width && height == image.extent.height && - descriptor.Depth() == 0 && descriptor.BaseLevel() == 0 && descriptor.LastLevel() == 0 && - descriptor.MaxMip() == 0 && descriptor.MinLod() == 0 && descriptor.BaseArray5() == 0 && + const bool supported_single_layer = + image.layers == 1 && descriptor.Depth() == 0 && descriptor.BaseArray5() == 0 && + (type == Prospero::ImageType::kColor2D || type == Prospero::ImageType::kColor2DArray); + const bool supported_cube = type == Prospero::ImageType::kCube && width == height && + image.layers >= 6 && image.layers % 6u == 0 && + static_cast(descriptor.Depth()) + 1u == image.layers && + descriptor.BaseArray5() == 0; + return image.type == VulkanImageType::DepthStencil && width == image.extent.width && + height == image.extent.height && (supported_single_layer || supported_cube) && + descriptor.BaseLevel() == 0 && descriptor.LastLevel() == 0 && descriptor.MaxMip() == 0 && + descriptor.MinLod() == 0 && descriptor.BaseArray5() == 0 && descriptor.TileMode() == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) && - supported_type && descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth() && - pitch >= width && pitch == image.guest_pitch; + descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth() && pitch >= width && + pitch == image.guest_pitch; } -static bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor) { +bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor) { constexpr uint32_t field1_reserved_mask = 0x200fff00u; constexpr uint32_t field2_reserved_mask = 0xf0003000u; constexpr uint32_t field3_common = 0x01800000u; constexpr uint32_t field5_expected = 0x00700000u; const uint32_t field3_expected = (descriptor.Type() << 28u) | field3_common | descriptor.DstSelXYZW(); + const uint32_t field4_expected = descriptor.Depth() | (descriptor.BaseArray5() << 16u); return (descriptor.fields[1] & field1_reserved_mask) == 0 && (descriptor.fields[2] & field2_reserved_mask) == 0 && - descriptor.fields[3] == field3_expected && descriptor.fields[4] == 0 && + descriptor.fields[3] == field3_expected && descriptor.fields[4] == field4_expected && descriptor.fields[5] == field5_expected && descriptor.fields[6] == 0 && descriptor.fields[7] == 0; } @@ -473,14 +479,52 @@ void ValidateMetadataReuseTexture(const ShaderRecompiler::IR::ImageResource& res const ShaderTextureResource& descriptor, uint64_t size) { constexpr uint32_t field1_reserved = 0x200fff00u; constexpr uint32_t field2_reserved = 0xf0003000u; + constexpr uint32_t field5_common = 0x00700000u; const auto format = descriptor.Format(); - if (!IsSupportedSampledColorResource(resource) || size == 0 || - (descriptor.fields[1] & field1_reserved) != 0 || - (descriptor.fields[2] & field2_reserved) != 0 || descriptor.fields[3] != 0x90500facu || - descriptor.fields[4] != 0 || descriptor.fields[5] != 0x00700000u || - descriptor.fields[6] != 0 || descriptor.fields[7] != 0 || - !Prospero::IsSupportedTextureFormat(format) || Prospero::IsUintTextureFormat(format)) { - EXIT("unsupported storage texture descriptor encoding\n"); + const bool resource_ok = IsSupportedSampledColorResource(resource); + const bool swizzle_ok = IsValidSampledColorSwizzle(descriptor.DstSelXYZW()); + const bool descriptor_ok = + resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D && + descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) && + descriptor.Depth() == 0 && descriptor.BaseArray5() == 0 && + descriptor.BaseLevel() <= descriptor.LastLevel() && + descriptor.LastLevel() <= descriptor.MaxMip() && descriptor.MaxMip() < 15 && + descriptor.TileMode() == Prospero::GpuEnumValue(Prospero::TileMode::kStandard4KB) && + swizzle_ok; + const uint32_t field3_expected = descriptor.DstSelXYZW() | + (static_cast(descriptor.BaseLevel()) << 12u) | + (static_cast(descriptor.LastLevel()) << 16u) | + (static_cast(descriptor.TileMode()) << 20u) | + (static_cast(descriptor.Type()) << 28u); + const uint32_t field4_expected = descriptor.Depth() | (descriptor.BaseArray5() << 16u); + const uint32_t field5_expected = + field5_common | (static_cast(descriptor.MaxMip()) << 4u); + const bool encoding_ok = + (descriptor.fields[1] & field1_reserved) == 0 && + (descriptor.fields[2] & field2_reserved) == 0 && descriptor.fields[3] == field3_expected && + descriptor.fields[4] == field4_expected && descriptor.fields[5] == field5_expected && + descriptor.fields[6] == 0 && descriptor.fields[7] == 0; + const bool format_ok = + Prospero::IsSupportedTextureFormat(format) && !Prospero::IsUintTextureFormat(format); + if (!resource_ok || !descriptor_ok || !encoding_ok || !format_ok || size == 0) { + EXIT("unsupported metadata-reuse sampled texture: resource=%d descriptor=%d encoding=%d " + "format=%d " + "kind=%u dimension=%u mip_mode=%u read=%d written=%d atomic=%d compare=%d " + "base_level=%u last_level=%u max_mip=%u base_array=%u swizzle_ok=%d " + "addr=0x%016" PRIx64 " size=0x%016" PRIx64 + " extent=%ux%ux%u type=%u format=%u tile=%u swizzle=0x%03x " + "dwords=%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x\n", + resource_ok, descriptor_ok, encoding_ok, format_ok, + static_cast(resource.kind), static_cast(resource.dimension), + static_cast(resource.mip_mode), resource.read, resource.written, + resource.atomic, resource.depth_compare, descriptor.BaseLevel(), + descriptor.LastLevel(), descriptor.MaxMip(), descriptor.BaseArray5(), swizzle_ok, + descriptor.Base40(), size, static_cast(descriptor.Width5()) + 1u, + static_cast(descriptor.Height5()) + 1u, + static_cast(descriptor.Depth()) + 1u, descriptor.Type(), format, + descriptor.TileMode(), descriptor.DstSelXYZW(), descriptor.fields[0], + descriptor.fields[1], descriptor.fields[2], descriptor.fields[3], descriptor.fields[4], + descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]); } } diff --git a/src/graphics/host_gpu/renderer/descriptors.h b/src/graphics/host_gpu/renderer/descriptors.h index b29a55a02..5f175e70f 100644 --- a/src/graphics/host_gpu/renderer/descriptors.h +++ b/src/graphics/host_gpu/renderer/descriptors.h @@ -36,6 +36,7 @@ ResolveTargetTextureView(const ShaderRecompiler::IR::ImageResource& resource, [[nodiscard]] bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor, const VulkanImage& image); +[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor); [[nodiscard]] bool IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& resource, const ShaderTextureResource& descriptor, const VulkanImage& image); diff --git a/src/graphics/host_gpu/renderer/imageInfo.h b/src/graphics/host_gpu/renderer/imageInfo.h index c21572760..0f089ac36 100644 --- a/src/graphics/host_gpu/renderer/imageInfo.h +++ b/src/graphics/host_gpu/renderer/imageInfo.h @@ -360,7 +360,7 @@ enum class RenderTargetOverlap : uint8_t { Unsupported }; enum class SampledOverlap : uint8_t { None, ReadOnlyAlias, Unsupported }; -enum class StorageSampledOverlap : uint8_t { None, ExactImage, Unsupported }; +enum class StorageSampledOverlap : uint8_t { None, ExactImage, RetireStorage, Unsupported }; enum class StorageSampledViewShape : uint8_t { Image2D, Image2DArray, Image3D, Unsupported }; enum class StorageImageOverlap : uint8_t { None, RetireSampled, PageNeighbor, Unsupported }; enum class HostWriteOverlap : uint8_t { None, InvalidateImage, Unsupported }; @@ -699,10 +699,11 @@ SelectDepthTransitionSource(bool depth_load_clear, bool sampled_native_available sampled.base_array == 0; } -[[nodiscard]] inline StorageSampledOverlap -ClassifyStorageSampledOverlap(const ImageInfo& requested, const ImageInfo& cached, - vk::Format requested_view_format, vk::Format cached_image_format, - bool cached_gpu_modified, bool cached_cpu_dirty, bool same_context) { +[[nodiscard]] inline StorageSampledOverlap ClassifyStorageSampledOverlap( + const ImageInfo& requested, const ImageInfo& cached, vk::Format requested_view_format, + vk::Format cached_image_format, bool cached_gpu_modified, bool cached_cpu_dirty, + bool same_context, bool exact_mip_subresource = false, bool cached_buffer_modified = false, + bool tracker_gpu_modified = true) { if (!ImagePageRangesOverlap(requested.address, requested.size, cached.address, cached.size)) { return StorageSampledOverlap::None; } @@ -717,10 +718,15 @@ ClassifyStorageSampledOverlap(const ImageInfo& requested, const ImageInfo& cache (requested.format == cached.format && requested_view_format == cached_image_format) || IsRgba8SrgbReinterpretation(cached_image_format, requested_view_format) || IsR32UintFloatReinterpretation(cached_image_format, requested_view_format); - return same_backing && compatible_format && cached_gpu_modified && !cached_cpu_dirty && - same_context - ? StorageSampledOverlap::ExactImage - : StorageSampledOverlap::Unsupported; + if (same_backing && compatible_format && cached_gpu_modified && !cached_cpu_dirty && + same_context) { + return StorageSampledOverlap::ExactImage; + } + if (exact_mip_subresource && cached_gpu_modified && tracker_gpu_modified && + !cached_buffer_modified && !cached_cpu_dirty && same_context) { + return StorageSampledOverlap::RetireStorage; + } + return StorageSampledOverlap::Unsupported; } [[nodiscard]] inline HostWriteOverlap diff --git a/src/graphics/host_gpu/renderer/renderCompute.cpp b/src/graphics/host_gpu/renderer/renderCompute.cpp index e2e33838d..5839b918e 100644 --- a/src/graphics/host_gpu/renderer/renderCompute.cpp +++ b/src/graphics/host_gpu/renderer/renderCompute.cpp @@ -162,13 +162,14 @@ static bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input, cons if (resources.buffers.size() != program.info.buffers.size()) { EXIT("compute runtime buffer count does not match shader metadata\n"); } - const auto& z = ctx.GetDepthRenderTarget(); - const uint64_t meta_addr = z.htile_data_base_addr; - auto* cache = g_render_ctx->GetTextureCache(); - uint32_t current_references = 0; - uint32_t registered_writes = 0; - uint64_t described_meta_size = 0; - HtileClearTarget registered_target {}; + const auto& z = ctx.GetDepthRenderTarget(); + const uint64_t meta_addr = z.htile_data_base_addr; + auto* cache = g_render_ctx->GetTextureCache(); + uint32_t current_references = 0; + uint32_t registered_writes = 0; + uint64_t described_meta_size = 0; + HtileClearTarget registered_target {}; + TextureCache::MetaRangeInfo registered_meta {}; for (uint32_t i = 0; i < program.info.buffers.size(); i++) { const auto& resource = program.info.buffers[i]; const auto descriptor = DecodeNativeDescriptor(resources.buffers[i]); @@ -179,9 +180,12 @@ static bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input, cons } // An exact registered metadata range remains // identifiable even when it is no longer the currently bound depth target. - if (resource.written && cache->IsMetaRange(descriptor.Base48(), descriptor_size)) { + TextureCache::MetaRangeInfo resolved_meta {}; + if (resource.written && + cache->ResolveMetaRange(descriptor.Base48(), descriptor_size, &resolved_meta)) { registered_writes++; registered_target = {.address = descriptor.Base48(), .size = descriptor_size}; + registered_meta = resolved_meta; } } if (current_references == 0 && registered_writes == 0) { @@ -207,6 +211,9 @@ static bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input, cons z.z_info.num_samples); } cache->RegisterMeta(g_render_ctx->GetGraphicCtx(), target.address, target.size); + if (!cache->ResolveMetaRange(target.address, target.size, ®istered_meta)) { + EXIT("failed to resolve registered HTile compute-clear range\n"); + } } else { target = registered_target; } @@ -249,7 +256,10 @@ static bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input, cons metadata_writes); } ValidateFullHtileClearDispatch(input, metadata_descriptor, group_x, group_y, group_z, mode); - if (!cache->ClearMeta(target.address)) { + const bool recorded = registered_meta.full ? cache->ClearMeta(registered_meta.metadata_address) + : cache->TouchMeta(registered_meta.metadata_address, + registered_meta.slice, true); + if (!recorded) { EXIT("failed to record HTile compute clear\n"); } return true; diff --git a/src/graphics/host_gpu/renderer/textureCache.cpp b/src/graphics/host_gpu/renderer/textureCache.cpp index 403d48fce..4449e5131 100644 --- a/src/graphics/host_gpu/renderer/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/textureCache.cpp @@ -113,6 +113,80 @@ ImageRangeOverlap ClassifyImageRangeOverlap(uint64_t left, uint64_t left_size, u } // namespace +bool IsExactRenderTargetMipStorage(const ImageInfo& sampled, const ImageInfo& storage, + vk::Format sampled_view_format, + vk::Format storage_image_format) noexcept { + const auto render_target = Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget); + const auto image_2d = Prospero::GpuEnumValue(Prospero::ImageType::kColor2D); + if (sampled.address == 0 || sampled.size == 0 || sampled.width == 0 || sampled.height == 0 || + (sampled.address & 0xffffu) != 0 || sampled.levels <= 1 || sampled.levels > 16 || + sampled.base_level != 0 || sampled.view_levels != sampled.levels || + sampled.tile != render_target || sampled.depth != 1 || sampled.type != image_2d || + sampled.base_array != 0 || storage.address == 0 || storage.size == 0 || + (storage.address & 0xffffu) != 0 || storage.width == 0 || storage.height == 0 || + storage.base_level != 0 || storage.levels != 1 || storage.view_levels != 1 || + storage.tile != render_target || storage.depth != 1 || storage.type != image_2d || + storage.base_array != 0 || sampled.format != storage.format || + sampled_view_format != storage_image_format) { + return false; + } + const auto bytes_per_element = Prospero::NumBytesPerElement(sampled.format); + if (bytes_per_element == 0) { + return false; + } + TileSizeAlign sampled_layout {}; + std::array level_layouts {}; + std::array level_padded {}; + if (!TileGetRenderTargetMipLayout(sampled.width, sampled.height, sampled.pitch, + bytes_per_element, sampled.levels, &sampled_layout, + level_layouts.data(), level_padded.data()) || + sampled_layout.align != 65536 || sampled_layout.size != sampled.size) { + return false; + } + TileSizeAlign storage_layout {}; + if (!TileGetRenderTargetSize(storage.width, storage.height, storage.pitch, bytes_per_element, + &storage_layout) || + storage_layout.align != 65536 || storage_layout.size != storage.size) { + return false; + } + for (uint32_t level = 0; level < sampled.levels; level++) { + const uint64_t divisor = 1ull << level; + const auto level_width = + static_cast((static_cast(sampled.width) + divisor - 1) / divisor); + const auto level_height = + static_cast((static_cast(sampled.height) + divisor - 1) / divisor); + const auto& layout = level_layouts[level]; + const bool dedicated_allocation = layout.size == layout.src_size && + layout.offset == layout.src_offset && layout.x == 0 && + layout.y == 0; + const auto& padded = level_padded[level]; + const bool packed_tail_allocation = + layout.src_size == 65536 && !dedicated_allocation && padded.width != 0 && + padded.height != 0 && storage.width == padded.width && + storage.height == padded.height && storage.pitch == padded.width && + storage.size == layout.src_size && storage_layout.size == layout.src_size && + layout.src_offset <= sampled.size && + layout.src_size <= sampled.size - layout.src_offset && + sampled.address <= UINT64_MAX - layout.src_offset && + storage.address == sampled.address + layout.src_offset; + if (packed_tail_allocation) { + // A PS5 mip tail is one physical thin-64 KiB tile. The storage descriptor exposes + // that complete padded block so its shader can address the packed mip locations. + return true; + } + if (!dedicated_allocation || storage.width != level_width || + storage.height != level_height || + storage.pitch != TileGetRenderTargetPitch(level_width, bytes_per_element) || + storage.size != layout.size || storage_layout.size != layout.size || + sampled.address > UINT64_MAX - layout.offset || + storage.address != sampled.address + layout.offset) { + continue; + } + return true; + } + return false; +} + struct TextureCache::CachedImage { enum class Kind { Texture, @@ -568,9 +642,20 @@ struct TextureCache::ReadbackWorker { !storage || (single_layer_storage && cached.info.base_level == 0 && cached.info.levels == 1 && cached.info.base_array == 0 && (linear || tiled_storage)); - const auto layers = target ? cached.target.layers : 1u; + const auto layers = target ? cached.target.layers : 1u; + TileSizeAlign target_mip_layout {}; + const bool target_mip_chain = + target && info.levels > 1 && layers == 1 && tiled_target && + TileGetRenderTargetMipLayout(info.width, info.height, info.pitch, + info.bytes_per_element, info.levels, &target_mip_layout, + nullptr, nullptr) && + target_mip_layout.align == 65536 && target_mip_layout.size == info.size && + cached.image != nullptr && cached.image->format == info.format && + cached.image->extent.width == info.width && + cached.image->extent.height == info.height && cached.image->layers == 1 && + cached.image->mip_levels == info.levels && cached.image->samples == 1; if (info.samples != 1 || (!linear && !tiled_target && !tiled_storage) || !basic_storage || - info.levels != 1 || info.size > UINT32_MAX) { + (info.levels != 1 && !target_mip_chain) || layers == 0 || info.size > UINT32_MAX) { EXIT("TextureCache: unsupported color-image readback layout, addr=0x%016" PRIx64 " size=0x%016" PRIx64 " extent=%ux%u pitch=%u bpe=%u levels=%u samples=%u tile=%u kind=%u\n", @@ -589,11 +674,49 @@ struct TextureCache::ReadbackWorker { } download.resize(info.size); std::fill(download.begin(), download.end(), 0); - const auto regions = Transfer::MakeLayeredImageBufferCopies(layers, slice_size, info.pitch, - info.width, info.height); + std::vector regions; + if (target_mip_chain) { + const auto format = ImageOps::RenderTargetTransferFormat(info.bytes_per_element); + auto layout = TextureCalcUploadLayout(format, info.width, info.height, info.levels, 1, + info.pitch, info.tile_mode, info.size, false, + false, false, "RenderTargetReadback"); + if (!layout.fmt_tiled_render_target || layout.pitch != info.pitch) { + EXIT("TextureCache: inconsistent render-target readback layout, addr=0x%016" PRIx64 + " size=0x%016" PRIx64 " pitch=%u/%u levels=%u\n", + info.address, info.size, info.pitch, layout.pitch, info.levels); + } + const auto uploads = TextureBuildUploadRegions( + layout, info.format, info.width, info.height, 1, info.levels, true, false, + TextureUploadDestination::MipLevels, TextureUploadSliceLayout::MipChainPerSlice); + regions.reserve(uploads.size()); + for (const auto& upload: uploads) { + regions.push_back({upload.offset, upload.pitch, upload.dst_level, upload.width, + upload.height, upload.copy_height, upload.dst_layer, + upload.dst_x, upload.dst_y, upload.dst_z, upload.aspect}); + } + } else { + regions = Transfer::MakeLayeredImageBufferCopies(layers, slice_size, info.pitch, + info.width, info.height); + } Transfer::DownloadImage(cached.ctx, download.data(), info.size, regions, cached.image, cached.image->layout); - if (tiled_target || tiled_storage) { + if (target_mip_chain) { + guest.resize(info.size); + ImageInfo layout {}; + layout.address = info.address; + layout.size = info.size; + layout.format = ImageOps::RenderTargetTransferFormat(info.bytes_per_element); + layout.width = info.width; + layout.height = info.height; + layout.pitch = info.pitch; + layout.levels = info.levels; + layout.view_levels = info.levels; + layout.tile = info.tile_mode; + layout.depth = 1; + layout.type = Prospero::GpuEnumValue(Prospero::ImageType::kColor2D); + cache.m_tiler.TileImage(guest.data(), download.data(), layout); + Libs::LibKernel::Memory::WriteBacking(info.address, guest.data(), info.size); + } else if (tiled_target || tiled_storage) { guest.resize(info.size); const RenderTargetInfo layout = target ? cached.target : RenderTargetInfo {info.address, @@ -1170,7 +1293,10 @@ void TextureCache::ResolveStorageImageOverlaps(GraphicContext* ctx, const ImageI tracker_gpu, cached->buffer_modified); } } - RequireRetirementIsolation(retire, "storage neighbor", requested.address, requested.size); + // Every exact byte overlap was classified above: only clean sampled images are retired and all + // retained byte aliases remain fatal. A retired full mip chain can still contain a cached, + // byte-disjoint subresource outside this storage request. The multi-owner index keeps those + // retained pages tracked and UnregisterImageLocked releases only pages whose final owner left. for (auto* cached: retire) { if (!cached->gpu_modified) { continue; @@ -1276,14 +1402,22 @@ VulkanImage* TextureCache::FindTexture(CommandBuffer* command, GraphicContext* c info.address, info.size, metadata_read); } std::shared_ptr storage_match; + std::vector storage_retire; const auto requested_view_format = TextureGetFormat(info.format); for (auto& cached: m_images) { if (cached->kind != CachedImage::Kind::StorageTexture) { continue; } - switch (ClassifyStorageSampledOverlap(info, cached->info, requested_view_format, - cached->image->format, cached->gpu_modified, - cached->info.IsCpuDirty(), cached->ctx == ctx)) { + const bool tracker_gpu = + m_memory_tracker.IsRegionGpuModified(cached->info.address, cached->info.size); + const bool cpu_dirty = + cached->info.IsCpuDirty() || + m_memory_tracker.IsRegionCpuModified(cached->info.address, cached->info.size); + const bool exact_mip = IsExactRenderTargetMipStorage( + info, cached->info, requested_view_format, cached->image->format); + switch (ClassifyStorageSampledOverlap( + info, cached->info, requested_view_format, cached->image->format, cached->gpu_modified, + cpu_dirty, cached->ctx == ctx, exact_mip, cached->buffer_modified, tracker_gpu)) { case StorageSampledOverlap::None: break; case StorageSampledOverlap::ExactImage: if (storage_match != nullptr) { @@ -1293,24 +1427,35 @@ VulkanImage* TextureCache::FindTexture(CommandBuffer* command, GraphicContext* c } storage_match = cached; break; + case StorageSampledOverlap::RetireStorage: + storage_retire.push_back(cached.get()); + break; case StorageSampledOverlap::Unsupported: EXIT("TextureCache: unsupported sampled/storage image alias, " "requested=0x%016" PRIx64 "+0x%016" PRIx64 " storage=0x%016" PRIx64 - "+0x%016" PRIx64 " gpu_modified=%d cpu_dirty=%d same_context=%d" + "+0x%016" PRIx64 + " gpu_modified=%d buffer_modified=%d tracker_gpu=%d cpu_dirty=%d" + " same_context=%d exact_mip=%d" " requested_info={format=%u extent=%ux%ux%u pitch=%u base=%u levels=%u" " view_levels=%u tile=%u swizzle=0x%03x type=%u base_array=%u}" " storage_info={format=%u extent=%ux%ux%u pitch=%u base=%u levels=%u" " view_levels=%u tile=%u swizzle=0x%03x type=%u base_array=%u}\n", info.address, info.size, cached->info.address, cached->info.size, - cached->gpu_modified, cached->info.IsCpuDirty(), cached->ctx == ctx, - info.format, info.width, info.height, info.depth, info.pitch, info.base_level, - info.levels, info.view_levels, info.tile, info.swizzle, info.type, - info.base_array, cached->info.format, cached->info.width, cached->info.height, - cached->info.depth, cached->info.pitch, cached->info.base_level, - cached->info.levels, cached->info.view_levels, cached->info.tile, - cached->info.swizzle, cached->info.type, cached->info.base_array); + cached->gpu_modified, cached->buffer_modified, tracker_gpu, cpu_dirty, + cached->ctx == ctx, exact_mip, info.format, info.width, info.height, + info.depth, info.pitch, info.base_level, info.levels, info.view_levels, + info.tile, info.swizzle, info.type, info.base_array, cached->info.format, + cached->info.width, cached->info.height, cached->info.depth, + cached->info.pitch, cached->info.base_level, cached->info.levels, + cached->info.view_levels, cached->info.tile, cached->info.swizzle, + cached->info.type, cached->info.base_array); } } + if (storage_match != nullptr && !storage_retire.empty()) { + EXIT("TextureCache: sampled binding has both exact and mip storage owners, " + "addr=0x%016" PRIx64 " size=0x%016" PRIx64 " mip_owners=%zu\n", + info.address, info.size, storage_retire.size()); + } if (storage_match != nullptr) { if (m_memory_tracker.IsRegionCpuModified(info.address, info.size) || !m_memory_tracker.IsRegionGpuModified(info.address, info.size) || @@ -1323,6 +1468,30 @@ VulkanImage* TextureCache::FindTexture(CommandBuffer* command, GraphicContext* c command->RetainResourceUntilFence(storage_match); return storage_match->image; } + if (!storage_retire.empty()) { + // shadPS4 resolves a modified cached mip before replacing it with the containing image. + // Kyty does not yet copy between those independently allocated Vulkan images, so use the + // existing synchronized tiled readback seam and rebuild the complete chain from coherent + // guest backing. This is an uncommon ownership transition, not a frame lookup fast path. + Transfer::WaitForGraphicsIdle(ctx); + for (auto* cached: storage_retire) { + if (!cached->gpu_modified || cached->buffer_modified || cached->info.IsCpuDirty() || + !m_memory_tracker.IsRegionGpuModified(cached->info.address, cached->info.size) || + m_memory_tracker.IsRegionCpuModified(cached->info.address, cached->info.size)) { + EXIT("TextureCache: sampled mip-storage ownership changed during transition, " + "addr=0x%016" PRIx64 " size=0x%016" PRIx64 " gpu=%d buffer=%d dirty=%d\n", + cached->info.address, cached->info.size, cached->gpu_modified, + cached->buffer_modified, cached->info.IsCpuDirty()); + } + const auto transfer = m_readback->DownloadColorImage(*cached); + for (const auto& range: transfer.Ranges()) { + m_memory_tracker.ForEachDownloadRange(range.address, range.size, + [](uint64_t, uint64_t) noexcept {}); + } + cached->gpu_modified = false; + } + RetireImages(storage_retire); + } if (m_memory_tracker.IsRegionCpuModified(info.address, info.size)) { MarkSampledAliasesCpuDirtyLocked(info.address, info.size); } @@ -3141,6 +3310,46 @@ TextureCache::RegionInfo TextureCache::QueryRegion(uint64_t vaddr, uint64_t size return result; } +bool TextureCache::ResolveMetaRange(uint64_t vaddr, uint64_t size, MetaRangeInfo* info) { + if (info == nullptr || vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE || + size > TRACKER_ADDRESS_SIZE - vaddr) { + return false; + } + FaultSafeTextureLock lock(this, m_lock); + MetaRangeInfo found {}; + bool matched = false; + for (const auto& [address, metadata]: m_surface_metas) { + const auto slice_size = metadata.size / metadata.layers; + const bool full = vaddr == address && size == metadata.size; + const auto offset = vaddr >= address ? vaddr - address : UINT64_MAX; + const bool slice = + !full && size == slice_size && offset < metadata.size && offset % slice_size == 0; + if (!full && !slice) { + continue; + } + MetaRangeInfo candidate {.metadata_address = address, + .metadata_size = metadata.size, + .slice = full ? 0u : static_cast(offset / slice_size), + .full = full}; + if (matched && (candidate.metadata_address != found.metadata_address || + candidate.metadata_size != found.metadata_size || + candidate.slice != found.slice || candidate.full != found.full)) { + EXIT("TextureCache: ambiguous exact metadata range, request=0x%016" PRIx64 + "+0x%016" PRIx64 " first=0x%016" PRIx64 "+0x%016" PRIx64 + " slice=%u full=%d second=0x%016" PRIx64 "+0x%016" PRIx64 " slice=%u full=%d\n", + vaddr, size, found.metadata_address, found.metadata_size, found.slice, found.full, + candidate.metadata_address, candidate.metadata_size, candidate.slice, + candidate.full); + } + found = candidate; + matched = true; + } + if (matched) { + *info = found; + } + return matched; +} + void TextureCache::RegisterMeta(GraphicContext* ctx, uint64_t vaddr, uint64_t size, uint32_t layers) { if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE || diff --git a/src/graphics/host_gpu/renderer/textureCache.h b/src/graphics/host_gpu/renderer/textureCache.h index d0e8f5338..bd1e1c848 100644 --- a/src/graphics/host_gpu/renderer/textureCache.h +++ b/src/graphics/host_gpu/renderer/textureCache.h @@ -31,6 +31,10 @@ class CommandBuffer; class DummyTextureCache; class ResourceMutex; +[[nodiscard]] bool IsExactRenderTargetMipStorage(const ImageInfo& sampled, const ImageInfo& storage, + vk::Format sampled_view_format, + vk::Format storage_image_format) noexcept; + class TextureCache { public: struct RegionInfo { @@ -42,6 +46,12 @@ public: bool metadata_bytes = false; bool gpu_metadata_bytes = false; }; + struct MetaRangeInfo { + uint64_t metadata_address = 0; + uint64_t metadata_size = 0; + uint32_t slice = 0; + bool full = false; + }; TextureCache(PageManager& page_manager, BufferCache& buffer_cache, ResourceMutex& resource_mutex); @@ -101,6 +111,7 @@ public: FindDepthTargetByRange(CommandBuffer* command, uint64_t vaddr, uint64_t size, bool allow_containing_sampled = false); [[nodiscard]] RegionInfo QueryRegion(uint64_t vaddr, uint64_t size); + [[nodiscard]] bool ResolveMetaRange(uint64_t vaddr, uint64_t size, MetaRangeInfo* info); void RegisterMeta(GraphicContext* ctx, uint64_t vaddr, uint64_t size, uint32_t layers = 1); [[nodiscard]] bool IsMeta(uint64_t vaddr); [[nodiscard]] bool IsMetaRange(uint64_t vaddr, uint64_t size); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 9b4f43148..772425b9f 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -9690,6 +9690,51 @@ void CheckSampledDepthDescriptor() { (descriptor.fields[3] & ~(0xfu << 28u)) | (Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) << 28u); + ShaderTextureResource cube_descriptor{{0x01267d00u, 0xc0700000u, 0x00ffc0ffu, + 0xb1800924u, 0x00000005u, 0x00700000u, + 0x00000000u, 0x00000000u}}; + DepthStencilVulkanImage cube_image; + cube_image.extent = {1024, 1024}; + cube_image.guest_pitch = 1024; + cube_image.layers = 6; + cube_image.format = vk::Format::eD32Sfloat; + ShaderRecompiler::IR::ImageResource cube_resource{}; + cube_resource.kind = ShaderRecompiler::IR::ResourceKind::Image; + cube_resource.dimension = + ShaderRecompiler::Decoder::ImageDimension::Dim2DArray; + cube_resource.read = true; + cube_resource.depth_compare = true; + const auto cube_view = ResolveTargetTextureView( + cube_resource, Prospero::ImageType::kCube, 0, cube_image.layers); + Require("SampledDepthDescriptor", "PPSA06084 six-face depth cubemap", + IsSupportedDepthTargetDescriptor(cube_descriptor, cube_image) && + IsSupportedDepthTextureEncoding(cube_descriptor) && + cube_view.type == vk::ImageViewType::e2DArray && + cube_view.base_layer == 0 && cube_view.layer_count == 6, + "captured depth cubemap did not resolve to its layered target view"); + auto partial_cube = cube_descriptor; + partial_cube.fields[4] = 4; + auto based_cube = cube_descriptor; + based_cube.fields[4] |= 1u << 16u; + auto reserved_cube = cube_descriptor; + reserved_cube.fields[4] |= 1u << 13u; + auto non_square_cube = cube_descriptor; + non_square_cube.fields[2] = + (non_square_cube.fields[2] & ~(0x3fffu << 14u)) | (511u << 14u); + DepthStencilVulkanImage non_square_image; + non_square_image.extent = cube_image.extent; + non_square_image.guest_pitch = cube_image.guest_pitch; + non_square_image.layers = cube_image.layers; + non_square_image.format = cube_image.format; + non_square_image.extent.height = 512; + Require( + "SampledDepthDescriptor", "cubemap hard guards", + !IsSupportedDepthTargetDescriptor(partial_cube, cube_image) && + !IsSupportedDepthTargetDescriptor(based_cube, cube_image) && + !IsSupportedDepthTextureEncoding(reserved_cube) && + !IsSupportedDepthTargetDescriptor(non_square_cube, non_square_image), + "partial, based, non-square, or reserved-bit depth cubemap was accepted"); + image.guest_pitch = 1344; Require("SampledDepthDescriptor", "target pitch mismatch", !IsSupportedDepthTargetDescriptor(descriptor, image), @@ -10712,6 +10757,77 @@ void CheckRenderTargetTileRoundTrip() { regions[2].src_layer == 2 && regions[2].pitch == pitch && regions[2].width == width && regions[2].height == height, "layered image-buffer regions did not preserve slice offsets"); + + constexpr uint32_t mip_format = + Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float); + ImageInfo mip_info{}; + mip_info.address = 0x10e3d0000ull; + mip_info.width = 512; + mip_info.height = 512; + mip_info.pitch = TileGetRenderTargetPitch(mip_info.width, 8); + mip_info.levels = 10; + mip_info.view_levels = mip_info.levels; + mip_info.tile = Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget); + mip_info.depth = 1; + mip_info.type = Prospero::GpuEnumValue(Prospero::ImageType::kColor2D); + mip_info.format = mip_format; + TileSizeAlign mip_storage{}; + Require("RenderTargetTileRoundTrip", "PPSA06084 mip layout", + mip_info.pitch == 512 && + TileGetRenderTargetMipLayout(mip_info.width, mip_info.height, + mip_info.pitch, 8, mip_info.levels, + &mip_storage, nullptr, nullptr) && + mip_storage.align == 0x10000 && mip_storage.size == 0x2b0000, + "captured render-target mip-chain layout regressed"); + mip_info.size = mip_storage.size; + const auto mip_layout = TextureCalcUploadLayout( + mip_info.format, mip_info.width, mip_info.height, mip_info.levels, + mip_info.depth, mip_info.pitch, mip_info.tile, mip_info.size, false, + false, false, "RenderTargetMipReadbackTest"); + const auto mip_regions = TextureBuildUploadRegions( + mip_layout, vk::Format::eR16G16B16A16Sfloat, mip_info.width, + mip_info.height, mip_info.depth, mip_info.levels, true, false, + TextureUploadDestination::MipLevels, + TextureUploadSliceLayout::MipChainPerSlice); + Require( + "RenderTargetTileRoundTrip", "PPSA06084 mip regions", + mip_regions.size() == mip_info.levels && + mip_layout.fmt_tiled_render_target && + mip_layout.pitch == mip_info.pitch, + "captured render-target mip-chain did not produce exact subresources"); + std::vector mip_linear(mip_info.size, 0); + for (uint32_t level = 0; level < mip_info.levels; level++) { + const auto ®ion = mip_regions[level]; + for (uint32_t y = 0; y < region.height; y++) { + auto *row = mip_linear.data() + region.offset + + static_cast(y) * region.pitch * 8u; + for (uint32_t x = 0; x < region.width * 8u; x++) { + row[x] = + static_cast((level * 47u + y * 19u + x * 11u) & 0xffu); + } + } + } + std::vector mip_guest(mip_info.size, 0xcd); + tiler.TileImage(mip_guest.data(), mip_linear.data(), mip_info); + std::vector mip_restored(mip_info.size, 0); + for (uint32_t level = 0; level < mip_info.levels; level++) { + const auto ®ion = mip_regions[level]; + const auto &level_size = mip_layout.level_sizes[level]; + const auto guest_offset = + level_size.src_size != 0 ? level_size.src_offset : level_size.offset; + TileConvertTiledToLinearRenderTarget( + mip_restored.data() + region.offset, mip_guest.data() + guest_offset, + region.width, region.height, region.pitch, 8u, level_size.size, + level_size.src_size, level_size.x, level_size.y); + for (uint32_t y = 0; y < region.height; y++) { + const auto row = + region.offset + static_cast(y) * region.pitch * 8u; + Require("RenderTargetTileRoundTrip", "PPSA06084 mip contents", + std::memcmp(mip_linear.data() + row, mip_restored.data() + row, + region.width * 8u) == 0, + "render-target mip readback conversion did not round-trip"); + } + } std::printf("[host] %-32s ok\n", "RenderTargetTileRoundTrip"); } @@ -11090,6 +11206,91 @@ void CheckStorageTextureSampledReuse() { true) == StorageSampledOverlap::None, "disjoint storage image was classified as an alias"); + ImageInfo mip_chain{}; + mip_chain.address = 0x10eb50000ull; + mip_chain.size = 0x2b0000; + mip_chain.format = + Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float); + mip_chain.width = 512; + mip_chain.height = 512; + mip_chain.pitch = 512; + mip_chain.levels = 10; + mip_chain.view_levels = 10; + mip_chain.tile = Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget); + mip_chain.swizzle = DstSel(4, 5, 6, 7); + mip_chain.type = image_2d; + auto mip_storage = mip_chain; + mip_storage.address = mip_chain.address + 0x30000; + mip_storage.size = 0x80000; + mip_storage.width = 256; + mip_storage.height = 256; + mip_storage.pitch = 256; + mip_storage.levels = 1; + mip_storage.view_levels = 1; + Require("StorageTextureSampledReuse", "captured mip transition", + IsExactRenderTargetMipStorage(mip_chain, mip_storage, + vk::Format::eR16G16B16A16Sfloat, + vk::Format::eR16G16B16A16Sfloat) && + ClassifyStorageSampledOverlap( + mip_chain, mip_storage, vk::Format::eR16G16B16A16Sfloat, + vk::Format::eR16G16B16A16Sfloat, true, false, true, true, + false, true) == StorageSampledOverlap::RetireStorage, + "captured GPU-owned level-1 storage allocation was not materialized " + "before " + "rebuilding its sampled chain"); + auto tail_storage = mip_chain; + tail_storage.size = 0x10000; + tail_storage.width = 128; + tail_storage.height = 64; + tail_storage.pitch = 128; + tail_storage.levels = 1; + tail_storage.view_levels = 1; + Require("StorageTextureSampledReuse", "captured mip-tail transition", + IsExactRenderTargetMipStorage(mip_chain, tail_storage, + vk::Format::eR16G16B16A16Sfloat, + vk::Format::eR16G16B16A16Sfloat) && + ClassifyStorageSampledOverlap( + mip_chain, tail_storage, vk::Format::eR16G16B16A16Sfloat, + vk::Format::eR16G16B16A16Sfloat, true, false, true, true, + false, true) == StorageSampledOverlap::RetireStorage, + "captured GPU-owned physical mip-tail block was not materialized " + "before rebuilding its sampled chain"); + auto malformed_mip = mip_storage; + malformed_mip.address += 0x10000; + auto malformed_tail = tail_storage; + malformed_tail.width = 64; + auto misaligned_chain = mip_chain; + misaligned_chain.address += 0x1000; + auto misaligned_tail = tail_storage; + misaligned_tail.address += 0x1000; + Require("StorageTextureSampledReuse", "mip transition guards", + !IsExactRenderTargetMipStorage(mip_chain, malformed_mip, + vk::Format::eR16G16B16A16Sfloat, + vk::Format::eR16G16B16A16Sfloat) && + !IsExactRenderTargetMipStorage(mip_chain, malformed_tail, + vk::Format::eR16G16B16A16Sfloat, + vk::Format::eR16G16B16A16Sfloat) && + !IsExactRenderTargetMipStorage(misaligned_chain, misaligned_tail, + vk::Format::eR16G16B16A16Sfloat, + vk::Format::eR16G16B16A16Sfloat) && + ClassifyStorageSampledOverlap( + mip_chain, mip_storage, vk::Format::eR16G16B16A16Sfloat, + vk::Format::eR16G16B16A16Sfloat, true, false, true, true, + true, true) == StorageSampledOverlap::Unsupported && + ClassifyStorageSampledOverlap( + mip_chain, mip_storage, vk::Format::eR16G16B16A16Sfloat, + vk::Format::eR16G16B16A16Sfloat, true, false, true, true, + false, false) == StorageSampledOverlap::Unsupported && + ClassifyStorageSampledOverlap( + mip_chain, mip_storage, vk::Format::eR16G16B16A16Sfloat, + vk::Format::eR16G16B16A16Sfloat, true, true, true, true, + false, true) == StorageSampledOverlap::Unsupported && + ClassifyStorageSampledOverlap( + mip_chain, mip_storage, vk::Format::eR16G16B16A16Sfloat, + vk::Format::eR16G16B16A16Sfloat, true, false, false, true, + false, true) == StorageSampledOverlap::Unsupported, + "malformed or unsafe mip-storage ownership was accepted"); + ImageInfo ppsa02604_storage{}; ppsa02604_storage.address = 0x7c690000; ppsa02604_storage.size = 0x870000; @@ -11358,6 +11559,11 @@ ShaderTextureResource BasicMetadataReuseDescriptor() { 0x00700000u, 0x00000000u, 0x00000000u}}; } +ShaderTextureResource Ppsa06084MetadataReuseDescriptor() { + return {{0x00722290u, 0xc0100000u, 0x000fc00fu, 0x90560800u, 0x00000000u, + 0x00700060u, 0x00000000u, 0x00000000u}}; +} + [[noreturn]] void RunMetadataDescriptorDeathCase(const char *kind) { auto resource = BasicMetadataReuseResource(); auto descriptor = BasicMetadataReuseDescriptor(); @@ -11375,6 +11581,14 @@ ShaderTextureResource BasicMetadataReuseDescriptor() { descriptor.fields[1] = (descriptor.fields[1] & ~0x1ff00000u) | (Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UInt) << 20u); + } else if (std::strcmp(kind, "invalid-swizzle") == 0) { + descriptor = Ppsa06084MetadataReuseDescriptor(); + descriptor.fields[3] = + (descriptor.fields[3] & ~0xfffu) | DstSel(2, 0, 0, 4); + } else if (std::strcmp(kind, "mip-view-outside-allocation") == 0) { + descriptor = Ppsa06084MetadataReuseDescriptor(); + descriptor.fields[3] = + (descriptor.fields[3] & ~(0xfu << 16u)) | (7u << 16u); } else { std::_Exit(0x7e); } @@ -11543,6 +11757,25 @@ void CheckGpuMetadataReuse() { page_manager.OnGpuMap(base, allocation_size); texture_cache.RegisterMeta(nullptr, base, metadata_size, layers); + TextureCache::MetaRangeInfo full_meta{}; + TextureCache::MetaRangeInfo slice_meta{}; + TextureCache::MetaRangeInfo invalid_meta{}; + constexpr uint64_t slice_size = metadata_size / layers; + Require("GpuMetadataReuse", "exact metadata ranges", + texture_cache.ResolveMetaRange(base, metadata_size, &full_meta) && + full_meta.metadata_address == base && + full_meta.metadata_size == metadata_size && full_meta.full && + full_meta.slice == 0 && + texture_cache.ResolveMetaRange(base + slice_size, slice_size, + &slice_meta) && + slice_meta.metadata_address == base && + slice_meta.metadata_size == metadata_size && !slice_meta.full && + slice_meta.slice == 1 && + !texture_cache.ResolveMetaRange(base + 0x1000, slice_size, + &invalid_meta) && + !texture_cache.ResolveMetaRange(base + slice_size, slice_size / 2, + &invalid_meta), + "whole and per-slice metadata ranges were not classified exactly"); Require("GpuMetadataReuse", "clear", texture_cache.ClearMeta(base), "metadata clear setup failed"); const bool full_image_transition = @@ -11583,13 +11816,27 @@ void CheckGpuMetadataReuse() { void CheckMetadataReuseDescriptors() { ValidateMetadataReuseTexture(BasicMetadataReuseResource(), BasicMetadataReuseDescriptor(), 0x10000); + const auto captured = Ppsa06084MetadataReuseDescriptor(); + ValidateMetadataReuseTexture(BasicMetadataReuseResource(), captured, 0x2000); + Require("MetadataReuseDescriptor", "PPSA06084 sampled mip chain", + captured.Format() == + Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm) && + captured.Type() == + Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) && + captured.BaseLevel() == 0 && captured.LastLevel() == 6 && + captured.MaxMip() == 6 && + captured.TileMode() == + Prospero::GpuEnumValue(Prospero::TileMode::kStandard4KB) && + captured.DstSelXYZW() == DstSel(0, 0, 0, 4), + "captured metadata-reuse descriptor fields decoded unexpectedly"); char path[MAX_PATH]{}; Require("MetadataReuseDescriptor", "host", GetModuleFileNameA(nullptr, path, MAX_PATH) != 0, "GetModuleFileName failed"); for (const char *kind : {"field1-reserved", "field2-low-reserved", "field2-high-reserved", - "unsupported-format", "uint-format"}) { + "unsupported-format", "uint-format", "invalid-swizzle", + "mip-view-outside-allocation"}) { std::string command = std::string("\"") + path + "\" --metadata-descriptor-death " + kind; std::vector mutable_command(command.begin(), command.end()); @@ -12766,6 +13013,40 @@ void CheckImageOverlapResolution() { retirement_conflict.Exists() && retirement_conflict.retired == 0 && retirement_conflict.retained == 1, "retiring a wide image silently untracked a retained tail alias"); + using MipOwnerIndex = MultiRangePageOwnerIndex; + constexpr uint64_t mip_chain_address = 0x110a10000ull; + constexpr uint64_t mip_chain_size = 0x2b0000; + constexpr uint64_t mip_zero_address = 0x110ac0000ull; + constexpr uint64_t mip_zero_size = 0x200000; + constexpr uint64_t mip_one_address = 0x110a40000ull; + constexpr uint64_t mip_one_size = 0x80000; + MipOwnerIndex mip_owners; + Require("ImageOverlapResolution", "PPSA06084 mip alias registration", + mip_owners.Register(1, {{mip_chain_address, mip_chain_size}}) && + mip_owners.Register(2, {{mip_zero_address, mip_zero_size}}) && + ClassifyStorageImageOverlap(mip_one_address, mip_one_size, + mip_chain_address, mip_chain_size, + true, true, false, false, false) == + StorageImageOverlap::RetireSampled && + ClassifyStorageImageOverlap(mip_one_address, mip_one_size, + mip_zero_address, mip_zero_size, true, + true, false, false, + false) == StorageImageOverlap::None, + "captured full-chain and disjoint mip owners were misclassified"); + std::vector mip_releases; + Require( + "ImageOverlapResolution", "PPSA06084 retained mip ownership", + mip_owners.Unregister(1, mip_releases) && mip_releases.size() == 1 && + mip_releases[0].address == mip_chain_address && + mip_releases[0].size == mip_zero_address - mip_chain_address && + mip_owners.Query(mip_zero_address, mip_zero_size) == + std::vector{2} && + mip_owners.Register(3, {{mip_one_address, mip_one_size}}) && + mip_owners.Query(mip_one_address, mip_one_size) == + std::vector{3} && + mip_owners.Query(mip_zero_address, mip_zero_size) == + std::vector{2}, + "retiring the full chain released or conflated a disjoint cached mip"); DepthTargetInfo depth{}; depth.address = sampled.address; depth.size = 0x6000;