From 013e9a448531ee5070eff21b4f40539f4ebed3e1 Mon Sep 17 00:00:00 2001 From: nmzik Date: Fri, 17 Jul 2026 02:24:22 +0200 Subject: [PATCH] Support single-channel float storage writes Support sampled views of storage texture arrays Support R8 UNORM storage writes --- src/graphics/host_gpu/renderer/imageInfo.h | 17 ++++ src/graphics/host_gpu/renderer/imageView.h | 22 +++-- .../host_gpu/renderer/textureCache.cpp | 18 ++-- tests/ShaderRecompilerComputeTests.cpp | 92 ++++++++++++++++++- 4 files changed, 133 insertions(+), 16 deletions(-) diff --git a/src/graphics/host_gpu/renderer/imageInfo.h b/src/graphics/host_gpu/renderer/imageInfo.h index d347df43a..0db9d94aa 100644 --- a/src/graphics/host_gpu/renderer/imageInfo.h +++ b/src/graphics/host_gpu/renderer/imageInfo.h @@ -187,6 +187,7 @@ enum class RenderTargetOverlap : uint8_t { }; enum class SampledOverlap : uint8_t { None, ReadOnlyAlias, Unsupported }; enum class StorageSampledOverlap : uint8_t { None, ExactImage, 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 }; enum class BufferImageBinding : uint8_t { Texture, VideoOut, RenderTarget, Unsupported }; @@ -199,6 +200,22 @@ enum class BufferImageWrite : uint8_t { }; enum class MetaImageOverlap : uint8_t { RetainSampled, RetireTarget, Unsupported }; +[[nodiscard]] inline constexpr StorageSampledViewShape +SelectStorageSampledViewShape(uint32_t type, uint32_t depth, uint32_t backing_layers) noexcept { + switch (static_cast(type)) { + case Prospero::ImageType::kColor2D: + return depth == 1 && backing_layers == 1 ? StorageSampledViewShape::Image2D + : StorageSampledViewShape::Unsupported; + case Prospero::ImageType::kColor2DArray: + return depth != 0 && depth == backing_layers ? StorageSampledViewShape::Image2DArray + : StorageSampledViewShape::Unsupported; + case Prospero::ImageType::kColor3D: + return depth != 0 && backing_layers == 1 ? StorageSampledViewShape::Image3D + : StorageSampledViewShape::Unsupported; + default: return StorageSampledViewShape::Unsupported; + } +} + [[nodiscard]] inline constexpr bool IsSupportedRenderTargetElementSize(uint32_t size) noexcept { switch (size) { case 1: diff --git a/src/graphics/host_gpu/renderer/imageView.h b/src/graphics/host_gpu/renderer/imageView.h index ef42c5779..59ff94ed9 100644 --- a/src/graphics/host_gpu/renderer/imageView.h +++ b/src/graphics/host_gpu/renderer/imageView.h @@ -9,11 +9,14 @@ namespace Libs::Graphics { [[nodiscard]] inline bool IsSupportedStorageSwizzle(uint32_t format, uint32_t swizzle) noexcept { - const bool single_channel_uint = format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8UInt) || - format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16UInt) || - format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt); + const bool single_channel = format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm) || + format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8UInt) || + format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16UInt) || + format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) || + format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16Float) || + format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float); return swizzle == DstSel(4, 5, 6, 7) || - (single_channel_uint && + (single_channel && (swizzle == DstSel(4, 0, 0, 0) || swizzle == DstSel(4, 0, 0, 1))) || (format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm) && (swizzle == DstSel(4, 5, 6, 1) || swizzle == DstSel(6, 5, 4, 7))) || @@ -114,12 +117,15 @@ IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resou [[nodiscard]] inline int SelectStorageColorView(VkFormat image_format, VkFormat view_format, uint32_t swizzle) noexcept { - const bool single_channel_uint = view_format == VK_FORMAT_R8_UINT || - view_format == VK_FORMAT_R16_UINT || - view_format == VK_FORMAT_R32_UINT; + const bool single_channel = view_format == VK_FORMAT_R8_UNORM || + view_format == VK_FORMAT_R8_UINT || + view_format == VK_FORMAT_R16_UINT || + view_format == VK_FORMAT_R32_UINT || + view_format == VK_FORMAT_R16_SFLOAT || + view_format == VK_FORMAT_R32_SFLOAT; const bool swizzle_ok = swizzle == DstSel(4, 5, 6, 7) || - (single_channel_uint && (swizzle == DstSel(4, 0, 0, 0) || swizzle == DstSel(4, 0, 0, 1))) || + (single_channel && (swizzle == DstSel(4, 0, 0, 0) || swizzle == DstSel(4, 0, 0, 1))) || (view_format == VK_FORMAT_R8G8B8A8_UNORM && (swizzle == DstSel(4, 5, 6, 1) || swizzle == DstSel(6, 5, 4, 7))) || (view_format == VK_FORMAT_R32G32B32A32_SFLOAT && swizzle == DstSel(5, 6, 7, 4)); diff --git a/src/graphics/host_gpu/renderer/textureCache.cpp b/src/graphics/host_gpu/renderer/textureCache.cpp index f709679fd..f4d0ff52a 100644 --- a/src/graphics/host_gpu/renderer/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/textureCache.cpp @@ -1557,11 +1557,10 @@ VkImageView TextureCache::GetRenderTargetStorageView(GraphicContext* c VkImageView TextureCache::GetStorageTextureSampledView(GraphicContext* ctx, StorageTextureVulkanImage* image, const ImageInfo& info) { - const bool image_2d = - info.type == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) && info.depth == 1; - const bool image_3d = - info.type == Prospero::GpuEnumValue(Prospero::ImageType::kColor3D) && info.depth != 0; - if (ctx == nullptr || image == nullptr || image->image == nullptr || (!image_2d && !image_3d) || + const auto shape = SelectStorageSampledViewShape( + info.type, info.depth, image != nullptr ? image->layers : 0); + if (ctx == nullptr || image == nullptr || image->image == nullptr || + shape == StorageSampledViewShape::Unsupported || info.base_array != 0 || info.levels != image->mip_levels || info.base_level >= info.levels || info.view_levels == 0 || info.base_level + info.view_levels > info.levels) { @@ -1593,14 +1592,19 @@ VkImageView TextureCache::GetStorageTextureSampledView(GraphicContext* create.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; create.pNext = &usage; create.image = image->image; - create.viewType = image_3d ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D; + create.viewType = shape == StorageSampledViewShape::Image3D + ? VK_IMAGE_VIEW_TYPE_3D + : shape == StorageSampledViewShape::Image2DArray + ? VK_IMAGE_VIEW_TYPE_2D_ARRAY + : VK_IMAGE_VIEW_TYPE_2D; create.format = view_format; create.components = TextureGetComponentMapping(info.swizzle); create.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; create.subresourceRange.baseMipLevel = info.base_level; create.subresourceRange.levelCount = info.view_levels; create.subresourceRange.baseArrayLayer = 0; - create.subresourceRange.layerCount = 1; + create.subresourceRange.layerCount = + shape == StorageSampledViewShape::Image2DArray ? info.depth : 1; VkImageView view = nullptr; const auto result = vkCreateImageView(ctx->device, &create, nullptr, &view); if (result != VK_SUCCESS || view == nullptr) { diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 678634bde..457c475af 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -8846,6 +8846,18 @@ void CheckSampledColorViews() { SelectStorageColorView(VK_FORMAT_R16_UINT, VK_FORMAT_R16_UINT, DstSel(4, 0, 0, 0)) == VulkanImage::VIEW_STORAGE, "R16_UINT R000 storage did not select the identity storage view"); + Require("SampledColorViews", "storage R16 float R001 write mapping", + SelectStorageColorView(VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16_SFLOAT, + DstSel(4, 0, 0, 1)) == VulkanImage::VIEW_STORAGE, + "R16_SFLOAT R001 storage did not select the identity storage view"); + Require("SampledColorViews", "storage R32 float R001 write mapping", + SelectStorageColorView(VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32_SFLOAT, + DstSel(4, 0, 0, 1)) == VulkanImage::VIEW_STORAGE, + "R32_SFLOAT R001 storage did not select the identity storage view"); + Require("SampledColorViews", "storage R8 unorm R001 write mapping", + SelectStorageColorView(VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, + DstSel(4, 0, 0, 1)) == VulkanImage::VIEW_STORAGE, + "R8_UNORM R001 storage did not select the identity storage view"); ShaderRecompiler::IR::ImageResource storage_resource{}; storage_resource.kind = ShaderRecompiler::IR::ResourceKind::StorageImage; storage_resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D; @@ -9062,6 +9074,21 @@ ShaderTextureResource BasicBgraStorageTextureDescriptor() { 0x00000000u, 0x00700000u, 0x00000000u, 0x00000000u}}; } +ShaderTextureResource Ppsa02527R16FloatStorageTextureDescriptor() { + return {{0x00ce3500u, 0xc0d00000u, 0x010dc1dfu, 0x91b00204u, + 0x00000000u, 0x00700000u, 0x00000000u, 0x00000000u}}; +} + +ShaderTextureResource Ppsa02527R32FloatStorageTextureDescriptor() { + return {{0x00cea900u, 0xc1600000u, 0x0086c0efu, 0x91b00204u, + 0x00000000u, 0x00700000u, 0x00000000u, 0x00000000u}}; +} + +ShaderTextureResource Ppsa02527R8UnormStorageTextureDescriptor() { + return {{0x00c7d500u, 0xc0100000u, 0x0086c0efu, 0x91b00204u, + 0x00000000u, 0x00700000u, 0x00000000u, 0x00000000u}}; +} + ShaderTextureResource BasicYzwxStorageTextureDescriptor() { return {{0x00627801u, 0xc4d00000u, 0x0001c001u, 0x900009f5u, 0x00000000u, 0x00700000u, 0x00000000u, 0x00000000u}}; @@ -9121,6 +9148,14 @@ ShaderTextureResource BasicUintVolumeStorageTextureDescriptor() { resource = BasicBgraStorageTextureResource(); descriptor = BasicBgraStorageTextureDescriptor(); resource.read = true; + } else if (std::strcmp(kind, "r16-float-read") == 0) { + resource = BasicBgraStorageTextureResource(); + descriptor = Ppsa02527R16FloatStorageTextureDescriptor(); + resource.read = true; + } else if (std::strcmp(kind, "r8-unorm-read") == 0) { + resource = BasicBgraStorageTextureResource(); + descriptor = Ppsa02527R8UnormStorageTextureDescriptor(); + resource.read = true; } else if (std::strcmp(kind, "yzwx-read") == 0) { resource = BasicLinearStorageTextureResource(); descriptor = BasicYzwxStorageTextureDescriptor(); @@ -9203,6 +9238,42 @@ void CheckBasicStorageTextureDescriptor() { "PPSA02604 BGRA 2D storage descriptor fixture is malformed"); ValidateStorageTexture(BasicBgraStorageTextureResource(), bgra, 0x870000); + const auto r16_float = Ppsa02527R16FloatStorageTextureDescriptor(); + Require("BasicStorageTexture", "PPSA02527 R16F descriptor", + r16_float.Base40() == 0xce350000ull && r16_float.Width5() + 1u == 1920 && + r16_float.Height5() + 1u == 1080 && r16_float.Depth() + 1u == 1 && + r16_float.Format() == + Prospero::GpuEnumValue(Prospero::BufferFormat::k16Float) && + r16_float.TileMode() == + Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) && + r16_float.DstSelXYZW() == DstSel(4, 0, 0, 1), + "PPSA02527 R16F 2D storage descriptor fixture is malformed"); + ValidateStorageTexture(BasicBgraStorageTextureResource(), r16_float, 0x480000); + + const auto r32_float = Ppsa02527R32FloatStorageTextureDescriptor(); + Require("BasicStorageTexture", "PPSA02527 R32F descriptor", + r32_float.Base40() == 0xcea90000ull && r32_float.Width5() + 1u == 960 && + r32_float.Height5() + 1u == 540 && r32_float.Depth() + 1u == 1 && + r32_float.Format() == + Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float) && + r32_float.TileMode() == + Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) && + r32_float.DstSelXYZW() == DstSel(4, 0, 0, 1), + "PPSA02527 R32F 2D storage descriptor fixture is malformed"); + ValidateStorageTexture(BasicBgraStorageTextureResource(), r32_float, 0x280000); + + const auto r8_unorm = Ppsa02527R8UnormStorageTextureDescriptor(); + Require("BasicStorageTexture", "PPSA02527 R8 UNORM descriptor", + r8_unorm.Base40() == 0xc7d50000ull && r8_unorm.Width5() + 1u == 960 && + r8_unorm.Height5() + 1u == 540 && r8_unorm.Depth() + 1u == 1 && + r8_unorm.Format() == + Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm) && + r8_unorm.TileMode() == + Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) && + r8_unorm.DstSelXYZW() == DstSel(4, 0, 0, 1), + "PPSA02527 R8 UNORM 2D storage descriptor fixture is malformed"); + ValidateStorageTexture(BasicBgraStorageTextureResource(), r8_unorm, 0xc0000); + const auto yzwx = BasicYzwxStorageTextureDescriptor(); Require("BasicStorageTexture", "YZWX descriptor", yzwx.Base40() == 0x62780100ull && yzwx.Width5() + 1u == 8 && @@ -9269,7 +9340,8 @@ void CheckBasicStorageTextureDescriptor() { GetModuleFileNameA(nullptr, path, MAX_PATH) != 0, "GetModuleFileName failed"); for (const char *kind : {"resource", "type", "tile", "mip", "swizzle", - "linear-rgb1-read", "bgra-read", "yzwx-read", "yzwx-format", + "linear-rgb1-read", "bgra-read", "r16-float-read", "r8-unorm-read", + "yzwx-read", "yzwx-format", "array-base-view", "reserved", "metadata", "uint-format", "uint-resource-float-format", "depth-tile-read", "depth-tile-extent"}) { @@ -9830,6 +9902,24 @@ void CheckStorageTextureGpuOwnedRebindState() { } void CheckStorageTextureSampledReuse() { + const auto image_2d = Prospero::GpuEnumValue(Prospero::ImageType::kColor2D); + const auto image_2d_array = + Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray); + const auto image_3d = Prospero::GpuEnumValue(Prospero::ImageType::kColor3D); + Require("StorageTextureSampledReuse", "view shapes", + SelectStorageSampledViewShape(image_2d, 1, 1) == + StorageSampledViewShape::Image2D && + SelectStorageSampledViewShape(image_2d_array, 16, 16) == + StorageSampledViewShape::Image2DArray && + SelectStorageSampledViewShape(image_3d, 16, 1) == + StorageSampledViewShape::Image3D && + SelectStorageSampledViewShape(image_2d, 16, 1) == + StorageSampledViewShape::Unsupported && + SelectStorageSampledViewShape(image_2d_array, 16, 1) == + StorageSampledViewShape::Unsupported && + SelectStorageSampledViewShape(image_3d, 16, 16) == + StorageSampledViewShape::Unsupported, + "sampled storage view shape or backing-layer validation is incorrect"); ImageInfo storage{}; storage.address = 0x78650000; storage.size = 0x210000;