From 31fb3ec5aff499597a3df034cbb65255d382b681 Mon Sep 17 00:00:00 2001 From: nmzik Date: Mon, 27 Jul 2026 04:27:55 +0200 Subject: [PATCH] graphics: broaden storage image and view format compatibility --- .../host_gpu/renderer/descriptors.cpp | 19 ++- src/graphics/host_gpu/renderer/imageInfo.h | 29 ---- src/graphics/host_gpu/renderer/imageView.h | 48 +----- .../host_gpu/renderer/textureCache.cpp | 3 +- tests/ShaderRecompilerComputeTests.cpp | 152 +++++++++++++----- 5 files changed, 129 insertions(+), 122 deletions(-) diff --git a/src/graphics/host_gpu/renderer/descriptors.cpp b/src/graphics/host_gpu/renderer/descriptors.cpp index 15c72c555..091c2a0cb 100644 --- a/src/graphics/host_gpu/renderer/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/descriptors.cpp @@ -281,24 +281,26 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag const auto tile = descriptor.TileMode(); const bool is_2d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D && descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) && - descriptor.Depth() == 0; + descriptor.Depth() == 0 && descriptor.BaseArray5() == 0; const bool is_2d_array = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray && descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray) && descriptor.BaseArray5() <= descriptor.Depth(); const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D && - descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor3D); + descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor3D) && + descriptor.BaseArray5() == 0; TileBlockLayout depth_block {}; const auto depth_bpe = Prospero::RenderTargetBytesPerElement(descriptor.Format()); const bool supported_depth_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) && !resource.read && !Prospero::IsFmaskTextureFormat(descriptor.Format()) && (is_2d || is_2d_array) && TileGetBlockLayout(TileBlockFamily::Depth64KB, depth_bpe, depth_block); - const bool supported_volume_tile = - is_3d && tile == Prospero::GpuEnumValue(Prospero::TileMode::kStandard4KB); + const bool supported_standard_tile = + tile == Prospero::GpuEnumValue(Prospero::TileMode::kStandard4KB) && + TileIsStandard4KBTextureSupported(descriptor.Format()); const bool supported_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kLinear) || tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) || - supported_depth_tile || supported_volume_tile; + supported_depth_tile || supported_standard_tile; const bool supported_swizzle = IsValidImageSwizzle(descriptor.DstSelXYZW()) && (descriptor.DstSelXYZW() == DstSel(4, 5, 6, 7) || !resource.read); @@ -306,8 +308,7 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag return (is_2d || is_2d_array || is_3d) && supported_tile && supported_mip_view && descriptor.BaseLevel() == descriptor.LastLevel() && descriptor.LastLevel() <= descriptor.MaxMip() && descriptor.MinLod() == 0 && - descriptor.BaseArray5() == 0 && supported_swizzle && descriptor.BCSwizzle() == 0 && - !descriptor.MsaaDepth(); + supported_swizzle && descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth(); } static bool IsSupportedStorageTextureEncoding(const ShaderTextureResource& descriptor) { @@ -320,9 +321,11 @@ static bool IsSupportedStorageTextureEncoding(const ShaderTextureResource& descr (static_cast(descriptor.LastLevel()) << 16u) | (static_cast(descriptor.TileMode()) << 20u) | (static_cast(descriptor.Type()) << 28u); + const uint32_t expected_field4 = + descriptor.Depth() | (static_cast(descriptor.BaseArray5()) << 16u); return (descriptor.fields[1] & field1_reserved_mask) == 0 && (descriptor.fields[2] & field2_reserved_mask) == 0 && - descriptor.fields[3] == expected_field3 && descriptor.fields[4] == descriptor.Depth() && + descriptor.fields[3] == expected_field3 && descriptor.fields[4] == expected_field4 && (descriptor.fields[5] & ~field5_max_mip_mask) == field5_expected; } diff --git a/src/graphics/host_gpu/renderer/imageInfo.h b/src/graphics/host_gpu/renderer/imageInfo.h index 065e9a04e..343cd3de0 100644 --- a/src/graphics/host_gpu/renderer/imageInfo.h +++ b/src/graphics/host_gpu/renderer/imageInfo.h @@ -466,35 +466,6 @@ IsSupportedDisplayRenderTargetTileMode(uint32_t tile_mode) noexcept { IsSupportedStandard64RenderTarget(info); } -[[nodiscard]] inline bool IsRgba8SrgbReinterpretation(vk::Format cached, - vk::Format requested) noexcept { - switch (cached) { - case vk::Format::eR8G8B8A8Unorm: return requested == vk::Format::eR8G8B8A8Srgb; - case vk::Format::eR8G8B8A8Srgb: return requested == vk::Format::eR8G8B8A8Unorm; - case vk::Format::eB8G8R8A8Unorm: return requested == vk::Format::eB8G8R8A8Srgb; - case vk::Format::eB8G8R8A8Srgb: return requested == vk::Format::eB8G8R8A8Unorm; - default: return false; - } -} - -[[nodiscard]] inline bool IsRgba16UintFloatReinterpretation(vk::Format cached, - vk::Format requested) noexcept { - switch (cached) { - case vk::Format::eR16G16B16A16Sfloat: return requested == vk::Format::eR16G16B16A16Uint; - case vk::Format::eR16G16B16A16Uint: return requested == vk::Format::eR16G16B16A16Sfloat; - default: return false; - } -} - -[[nodiscard]] inline bool IsRgba8UnormUintReinterpretation(vk::Format cached, - vk::Format requested) noexcept { - switch (cached) { - case vk::Format::eR8G8B8A8Unorm: return requested == vk::Format::eR8G8B8A8Uint; - case vk::Format::eR8G8B8A8Uint: return requested == vk::Format::eR8G8B8A8Unorm; - default: return false; - } -} - [[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint32_t packed, vk::ClearColorValue& clear) { vk::ClearColorValue next {}; diff --git a/src/graphics/host_gpu/renderer/imageView.h b/src/graphics/host_gpu/renderer/imageView.h index 4bbf728e4..b1e688953 100644 --- a/src/graphics/host_gpu/renderer/imageView.h +++ b/src/graphics/host_gpu/renderer/imageView.h @@ -8,6 +8,12 @@ namespace Libs::Graphics { +namespace ImageViewOps { + +[[nodiscard]] vk::ImageAspectFlags DepthAspectMask(vk::Format format); +[[nodiscard]] bool FormatsCompatible(vk::Format base, vk::Format view) noexcept; +} // namespace ImageViewOps + [[nodiscard]] inline bool IsValidImageSwizzle(uint32_t swizzle) noexcept { if ((swizzle & ~0xfffu) != 0) { return false; @@ -32,28 +38,6 @@ namespace Libs::Graphics { static_cast(image_format), static_cast(view_format), swizzle); } -[[nodiscard]] inline vk::Format BgraToRgbaSampledViewFormat(vk::Format image_format) noexcept { - switch (image_format) { - case vk::Format::eB8G8R8A8Unorm: return vk::Format::eR8G8B8A8Unorm; - case vk::Format::eB8G8R8A8Srgb: return vk::Format::eR8G8B8A8Srgb; - case vk::Format::eA2R10G10B10UnormPack32: return vk::Format::eA2B10G10R10UnormPack32; - default: return vk::Format::eUndefined; - } -} - -[[nodiscard]] inline bool IsBgraToRgbaSampledView(vk::Format image_format, - vk::Format view_format) noexcept { - switch (image_format) { - case vk::Format::eB8G8R8A8Unorm: - case vk::Format::eB8G8R8A8Srgb: - return view_format == vk::Format::eR8G8B8A8Unorm || - view_format == vk::Format::eR8G8B8A8Srgb; - case vk::Format::eA2R10G10B10UnormPack32: - return view_format == vk::Format::eA2B10G10R10UnormPack32; - default: return false; - } -} - [[nodiscard]] inline vk::Format BgraSrgbStorageViewFormat(vk::Format image_format) noexcept { return image_format == vk::Format::eB8G8R8A8Srgb ? vk::Format::eR8G8B8A8Unorm : vk::Format::eUndefined; @@ -72,18 +56,8 @@ namespace Libs::Graphics { [[nodiscard]] inline bool IsSupportedSampledColorView(vk::Format image_format, vk::Format view_format, uint32_t swizzle) noexcept { - if (!IsValidImageSwizzle(swizzle)) { - return false; - } - if (image_format == view_format || IsRgba8SrgbReinterpretation(image_format, view_format)) { - return true; - } - if ((IsRgba16UintFloatReinterpretation(image_format, view_format) || - IsRgba8UnormUintReinterpretation(image_format, view_format)) && - swizzle == DstSel(4, 5, 6, 7)) { - return true; - } - return IsBgraToRgbaSampledView(image_format, view_format) && swizzle == DstSel(6, 5, 4, 7); + return IsValidImageSwizzle(swizzle) && + ImageViewOps::FormatsCompatible(image_format, view_format); } [[nodiscard]] inline uint32_t @@ -165,12 +139,6 @@ ValidateStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource } } -namespace ImageViewOps { - -[[nodiscard]] vk::ImageAspectFlags DepthAspectMask(vk::Format format); -[[nodiscard]] bool FormatsCompatible(vk::Format base, vk::Format view) noexcept; -} // namespace ImageViewOps - } // namespace Libs::Graphics #endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_ diff --git a/src/graphics/host_gpu/renderer/textureCache.cpp b/src/graphics/host_gpu/renderer/textureCache.cpp index 4fe7ccd47..3e06e0600 100644 --- a/src/graphics/host_gpu/renderer/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/textureCache.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace Libs::Graphics { @@ -486,7 +487,7 @@ void TextureCache::CopyImage(ImageId destination_id, ImageId source_id) { const bool direct_copy = source.backing.format == destination.backing.format || (!source_depth && !dest_depth && - ImageViewOps::FormatsCompatible(source.backing.format, destination.backing.format)); + vk::blockSize(source.backing.format) == vk::blockSize(destination.backing.format)); if (direct_copy) { destination.CopyImage(source); } else if (!CopyD16(destination, source)) { diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 94d59e80f..644894aca 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -2539,6 +2539,45 @@ public: vk::AccessFlagBits2::eTransferRead), "Image::CopyImage did not retain pinned source/destination states"); + constexpr uint64_t block_alias_offset = 0x23000; + constexpr std::array block_alias_data{ + 0x01234567u, 0x89abcdefu, 0xfedcba98u, 0x76543210u}; + std::memcpy(memory + block_alias_offset, block_alias_data.data(), + sizeof(block_alias_data)); + auto uncompressed_block = MakeLinearDesc( + base + block_alias_offset, sizeof(block_alias_data), + vk::Format::eR32G32B32A32Uint, + Prospero::GpuEnumValue( + Prospero::BufferFormat::k32_32_32_32UInt), + Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 16, 1); + const auto uncompressed_block_image = + texture_cache.FindImage(uncompressed_block); + texture_cache.MarkGpuWritten(uncompressed_block_image); + auto compressed_block = MakeLinearDesc( + base + block_alias_offset, sizeof(block_alias_data), + vk::Format::eBc3UnormBlock, + Prospero::GpuEnumValue(Prospero::BufferFormat::kBc3UNorm), + Prospero::ImageType::kColor2D, {4, 4, 1}, 1, 16, 1); + const auto compressed_block_image = + texture_cache.FindImage(compressed_block); + const bool compressed_block_download = + TextureCacheTestAccess::TryDownload(texture_cache, + compressed_block_image); + scheduler.FinishCurrent(); + scheduler.DrainPriorityOperations(); + std::array block_alias_after{}; + std::memcpy(block_alias_after.data(), memory + block_alias_offset, + sizeof(block_alias_after)); + Require(name, "compressed view expansion", + compressed_block_image && + compressed_block_image != uncompressed_block_image && + texture_cache.GetImage(compressed_block_image) + .backing.format == vk::Format::eBc3UnormBlock && + compressed_block_download && + block_alias_after == block_alias_data, + "size-compatible compressed alias did not preserve its native " + "contents"); + ImageInfo resolve_source_info{}; resolve_source_info.pixel_format = vk::Format::eR8G8B8A8Unorm; resolve_source_info.guest_format = @@ -14686,38 +14725,14 @@ void CheckReverseRenderTargetFormatContract() { } [[noreturn]] void RunImageViewDeathCase(const char *kind) { - if (std::strcmp(kind, "sampled") == 0) { + if (std::strcmp(kind, "sampled-invalid-selector") == 0) { (void)SelectSampledColorView(vk::Format::eR8G8B8A8Unorm, vk::Format::eR8G8B8A8Unorm, DstSel(4, 5, 6, 2)); - } else if (std::strcmp(kind, "sampled-compatible-swizzle") == 0) { - (void)SelectSampledColorView(vk::Format::eB8G8R8A8Unorm, - vk::Format::eR8G8B8A8Unorm, - DstSel(4, 5, 6, 7)); - } else if (std::strcmp(kind, "sampled-compatible-reverse") == 0) { - (void)SelectSampledColorView(vk::Format::eR8G8B8A8Unorm, - vk::Format::eB8G8R8A8Unorm, - DstSel(6, 5, 4, 7)); - } else if (std::strcmp(kind, "sampled-compatible-class") == 0) { - (void)SelectSampledColorView(vk::Format::eR8G8B8A8Uint, - vk::Format::eR8G8B8A8Unorm, - DstSel(4, 5, 6, 7)); - } else if (std::strcmp(kind, "sampled-compatible-colorspace") == 0) { - (void)SelectSampledColorView(vk::Format::eB8G8R8A8Srgb, - vk::Format::eR8G8B8A8Unorm, - DstSel(4, 5, 6, 7)); - } else if (std::strcmp(kind, "sampled-compatible-snorm") == 0) { - (void)SelectSampledColorView(vk::Format::eR8G8B8A8Snorm, - vk::Format::eR8G8B8A8Unorm, - DstSel(4, 5, 6, 7)); - } else if (std::strcmp(kind, "sampled-rgb10-swizzle") == 0) { - (void)SelectSampledColorView(vk::Format::eA2R10G10B10UnormPack32, - vk::Format::eA2B10G10R10UnormPack32, - DstSel(4, 5, 6, 7)); - } else if (std::strcmp(kind, "sampled-rgb10-reverse") == 0) { - (void)SelectSampledColorView(vk::Format::eA2B10G10R10UnormPack32, - vk::Format::eA2R10G10B10UnormPack32, - DstSel(6, 5, 4, 7)); + } else if (std::strcmp(kind, "sampled-incompatible-format") == 0) { + (void)SelectSampledColorView(vk::Format::eR8Unorm, + vk::Format::eR16Unorm, + DstSel(4, 0, 0, 1)); } else if (std::strcmp(kind, "sampled-invalid-high") == 0) { (void)SelectSampledColorView(vk::Format::eR8G8B8A8Unorm, vk::Format::eR8G8B8A8Unorm, @@ -14841,6 +14856,11 @@ void CheckSampledColorViews() { SelectSampledColorView(vk::Format::eR8Unorm, vk::Format::eR8Unorm, DstSel(0, 0, 0, 4)) == DstSel(0, 0, 0, 4), "R8 did not select its 000R component-mapped view"); + Require("SampledColorViews", "mutable R8 uint/unorm 000R", + SelectSampledColorView(vk::Format::eR8Uint, + vk::Format::eR8Unorm, + DstSel(0, 0, 0, 4)) == DstSel(0, 0, 0, 4), + "compatible R8 integer target sampled view was rejected"); Require("SampledColorViews", "R16G16 RG01", SelectSampledColorView(vk::Format::eR16G16Sfloat, vk::Format::eR16G16Sfloat, @@ -14863,11 +14883,9 @@ void CheckSampledColorViews() { DstSel(6, 5, 4, 7)) == DstSel(6, 5, 4, 7), "UNORM BGRA target did not select its compatible sRGB RGBA sampled view"); Require("SampledColorViews", "mutable sRGB BGRA target", - BgraToRgbaSampledViewFormat(vk::Format::eB8G8R8A8Srgb) == - vk::Format::eR8G8B8A8Srgb && - SelectSampledColorView(vk::Format::eB8G8R8A8Srgb, - vk::Format::eR8G8B8A8Srgb, - DstSel(6, 5, 4, 7)) == DstSel(6, 5, 4, 7), + SelectSampledColorView(vk::Format::eB8G8R8A8Srgb, + vk::Format::eR8G8B8A8Srgb, + DstSel(6, 5, 4, 7)) == DstSel(6, 5, 4, 7), "sRGB BGRA target did not select its matching mutable RGBA view"); constexpr auto colorspace_swizzle = DstSel(5, 1, 7, 0); Require("SampledColorViews", "mutable sRGB/UNORM views", @@ -14879,11 +14897,9 @@ void CheckSampledColorViews() { colorspace_swizzle) == colorspace_swizzle, "same-order mutable sRGB/UNORM sampled views were rejected"); Require("SampledColorViews", "mutable packed RGB10 view", - BgraToRgbaSampledViewFormat(vk::Format::eA2R10G10B10UnormPack32) == - vk::Format::eA2B10G10R10UnormPack32 && - SelectSampledColorView(vk::Format::eA2R10G10B10UnormPack32, - vk::Format::eA2B10G10R10UnormPack32, - DstSel(6, 5, 4, 7)) == DstSel(6, 5, 4, 7), + SelectSampledColorView(vk::Format::eA2R10G10B10UnormPack32, + vk::Format::eA2B10G10R10UnormPack32, + DstSel(6, 5, 4, 7)) == DstSel(6, 5, 4, 7), "packed RGB10 target did not select its matching mutable " "channel-order view"); Require( @@ -14960,9 +14976,7 @@ void CheckSampledColorViews() { GetModuleFileNameA(nullptr, path, MAX_PATH) != 0, "GetModuleFileName failed"); for (const char *kind : - {"sampled", "sampled-compatible-swizzle", "sampled-compatible-reverse", - "sampled-compatible-colorspace", "sampled-compatible-snorm", - "sampled-rgb10-swizzle", "sampled-rgb10-reverse", + {"sampled-invalid-selector", "sampled-incompatible-format", "sampled-invalid-high", "sampled-depth-format", "sampled-depth-swizzle", "storage-kind", "storage-no-write", "storage-atomic", "storage-compare", "storage-mip", "storage-dimension"}) { @@ -15601,6 +15615,11 @@ ShaderTextureResource BasicUintArrayStorageTextureDescriptor() { 0x00700000u, 0x00000000u, 0x00000000u}}; } +ShaderTextureResource Standard4KBUintArrayStorageTextureDescriptor() { + return {{0x006c6600u, 0x01400000u, 0x00000000u, 0xd0500204u, 0x00000000u, + 0x00700000u, 0x00000000u, 0x00000000u}}; +} + ShaderRecompiler::IR::ImageResource Ppsa14053DepthTileStorageTextureResource() { return BasicUintArrayStorageTextureResource(); } @@ -15670,7 +15689,7 @@ ShaderTextureResource BasicUintVolumeStorageTextureDescriptor() { } else if (std::strcmp(kind, "swizzle") == 0) { descriptor.fields[3] = (descriptor.fields[3] & ~0xfffu) | DstSel(4, 5, 6, 1); - } else if (std::strcmp(kind, "array-base-view") == 0) { + } else if (std::strcmp(kind, "array-base-out-of-range") == 0) { resource = BasicArrayStorageTextureResource(); descriptor = BasicArrayStorageTextureDescriptor(); descriptor.fields[4] |= 1u << 16u; @@ -15902,6 +15921,51 @@ void CheckBasicStorageTextureDescriptor() { ValidateStorageTexture(BasicUintArrayStorageTextureResource(), uint_array, 0x10000); + const auto standard4kb_array = + Standard4KBUintArrayStorageTextureDescriptor(); + const auto standard4kb_pitch = TileGetTexturePitch( + standard4kb_array.Format(), 1, 1, standard4kb_array.TileMode()); + TileSizeAlign standard4kb_size{}; + TileGetTextureTotalSize(standard4kb_array.Format(), 1, 1, 1, + standard4kb_pitch, 1, + standard4kb_array.TileMode(), false, + standard4kb_size); + Require("BasicStorageTexture", "Standard4KB uint 2D-array descriptor", + standard4kb_array.Type() == + Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray) && + standard4kb_array.Format() == + Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) && + standard4kb_array.TileMode() == + Prospero::GpuEnumValue(Prospero::TileMode::kStandard4KB) && + standard4kb_array.DstSelXYZW() == DstSel(4, 0, 0, 1) && + standard4kb_size.size == 0x1000 && + standard4kb_size.align == 0x1000, + "captured Standard4KB uint 2D-array descriptor is malformed"); + ValidateStorageTexture(BasicUintArrayStorageTextureResource(), + standard4kb_array, standard4kb_size.size); + + auto based_standard4kb_array = standard4kb_array; + based_standard4kb_array.fields[0] = 0x006c6800u; + based_standard4kb_array.fields[4] = 0x00010001u; + const auto based_standard4kb_pitch = TileGetTexturePitch( + based_standard4kb_array.Format(), 1, 1, + based_standard4kb_array.TileMode()); + TileSizeAlign based_standard4kb_size{}; + TileGetTextureTotalSize( + based_standard4kb_array.Format(), 1, 1, + based_standard4kb_array.Depth() + 1u, based_standard4kb_pitch, 1, + based_standard4kb_array.TileMode(), false, based_standard4kb_size); + Require("BasicStorageTexture", "based Standard4KB array view", + based_standard4kb_array.Base40() == 0x6c680000ull && + based_standard4kb_array.BaseArray5() == 1 && + based_standard4kb_array.Depth() == 1 && + based_standard4kb_size.size == 0x2000 && + based_standard4kb_size.align == 0x1000, + "captured based Standard4KB array view is malformed"); + ValidateStorageTexture(BasicUintArrayStorageTextureResource(), + based_standard4kb_array, + based_standard4kb_size.size); + const auto uint_volume = BasicUintVolumeStorageTextureDescriptor(); Require("BasicStorageTexture", "uint 3D descriptor", uint_volume.Base40() == 0x2018060000ull && @@ -16000,7 +16064,7 @@ void CheckBasicStorageTextureDescriptor() { for (const char *kind : {"resource", "type", "tile", "mip", "swizzle", "linear-rgb1-read", "bgra-read", "r16-float-read", "r8-unorm-read", "yzwx-read", - "reserved-swizzle", "array-base-view", "array-mip-view", "reserved", + "reserved-swizzle", "array-base-out-of-range", "array-mip-view", "reserved", "uint-format", "uint-resource-float-format", "depth-tile-read", "depth-tile-extent", "depth-tile-fmask"}) { std::string command = std::string("\"") + path +