From 8589731a2756bb6d2f2d7c51b055e4d37e686755 Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 16 Sep 2026 18:15:14 +0400 Subject: [PATCH] Preserve PS5 mip-tail views and GPU data during image expansion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related work: https://github.com/KytyPS5/KytyPS5/pull/558 Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com> --- src/graphics/guest_gpu/tile.h | 1 + .../host_gpu/renderer/cache/textureCache.cpp | 17 ++++- .../renderer/pipeline/descriptors.cpp | 70 ++++++++++++------- tests/ShaderRecompilerComputeTests.cpp | 62 ++++++++++------ 4 files changed, 101 insertions(+), 49 deletions(-) diff --git a/src/graphics/guest_gpu/tile.h b/src/graphics/guest_gpu/tile.h index 5cbbba8f2..e259dc63d 100644 --- a/src/graphics/guest_gpu/tile.h +++ b/src/graphics/guest_gpu/tile.h @@ -86,6 +86,7 @@ struct TileMipLayout { uint32_t padded_height = 0; uint32_t tail_x = 0; uint32_t tail_y = 0; + bool operator==(const TileMipLayout&) const = default; }; struct TileSurfaceLayout { diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index a863f98c9..d52dd5d76 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -208,6 +208,10 @@ bool TextureCache::SameBacking(const ImageInfo& cached, const ImageInfo& request if (cached.extent != requested.extent) { return false; } + if (cached.resources.levels < requested.resources.levels || + cached.resources.layers < requested.resources.layers) { + return false; + } if (cached.samples != requested.samples) { return false; } @@ -843,6 +847,16 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques ImageViewOps::FormatsCompatible(cached.info.pixel_format, requested.pixel_format)) { return {ExpandImage(requested, cached_id)}; } + // PS5 mip tails can expose more levels without increasing the guest allocation. + if (requested.pixel_format == cached.info.pixel_format && + requested.type == cached.info.type && requested.resources > cached.info.resources && + (requested.data.size > cached.info.data.size || + (requested.data.size == cached.info.data.size && + requested.extent == cached.info.extent && + cached.info.resources.levels > 1 && + requested.resources.layers == cached.info.resources.layers))) { + return {ExpandImage(requested, cached_id)}; + } if (requested.pixel_format != cached.info.pixel_format || requested.data.size <= cached.info.data.size) { const auto result_id = merged_id ? merged_id : cached_id; @@ -852,9 +866,6 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques ? result_id : ImageId {}}; } - if (requested.type == cached.info.type && requested.resources > cached.info.resources) { - return {ExpandImage(requested, cached_id)}; - } EXIT("TextureCache: unresolvable equal-address image overlap, address=0x%016" PRIx64 " requested=%ux%u " "cached=%ux%u requested_size=0x%016" PRIx64 " cached_size=0x%016" PRIx64 diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index d23f6edd7..4bf58663e 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -293,13 +293,8 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag const bool supported_swizzle = IsValidImageSwizzle(swizzle) && (swizzle == DstSel(4, 5, 6, 7) || !resource.read || resource.atomic); - const auto max_mip = resource.r128 ? descriptor.LastLevel() : descriptor.MaxMip(); - const auto view_last_level = - resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage - ? descriptor.LastLevel() - : std::min(descriptor.LastLevel(), max_mip); return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d) && supported_tile && - descriptor.BaseLevel() <= view_last_level && view_last_level <= max_mip && + descriptor.BaseLevel() <= descriptor.LastLevel() && descriptor.MinLod() == 0 && supported_swizzle && descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth(); } @@ -507,6 +502,21 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& return view; } +static bool TextureViewPreservesMipLayout(const TileSurfaceDescription& description, + uint32_t view_levels) { + TileSurfaceLayout physical {}; + TileSurfaceLayout view {}; + auto view_description = description; + view_description.levels = view_levels; + return TileGetTiledTextureLayout(description, physical) && + TileGetTiledTextureLayout(view_description, view) && + physical.first_tail_level == view.first_tail_level && + physical.block_slice_size == view.block_slice_size && + physical.total_size == view.total_size && + std::equal(std::begin(physical.mips), std::begin(physical.mips) + description.levels, + std::begin(view.mips)); +} + TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource, const ShaderRecompiler::IR::DescriptorValue& value) { auto descriptor = DecodeNativeDescriptor(value); @@ -523,24 +533,22 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR return {id, nullptr, std::move(desc)}; } - const auto address = descriptor.Base40(); - const auto width = static_cast(descriptor.Width5()) + 1u; - const auto height = static_cast(descriptor.Height5()) + 1u; - const auto base_level = descriptor.BaseLevel(); - const auto last_level = descriptor.LastLevel(); - const auto type = TextureType(descriptor); - const bool multisampled = IsMultisampledTexture(type); - const auto max_mip = resource.r128 ? last_level : descriptor.MaxMip(); - const auto levels = multisampled ? 1u : static_cast(max_mip) + 1u; - const bool dynamic_storage = - storage && resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage; - const auto view_last_level = - !multisampled && !dynamic_storage ? std::min(last_level, max_mip) : last_level; + const auto address = descriptor.Base40(); + const auto width = static_cast(descriptor.Width5()) + 1u; + const auto height = static_cast(descriptor.Height5()) + 1u; + const auto base_level = descriptor.BaseLevel(); + const auto last_level = descriptor.LastLevel(); + const auto type = TextureType(descriptor); + const bool multisampled = IsMultisampledTexture(type); + const auto max_mip = resource.r128 ? last_level : descriptor.MaxMip(); + const auto physical_levels = multisampled ? 1u : static_cast(max_mip) + 1u; + const auto levels = + multisampled ? 1u : std::max(physical_levels, static_cast(last_level) + 1u); const auto tile = descriptor.TileMode(); const bool depth_tile = tile == Prospero::TileMode::kDepth; const bool msaa_tile = depth_tile || tile == Prospero::TileMode::kRenderTarget; const bool msaa_array = type == Prospero::ImageType::kColor2DMsaaArray; - if ((!multisampled && (base_level > view_last_level || view_last_level >= levels)) || + if ((!multisampled && base_level > last_level) || (multisampled && (base_level != 0 || last_level == 0 || last_level > 3 || max_mip != last_level || !msaa_tile || (descriptor.MsaaDepth() && !depth_tile) || @@ -559,7 +567,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR } const auto samples = multisampled ? 1u << last_level : 1u; const auto view_levels = - multisampled ? 1u : static_cast(view_last_level - base_level) + 1u; + multisampled ? 1u : static_cast(last_level - base_level) + 1u; const auto depth = static_cast(descriptor.Depth()) + 1u; const auto format = descriptor.Format(); const auto surface_format = TextureGetSurfaceFormatInfo(format); @@ -578,7 +586,19 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR type == Prospero::ImageType::kColor2DArray || type == Prospero::ImageType::kColor2DMsaaArray; const auto image_layers = layered ? depth : 1u; - uint32_t pitch = 0; + if (levels > physical_levels) { + const TileSurfaceDescription physical { + format, tile, volume ? TileSurfaceDimension::Dim3D : TileSurfaceDimension::Dim2D, + width, height, volume ? depth : 1u, physical_levels, image_layers}; + // Texture mip views take precedence over the resource count, but must keep its storage layout. + if (!TextureViewPreservesMipLayout(physical, levels)) { + EXIT("unsupported texture mip view changes physical layout: base=%u last=%u max=%u " + "extent=%ux%ux%u tile=%u\n", + base_level, last_level, max_mip, width, height, depth, + static_cast(tile)); + } + } + uint32_t pitch = 0; TileSizeAlign size {}; if (multisampled) { const auto bytes = Prospero::NumBytesPerElement(format); @@ -591,8 +611,8 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR size.size *= image_layers; } else { pitch = TileGetTexturePitch(format, width, tile); - TileGetTextureTotalSize(format, width, height, volume ? depth : image_layers, levels, tile, - volume, size); + TileGetTextureTotalSize(format, width, height, volume ? depth : image_layers, + physical_levels, tile, volume, size); } EXIT_NOT_IMPLEMENTED(size.size == 0 || size.align == 0 || (address & (static_cast(size.align) - 1u)) != 0); @@ -629,7 +649,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR !desc.info.IsDepth()) { TileSizeAlign metadata_size {}; (void)TileGetDccSize(width, height, volume ? depth : image_layers, - desc.info.bytes_per_block, levels, tile, metadata_size, + desc.info.bytes_per_block, physical_levels, tile, metadata_size, std::countr_zero(samples)); desc.info.metadata.kind = ImageMetadataKind::Dcc; desc.info.metadata.range = {descriptor.MetaAddr() << 8u, metadata_size.size}; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index e17954da0..5f6fa7692 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -8594,7 +8594,8 @@ public: std::vector ReadCachedTexel(const char *name, RenderContext &context, ImageId id, vk::Offset3D offset = {}, - vk::Extent3D extent = {1, 1, 1}, uint32_t layer = 0) { + vk::Extent3D extent = {1, 1, 1}, uint32_t layer = 0, + uint32_t mip = 0) { auto &scheduler = context.GetCommandScheduler(); auto &image = context.GetTextureCache().GetImage(id); const auto bytes = image.info.bytes_per_block * extent.width * @@ -8607,7 +8608,7 @@ public: scheduler.Current().Handle()); vk::BufferImageCopy copy{}; copy.imageSubresource = {image.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth - : vk::ImageAspectFlagBits::eColor, 0, layer, 1}; + : vk::ImageAspectFlagBits::eColor, mip, layer, 1}; copy.imageOffset = offset; copy.imageExtent = extent; scheduler.Current().Handle().copyImageToBuffer( @@ -9602,6 +9603,9 @@ public: constexpr uint64_t allocation_alignment = 0x10000; constexpr uint64_t depth_address = base + 0x40000; constexpr uint64_t stencil_address = base + 0x70000; + constexpr uint64_t mipped_storage_address = base + 0x100000; + constexpr uint32_t tail_backing_value = 0x2468ace0u; + constexpr uint32_t tail_gpu_value = 0x13579bdfu; EnsureRuntimeContext(); int64_t direct_offset = -1; @@ -9618,6 +9622,8 @@ public: mapped == reinterpret_cast(base), "descriptor discovery fixed mapping failed"); std::memset(mapped, 0, allocation_size); + std::fill_n(reinterpret_cast(mipped_storage_address), + 0x10000 / 4, tail_backing_value); { RenderContext context(m_runtime_context); @@ -9884,17 +9890,17 @@ public: storage_descriptor.dwords.begin()); storage_descriptor.dword_count = 8; auto mipped_storage = storage; - constexpr uint64_t mipped_storage_address = base + 0x100000; const auto encoded_mipped_storage_address = mipped_storage_address >> 8u; mipped_storage.fields[0] = static_cast(encoded_mipped_storage_address); mipped_storage.fields[1] = static_cast(encoded_mipped_storage_address >> 32u) | - (static_cast(stencil_format) << 20u) | (3u << 30u); - mipped_storage.fields[2] = 1u | (7u << 14u); + (static_cast(Prospero::BufferFormat::k32UInt) << 20u) | + (3u << 30u); + mipped_storage.fields[2] = 15u | (63u << 14u); mipped_storage.fields[3] = DstSel(4, 5, 6, 7) | (1u << 12u) | (3u << 16u) | - (static_cast(linear) << 20u) | + (static_cast(Prospero::TileMode::kStandard64KB) << 20u) | (static_cast(Prospero::ImageType::kColor2D) << 28u); mipped_storage.fields[5] = 0x00700000u | (3u << 4u); ShaderRecompiler::IR::DescriptorValue mipped_storage_descriptor{}; @@ -9911,6 +9917,14 @@ public: std::end(overwide_mipped_storage.fields), overwide_mipped_storage_descriptor.dwords.begin()); overwide_mipped_storage_descriptor.dword_count = 8; +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + ExpectFatal("MipViewPhysicalLayoutChange", [&] { + auto linear_view = overwide_mipped_storage_descriptor; + linear_view.dwords[3] &= ~(0x1fu << 20u); + (void)RenderExecutorTestAccess::ResolveTexture(executor, storage_resource, + linear_view); + }); +#endif auto mipped_storage_resource = storage_resource; mipped_storage_resource.mip_mode = ShaderRecompiler::IR::ImageMipMode::DynamicStorage; @@ -9925,6 +9939,11 @@ public: auto plain_mipped_storage_binding = RenderExecutorTestAccess::ResolveTexture(executor, storage_resource, mipped_storage_descriptor); + vk::ClearValue tail_clear{}; + tail_clear.color.uint32[0] = tail_gpu_value; + TextureCacheTestAccess::ClearImage( + texture_cache, scheduler.Current(), plain_mipped_storage_binding.image_id, + {vk::ImageAspectFlagBits::eColor, 0, 4, 0, 1}, tail_clear); auto mipped_storage_binding = RenderExecutorTestAccess::ResolveTexture( executor, mipped_storage_resource, mipped_storage_descriptor); auto overwide_mipped_storage_binding = @@ -9933,6 +9952,16 @@ public: auto sampled_overwide_resolved = RenderExecutorTestAccess::ResolveTexture( executor, sampled_overwide_resource, overwide_mipped_storage_descriptor); + const auto tail0 = ReadCachedTexel(name, context, overwide_mipped_storage_binding.image_id); + const auto tail3 = ReadCachedTexel(name, context, overwide_mipped_storage_binding.image_id, + {}, {1, 1, 1}, 0, 3); + const auto tail4 = ReadCachedTexel(name, context, overwide_mipped_storage_binding.image_id, + {}, {1, 1, 1}, 0, 4); + Require(name, "expanded tail preserves GPU and backing data", + tail0 == std::vector{tail_gpu_value} && + tail3 == std::vector{tail_gpu_value} && + tail4 == std::vector{tail_backing_value}, + "expanding a shared mip tail lost GPU writes or the added mip's backing bytes"); PreparedBindings mipped_prepared{}; ShaderRecompiler::IR::CompiledShaderInfo mipped_program{}; mipped_program.info.images.push_back(storage_resource); @@ -9994,23 +10023,22 @@ public: overwide_mipped_storage.MaxMip() == 3 && overwide_mipped_binding.image_id == plain_mipped_binding.image_id && - overwide_mipped_binding.desc.info.resources.levels == 4 && + overwide_mipped_binding.desc.info.resources.levels == 5 && overwide_mipped_binding.desc.view_info.base_level == 1 && - overwide_mipped_binding.desc.view_info.level_count == 3 && + overwide_mipped_binding.desc.view_info.level_count == 4 && overwide_mipped_binding.mip_views.empty() && overwide_mipped_binding.image_view == plain_mipped_binding.image_view, - "fixed storage view was not intersected with its physical mip " - "range before Vulkan acquisition"); + "fixed storage view lost addressable mips in the allocated tail"); Require(name, "over-wide sampled mip view", sampled_overwide_binding.image_id == plain_mipped_binding.image_id && - sampled_overwide_binding.desc.info.resources.levels == 4 && + sampled_overwide_binding.desc.info.resources.levels == 5 && sampled_overwide_binding.desc.view_info.base_level == 1 && - sampled_overwide_binding.desc.view_info.level_count == 3 && + sampled_overwide_binding.desc.view_info.level_count == 4 && sampled_overwide_binding.mip_views.empty() && sampled_overwide_binding.image_view != nullptr, - "sampled view was not intersected with its physical mip range"); + "sampled view lost addressable mips in the allocated tail"); auto srgb_storage = storage; constexpr auto srgb_format = static_cast(Prospero::BufferFormat::k8_8_8_8Srgb); @@ -27983,12 +28011,6 @@ ShaderTextureResource AtomicStorageTextureDescriptor() { descriptor.fields[3] = (descriptor.fields[3] & ~(0x1fu << 20u)) | (static_cast(Prospero::TileMode::kStandard256B) << 20u); - } else if (std::strcmp(kind, "base-mip-out-of-resource") == 0) { - descriptor.fields[3] |= (1u << 12u) | (1u << 16u); - } else if (std::strcmp(kind, "dynamic-mip-out-of-resource") == 0) { - resource.mip_mode = ShaderRecompiler::IR::ImageMipMode::DynamicStorage; - resource.mip_count = 2; - descriptor.fields[3] |= 1u << 16u; } else if (std::strcmp(kind, "inverted-mip-range") == 0) { descriptor.fields[3] |= 1u << 12u; descriptor.fields[5] |= 1u << 4u; @@ -28460,8 +28482,6 @@ void CheckBasicStorageTextureDescriptor() { for (const char *kind : {"resource", "type", "standard256b-volume", - "base-mip-out-of-resource", - "dynamic-mip-out-of-resource", "inverted-mip-range", "swizzle", "linear-rgb1-read",