From 719e20d470cc5391b3552c4224db964921879f71 Mon Sep 17 00:00:00 2001 From: nmzik Date: Fri, 21 Aug 2026 01:04:33 +0200 Subject: [PATCH] shader: support bit-packed image surfaces --- src/graphics/guest_gpu/gpu_format.cpp | 166 +++++----- src/graphics/guest_gpu/gpu_format.h | 5 +- .../host_gpu/renderer/image/textureCommon.cpp | 11 +- .../host_gpu/renderer/image/textureCommon.h | 13 +- .../renderer/pipeline/descriptors.cpp | 69 ++-- .../backend/spirv/spirvEmitterImage.cpp | 171 ++++++++-- src/graphics/shader/recompiler/ir/ShaderIR.h | 10 +- .../ir/passes/ResourceMaterialization.cpp | 137 ++++++-- src/graphics/shader/shader.cpp | 8 +- src/graphics/shader/shaderBindings.h | 10 + tests/ShaderRecompilerComputeTests.cpp | 302 ++++++++++++++++-- 11 files changed, 707 insertions(+), 195 deletions(-) diff --git a/src/graphics/guest_gpu/gpu_format.cpp b/src/graphics/guest_gpu/gpu_format.cpp index 84898b5fd..e535ce3ce 100644 --- a/src/graphics/guest_gpu/gpu_format.cpp +++ b/src/graphics/guest_gpu/gpu_format.cpp @@ -9,80 +9,80 @@ namespace Libs::Graphics::Prospero { namespace { struct FormatInfo { - BufferFormat format; - uint32_t bytes_per_element; - uint32_t block_compressed_bytes_per_block; - uint32_t render_target_bytes_per_element; - bool supported_sampled_texture_format; - bool unsigned_integer_texture_format; + BufferFormat format; + uint32_t bytes_per_element; + uint32_t block_compressed_bytes_per_block; + uint32_t render_target_bytes_per_element; + bool sampled_texture; + bool uint_texture; }; constexpr FormatInfo kFormatInfo[] = { - {BufferFormat::k8UNorm, 1, 0, 1, true, false}, - {BufferFormat::k8SNorm, 0, 0, 1, false, false}, - {BufferFormat::k8UInt, 1, 0, 1, true, true}, - {BufferFormat::k16UNorm, 2, 0, 2, true, false}, - {BufferFormat::k16SNorm, 2, 0, 2, true, false}, - {BufferFormat::k16UInt, 2, 0, 2, true, true}, - {BufferFormat::k16SInt, 2, 0, 2, false, false}, - {BufferFormat::k16Float, 2, 0, 2, true, false}, - {BufferFormat::k8_8UNorm, 2, 0, 2, true, false}, - {BufferFormat::k8_8SNorm, 2, 0, 2, true, false}, - {BufferFormat::k8_8UInt, 2, 0, 2, true, true}, - {BufferFormat::k8_8SInt, 2, 0, 2, false, false}, - {BufferFormat::k32UInt, 4, 0, 4, true, true}, - {BufferFormat::k32SInt, 4, 0, 4, false, false}, - {BufferFormat::k32Float, 4, 0, 4, true, false}, - {BufferFormat::k16_16UNorm, 4, 0, 4, true, false}, - {BufferFormat::k16_16SNorm, 4, 0, 4, true, false}, - {BufferFormat::k16_16UInt, 4, 0, 4, true, true}, - {BufferFormat::k16_16SInt, 4, 0, 4, false, false}, - {BufferFormat::k16_16Float, 4, 0, 4, true, false}, - {BufferFormat::k11_11_10UInt, 4, 0, 4, true, true}, - {BufferFormat::k11_11_10Float, 4, 0, 4, true, false}, - {BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false}, - {BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true}, - {BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false}, - {BufferFormat::k8_8_8_8SNorm, 4, 0, 4, true, false}, - {BufferFormat::k8_8_8_8UInt, 4, 0, 4, true, true}, - {BufferFormat::k8_8_8_8SInt, 4, 0, 4, false, false}, - {BufferFormat::k32_32UInt, 8, 0, 8, true, true}, - {BufferFormat::k32_32SInt, 8, 0, 8, false, false}, - {BufferFormat::k32_32Float, 8, 0, 8, true, false}, - {BufferFormat::k16_16_16_16UNorm, 8, 0, 8, true, false}, - {BufferFormat::k16_16_16_16SNorm, 8, 0, 8, true, false}, - {BufferFormat::k16_16_16_16UInt, 8, 0, 8, true, true}, - {BufferFormat::k16_16_16_16SInt, 8, 0, 8, false, false}, - {BufferFormat::k16_16_16_16Float, 8, 0, 8, true, false}, - {BufferFormat::k32_32_32UInt, 12, 0, 12, true, true}, - {BufferFormat::k32_32_32SInt, 12, 0, 12, false, false}, - {BufferFormat::k32_32_32Float, 12, 0, 12, true, false}, - {BufferFormat::k32_32_32_32UInt, 16, 0, 16, true, true}, - {BufferFormat::k32_32_32_32SInt, 16, 0, 16, false, false}, - {BufferFormat::k32_32_32_32Float, 16, 0, 16, true, false}, - {BufferFormat::k8Srgb, 1, 0, 0, true, false}, - {BufferFormat::k8_8Srgb, 2, 0, 0, true, false}, - {BufferFormat::k8_8_8_8Srgb, 4, 0, 4, true, false}, - {BufferFormat::k9_9_9_5Float, 4, 0, 0, true, false}, - {BufferFormat::k5_6_5UNorm, 2, 0, 2, true, false}, - {BufferFormat::k5_5_5_1UNorm, 2, 0, 2, true, false}, - {BufferFormat::k1_5_5_5UNorm, 0, 0, 2, false, false}, - {BufferFormat::k4_4_4_4UNorm, 2, 0, 2, true, false}, - {BufferFormat::kFmask8_S4_F4, 1, 0, 1, true, false}, - {BufferFormat::kBc1UNorm, 0, 8, 0, true, false}, - {BufferFormat::kBc1Srgb, 0, 8, 0, true, false}, - {BufferFormat::kBc2UNorm, 0, 16, 0, true, false}, - {BufferFormat::kBc2Srgb, 0, 16, 0, true, false}, - {BufferFormat::kBc3UNorm, 0, 16, 0, true, false}, - {BufferFormat::kBc3Srgb, 0, 16, 0, true, false}, - {BufferFormat::kBc4UNorm, 0, 8, 0, true, false}, - {BufferFormat::kBc4SNorm, 0, 8, 0, true, false}, - {BufferFormat::kBc5UNorm, 0, 16, 0, true, false}, - {BufferFormat::kBc5SNorm, 0, 16, 0, true, false}, - {BufferFormat::kBc6UFloat, 0, 16, 0, true, false}, - {BufferFormat::kBc6SFloat, 0, 16, 0, true, false}, - {BufferFormat::kBc7UNorm, 0, 16, 0, true, false}, - {BufferFormat::kBc7Srgb, 0, 16, 0, true, false}, + {BufferFormat::k8UNorm, 1, 0, 1, true, false}, + {BufferFormat::k8SNorm, 0, 0, 1, false, false}, + {BufferFormat::k8UInt, 1, 0, 1, true, true}, + {BufferFormat::k16UNorm, 2, 0, 2, true, false}, + {BufferFormat::k16SNorm, 2, 0, 2, true, false}, + {BufferFormat::k16UInt, 2, 0, 2, true, true}, + {BufferFormat::k16SInt, 2, 0, 2, false, false}, + {BufferFormat::k16Float, 2, 0, 2, true, false}, + {BufferFormat::k8_8UNorm, 2, 0, 2, true, false}, + {BufferFormat::k8_8SNorm, 2, 0, 2, true, false}, + {BufferFormat::k8_8UInt, 2, 0, 2, true, true}, + {BufferFormat::k8_8SInt, 2, 0, 2, false, false}, + {BufferFormat::k32UInt, 4, 0, 4, true, true}, + {BufferFormat::k32SInt, 4, 0, 4, false, false}, + {BufferFormat::k32Float, 4, 0, 4, true, false}, + {BufferFormat::k16_16UNorm, 4, 0, 4, true, false}, + {BufferFormat::k16_16SNorm, 4, 0, 4, true, false}, + {BufferFormat::k16_16UInt, 4, 0, 4, true, true}, + {BufferFormat::k16_16SInt, 4, 0, 4, false, false}, + {BufferFormat::k16_16Float, 4, 0, 4, true, false}, + {BufferFormat::k11_11_10UInt, 4, 0, 4, true, true}, + {BufferFormat::k11_11_10Float, 4, 0, 4, true, false}, + {BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false}, + {BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true}, + {BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false}, + {BufferFormat::k8_8_8_8SNorm, 4, 0, 4, true, false}, + {BufferFormat::k8_8_8_8UInt, 4, 0, 4, true, true}, + {BufferFormat::k8_8_8_8SInt, 4, 0, 4, false, false}, + {BufferFormat::k32_32UInt, 8, 0, 8, true, true}, + {BufferFormat::k32_32SInt, 8, 0, 8, false, false}, + {BufferFormat::k32_32Float, 8, 0, 8, true, false}, + {BufferFormat::k16_16_16_16UNorm, 8, 0, 8, true, false}, + {BufferFormat::k16_16_16_16SNorm, 8, 0, 8, true, false}, + {BufferFormat::k16_16_16_16UInt, 8, 0, 8, true, true}, + {BufferFormat::k16_16_16_16SInt, 8, 0, 8, false, false}, + {BufferFormat::k16_16_16_16Float, 8, 0, 8, true, false}, + {BufferFormat::k32_32_32UInt, 12, 0, 12, true, true}, + {BufferFormat::k32_32_32SInt, 12, 0, 12, false, false}, + {BufferFormat::k32_32_32Float, 12, 0, 12, true, false}, + {BufferFormat::k32_32_32_32UInt, 16, 0, 16, true, true}, + {BufferFormat::k32_32_32_32SInt, 16, 0, 16, false, false}, + {BufferFormat::k32_32_32_32Float, 16, 0, 16, true, false}, + {BufferFormat::k8Srgb, 1, 0, 0, true, false}, + {BufferFormat::k8_8Srgb, 2, 0, 0, true, false}, + {BufferFormat::k8_8_8_8Srgb, 4, 0, 4, true, false}, + {BufferFormat::k9_9_9_5Float, 4, 0, 0, true, false}, + {BufferFormat::k5_6_5UNorm, 2, 0, 2, true, false}, + {BufferFormat::k5_5_5_1UNorm, 2, 0, 2, true, false}, + {BufferFormat::k1_5_5_5UNorm, 0, 0, 2, false, false}, + {BufferFormat::k4_4_4_4UNorm, 2, 0, 2, true, false}, + {BufferFormat::kFmask8_S4_F4, 1, 0, 1, true, false}, + {BufferFormat::kBc1UNorm, 0, 8, 0, true, false}, + {BufferFormat::kBc1Srgb, 0, 8, 0, true, false}, + {BufferFormat::kBc2UNorm, 0, 16, 0, true, false}, + {BufferFormat::kBc2Srgb, 0, 16, 0, true, false}, + {BufferFormat::kBc3UNorm, 0, 16, 0, true, false}, + {BufferFormat::kBc3Srgb, 0, 16, 0, true, false}, + {BufferFormat::kBc4UNorm, 0, 8, 0, true, false}, + {BufferFormat::kBc4SNorm, 0, 8, 0, true, false}, + {BufferFormat::kBc5UNorm, 0, 16, 0, true, false}, + {BufferFormat::kBc5SNorm, 0, 16, 0, true, false}, + {BufferFormat::kBc6UFloat, 0, 16, 0, true, false}, + {BufferFormat::kBc6SFloat, 0, 16, 0, true, false}, + {BufferFormat::kBc7UNorm, 0, 16, 0, true, false}, + {BufferFormat::kBc7Srgb, 0, 16, 0, true, false}, }; constexpr uint8_t RenderTargetTypeBit(ChannelType type) { @@ -202,18 +202,22 @@ uint32_t RenderTargetBytesPerElement(BufferFormat format) { return info != nullptr ? info->render_target_bytes_per_element : 0; } -bool IsSupportedTextureFormat(BufferFormat format) { - const auto* info = FindFormatInfo(format); - return info != nullptr && info->supported_sampled_texture_format; -} - -bool IsUintTextureFormat(BufferFormat format) { - const auto* info = FindFormatInfo(format); - return info != nullptr && info->unsigned_integer_texture_format; -} - bool IsFmaskTextureFormat(BufferFormat format) { return format == BufferFormat::kFmask8_S4_F4; } +bool IsSampledTextureFormat(BufferFormat format) { + const auto* info = FindFormatInfo(format); + return info != nullptr && info->sampled_texture; +} + +bool IsUintTextureFormat(BufferFormat format) { + const auto* info = FindFormatInfo(format); + return info != nullptr && info->uint_texture; +} + +BufferFormat RemapTextureFormat(BufferFormat format) { + return format == BufferFormat::k11_11_10UInt ? BufferFormat::k32UInt : format; +} + } // namespace Libs::Graphics::Prospero diff --git a/src/graphics/guest_gpu/gpu_format.h b/src/graphics/guest_gpu/gpu_format.h index 6e88a9210..20d63347b 100644 --- a/src/graphics/guest_gpu/gpu_format.h +++ b/src/graphics/guest_gpu/gpu_format.h @@ -41,9 +41,10 @@ RenderTargetFormatEncoding ResolveRenderTargetFormat(ChannelLayout layout, Chann uint32_t NumBytesPerElement(BufferFormat format); uint32_t BlockCompressedBytesPerBlock(BufferFormat format); uint32_t RenderTargetBytesPerElement(BufferFormat format); -bool IsSupportedTextureFormat(BufferFormat format); -bool IsUintTextureFormat(BufferFormat format); bool IsFmaskTextureFormat(BufferFormat format); +bool IsSampledTextureFormat(BufferFormat format); +bool IsUintTextureFormat(BufferFormat format); +BufferFormat RemapTextureFormat(BufferFormat format); } // namespace Libs::Graphics::Prospero diff --git a/src/graphics/host_gpu/renderer/image/textureCommon.cpp b/src/graphics/host_gpu/renderer/image/textureCommon.cpp index fd35a1216..bd72601db 100644 --- a/src/graphics/host_gpu/renderer/image/textureCommon.cpp +++ b/src/graphics/host_gpu/renderer/image/textureCommon.cpp @@ -119,13 +119,16 @@ vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) { return components; } -vk::Format TextureGetFormat(Prospero::BufferFormat format) { - const auto vk_format = VulkanFormat(format); +SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format) { + const auto backing_format = Prospero::RemapTextureFormat(format); + const auto vk_format = VulkanFormat(backing_format); + const auto conversion_format = + backing_format != format ? format : Prospero::BufferFormat::kInvalid; if (vk_format != vk::Format::eUndefined) { - return vk_format; + return SurfaceFormatInfo(vk_format, conversion_format); } EXIT("unknown format: fmt = %u\n", static_cast(format)); - return vk::Format::eUndefined; + return SurfaceFormatInfo(vk::Format::eUndefined, Prospero::BufferFormat::kInvalid); } namespace { diff --git a/src/graphics/host_gpu/renderer/image/textureCommon.h b/src/graphics/host_gpu/renderer/image/textureCommon.h index dc3bef4e4..849e27599 100644 --- a/src/graphics/host_gpu/renderer/image/textureCommon.h +++ b/src/graphics/host_gpu/renderer/image/textureCommon.h @@ -3,7 +3,7 @@ #include "common/abi.h" #include "common/common.h" -#include "graphics/guest_gpu/gpu_defs.h" +#include "graphics/guest_gpu/gpu_format.h" #include "graphics/guest_gpu/tile.h" #include "graphics/host_gpu/vulkanCommon.h" @@ -19,6 +19,15 @@ struct RenderTargetFormatInfo { Prospero::ColorComponentMapping export_mapping; }; +struct SurfaceFormatInfo { + explicit constexpr SurfaceFormatInfo(vk::Format format, + Prospero::BufferFormat conversion_format) + : vk_format(format), conversion_format(conversion_format) {} + + vk::Format vk_format; + Prospero::BufferFormat conversion_format; +}; + struct TextureUploadMipLayout { uint64_t offset = 0; uint64_t size = 0; @@ -35,7 +44,7 @@ struct TextureUploadLayout { }; vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle); -vk::Format TextureGetFormat(Prospero::BufferFormat format); +SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format); RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout, Prospero::ChannelType type, Prospero::ChannelOrder order); diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index fdcd3d51e..59f36da3f 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -261,13 +261,12 @@ bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor, c descriptor.BaseArray5() <= descriptor.Depth() && descriptor.Depth() < image.info.resources.layers; const bool levels_ok = - multisampled - ? descriptor.BaseLevel() == 0 && descriptor.LastLevel() >= 1 && - descriptor.LastLevel() <= 3 && - (r128 || descriptor.MaxMip() == descriptor.LastLevel()) && - image.info.resources.levels == 1 && image.info.samples == samples - : descriptor.BaseLevel() == 0 && descriptor.LastLevel() == 0 && - (r128 || descriptor.MaxMip() == 0) && image.info.samples == 1; + multisampled ? descriptor.BaseLevel() == 0 && descriptor.LastLevel() >= 1 && + descriptor.LastLevel() <= 3 && + (r128 || descriptor.MaxMip() == descriptor.LastLevel()) && + image.info.resources.levels == 1 && image.info.samples == samples + : descriptor.BaseLevel() == 0 && descriptor.LastLevel() == 0 && + (r128 || descriptor.MaxMip() == 0) && image.info.samples == 1; return image.info.IsDepth() && width == image.info.extent.width && height == image.info.extent.height && (supported_2d || supported_array || supported_cube || supported_msaa_2d || @@ -289,9 +288,9 @@ bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor, co const uint32_t field4_expected = descriptor.Depth() | (descriptor.BaseArray5() << 16u); const uint32_t field5_expected = (static_cast(descriptor.PerfMod5()) << 20u) | (static_cast(descriptor.MaxMip()) << 4u); - const bool common = (descriptor.fields[1] & field1_reserved_mask) == 0 && - (descriptor.fields[2] & field2_reserved_mask) == 0 && - descriptor.fields[3] == field3_expected; + const bool common = (descriptor.fields[1] & field1_reserved_mask) == 0 && + (descriptor.fields[2] & field2_reserved_mask) == 0 && + descriptor.fields[3] == field3_expected; if (r128) { return common && descriptor.fields[4] == 0 && descriptor.fields[5] == 0 && descriptor.fields[6] == 0 && descriptor.fields[7] == 0; @@ -422,9 +421,8 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag !descriptor.MsaaDepth(); } -static bool IsSupportedStorageTextureEncoding( - const ShaderRecompiler::IR::ImageResource& resource, - const ShaderTextureResource& descriptor) { +static bool IsSupportedStorageTextureEncoding(const ShaderRecompiler::IR::ImageResource& resource, + const ShaderTextureResource& descriptor) { constexpr uint32_t field1_reserved_mask = 0x200fff00u; constexpr uint32_t field2_reserved_mask = 0xf0003000u; constexpr uint32_t field5_expected = 0x00700000u; @@ -458,9 +456,10 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource, const bool raw_sint_storage = format == Prospero::BufferFormat::k32SInt && uint_resource && resource.written && !resource.read && !resource.atomic; const bool format_ok = - raw_sint_storage || (Prospero::IsSupportedTextureFormat(format) && - uint_resource == Prospero::IsUintTextureFormat(format) && - (!resource.atomic || format == Prospero::BufferFormat::k32UInt)); + raw_sint_storage || + (Prospero::IsSampledTextureFormat(format) && + uint_resource == Prospero::IsUintTextureFormat(format) && + (!resource.atomic || format == Prospero::BufferFormat::k32UInt)); if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) { return; } @@ -567,15 +566,17 @@ static void PopulateTextureMipLayout(ImageInfo& info) { static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& resource, const ShaderTextureResource& descriptor, vk::Format format, - bool storage, uint32_t view_levels, uint32_t image_layers) { + bool shader_conversion, bool storage, + uint32_t view_levels, uint32_t image_layers) { ImageViewInfo view {}; view.format = format; view.aspect = vk::ImageAspectFlagBits::eColor; view.base_level = descriptor.BaseLevel(); view.level_count = view_levels; - view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled; - view.mapping = - storage ? vk::ComponentMapping {} : TextureGetComponentMapping(descriptor.DstSelXYZW()); + view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled; + view.mapping = storage || shader_conversion + ? vk::ComponentMapping {} + : TextureGetComponentMapping(descriptor.DstSelXYZW()); switch (resource.dimension) { case ShaderRecompiler::Decoder::ImageDimension::Dim1D: view.type = vk::ImageViewType::e1D; @@ -658,8 +659,8 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso const bool msaa_array = type == Prospero::ImageType::kColor2DMsaaArray; if ((!multisampled && (base_level > view_last_level || view_last_level >= levels)) || (multisampled && - (base_level != 0 || last_level == 0 || last_level > 3 || max_mip != last_level || !msaa_tile || - (descriptor.MsaaDepth() && !depth_tile) || + (base_level != 0 || last_level == 0 || last_level > 3 || max_mip != last_level || + !msaa_tile || (descriptor.MsaaDepth() && !depth_tile) || (!msaa_array && (descriptor.Depth() != 0 || descriptor.BaseArray5() != 0))))) { EXIT("unsupported texture mip view: base=%u last=%u levels=%u max=%u type=%u tile=%u " "kind=%u dimension=%u mip_mode=%u read=%d written=%d " @@ -675,11 +676,16 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso const auto samples = multisampled ? 1u << last_level : 1u; const auto view_levels = multisampled ? 1u : static_cast(view_last_level - base_level) + 1u; - const auto depth = static_cast(descriptor.Depth()) + 1u; - const auto format = descriptor.Format(); + const auto depth = static_cast(descriptor.Depth()) + 1u; + const auto format = descriptor.Format(); + const auto surface_format = TextureGetSurfaceFormatInfo(format); + const bool shader_conversion = + surface_format.conversion_format != Prospero::BufferFormat::kInvalid; const bool sampled_numeric_class = - storage || ((resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) == - Prospero::IsUintTextureFormat(format)); + storage || + (Prospero::IsSampledTextureFormat(format) && + (resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) == + Prospero::IsUintTextureFormat(format)); if (!storage && (resource.kind == ShaderRecompiler::IR::ResourceKind::Image || resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) && @@ -715,7 +721,7 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso ValidateStorageTexture(resource, descriptor, size.size); } - const auto pixel_format = TextureGetFormat(format); + const auto pixel_format = surface_format.vk_format; const auto storage_view_format = storage && format == Prospero::BufferFormat::k32SInt ? vk::Format::eR32Uint : SrgbStorageViewFormat(pixel_format); @@ -740,11 +746,11 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso } else { PopulateTextureMipLayout(desc.info); } - desc.view_info = TextureViewInfo(resource, descriptor, view_format, storage, view_levels, - desc.info.resources.layers); + desc.view_info = TextureViewInfo(resource, descriptor, view_format, shader_conversion, storage, + view_levels, desc.info.resources.layers); desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture; - auto id = texture_cache.FindImage(desc); + auto id = texture_cache.FindImage(desc, shader_conversion); auto* image = &texture_cache.GetImage(id); const bool stencil_association = static_cast(image->depth_id); if (stencil_association) { @@ -780,6 +786,9 @@ static vk::Sampler NativeSampler(RenderContext& context, if (!depth_compare) { descriptor.fields[0] &= ~(0x7u << 12u); } + if (program.info.samplers[index].force_point_filtering) { + descriptor.SetPointFiltering(); + } return context.GetSamplerCache().GetSampler(descriptor); } diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp index 5bd22061b..353429f9d 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp @@ -1,3 +1,4 @@ +#include "graphics/guest_gpu/gpu_format.h" #include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h" #include @@ -201,15 +202,15 @@ uint32_t FloatBits(ValueEmitContext& ctx, uint32_t value) { uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, bool integer, bool dref, const IR::MemoryInfo& mem, bool gather = false) { if (mem.data_bits == 16u) { - uint32_t packed[4] = {ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0), - ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0)}; - const auto scalar = [&](uint32_t index) { + uint32_t packed[4] = {ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0), + ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0)}; + const auto scalar = [&](uint32_t index) { if (dref) return value; const auto component = gather ? index : DmaskComponent(mem.dmask, index); const auto result = ctx.state.builder.AllocateId(); - ctx.state.builder.AddFunction( - {OpCompositeExtract, integer ? TypeU32(ctx.state) : TypeF32(ctx.state), result, - value, component}); + ctx.state.builder.AddFunction({OpCompositeExtract, + integer ? TypeU32(ctx.state) : TypeF32(ctx.state), + result, value, component}); return result; }; for (uint32_t word = 0; word < mem.data_dwords; word++) { @@ -221,12 +222,12 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, bool integer, bool : (integer ? ConstantU32(ctx.state, 0) : ZeroF32(ctx.state)); if (integer) { const auto mask = ConstantU32(ctx.state, 0xffffu); - packed[word] = Binary( - ctx.state, OpBitwiseOr, TypeU32(ctx.state), - Binary(ctx.state, OpBitwiseAnd, TypeU32(ctx.state), low, mask), - Binary(ctx.state, OpShiftLeftLogical, TypeU32(ctx.state), - Binary(ctx.state, OpBitwiseAnd, TypeU32(ctx.state), high, mask), - ConstantU32(ctx.state, 16u))); + packed[word] = + Binary(ctx.state, OpBitwiseOr, TypeU32(ctx.state), + Binary(ctx.state, OpBitwiseAnd, TypeU32(ctx.state), low, mask), + Binary(ctx.state, OpShiftLeftLogical, TypeU32(ctx.state), + Binary(ctx.state, OpBitwiseAnd, TypeU32(ctx.state), high, mask), + ConstantU32(ctx.state, 16u))); } else { const auto pair = ctx.state.builder.AllocateId(); ctx.state.builder.AddFunction( @@ -357,8 +358,120 @@ uint32_t InverseSwizzle(uint32_t swizzle, uint32_t component) { return UINT32_MAX; } +Format::BufferFormatInfo ImageConversionFormat(const EmitterState& state, + const IR::MemoryInfo& mem) { + const auto format = state.program.info.images[mem.resource].conversion_format; + if (format == Prospero::BufferFormat::kInvalid) return {}; + const auto info = Format::GetFormatInfo(format); + EXIT_IF(!Prospero::IsSampledTextureFormat(format) || + !Prospero::IsUintTextureFormat(format) || + Prospero::RemapTextureFormat(format) == format || + info.type != Format::ComponentType::Uint || !info.packed_bitfield || + info.byte_size != sizeof(uint32_t) || info.component_count == 0u || + info.component_count > 4u); + return info; +} + +uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t texel) { + const auto info = ImageConversionFormat(ctx.state, mem); + if (info.format == Prospero::BufferFormat::kInvalid) return texel; + + const auto packed = ctx.state.builder.AllocateId(); + ctx.state.builder.AddFunction({OpCompositeExtract, TypeU32(ctx.state), packed, texel, 0u}); + uint32_t components[4] = {ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0), + ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0)}; + for (uint32_t component = 0; component < info.component_count; component++) { + components[component] = ctx.state.builder.AllocateId(); + ctx.state.builder.AddFunction({OpBitFieldUExtract, TypeU32(ctx.state), + components[component], packed, + ConstantU32(ctx.state, info.component_bit_offset[component]), + ConstantU32(ctx.state, info.component_bits[component])}); + } + for (uint32_t component = info.component_count; component < 4u; component++) { + components[component] = components[component % info.component_count]; + } + + const auto swizzle = ctx.state.program.info.images[mem.resource].shader_swizzle; + uint32_t selected[4] {}; + for (uint32_t component = 0; component < 4u; component++) { + const auto selector = (swizzle >> (component * 3u)) & 7u; + if (selector == 1u) { + selected[component] = ConstantU32(ctx.state, 1u); + } else if (selector >= 4u) { + selected[component] = components[selector - 4u]; + } else { + selected[component] = ConstantU32(ctx.state, 0u); + } + } + const auto result = ctx.state.builder.AllocateId(); + ctx.state.builder.AddFunction({OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result, + selected[0], selected[1], selected[2], selected[3]}); + return result; +} + +uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t gathered) { + const auto info = ImageConversionFormat(ctx.state, mem); + if (info.format == Prospero::BufferFormat::kInvalid) return gathered; + + const auto component = ImageGatherComponent(mem.dmask); + const auto selector = + (ctx.state.program.info.images[mem.resource].shader_swizzle >> (component * 3u)) & 7u; + if (selector < 4u) { + const auto value = ConstantU32(ctx.state, selector == 1u ? 1u : 0u); + return ctx.state.builder.Constant(OpConstantComposite, TypeU32Vector(ctx.state, 4), + {value, value, value, value}); + } + + uint32_t values[4] {}; + for (uint32_t lane = 0; lane < 4u; lane++) { + const auto packed = ctx.state.builder.AllocateId(); + ctx.state.builder.AddFunction( + {OpCompositeExtract, TypeU32(ctx.state), packed, gathered, lane}); + values[lane] = ctx.state.builder.AllocateId(); + const auto physical = (selector - 4u) % info.component_count; + ctx.state.builder.AddFunction({OpBitFieldUExtract, TypeU32(ctx.state), values[lane], packed, + ConstantU32(ctx.state, info.component_bit_offset[physical]), + ConstantU32(ctx.state, info.component_bits[physical])}); + } + const auto result = ctx.state.builder.AllocateId(); + ctx.state.builder.AddFunction({OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result, + values[0], values[1], values[2], values[3]}); + return result; +} + +uint32_t PackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t texel) { + const auto info = ImageConversionFormat(ctx.state, mem); + if (info.format == Prospero::BufferFormat::kInvalid) return texel; + + auto packed = ConstantU32(ctx.state, 0u); + for (uint32_t component = 0; component < info.component_count; component++) { + const auto value = ctx.state.builder.AllocateId(); + ctx.state.builder.AddFunction( + {OpCompositeExtract, TypeU32(ctx.state), value, texel, component}); + const auto maximum = + ConstantU32(ctx.state, info.component_bits[component] == 32u + ? UINT32_MAX + : (1u << info.component_bits[component]) - 1u); + const auto within = Binary(ctx.state, OpULessThan, TypeBool(ctx.state), value, maximum); + const auto clamped = ctx.state.builder.AllocateId(); + ctx.state.builder.AddFunction( + {OpSelect, TypeU32(ctx.state), clamped, within, value, maximum}); + const auto shifted = + info.component_bit_offset[component] == 0u + ? clamped + : Binary(ctx.state, OpShiftLeftLogical, TypeU32(ctx.state), clamped, + ConstantU32(ctx.state, info.component_bit_offset[component])); + packed = Binary(ctx.state, OpBitwiseOr, TypeU32(ctx.state), packed, shifted); + } + const auto zero = ConstantU32(ctx.state, 0u); + const auto result = ctx.state.builder.AllocateId(); + ctx.state.builder.AddFunction( + {OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result, packed, zero, zero, zero}); + return result; +} + uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t data, bool integer) { - const auto swizzle = ctx.state.program.info.images[mem.resource].storage_swizzle; + const auto swizzle = ctx.state.program.info.images[mem.resource].shader_swizzle; uint32_t values[4] {}; const auto dmask = mem.dmask != 0u ? mem.dmask : 1u; for (uint32_t component = 0; component < 4u; component++) { @@ -380,15 +493,15 @@ uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t d } } values[component] = integer ? raw - : mem.data_bits == 16u ? EmitF16BitsToF32(ctx.state, raw) - : Unary(ctx.state, OpBitcast, - TypeF32(ctx.state), raw); + : mem.data_bits == 16u + ? EmitF16BitsToF32(ctx.state, raw) + : Unary(ctx.state, OpBitcast, TypeF32(ctx.state), raw); } const auto texel = ctx.state.builder.AllocateId(); ctx.state.builder.AddFunction( {OpCompositeConstruct, integer ? TypeU32Vector(ctx.state, 4) : TypeF32Vector(ctx.state, 4), texel, values[0], values[1], values[2], values[3]}); - return texel; + return PackImageTexel(ctx, mem, texel); } uint32_t ImageAtomicOpcode(IR::ValueOpcode opcode) { @@ -468,7 +581,8 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4), color, image, coord, ImageOperandsLodMask, LodU32(ctx, mem, *address, view)}); } - return ResultVector(ctx, color, integer, false, mem); + return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), integer, false, + mem); })); return true; } @@ -491,6 +605,11 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { const auto layout = Layout(mem, view); const bool integer = mem.kind == IR::ResourceKind::ImageUint; const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare); + if (dref && state.program.info.images[mem.resource].conversion_format != + Prospero::BufferFormat::kInvalid) { + ctx.Fail(inst, "uses depth comparison with a packed integer image"); + return true; + } const auto coord = CoordF32(ctx, mem, *address, layout.coord, ImageViewCoordinateComponents(view)); if (op == IR::ValueOpcode::ImageGatherRaw) { @@ -511,7 +630,10 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { sample, sampled, coord, - ConstantU32(state, ImageGatherComponent(mem.dmask))}; + ConstantU32(state, ImageConversionFormat(state, mem).format == + Prospero::BufferFormat::kInvalid + ? ImageGatherComponent(mem.dmask) + : 0u)}; if (HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) { words.push_back(ImageOperandsConstOffsetsMask); words.push_back(HorizontalOffsets(state, view)); @@ -520,7 +642,8 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { words.push_back(PackedOffset(ctx, mem, *address, layout, view)); } state.builder.AddFunction(words); - ctx.Define(inst, ResultVector(ctx, sample, integer && !dref, false, mem, true)); + ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample), + integer && !dref, false, mem, true)); return true; } const bool explicit_lod = HasFlag(mem, Decoder::ImageSampleFlagDerivative) || @@ -570,7 +693,9 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { }; const auto& image = state.program.info.images[mem.resource]; if (image.indirect_root != mem.resource) { - ctx.Define(inst, ResultVector(ctx, EmitSample(mem.resource), integer, dref, mem)); + const auto sample = EmitSample(mem.resource); + ctx.Define(inst, ResultVector(ctx, dref ? sample : UnpackImageTexel(ctx, mem, sample), + integer, dref, mem)); return true; } const auto* handle = image_arg.ResolveInstruction(); @@ -661,7 +786,9 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { } EmitLabel(state, merge_label); state.builder.AddFunction(phi_words); - ctx.Define(inst, ResultVector(ctx, phi_words[2], integer, dref, mem)); + ctx.Define(inst, + ResultVector(ctx, dref ? phi_words[2] : UnpackImageTexel(ctx, mem, phi_words[2]), + integer, dref, mem)); return true; } const auto atomic_opcode = ImageAtomicOpcode(op); diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 232dcbc68..0b3836d24 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -210,7 +210,7 @@ struct BufferResource { enum class ImageMipMode { None, DynamicStorage }; -constexpr uint32_t StorageImageIdentitySwizzle = 0x00000facu; +constexpr uint32_t ShaderImageIdentitySwizzle = 0x00000facu; struct ImageResource { static constexpr uint32_t NoIndirectImage = UINT32_MAX; @@ -221,7 +221,8 @@ struct ImageResource { Decoder::ImageDimension dimension = Decoder::ImageDimension::Unknown; ImageMipMode mip_mode = ImageMipMode::None; uint32_t mip_count = 1; - uint32_t storage_swizzle = StorageImageIdentitySwizzle; + Prospero::BufferFormat conversion_format = Prospero::BufferFormat::kInvalid; + uint32_t shader_swizzle = ShaderImageIdentitySwizzle; bool read = false; bool written = false; bool atomic = false; @@ -237,8 +238,9 @@ struct ImageResource { }; struct SamplerResource { - uint32_t source = 0; - uint32_t first_use_pc = 0; + uint32_t source = 0; + uint32_t first_use_pc = 0; + bool force_point_filtering = false; bool operator==(const SamplerResource& other) const = default; }; diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp index 7ee465d3b..14efdc560 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp @@ -58,8 +58,8 @@ bool ValidImageDescriptor(const DescriptorValue& descriptor, bool r128 = false) if (type < Prospero::ImageType::kColor1D || format == Prospero::BufferFormat::kInvalid) { return false; } - if (r128 && type != Prospero::ImageType::kColor1D && - type != Prospero::ImageType::kColor2D && type != Prospero::ImageType::kColor2DMsaa) { + if (r128 && type != Prospero::ImageType::kColor1D && type != Prospero::ImageType::kColor2D && + type != Prospero::ImageType::kColor2DMsaa) { return false; } if (type == Prospero::ImageType::kColor2DMsaa || @@ -77,6 +77,11 @@ uint32_t DescriptorImageSwizzle(const DescriptorValue& descriptor) { return descriptor.dwords[3] & 0xfffu; } +Prospero::BufferFormat ImageConversionFormat(Prospero::BufferFormat format) { + return Prospero::RemapTextureFormat(format) != format ? format + : Prospero::BufferFormat::kInvalid; +} + bool DescriptorIsCube(const DescriptorValue& descriptor) { return static_cast((descriptor.dwords[3] >> 28u) & 0xfu) == Prospero::ImageType::kCube; @@ -428,7 +433,8 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh program.info.images[root].dimension != image.dimension || program.info.images[root].mip_mode != image.mip_mode || program.info.images[root].mip_count != image.mip_count || - program.info.images[root].storage_swizzle != image.storage_swizzle || + program.info.images[root].conversion_format != image.conversion_format || + program.info.images[root].shader_swizzle != image.shader_swizzle || program.info.images[root].cube != image.cube) { if (error != nullptr) { *error = fmt::format( @@ -471,19 +477,37 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh image.kind == ResourceKind::StorageImageUint) { const bool storage = image.kind == ResourceKind::StorageImage || image.kind == ResourceKind::StorageImageUint; - if (storage && image.storage_swizzle != DescriptorImageSwizzle(descriptor)) { + const auto format = + static_cast((descriptor.dwords[1] >> 20u) & 0x1ffu); + if (image.atomic && format != Prospero::BufferFormat::k32UInt) { if (error != nullptr) { - *error = fmt::format("storage image descriptor {} changed swizzle", i); + *error = fmt::format( + "atomic image descriptor {} changed to unsupported format {}", i, + static_cast(format)); } return false; } - const auto format = - static_cast((descriptor.dwords[1] >> 20u) & 0x1ffu); - const bool raw_sint_storage = storage && format == Prospero::BufferFormat::k32SInt && - !image.read && !image.atomic; - const bool uint_descriptor = Prospero::IsUintTextureFormat(format) || raw_sint_storage; - const auto uint_program = image.kind == ResourceKind::ImageUint || - image.kind == ResourceKind::StorageImageUint; + const auto conversion_format = ImageConversionFormat(format); + if (image.conversion_format != conversion_format) { + if (error != nullptr) { + *error = fmt::format("image descriptor {} changed format conversion", i); + } + return false; + } + if ((storage || conversion_format != Prospero::BufferFormat::kInvalid) && + image.shader_swizzle != DescriptorImageSwizzle(descriptor)) { + if (error != nullptr) { + *error = fmt::format("image descriptor {} changed swizzle", i); + } + return false; + } + const bool raw_sint_storage = + storage && format == Prospero::BufferFormat::k32SInt && image.written && + !image.read && !image.atomic; + const bool uint_descriptor = + Prospero::IsUintTextureFormat(format) || raw_sint_storage; + const auto uint_program = image.kind == ResourceKind::ImageUint || + image.kind == ResourceKind::StorageImageUint; if (uint_descriptor != uint_program && !(image.atomic && uint_program)) { if (error != nullptr) { *error = fmt::format( @@ -811,16 +835,23 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri } image.dimension = descriptor_dimension; image.cube = DescriptorIsCube(descriptor); - if (image.kind == ResourceKind::StorageImage || - image.kind == ResourceKind::StorageImageUint) { - image.storage_swizzle = DescriptorImageSwizzle(descriptor); - } const auto format = static_cast((descriptor.dwords[1] >> 20u) & 0x1ffu); + if (image.atomic && format != Prospero::BufferFormat::k32UInt) { + if (error != nullptr) { + *error = fmt::format("atomic image descriptor {} uses unsupported format {}", i, + static_cast(format)); + } + return false; + } const bool storage = image.kind == ResourceKind::StorageImage || image.kind == ResourceKind::StorageImageUint; - const bool raw_sint_storage = - storage && format == Prospero::BufferFormat::k32SInt && !image.read && !image.atomic; + image.conversion_format = ImageConversionFormat(format); + if (storage || image.conversion_format != Prospero::BufferFormat::kInvalid) { + image.shader_swizzle = DescriptorImageSwizzle(descriptor); + } + const bool raw_sint_storage = storage && format == Prospero::BufferFormat::k32SInt && + image.written && !image.read && !image.atomic; const bool uint_image = Prospero::IsUintTextureFormat(format) || raw_sint_storage; if (uint_image) { switch (image.kind) { @@ -871,16 +902,18 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri continue; } if (NullImageDescriptor(next_snapshot.images[candidate])) { - image.kind = image_class.kind; - image.dimension = image_class.dimension; - image.mip_count = image_class.mip_count; - image.storage_swizzle = image_class.storage_swizzle; - image.cube = image_class.cube; + image.kind = image_class.kind; + image.dimension = image_class.dimension; + image.mip_count = image_class.mip_count; + image.conversion_format = image_class.conversion_format; + image.shader_swizzle = image_class.shader_swizzle; + image.cube = image_class.cube; } if (image.kind != image_class.kind || image.dimension != image_class.dimension || image.mip_mode != image_class.mip_mode || image.mip_count != image_class.mip_count || - image.storage_swizzle != image_class.storage_swizzle || + image.conversion_format != image_class.conversion_format || + image.shader_swizzle != image_class.shader_swizzle || image.depth_compare != image_class.depth_compare || image.cube != image_class.cube) { if (error != nullptr) { @@ -899,9 +932,58 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri return false; } auto memory_info = program.values->memory_info; + + struct SamplerUsage { + bool native = false; + bool point_only = false; + }; + std::vector sampler_usage(next.samplers.size()); + for (const auto& pair: next.sampled_pairs) { + if (pair.image >= next.images.size() || pair.sampler >= next.samplers.size()) { + if (error != nullptr) { + *error = fmt::format("sampled resource pair at pc 0x{:08x} is invalid", + pair.first_use_pc); + } + return false; + } + auto& usage = sampler_usage[pair.sampler]; + if (next.images[pair.image].conversion_format != Prospero::BufferFormat::kInvalid) { + usage.point_only = true; + } else { + usage.native = true; + } + } + std::vector point_sampler(next.samplers.size(), UINT32_MAX); + for (uint32_t i = 0; i < sampler_usage.size(); i++) { + if (!sampler_usage[i].point_only) { + continue; + } + if (!sampler_usage[i].native) { + next.samplers[i].force_point_filtering = true; + point_sampler[i] = i; + continue; + } + if (next.samplers.size() >= ShaderInfo::MaxSamplers) { + if (error != nullptr) { + *error = "point-filter sampler variants exceed the dense sampler resource limit"; + } + return false; + } + point_sampler[i] = static_cast(next.samplers.size()); + auto sampler = next.samplers[i]; + sampler.force_point_filtering = true; + next.samplers.push_back(sampler); + next_snapshot.samplers.push_back(next_snapshot.samplers[i]); + } + for (auto& pair: next.sampled_pairs) { + if (next.images[pair.image].conversion_format != Prospero::BufferFormat::kInvalid) { + pair.sampler = point_sampler[pair.sampler]; + } + } for (const auto* block: program.values->blocks) { for (const auto& inst: *block) { - if (ImageOpcodeInfoOf(inst.GetOpcode()).access == ImageAccess::None) { + const auto image_opcode = ImageOpcodeInfoOf(inst.GetOpcode()); + if (image_opcode.access == ImageAccess::None) { continue; } const auto index = inst.Flags().index; @@ -921,6 +1003,11 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri return false; } const auto& image = next.images[memory.resource]; + if (image_opcode.needs_sampler && + image.conversion_format != Prospero::BufferFormat::kInvalid && + memory.sampler < point_sampler.size()) { + memory.sampler = point_sampler[memory.sampler]; + } if (image.indirect_root == memory.resource && inst.GetOpcode() != ValueOpcode::ImageSampleRaw) { if (error != nullptr) { diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index f61acc877..e115e3c7e 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -1040,9 +1040,10 @@ static std::string ShaderDescribeSpecialization(const ShaderRecompiler::IR::Prog for (uint32_t i = 0; i < program.info.images.size(); i++) { const auto& image = program.info.images[i]; ret += fmt::format( - " i{}[kind={} dim={} mip={}/{} swizzle=0x{:03x} indirect={}/{}+{}]", i, + " i{}[kind={} dim={} mip={}/{} format={} swizzle=0x{:03x} indirect={}/{}+{}]", i, static_cast(image.kind), static_cast(image.dimension), - static_cast(image.mip_mode), image.mip_count, image.storage_swizzle, + static_cast(image.mip_mode), image.mip_count, + static_cast(image.conversion_format), image.shader_swizzle, image.indirect_root, image.indirect_mapping_offset, image.indirect_mapping_capacity); } for (uint32_t i = 0; i < program.info.addresses.size(); i++) { @@ -1083,7 +1084,8 @@ static void ShaderAppendNativeSpecialization(std::vector& ids.push_back(static_cast(image.cube)); ids.push_back(static_cast(image.mip_mode)); ids.push_back(image.mip_count); - ids.push_back(image.storage_swizzle); + ids.push_back(static_cast(image.conversion_format)); + ids.push_back(image.shader_swizzle); ids.push_back(image.indirect_root); ids.push_back(image.indirect_mapping_offset); ids.push_back(image.indirect_mapping_capacity); diff --git a/src/graphics/shader/shaderBindings.h b/src/graphics/shader/shaderBindings.h index 9bc0ac424..301340ca4 100644 --- a/src/graphics/shader/shaderBindings.h +++ b/src/graphics/shader/shaderBindings.h @@ -157,6 +157,16 @@ struct ShaderSamplerResource { [[nodiscard]] bool PointPreclamp() const { return ((fields[2] >> 28u) & 0x1u) == 1; } [[nodiscard]] bool AnisoOverride() const { return ((fields[2] >> 29u) & 0x1u) == 1; } [[nodiscard]] bool BlendZeroPrt() const { return ((fields[2] >> 30u) & 0x1u) == 1; } + + void SetPointFiltering() { + const bool mipmapped = static_cast(MipFilter()) != + Prospero::SamplerMipFilter::kNone; + fields[2] &= ~(0xffu << 20u); + fields[2] |= 1u << 24u; + if (mipmapped) { + fields[2] |= static_cast(Prospero::SamplerMipFilter::kPoint) << 26u; + } + } }; struct ShaderVertexInputBuffer { diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 64bb000c9..99a2f49f5 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -864,6 +864,7 @@ struct TestCase { size_t storage_buffer_range_dwords = 0; std::vector storage_buffer_offsets; bool force_shader_data_storage = false; + bool expected_force_point_sampler = false; std::optional flat_memory_base; std::vector gds_initial; std::vector expected_gds; @@ -9946,30 +9947,52 @@ public: constexpr u32 width = 128, height = 128, levels = 8; TileSurfaceLayout surface{}; const TileSurfaceDescription description{ - format, tile, TileSurfaceDimension::Dim2D, width, height, 1, levels, 1}; + format, tile, TileSurfaceDimension::Dim2D, width, height, 1, + levels, 1}; TileSizeAlign total{}; TileGetTextureTotalSize(format, width, height, 1, levels, tile, false, total); const bool built = TileGetTiledTextureLayout(description, surface); - constexpr u32 tail_xy[7][2] = { - {64, 0}, {0, 64}, {32, 0}, {0, 32}, {16, 0}, {8, 16}, {0, 24}}; - bool valid = built && Prospero::NumBytesPerElement(format) == 4 && - Prospero::BlockCompressedBytesPerBlock(format) == 0 && - Prospero::IsSupportedTextureFormat(format) && - Prospero::IsUintTextureFormat(format) && - surface.texture.block.block_width == 128 && - surface.texture.block.block_height == 128 && - surface.texture.block.block_depth == 1 && - surface.first_tail_level == 1 && - surface.mips[0].offset == 0x10000 && total.size == 0x20000 && - total.align == 0x10000; + const auto surface_format = TextureGetSurfaceFormatInfo(format); + constexpr u32 tail_xy[7][2] = {{64, 0}, {0, 64}, {32, 0}, {0, 32}, + {16, 0}, {8, 16}, {0, 24}}; + bool valid = + built && Prospero::NumBytesPerElement(format) == 4 && + Prospero::BlockCompressedBytesPerBlock(format) == 0 && + Prospero::IsSampledTextureFormat(format) && + Prospero::IsUintTextureFormat(format) && + Prospero::RemapTextureFormat(format) == + Prospero::BufferFormat::k32UInt && + surface_format.vk_format == vk::Format::eR32Uint && + surface_format.conversion_format == format && + VulkanFormat(format) == vk::Format::eUndefined && + surface.texture.block.block_width == 128 && + surface.texture.block.block_height == 128 && + surface.texture.block.block_depth == 1 && + surface.first_tail_level == 1 && surface.mips[0].offset == 0x10000 && + total.size == 0x20000 && total.align == 0x10000; for (u32 level = 1; level < levels && valid; level++) { valid &= surface.mips[level].offset == 0 && surface.mips[level].tail_x == tail_xy[level - 1][0] && surface.mips[level].tail_y == tail_xy[level - 1][1]; } Require(name, "11_11_10 uint layout", valid, - "PS5 packed integer texture metadata or Standard64KB mip tail changed"); + "PS5 packed integer texture metadata or Standard64KB mip tail " + "changed"); + + const auto storage_only_format = Prospero::BufferFormat::k32SInt; + const auto storage_only_surface = + TextureGetSurfaceFormatInfo(storage_only_format); + Require( + name, "storage-only surface mapping", + !Prospero::IsSampledTextureFormat(storage_only_format) && + !Prospero::IsUintTextureFormat(storage_only_format) && + Prospero::RemapTextureFormat(storage_only_format) == + storage_only_format && + storage_only_surface.vk_format == vk::Format::eR32Sint && + storage_only_surface.conversion_format == + Prospero::BufferFormat::kInvalid, + "storage-only native backing was conflated with sampled support"); } { @@ -10914,11 +10937,17 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) { if (test.image_descriptor_swizzle != DstSel(4, 5, 6, 7)) { Require(test.name, "resource specialization", !compiled.program.info.images.empty() && - compiled.program.info.images[0].storage_swizzle == + compiled.program.info.images[0].shader_swizzle == test.image_descriptor_swizzle, "storage image descriptor swizzle did not reach the specialized " "program"); } + if (test.expected_force_point_sampler) { + Require(test.name, "sampler specialization", + compiled.program.info.samplers.size() == 1u && + compiled.program.info.samplers[0].force_point_filtering, + "bit-packed sampled image did not force point filtering"); + } if (test.compile_only) { std::printf("[compute] %-32s ok\n", test.name); return; @@ -17658,6 +17687,72 @@ TestCase ImageLoadR32UintUsesIntegerSampledImage() { return test; } +TestCase ImageLoadPackedUintUnpacksAndSwizzles() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovU32(&code, 20, 2); + AppendVMovU32(&code, 21, 1); + code.push_back(EncodeMimg0(0x00, 0xf)); + code.push_back(EncodeMimg1(0, 20)); + for (u32 component = 0; component < 4; component++) { + AppendStoreVgpr(&code, component, component); + } + AppendEnd(&code); + + TestCase test; + test.name = "ImageLoadPackedUintUnpacksAndSwizzles"; + test.code = std::move(code); + test.expected = {0x2abu, 0x456u, 0x321u, 0x321u}; + test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD, O::BUFFER_STORE_DWORD, + O::S_ENDPGM}; + test.sampled_image_rgba.resize(16); + test.sampled_image_rgba[6] = 0xaae2b321u; + test.sampled_image_format = vk::Format::eR32Uint; + test.sampled_image_dwords_per_pixel = 1; + test.user_data = + MakeSampledTextureData(Prospero::BufferFormat::k11_11_10UInt); + test.has_user_data = true; + test.image_descriptor_swizzle = DstSel(6, 5, 4, 7); + test.required_spirv = {"OpImageFetch %v4uint", "OpBitFieldUExtract"}; + return test; +} + +TestCase ImageSamplePackedUintConvertsSampleAndGather() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovLiteral(&code, 20, 0x3f200000u); + AppendVMovLiteral(&code, 21, 0x3ec00000u); + code.push_back(EncodeMimg0(0x20, 0xf)); + code.push_back(EncodeMimg1(0, 20)); + code.push_back(EncodeMimg0(0x47, 0x2)); + code.push_back(EncodeMimg1(4, 20)); + for (u32 component = 0; component < 8; component++) { + AppendStoreVgpr(&code, component, component); + } + AppendEnd(&code); + + TestCase test; + test.name = "ImageSamplePackedUintConvertsSampleAndGather"; + test.code = std::move(code); + test.expected = {0x2abu, 0x456u, 0x321u, 0x321u, + 0x456u, 0x456u, 0x456u, 0x456u}; + test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::IMAGE_GATHER4_LZ, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.sampled_image_rgba = std::vector(16, 0xaae2b321u); + test.sampled_image_format = vk::Format::eR32Uint; + test.sampled_image_dwords_per_pixel = 1; + test.user_data = + MakeSampledTextureData(Prospero::BufferFormat::k11_11_10UInt); + test.has_user_data = true; + test.image_descriptor_swizzle = DstSel(6, 5, 4, 7); + test.expected_force_point_sampler = true; + test.required_spirv = {"OpImageSampleExplicitLod", "OpImageGather", + "OpBitFieldUExtract"}; + return test; +} + TestCase ImageLoadR128IgnoresAdjacentMaskSgprs() { using O = ShaderOpcode; @@ -18569,6 +18664,67 @@ TestCase ImageStoreR32UintUsesUintStorageImage() { return test; } +TestCase ImageStorePackedUintSaturatesChannels() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovU32(&code, 20, 2); + AppendVMovU32(&code, 21, 1); + AppendVMovU32(&code, 22, 0); + AppendVMovU32(&code, 0, 0x800u); + AppendVMovLiteral(&code, 1, UINT32_MAX); + AppendVMovU32(&code, 2, 0x400u); + AppendVMovU32(&code, 3, 0x55u); + code.push_back(EncodeMimg0(0x08, 0xf)); + code.push_back(EncodeMimg1(0, 20)); + AppendEnd(&code); + + std::vector expected_image(16, 0); + expected_image[1 * 4 + 2] = UINT32_MAX; + + TestCase test; + test.name = "ImageStorePackedUintSaturatesChannels"; + test.code = std::move(code); + test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::S_ENDPGM}; + test.user_data = + MakeStorageTextureData(Prospero::BufferFormat::k11_11_10UInt); + test.has_user_data = true; + test.image_descriptor_swizzle = DstSel(4, 5, 6, 0); + test.storage_image_r32ui = std::vector(16, 0); + test.expected_storage_image_r32ui = std::move(expected_image); + test.required_spirv = {"R32ui", "OpULessThan", "OpSelect"}; + return test; +} + +TestCase ImageStorePackedUintHonorsSparseDmask() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovU32(&code, 20, 2); + AppendVMovU32(&code, 21, 1); + AppendVMovU32(&code, 22, 0); + AppendVMovU32(&code, 0, 0x321u); + AppendVMovU32(&code, 1, 0x2abu); + code.push_back(EncodeMimg0(0x08, 0x5)); + code.push_back(EncodeMimg1(0, 20)); + AppendEnd(&code); + + std::vector expected_image(16, 0); + expected_image[1 * 4 + 2] = 0xaac00321u; + + TestCase test; + test.name = "ImageStorePackedUintHonorsSparseDmask"; + test.code = std::move(code); + test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::S_ENDPGM}; + test.user_data = + MakeStorageTextureData(Prospero::BufferFormat::k11_11_10UInt); + test.has_user_data = true; + test.image_descriptor_swizzle = DstSel(4, 5, 6, 0); + test.storage_image_r32ui = std::vector(16, 0); + test.expected_storage_image_r32ui = std::move(expected_image); + return test; +} + TestCase ComputeTgSizeSgprUsesWaveMetadata() { using O = ShaderOpcode; @@ -19157,6 +19313,8 @@ std::vector MakeCases() { AddCase(BufferAtomicFMaxContendedWorkgroup); AddCase(ImageLoadVariants); AddCase(ImageLoadR32UintUsesIntegerSampledImage); + AddCase(ImageLoadPackedUintUnpacksAndSwizzles); + AddCase(ImageSamplePackedUintConvertsSampleAndGather); AddCase(ImageLoadR128IgnoresAdjacentMaskSgprs); AddCase(ImageLoad1DUsesScalarCoordinate); AddCase(ImageLoad1DArrayUsesLayerCoordinate); @@ -19183,6 +19341,8 @@ std::vector MakeCases() { AddCase(ImageStoreR32FloatUsesFormatlessStorageImage); AddCase(ImageStoreR32SintUsesRawUintView); AddCase(ImageStoreR32UintUsesUintStorageImage); + AddCase(ImageStorePackedUintSaturatesChannels); + AddCase(ImageStorePackedUintHonorsSparseDmask); AddCase(ComputeTgSizeSgprUsesWaveMetadata); AddCase(ImageAtomicVariants); AddCase(ImageAtomicGlc0DoesNotReturnOldValue); @@ -21227,7 +21387,7 @@ void CheckStorageTextureDepthTileUploadLayout() { "1x1 R8_UINT depth tile lost its 64 KiB source footprint"); std::printf("[host] %-32s ok\n", "StorageTextureDepthTileUpload"); } -void CheckStorageImageSwizzleSpecializationId() { +void CheckImageSpecializationId() { std::array code{}; HW::ComputeShaderInfo regs{}; regs.cs_regs.data_addr = reinterpret_cast(code.data()); @@ -21239,7 +21399,10 @@ void CheckStorageImageSwizzleSpecializationId() { image.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D; identity_program.info.images.push_back(image); auto rgb1_program = identity_program; - rgb1_program.info.images[0].storage_swizzle = DstSel(4, 5, 6, 1); + rgb1_program.info.images[0].shader_swizzle = DstSel(4, 5, 6, 1); + auto converted_program = identity_program; + converted_program.info.images[0].conversion_format = + Prospero::BufferFormat::k11_11_10UInt; auto mip1_program = identity_program; mip1_program.info.images[0].mip_mode = ShaderRecompiler::IR::ImageMipMode::DynamicStorage; @@ -21260,6 +21423,9 @@ void CheckStorageImageSwizzleSpecializationId() { auto rgb1_info = identity_info; rgb1_info.stage.program = std::make_shared(rgb1_program); + auto converted_info = identity_info; + converted_info.stage.program = + std::make_shared(converted_program); auto mip1_info = identity_info; mip1_info.stage.program = std::make_shared(mip1_program); @@ -21275,17 +21441,109 @@ void CheckStorageImageSwizzleSpecializationId() { const auto identity_id = ShaderGetIdCS(regs, identity_info, true); const auto rgb1_id = ShaderGetIdCS(regs, rgb1_info, true); + const auto converted_id = ShaderGetIdCS(regs, converted_info, true); const auto mip1_id = ShaderGetIdCS(regs, mip1_info, true); const auto mip3_id = ShaderGetIdCS(regs, mip3_info, true); const auto array_id = ShaderGetIdCS(regs, array_info, true); const auto cube_id = ShaderGetIdCS(regs, cube_info, true); - Require("StorageImageSwizzleSpecializationId", "pipeline cache key", + Require("ImageSpecializationId", "pipeline cache key", identity_id != rgb1_id && identity_id.ids.size() == rgb1_id.ids.size() && + identity_id != converted_id && + identity_id.ids.size() == converted_id.ids.size() && mip1_id != mip3_id && mip1_id.ids.size() == mip3_id.ids.size() && array_id != cube_id && array_id.ids.size() == cube_id.ids.size(), - "storage swizzle, mip-count, or cube variants share a pipeline ID"); - std::printf("[host] %-32s ok\n", "StorageImageSwizzlePipelineId"); + "image conversion, swizzle, mip-count, or cube variants share a " + "pipeline ID"); + + ShaderSamplerResource filtered_sampler{}; + filtered_sampler.fields[2] = + (1u << 20u) | (3u << 22u) | (2u << 24u) | (2u << 26u); + filtered_sampler.SetPointFiltering(); + Require("ImageSpecializationId", "forced point sampler", + filtered_sampler.XyMagFilter() == 0u && + filtered_sampler.XyMinFilter() == 0u && + filtered_sampler.ZFilter() == 1u && + filtered_sampler.MipFilter() == 1u, + "point-only image format retained a filtered sampler mode"); + + ShaderSamplerResource base_mip_sampler{}; + base_mip_sampler.fields[2] = (3u << 20u) | (1u << 22u) | (2u << 24u); + base_mip_sampler.SetPointFiltering(); + Require("ImageSpecializationId", "base-mip point sampler", + base_mip_sampler.XyMagFilter() == 0u && + base_mip_sampler.XyMinFilter() == 0u && + base_mip_sampler.ZFilter() == 1u && + base_mip_sampler.MipFilter() == 0u, + "forcing point filtering enabled mipmapping on a base-mip sampler"); + + ShaderRecompiler::IR::Program mixed_sampler_program; + mixed_sampler_program.values = + std::make_shared(); + mixed_sampler_program.resource_tracking_complete = true; + ShaderRecompiler::IR::ImageResource sampled_image; + sampled_image.kind = ShaderRecompiler::IR::ResourceKind::Image; + sampled_image.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D; + sampled_image.read = true; + mixed_sampler_program.info.images = {sampled_image, sampled_image}; + mixed_sampler_program.info.samplers.push_back({0u, 4u}); + mixed_sampler_program.info.sampled_pairs = {{0u, 0u, 8u}, {1u, 0u, 12u}}; + + ShaderRecompiler::IR::DescriptorValue native_image_descriptor{}; + native_image_descriptor.dword_count = 8; + native_image_descriptor.dwords[0] = 0x1000u; + native_image_descriptor.dwords[1] = + static_cast(Prospero::BufferFormat::k32UInt) << 20u; + native_image_descriptor.dwords[2] = 3u | (3u << 14u); + native_image_descriptor.dwords[3] = + DstSel(4, 5, 6, 7) | + (static_cast(Prospero::ImageType::kColor2D) << 28u); + auto packed_image_descriptor = native_image_descriptor; + packed_image_descriptor.dwords[0] = 0x2000u; + packed_image_descriptor.dwords[1] = + static_cast(Prospero::BufferFormat::k11_11_10UInt) << 20u; + + ShaderRecompiler::IR::ResourceSnapshot mixed_sampler_snapshot; + mixed_sampler_snapshot.images = {native_image_descriptor, + packed_image_descriptor}; + ShaderRecompiler::IR::DescriptorValue sampler_descriptor{}; + sampler_descriptor.dword_count = 4; + mixed_sampler_snapshot.samplers.push_back(sampler_descriptor); + std::string specialization_error; + Require("ImageSpecializationId", "mixed sampler specialization", + ShaderRecompiler::IR::SpecializeResources(mixed_sampler_program, + mixed_sampler_snapshot, + &specialization_error), + specialization_error); + Require("ImageSpecializationId", "mixed sampler variant", + mixed_sampler_program.info.samplers.size() == 2u && + !mixed_sampler_program.info.samplers[0].force_point_filtering && + mixed_sampler_program.info.samplers[1].force_point_filtering && + mixed_sampler_program.info.sampled_pairs[0].sampler == 0u && + mixed_sampler_program.info.sampled_pairs[1].sampler == 1u && + mixed_sampler_snapshot.samplers.size() == 2u, + "a shared native/bit-packed sampler was not split into point and " + "native variants"); + + ShaderRecompiler::IR::Program packed_atomic_program; + packed_atomic_program.values = + std::make_shared(); + packed_atomic_program.resource_tracking_complete = true; + auto packed_atomic_image = sampled_image; + packed_atomic_image.kind = + ShaderRecompiler::IR::ResourceKind::StorageImageUint; + packed_atomic_image.written = true; + packed_atomic_image.atomic = true; + packed_atomic_program.info.images.push_back(packed_atomic_image); + ShaderRecompiler::IR::ResourceSnapshot packed_atomic_snapshot; + packed_atomic_snapshot.images.push_back(packed_image_descriptor); + std::string atomic_error; + Require("ImageSpecializationId", "packed atomic rejection", + !ShaderRecompiler::IR::SpecializeResources( + packed_atomic_program, packed_atomic_snapshot, &atomic_error) && + atomic_error.find("unsupported format 34") != std::string::npos, + "a non-atomic PS5 packed format entered the typed atomic path"); + std::printf("[host] %-32s ok\n", "ImageSpecializationPipelineId"); } void CheckStorageTextureVolumeUploadLayout() { @@ -22910,7 +23168,7 @@ int main(int argc, char **argv) { return 0; } if (argc == 2 && std::strcmp(argv[1], "--indirect-image-only") == 0) { - CheckStorageImageSwizzleSpecializationId(); + CheckImageSpecializationId(); CheckIndirectImageKeySwitch(); return 0; } @@ -22952,7 +23210,7 @@ int main(int argc, char **argv) { CheckBasicStorageTextureDescriptor(); CheckStorageTextureLinearUploadLayout(); CheckStorageTextureDepthTileUploadLayout(); - CheckStorageImageSwizzleSpecializationId(); + CheckImageSpecializationId(); CheckStandard64RenderTargetTileRoundTrip(); CheckStorageTextureVolumeUploadLayout(); CheckStorageTextureVolumeMipRegions();