mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
Implement texture-alias, mip-tail
This commit is contained in:
@@ -702,9 +702,13 @@ std::pair<VulkanBuffer*, uint64_t> 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
|
||||
|
||||
@@ -321,27 +321,33 @@ bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
|
||||
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
|
||||
const auto pitch = TileGetTexturePitch(descriptor.Format(), width, 1, descriptor.TileMode());
|
||||
const auto type = static_cast<Prospero::ImageType>(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<uint32_t>(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<uint32_t>(descriptor.BaseLevel()) << 12u) |
|
||||
(static_cast<uint32_t>(descriptor.LastLevel()) << 16u) |
|
||||
(static_cast<uint32_t>(descriptor.TileMode()) << 20u) |
|
||||
(static_cast<uint32_t>(descriptor.Type()) << 28u);
|
||||
const uint32_t field4_expected = descriptor.Depth() | (descriptor.BaseArray5() << 16u);
|
||||
const uint32_t field5_expected =
|
||||
field5_common | (static_cast<uint32_t>(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<uint32_t>(resource.kind), static_cast<uint32_t>(resource.dimension),
|
||||
static_cast<uint32_t>(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<uint32_t>(descriptor.Width5()) + 1u,
|
||||
static_cast<uint32_t>(descriptor.Height5()) + 1u,
|
||||
static_cast<uint32_t>(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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<ShaderBufferResource>(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;
|
||||
|
||||
@@ -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<TileSizeOffset, 16> level_layouts {};
|
||||
std::array<TilePaddedSize, 16> 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<uint32_t>((static_cast<uint64_t>(sampled.width) + divisor - 1) / divisor);
|
||||
const auto level_height =
|
||||
static_cast<uint32_t>((static_cast<uint64_t>(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<ImageBufferCopy> 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<CachedImage> storage_match;
|
||||
std::vector<CachedImage*> 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<true>(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<uint32_t>(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 ||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<uint8_t> 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<uint64_t>(y) * region.pitch * 8u;
|
||||
for (uint32_t x = 0; x < region.width * 8u; x++) {
|
||||
row[x] =
|
||||
static_cast<uint8_t>((level * 47u + y * 19u + x * 11u) & 0xffu);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> mip_guest(mip_info.size, 0xcd);
|
||||
tiler.TileImage(mip_guest.data(), mip_linear.data(), mip_info);
|
||||
std::vector<uint8_t> 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<uint64_t>(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<char> 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<uint32_t>;
|
||||
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<MipOwnerIndex::ByteRange> 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<uint32_t>{2} &&
|
||||
mip_owners.Register(3, {{mip_one_address, mip_one_size}}) &&
|
||||
mip_owners.Query(mip_one_address, mip_one_size) ==
|
||||
std::vector<uint32_t>{3} &&
|
||||
mip_owners.Query(mip_zero_address, mip_zero_size) ==
|
||||
std::vector<uint32_t>{2},
|
||||
"retiring the full chain released or conflated a disjoint cached mip");
|
||||
DepthTargetInfo depth{};
|
||||
depth.address = sampled.address;
|
||||
depth.size = 0x6000;
|
||||
|
||||
Reference in New Issue
Block a user