Preserve PS5 mip-tail views and GPU data during image expansion

Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
This commit is contained in:
nmzikandÇağatay Güçlü committed 2026-09-16 22:33:19 +04:00
1 parent c1dccd7db1
commit 8589731a27
4 files changed
+101 -49

No files matched your search

+1
View File
@@ -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 {
+14 -3
View File
@@ -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
@@ -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<ShaderTextureResource>(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<uint32_t>(descriptor.Width5()) + 1u;
const auto height = static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(descriptor.Width5()) + 1u;
const auto height = static_cast<uint32_t>(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<uint32_t>(max_mip) + 1u;
const auto levels =
multisampled ? 1u : std::max(physical_levels, static_cast<uint32_t>(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<uint32_t>(view_last_level - base_level) + 1u;
multisampled ? 1u : static_cast<uint32_t>(last_level - base_level) + 1u;
const auto depth = static_cast<uint32_t>(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<uint32_t>(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<uint64_t>(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};
+41 -21
View File
@@ -8594,7 +8594,8 @@ public:
std::vector<u32> 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<void *>(base),
"descriptor discovery fixed mapping failed");
std::memset(mapped, 0, allocation_size);
std::fill_n(reinterpret_cast<uint32_t *>(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<uint32_t>(encoded_mipped_storage_address);
mipped_storage.fields[1] =
static_cast<uint32_t>(encoded_mipped_storage_address >> 32u) |
(static_cast<uint32_t>(stencil_format) << 20u) | (3u << 30u);
mipped_storage.fields[2] = 1u | (7u << 14u);
(static_cast<uint32_t>(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<uint32_t>(linear) << 20u) |
(static_cast<uint32_t>(Prospero::TileMode::kStandard64KB) << 20u) |
(static_cast<uint32_t>(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<u32>{tail_gpu_value} &&
tail3 == std::vector<u32>{tail_gpu_value} &&
tail4 == std::vector<u32>{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<uint32_t>(Prospero::BufferFormat::k8_8_8_8Srgb);
@@ -27983,12 +28011,6 @@ ShaderTextureResource AtomicStorageTextureDescriptor() {
descriptor.fields[3] =
(descriptor.fields[3] & ~(0x1fu << 20u)) |
(static_cast<uint32_t>(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",