From 7351eae117abb58450b77f7361fb966a91575980 Mon Sep 17 00:00:00 2001 From: nmzik Date: Mon, 27 Jul 2026 14:10:51 +0200 Subject: [PATCH] graphics: add 1D image descriptor support --- .../host_gpu/renderer/descriptorCache.cpp | 8 + .../host_gpu/renderer/descriptors.cpp | 69 ++- src/graphics/host_gpu/renderer/imageView.cpp | 30 +- src/graphics/host_gpu/renderer/imageView.h | 27 +- .../shader/recompiler/BindingLayout.cpp | 16 +- src/graphics/shader/recompiler/ImageOps.cpp | 17 +- .../recompiler/ResourceMaterialization.cpp | 47 +- .../shader/recompiler/ShaderDecoder.cpp | 20 +- .../shader/recompiler/ShaderDecoder.h | 3 + src/graphics/shader/recompiler/ShaderIR.h | 8 + .../shader/recompiler/SpirvEmitter.cpp | 13 + .../recompiler/shaderIR/ShaderIRLog.cpp | 11 +- .../spirvEmitter/spirvEmitterAnalysis.cpp | 79 +-- .../spirvEmitter/spirvEmitterImageHelpers.cpp | 156 +++--- .../spirvEmitter/spirvEmitterImageOps.cpp | 93 ++-- .../spirvEmitter/spirvEmitterInternal.h | 116 +++-- .../spirvEmitter/spirvEmitterModule.cpp | 222 +++------ tests/ResourceTrackingTests.cpp | 124 ++++- tests/ShaderRecompilerComputeTests.cpp | 461 +++++++++++++++--- tests/shaderCfgTests.cpp | 159 +++++- 20 files changed, 1148 insertions(+), 531 deletions(-) diff --git a/src/graphics/host_gpu/renderer/descriptorCache.cpp b/src/graphics/host_gpu/renderer/descriptorCache.cpp index e4c581067..490eae412 100644 --- a/src/graphics/host_gpu/renderer/descriptorCache.cpp +++ b/src/graphics/host_gpu/renderer/descriptorCache.cpp @@ -22,9 +22,13 @@ vk::ShaderStageFlags StageFlags(DescriptorCache::Stage stage) { bool IsSampledImage(BindingKind kind) { switch (kind) { + case BindingKind::Sampled1D: + case BindingKind::Sampled1DArray: case BindingKind::Sampled2D: case BindingKind::Sampled2DArray: case BindingKind::Sampled3D: + case BindingKind::SampledUint1D: + case BindingKind::SampledUint1DArray: case BindingKind::SampledUint2D: case BindingKind::SampledUint2DArray: case BindingKind::SampledUint3D: return true; @@ -34,9 +38,13 @@ bool IsSampledImage(BindingKind kind) { bool IsStorageImage(BindingKind kind) { switch (kind) { + case BindingKind::Storage1D: + case BindingKind::Storage1DArray: case BindingKind::Storage2D: case BindingKind::Storage2DArray: case BindingKind::Storage3D: + case BindingKind::StorageUint1D: + case BindingKind::StorageUint1DArray: case BindingKind::StorageUint2D: case BindingKind::StorageUint2DArray: case BindingKind::StorageUint3D: return true; diff --git a/src/graphics/host_gpu/renderer/descriptors.cpp b/src/graphics/host_gpu/renderer/descriptors.cpp index 99c61d139..df58d1aeb 100644 --- a/src/graphics/host_gpu/renderer/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/descriptors.cpp @@ -155,6 +155,8 @@ NativeAddressBuffer(RenderContext& context, CommandBuffer& command_buffer, static bool IsSupportedSampledColorResource(const ShaderRecompiler::IR::ImageResource& resource) { bool supported_dimension = false; switch (resource.dimension) { + case ShaderRecompiler::Decoder::ImageDimension::Dim1D: + case ShaderRecompiler::Decoder::ImageDimension::Dim1DArray: case ShaderRecompiler::Decoder::ImageDimension::Dim2D: case ShaderRecompiler::Decoder::ImageDimension::Dim2DArray: supported_dimension = true; @@ -295,14 +297,32 @@ static void ValidateDepthTargetBinding(const ShaderRecompiler::IR::ImageResource static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::ImageResource& resource, const ShaderTextureResource& descriptor) { - const auto tile = descriptor.TileMode(); + const auto tile = descriptor.TileMode(); + const bool is_color_1d = + descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor1D); + const bool is_color_1d_array = + descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor1DArray); + const bool valid_1d_slice = + (is_color_1d && descriptor.Depth() == 0 && descriptor.BaseArray5() == 0) || + (is_color_1d_array && descriptor.BaseArray5() <= descriptor.Depth()); + const bool is_1d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D && + descriptor.Height5() == 0 && valid_1d_slice; + const bool is_1d_array = + resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray && + is_color_1d_array && descriptor.Height5() == 0 && + descriptor.BaseArray5() <= descriptor.Depth(); + const bool is_color_2d = + descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D); + const bool is_color_2d_array = + descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray); + const bool valid_2d_slice = + (is_color_2d && descriptor.Depth() == 0 && descriptor.BaseArray5() == 0) || + (is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth()); const bool is_2d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D && - descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) && - descriptor.Depth() == 0 && descriptor.BaseArray5() == 0; + valid_2d_slice; const bool is_2d_array = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray && - descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray) && - descriptor.BaseArray5() <= descriptor.Depth(); + is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth(); const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D && descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor3D) && descriptor.BaseArray5() == 0; @@ -321,8 +341,9 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag const bool supported_swizzle = IsValidImageSwizzle(descriptor.DstSelXYZW()) && (descriptor.DstSelXYZW() == DstSel(4, 5, 6, 7) || !resource.read); - const bool supported_mip_view = descriptor.BaseLevel() == 0 || is_2d; - return (is_2d || is_2d_array || is_3d) && supported_tile && supported_mip_view && + const bool supported_mip_view = descriptor.BaseLevel() == 0 || is_1d || is_2d; + return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d) && supported_tile && + supported_mip_view && descriptor.BaseLevel() == descriptor.LastLevel() && descriptor.LastLevel() <= descriptor.MaxMip() && descriptor.MinLod() == 0 && supported_swizzle && descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth(); @@ -471,6 +492,22 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& view.mapping = storage ? vk::ComponentMapping {} : TextureGetComponentMapping(descriptor.DstSelXYZW()); switch (resource.dimension) { + case ShaderRecompiler::Decoder::ImageDimension::Dim1D: + view.type = vk::ImageViewType::e1D; + view.base_layer = descriptor.BaseArray5(); + if (view.base_layer >= image_layers) { + EXIT("texture base layer is out of bounds\n"); + } + view.layer_count = 1; + break; + case ShaderRecompiler::Decoder::ImageDimension::Dim1DArray: + view.type = vk::ImageViewType::e1DArray; + view.base_layer = descriptor.BaseArray5(); + if (view.base_layer >= image_layers) { + EXIT("texture array base layer is out of bounds\n"); + } + view.layer_count = image_layers - view.base_layer; + break; case ShaderRecompiler::Decoder::ImageDimension::Dim3D: view.type = vk::ImageViewType::e3D; view.base_layer = 0; @@ -484,11 +521,15 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& } view.layer_count = image_layers - view.base_layer; break; - default: - view.type = vk::ImageViewType::e2D; - view.base_layer = 0; + case ShaderRecompiler::Decoder::ImageDimension::Dim2D: + view.type = vk::ImageViewType::e2D; + view.base_layer = descriptor.BaseArray5(); + if (view.base_layer >= image_layers) { + EXIT("texture base layer is out of bounds\n"); + } view.layer_count = 1; break; + default: EXIT("unsupported texture view dimension\n"); } return view; } @@ -576,6 +617,10 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso } const auto pixel_format = TextureGetFormat(format); + const auto storage_view_format = SrgbStorageViewFormat(pixel_format); + const auto view_format = + storage && storage_view_format != vk::Format::eUndefined ? storage_view_format + : pixel_format; const auto block_bytes = Prospero::BlockCompressedBytesPerBlock(format); TextureCache::ImageDesc desc {}; desc.info.data = {address, size.size}; @@ -594,7 +639,7 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso } else { PopulateTextureMipLayout(desc.info); } - desc.view_info = TextureViewInfo(resource, descriptor, pixel_format, storage, view_levels, + desc.view_info = TextureViewInfo(resource, descriptor, view_format, storage, view_levels, desc.info.resources.layers); desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture; @@ -612,7 +657,7 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso (void)SelectSampledDepthView(image->info.pixel_format, pixel_format, descriptor.DstSelXYZW()); } else if (storage) { - ValidateStorageColorView(image->info.pixel_format, pixel_format, descriptor.DstSelXYZW()); + ValidateStorageColorView(image->info.pixel_format, view_format, descriptor.DstSelXYZW()); } else { (void)SelectSampledColorView(image->info.pixel_format, pixel_format, descriptor.DstSelXYZW()); diff --git a/src/graphics/host_gpu/renderer/imageView.cpp b/src/graphics/host_gpu/renderer/imageView.cpp index b1a25f83b..7c926a6bc 100644 --- a/src/graphics/host_gpu/renderer/imageView.cpp +++ b/src/graphics/host_gpu/renderer/imageView.cpp @@ -69,8 +69,19 @@ namespace { default: return false; } case vk::ImageType::e3D: - return info.type == vk::ImageViewType::e3D && info.base_layer == 0 && - info.layer_count == 1; + switch (info.type) { + case vk::ImageViewType::e3D: + return info.base_layer == 0 && info.layer_count == 1; + case vk::ImageViewType::e2D: + return static_cast( + image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) && + info.level_count == 1 && info.layer_count == 1; + case vk::ImageViewType::e2DArray: + return static_cast( + image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) && + info.level_count == 1; + default: return false; + } default: return false; } } @@ -333,11 +344,18 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) { is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {}; const bool format_compatible = normalized.format != vk::Format::eUndefined && IsCompatibleViewFormat(image.format, normalized.format); - const bool ranges_valid = normalized.level_count != 0 && + const bool slice_view = image.image_type == vk::ImageType::e3D && + (normalized.type == vk::ImageViewType::e2D || + normalized.type == vk::ImageViewType::e2DArray); + const bool levels_valid = normalized.level_count != 0 && normalized.base_level < image.mip_levels && - normalized.level_count <= image.mip_levels - normalized.base_level && - normalized.layer_count != 0 && normalized.base_layer < image.layers && - normalized.layer_count <= image.layers - normalized.base_layer; + normalized.level_count <= image.mip_levels - normalized.base_level; + const auto view_layers = slice_view && levels_valid + ? std::max(image.extent.depth >> normalized.base_level, 1u) + : image.layers; + const bool ranges_valid = levels_valid && + normalized.layer_count != 0 && normalized.base_layer < view_layers && + normalized.layer_count <= view_layers - normalized.base_layer; const bool mapping_valid = IsComponentSwizzle(normalized.mapping.r) && IsComponentSwizzle(normalized.mapping.g) && IsComponentSwizzle(normalized.mapping.b) && IsComponentSwizzle(normalized.mapping.a); diff --git a/src/graphics/host_gpu/renderer/imageView.h b/src/graphics/host_gpu/renderer/imageView.h index b1e688953..4dfdd1538 100644 --- a/src/graphics/host_gpu/renderer/imageView.h +++ b/src/graphics/host_gpu/renderer/imageView.h @@ -38,19 +38,12 @@ namespace ImageViewOps { static_cast(image_format), static_cast(view_format), swizzle); } -[[nodiscard]] inline vk::Format BgraSrgbStorageViewFormat(vk::Format image_format) noexcept { - return image_format == vk::Format::eB8G8R8A8Srgb ? vk::Format::eR8G8B8A8Unorm - : vk::Format::eUndefined; -} - [[nodiscard]] inline vk::Format SrgbStorageViewFormat(vk::Format image_format) noexcept { - return image_format == vk::Format::eR8G8B8A8Srgb ? vk::Format::eR8G8B8A8Unorm - : BgraSrgbStorageViewFormat(image_format); -} - -[[nodiscard]] inline bool IsBgraSrgbStorageView(vk::Format image_format, vk::Format view_format, - uint32_t swizzle) noexcept { - return view_format == BgraSrgbStorageViewFormat(image_format) && swizzle == DstSel(6, 5, 4, 7); + switch (image_format) { + case vk::Format::eR8G8B8A8Srgb: + case vk::Format::eB8G8R8A8Srgb: return vk::Format::eR8G8B8A8Unorm; + default: return vk::Format::eUndefined; + } } [[nodiscard]] inline bool IsSupportedSampledColorView(vk::Format image_format, @@ -110,8 +103,10 @@ IsSupportedSampledDepthUintResource(const ShaderRecompiler::IR::ImageResource& r inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format, uint32_t swizzle) noexcept { - if ((image_format != view_format && - !IsBgraSrgbStorageView(image_format, view_format, swizzle)) || + const auto srgb_view = SrgbStorageViewFormat(image_format); + const bool srgb_storage_view = + srgb_view != vk::Format::eUndefined && view_format == srgb_view; + if ((image_format != view_format && !srgb_storage_view) || !IsValidImageSwizzle(swizzle)) { UnsupportedColorView("storage", image_format, view_format, swizzle); } @@ -121,7 +116,9 @@ inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_fo IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept { return (resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImage || resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) && - (resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D || + (resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D || + resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray || + resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D || resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D || resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) && resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.written && diff --git a/src/graphics/shader/recompiler/BindingLayout.cpp b/src/graphics/shader/recompiler/BindingLayout.cpp index 3415d6541..f1aedf3f6 100644 --- a/src/graphics/shader/recompiler/BindingLayout.cpp +++ b/src/graphics/shader/recompiler/BindingLayout.cpp @@ -13,11 +13,15 @@ namespace { constexpr uint32_t MaxPushConstantBytes = 128; constexpr std::array ImageBindingKinds = { + DescriptorBindingKind::Sampled1D, DescriptorBindingKind::Sampled1DArray, DescriptorBindingKind::Sampled2D, DescriptorBindingKind::Sampled2DArray, - DescriptorBindingKind::Sampled3D, DescriptorBindingKind::SampledUint2D, + DescriptorBindingKind::Sampled3D, DescriptorBindingKind::SampledUint1D, + DescriptorBindingKind::SampledUint1DArray, DescriptorBindingKind::SampledUint2D, DescriptorBindingKind::SampledUint2DArray, DescriptorBindingKind::SampledUint3D, + DescriptorBindingKind::Storage1D, DescriptorBindingKind::Storage1DArray, DescriptorBindingKind::Storage2D, DescriptorBindingKind::Storage2DArray, - DescriptorBindingKind::Storage3D, DescriptorBindingKind::StorageUint2D, + DescriptorBindingKind::Storage3D, DescriptorBindingKind::StorageUint1D, + DescriptorBindingKind::StorageUint1DArray, DescriptorBindingKind::StorageUint2D, DescriptorBindingKind::StorageUint2DArray, DescriptorBindingKind::StorageUint3D, }; @@ -28,6 +32,8 @@ bool ImageBinding(const ImageResource& image, DescriptorBindingKind& result) { switch (image.kind) { case ResourceKind::Image: switch (image.dimension) { + case Dimension::Dim1D: result = Kind::Sampled1D; return true; + case Dimension::Dim1DArray: result = Kind::Sampled1DArray; return true; case Dimension::Dim2D: result = Kind::Sampled2D; return true; case Dimension::Dim2DArray: result = Kind::Sampled2DArray; return true; case Dimension::Dim3D: result = Kind::Sampled3D; return true; @@ -35,6 +41,8 @@ bool ImageBinding(const ImageResource& image, DescriptorBindingKind& result) { } case ResourceKind::ImageUint: switch (image.dimension) { + case Dimension::Dim1D: result = Kind::SampledUint1D; return true; + case Dimension::Dim1DArray: result = Kind::SampledUint1DArray; return true; case Dimension::Dim2D: result = Kind::SampledUint2D; return true; case Dimension::Dim2DArray: result = Kind::SampledUint2DArray; return true; case Dimension::Dim3D: result = Kind::SampledUint3D; return true; @@ -42,6 +50,8 @@ bool ImageBinding(const ImageResource& image, DescriptorBindingKind& result) { } case ResourceKind::StorageImage: switch (image.dimension) { + case Dimension::Dim1D: result = Kind::Storage1D; return true; + case Dimension::Dim1DArray: result = Kind::Storage1DArray; return true; case Dimension::Dim2D: result = Kind::Storage2D; return true; case Dimension::Dim2DArray: result = Kind::Storage2DArray; return true; case Dimension::Dim3D: result = Kind::Storage3D; return true; @@ -49,6 +59,8 @@ bool ImageBinding(const ImageResource& image, DescriptorBindingKind& result) { } case ResourceKind::StorageImageUint: switch (image.dimension) { + case Dimension::Dim1D: result = Kind::StorageUint1D; return true; + case Dimension::Dim1DArray: result = Kind::StorageUint1DArray; return true; case Dimension::Dim2D: result = Kind::StorageUint2D; return true; case Dimension::Dim2DArray: result = Kind::StorageUint2DArray; return true; case Dimension::Dim3D: result = Kind::StorageUint3D; return true; diff --git a/src/graphics/shader/recompiler/ImageOps.cpp b/src/graphics/shader/recompiler/ImageOps.cpp index 610a228b9..0ed483aa7 100644 --- a/src/graphics/shader/recompiler/ImageOps.cpp +++ b/src/graphics/shader/recompiler/ImageOps.cpp @@ -30,9 +30,11 @@ struct MimgAtomicInfo { constexpr ImageDimension DecodeImageDimension(uint32_t dim) { switch (dim) { + case 0u: return ImageDimension::Dim1D; case 1u: return ImageDimension::Dim2D; case 2u: return ImageDimension::Dim3D; case 3u: return ImageDimension::Dim2DArray; + case 4u: return ImageDimension::Dim1DArray; case 5u: case 7u: return ImageDimension::Dim2DArray; case 6u: return ImageDimension::Dim2D; @@ -42,12 +44,23 @@ constexpr ImageDimension DecodeImageDimension(uint32_t dim) { constexpr uint32_t ImageCoordComponents(ImageDimension dimension) { switch (dimension) { + case ImageDimension::Dim1D: return 1u; + case ImageDimension::Dim1DArray: return 2u; case ImageDimension::Dim3D: case ImageDimension::Dim2DArray: return 3u; default: return 2u; } } +constexpr uint32_t ImageGradientComponents(ImageDimension dimension) { + switch (dimension) { + case ImageDimension::Dim1D: + case ImageDimension::Dim1DArray: return 1u; + case ImageDimension::Dim3D: return 3u; + default: return 2u; + } +} + constexpr uint32_t ImageSampleAddressComponents(uint32_t flags, ImageDimension dimension) { const auto coord_components = ImageCoordComponents(dimension); uint32_t components = coord_components; @@ -64,7 +77,7 @@ constexpr uint32_t ImageSampleAddressComponents(uint32_t flags, ImageDimension d components++; } if ((flags & ImageSampleFlagDerivative) != 0) { - components += 4u; + components += ImageGradientComponents(dimension) * 2u; } return components; } @@ -255,7 +268,7 @@ uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension, return ImageSampleAddressComponents(gather->flags, dimension); } if (atomic != nullptr) { - return 3u; + return ImageCoordComponents(dimension); } switch (opcode) { diff --git a/src/graphics/shader/recompiler/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ResourceMaterialization.cpp index 5546f3fd6..b9b2d5a2d 100644 --- a/src/graphics/shader/recompiler/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ResourceMaterialization.cpp @@ -12,14 +12,25 @@ namespace { constexpr uint64_t AddressMask = 0x0000ffffffffffffull; -Decoder::ImageDimension DescriptorDimension(const DescriptorValue& descriptor) { +Decoder::ImageDimension DescriptorDimension(const DescriptorValue& descriptor, + Decoder::ImageDimension requested) { + const bool is_array = requested == Decoder::ImageDimension::Dim1DArray || + requested == Decoder::ImageDimension::Dim2DArray; switch (static_cast((descriptor.dwords[3] >> 28u) & 0xfu)) { - case Prospero::ImageType::kColor3D: return Decoder::ImageDimension::Dim3D; - case Prospero::ImageType::kCube: + case Prospero::ImageType::kColor1D: return Decoder::ImageDimension::Dim1D; case Prospero::ImageType::kColor1DArray: + if (is_array) { + return Decoder::ImageDimension::Dim1DArray; + } + return Decoder::ImageDimension::Dim1D; + case Prospero::ImageType::kColor3D: return Decoder::ImageDimension::Dim3D; + case Prospero::ImageType::kCube: return Decoder::ImageDimension::Dim2DArray; case Prospero::ImageType::kColor2DArray: - case Prospero::ImageType::kColor2DMsaaArray: return Decoder::ImageDimension::Dim2DArray; - case Prospero::ImageType::kColor1D: + case Prospero::ImageType::kColor2DMsaaArray: + if (is_array) { + return Decoder::ImageDimension::Dim2DArray; + } + return Decoder::ImageDimension::Dim2D; case Prospero::ImageType::kColor2D: case Prospero::ImageType::kColor2DMsaa: return Decoder::ImageDimension::Dim2D; default: return Decoder::ImageDimension::Unknown; @@ -160,9 +171,21 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh const auto& image = program.info.images[i]; const auto& descriptor = snapshot.images[i]; if (NullImageDescriptor(descriptor)) { + bool canonical_kind = image.kind == ResourceKind::Image || + image.kind == ResourceKind::StorageImage; + if (image.atomic) { + canonical_kind = image.kind == ResourceKind::StorageImageUint; + } + if (image.dimension != Decoder::ImageDimension::Dim2D || !canonical_kind) { + if (error != nullptr) { + *error = fmt::format( + "image descriptor {} no longer matches canonical null specialization", i); + } + return false; + } continue; } - const auto dimension = DescriptorDimension(descriptor); + const auto dimension = DescriptorDimension(descriptor, image.dimension); if (dimension == Decoder::ImageDimension::Unknown || dimension != image.dimension) { if (error != nullptr) { *error = @@ -337,9 +360,19 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std const auto& descriptor = snapshot.images[i]; auto& image = next.images[i]; if (NullImageDescriptor(descriptor)) { + image.dimension = Decoder::ImageDimension::Dim2D; + switch (image.kind) { + case ResourceKind::ImageUint: image.kind = ResourceKind::Image; break; + case ResourceKind::StorageImageUint: + if (!image.atomic) { + image.kind = ResourceKind::StorageImage; + } + break; + default: break; + } continue; } - const auto descriptor_dimension = DescriptorDimension(descriptor); + const auto descriptor_dimension = DescriptorDimension(descriptor, image.dimension); if (descriptor_dimension == Decoder::ImageDimension::Unknown) { if (error != nullptr) { *error = fmt::format( diff --git a/src/graphics/shader/recompiler/ShaderDecoder.cpp b/src/graphics/shader/recompiler/ShaderDecoder.cpp index 76b302ce0..b18da20c9 100644 --- a/src/graphics/shader/recompiler/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/ShaderDecoder.cpp @@ -134,15 +134,6 @@ std::string ImageSampleFlagsToString(uint32_t flags) { return text; } -const char* ImageDimensionToString(ImageDimension dimension) { - switch (dimension) { - case ImageDimension::Dim2D: return "2d"; - case ImageDimension::Dim3D: return "3d"; - case ImageDimension::Dim2DArray: return "2d_array"; - default: return "unknown"; - } -} - std::string FormatMimg(const Instruction& inst) { const char* sample_name = inst.opcode == Opcode::ImageSample ? MimgSampleOpcodeName(inst.opcode_id) : nullptr; @@ -196,6 +187,17 @@ std::string FormatExp(const Instruction& inst) { } // namespace +const char* ImageDimensionToString(ImageDimension dimension) { + switch (dimension) { + case ImageDimension::Dim1D: return "1d"; + case ImageDimension::Dim1DArray: return "1d_array"; + case ImageDimension::Dim2D: return "2d"; + case ImageDimension::Dim3D: return "3d"; + case ImageDimension::Dim2DArray: return "2d_array"; + default: return "unknown"; + } +} + bool DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand, std::string* error) { operand = {}; diff --git a/src/graphics/shader/recompiler/ShaderDecoder.h b/src/graphics/shader/recompiler/ShaderDecoder.h index c4a144f32..8d25e2c18 100644 --- a/src/graphics/shader/recompiler/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/ShaderDecoder.h @@ -570,6 +570,8 @@ enum ImageSampleFlag : uint32_t { enum class ImageDimension : uint32_t { Unknown, + Dim1D, + Dim1DArray, Dim2D, Dim3D, Dim2DArray, @@ -669,6 +671,7 @@ void SetUnsupported(Instruction& inst, Family family, uint32_t opcode_id, const std::string FamilyToString(Family family); std::string OpcodeToString(Opcode opcode); std::string OperandToString(const Operand& operand); +const char* ImageDimensionToString(ImageDimension dimension); std::string InstructionToString(const Instruction& inst); std::string ProgramToString(const Program& program); diff --git a/src/graphics/shader/recompiler/ShaderIR.h b/src/graphics/shader/recompiler/ShaderIR.h index 7124f5e08..72215d59b 100644 --- a/src/graphics/shader/recompiler/ShaderIR.h +++ b/src/graphics/shader/recompiler/ShaderIR.h @@ -673,15 +673,23 @@ struct StageOutput { enum class DescriptorBindingKind { Buffers, + Sampled1D, + Sampled1DArray, Sampled2D, Sampled2DArray, Sampled3D, + SampledUint1D, + SampledUint1DArray, SampledUint2D, SampledUint2DArray, SampledUint3D, + Storage1D, + Storage1DArray, Storage2D, Storage2DArray, Storage3D, + StorageUint1D, + StorageUint1DArray, StorageUint2D, StorageUint2DArray, StorageUint3D, diff --git a/src/graphics/shader/recompiler/SpirvEmitter.cpp b/src/graphics/shader/recompiler/SpirvEmitter.cpp index f7799ada5..278ba0aba 100644 --- a/src/graphics/shader/recompiler/SpirvEmitter.cpp +++ b/src/graphics/shader/recompiler/SpirvEmitter.cpp @@ -25,6 +25,10 @@ bool ImageBinding(const IR::ImageResource& image, IR::DescriptorBindingKind& kin if (image.kind == IR::ResourceKind::Image || image.kind == IR::ResourceKind::ImageUint) { const bool integer = image.kind == IR::ResourceKind::ImageUint; switch (image.dimension) { + case Dim::Dim1D: kind = integer ? Kind::SampledUint1D : Kind::Sampled1D; return true; + case Dim::Dim1DArray: + kind = integer ? Kind::SampledUint1DArray : Kind::Sampled1DArray; + return true; case Dim::Dim2D: kind = integer ? Kind::SampledUint2D : Kind::Sampled2D; return true; case Dim::Dim3D: kind = integer ? Kind::SampledUint3D : Kind::Sampled3D; return true; case Dim::Dim2DArray: @@ -38,6 +42,10 @@ bool ImageBinding(const IR::ImageResource& image, IR::DescriptorBindingKind& kin return false; } switch (image.dimension) { + case Dim::Dim1D: kind = uint_image ? Kind::StorageUint1D : Kind::Storage1D; return true; + case Dim::Dim1DArray: + kind = uint_image ? Kind::StorageUint1DArray : Kind::Storage1DArray; + return true; case Dim::Dim2D: kind = uint_image ? Kind::StorageUint2D : Kind::Storage2D; return true; case Dim::Dim3D: kind = uint_image ? Kind::StorageUint3D : Kind::Storage3D; return true; case Dim::Dim2DArray: @@ -175,6 +183,11 @@ bool ValidateInstructionContract(const IR::Instruction& inst, std::string* error inst.src_count != 1)) { return Fail(error, "sampled image instruction has an invalid resource class"); } + if (inst.op == IR::Opcode::ImageGather4 && + (inst.memory.image_dimension == Decoder::ImageDimension::Dim1D || + inst.memory.image_dimension == Decoder::ImageDimension::Dim1DArray)) { + return Fail(error, "1D image gather is not supported by SPIR-V"); + } if (inst.op == IR::Opcode::ImageStore && ((kind != IR::ResourceKind::StorageImage && kind != IR::ResourceKind::StorageImageUint) || inst.src_count != 2)) { diff --git a/src/graphics/shader/recompiler/shaderIR/ShaderIRLog.cpp b/src/graphics/shader/recompiler/shaderIR/ShaderIRLog.cpp index ffedb12b4..0b72823c5 100644 --- a/src/graphics/shader/recompiler/shaderIR/ShaderIRLog.cpp +++ b/src/graphics/shader/recompiler/shaderIR/ShaderIRLog.cpp @@ -86,15 +86,6 @@ std::string ResourceKindToString(ResourceKind kind) { } } -const char* ImageDimensionToString(Decoder::ImageDimension dimension) { - switch (dimension) { - case Decoder::ImageDimension::Dim2D: return "2d"; - case Decoder::ImageDimension::Dim3D: return "3d"; - case Decoder::ImageDimension::Dim2DArray: return "2d_array"; - default: return "unknown"; - } -} - std::string MemoryInfoToString(const MemoryInfo& mem) { if (mem.kind == ResourceKind::None) { return ""; @@ -109,7 +100,7 @@ std::string MemoryInfoToString(const MemoryInfo& mem) { mem.component_count, mem.data_format, mem.number_format, mem.data_signed ? 1u : 0u, mem.typed ? 1u : 0u, mem.formatted ? 1u : 0u, mem.memory_segment, mem.glc ? 1u : 0u, mem.slc ? 1u : 0u, mem.idxen ? 1u : 0u, mem.offen ? 1u : 0u, mem.image_sample_flags, - ImageDimensionToString(mem.image_dimension), mem.image_address_components, + Decoder::ImageDimensionToString(mem.image_dimension), mem.image_address_components, mem.image_has_mip ? 1u : 0u); if (mem.image_nsa_dwords != 0) { text += fmt::format(" image_nsa_dwords={} image_nsa_addr=", mem.image_nsa_dwords); diff --git a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterAnalysis.cpp b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterAnalysis.cpp index 30e29fe96..2dae22a7a 100644 --- a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterAnalysis.cpp +++ b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterAnalysis.cpp @@ -572,6 +572,8 @@ uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type, ImageViewKind ImageViewKindFromDimension(Decoder::ImageDimension dimension) { switch (dimension) { + case Decoder::ImageDimension::Dim1D: return ImageViewKind::Dim1D; + case Decoder::ImageDimension::Dim1DArray: return ImageViewKind::Dim1DArray; case Decoder::ImageDimension::Dim2DArray: return ImageViewKind::Dim2DArray; case Decoder::ImageDimension::Dim3D: return ImageViewKind::Dim3D; default: return ImageViewKind::Dim2D; @@ -595,9 +597,23 @@ ImageViewKind StorageImageViewKind(const EmitterState& state, const IR::MemoryIn uint32_t ImageViewCoordinateComponents(ImageViewKind view) { switch (view) { + case ImageViewKind::Dim1D: return 1u; + case ImageViewKind::Dim1DArray: + case ImageViewKind::Dim2D: return 2u; case ImageViewKind::Dim2DArray: case ImageViewKind::Dim3D: return 3u; - default: return 2u; + default: return 0u; + } +} + +uint32_t ImageViewSpatialComponents(ImageViewKind view) { + switch (view) { + case ImageViewKind::Dim1D: + case ImageViewKind::Dim1DArray: return 1u; + case ImageViewKind::Dim2D: + case ImageViewKind::Dim2DArray: return 2u; + case ImageViewKind::Dim3D: return 3u; + default: return 0u; } } @@ -610,67 +626,24 @@ uint32_t ImageViewSampledImageType(const EmitterState& state, ImageViewKind view } uint32_t ImageViewSizeType(const EmitterState& state, ImageViewKind view) { - return ImageViewCoordinateComponents(view) == 3u ? state.vec3_uint_type : state.vec2_uint_type; + switch (ImageViewCoordinateComponents(view)) { + case 1u: return state.uint_type; + case 2u: return state.vec2_uint_type; + case 3u: return state.vec3_uint_type; + default: return 0; + } } uint32_t StorageImageType(const EmitterState& state, bool uint_image, ImageViewKind view) { - if (uint_image) { - switch (view) { - case ImageViewKind::Dim2DArray: return state.storage_image_uint_2d_array_type; - case ImageViewKind::Dim3D: return state.storage_image_uint_3d_type; - default: return state.storage_image_uint_type; - } - } - switch (view) { - case ImageViewKind::Dim2DArray: return state.storage_image_2d_array_type; - case ImageViewKind::Dim3D: return state.storage_image_3d_type; - default: return state.storage_image_type; - } + return state.storage_images[StorageImageIndex(uint_image, view)].image_type; } uint32_t StorageImagePointerType(const EmitterState& state, bool uint_image, ImageViewKind view) { - if (uint_image) { - switch (view) { - case ImageViewKind::Dim2DArray: return state.ptr_uniform_storage_image_uint_2d_array; - case ImageViewKind::Dim3D: return state.ptr_uniform_storage_image_uint_3d; - default: return state.ptr_uniform_storage_image_uint; - } - } - switch (view) { - case ImageViewKind::Dim2DArray: return state.ptr_uniform_storage_image_2d_array; - case ImageViewKind::Dim3D: return state.ptr_uniform_storage_image_3d; - default: return state.ptr_uniform_storage_image; - } + return state.storage_images[StorageImageIndex(uint_image, view)].pointer_type; } uint32_t StorageImageVariable(const EmitterState& state, bool uint_image, ImageViewKind view) { - if (uint_image) { - switch (view) { - case ImageViewKind::Dim2DArray: return state.storage_image_uint_2d_array_variable; - case ImageViewKind::Dim3D: return state.storage_image_uint_3d_variable; - default: return state.storage_image_uint_variable; - } - } - switch (view) { - case ImageViewKind::Dim2DArray: return state.storage_image_2d_array_variable; - case ImageViewKind::Dim3D: return state.storage_image_3d_variable; - default: return state.storage_image_variable; - } -} - -IR::DescriptorBindingKind StorageBindingKind(bool uint_image, ImageViewKind view) { - if (uint_image) { - switch (view) { - case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::StorageUint2DArray; - case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::StorageUint3D; - default: return IR::DescriptorBindingKind::StorageUint2D; - } - } - switch (view) { - case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::Storage2DArray; - case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::Storage3D; - default: return IR::DescriptorBindingKind::Storage2D; - } + return state.storage_images[StorageImageIndex(uint_image, view)].variable; } uint32_t LoadSampledImageDescriptor(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc, diff --git a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterImageHelpers.cpp b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterImageHelpers.cpp index 000585b89..aff9be2d5 100644 --- a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterImageHelpers.cpp +++ b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterImageHelpers.cpp @@ -20,10 +20,11 @@ ImageSampleLayout MakeImageSampleLayout(const IR::Instruction& inst, ImageViewKi layout.dref = cursor++; } if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagDerivative)) { + const auto components = ImageViewSpatialComponents(view); layout.grad_x = cursor; - cursor += 2u; + cursor += components; layout.grad_y = cursor; - cursor += 2u; + cursor += components; } layout.coord = cursor; cursor += ImageViewCoordinateComponents(view); @@ -35,12 +36,16 @@ ImageSampleLayout MakeImageSampleLayout(const IR::Instruction& inst, ImageViewKi uint32_t EmitImageCoordF32(EmitterState& state, const IR::Instruction& inst, const ImageSampleLayout& layout, ImageViewKind view) { - const auto x = EmitImageAddressFloatLoad(state, inst, inst.src[0], layout.coord); - const auto y = inst.memory.image_address_components > layout.coord + 1u + const auto x = EmitImageAddressFloatLoad(state, inst, inst.src[0], layout.coord); + const auto components = ImageViewCoordinateComponents(view); + if (components == 1u) { + return x; + } + const auto y = inst.memory.image_address_components > layout.coord + 1u ? EmitImageAddressFloatLoad(state, inst, inst.src[0], layout.coord + 1u) : EmitZeroF32(state); const auto coord = state.builder.AllocateId(); - if (ImageViewCoordinateComponents(view) == 3u) { + if (components == 3u) { const auto z = inst.memory.image_address_components > layout.coord + 2u ? EmitImageAddressFloatLoad(state, inst, inst.src[0], layout.coord + 2u) : EmitZeroF32(state); @@ -77,108 +82,87 @@ uint32_t EmitImageBiasF32(EmitterState& state, const IR::Instruction& inst, } uint32_t EmitImageGradientF32(EmitterState& state, const IR::Instruction& inst, - uint32_t first_component) { + uint32_t first_component, ImageViewKind view) { const auto x = inst.memory.image_address_components > first_component ? EmitImageAddressFloatLoad(state, inst, inst.src[0], first_component) : EmitZeroF32(state); + const auto components = ImageViewSpatialComponents(view); + if (components == 1u) { + return x; + } const auto y = inst.memory.image_address_components > first_component + 1u ? EmitImageAddressFloatLoad(state, inst, inst.src[0], first_component + 1u) : EmitZeroF32(state); const auto grad = state.builder.AllocateId(); - state.builder.AddFunction({OpCompositeConstruct, state.vec2_float_type, grad, x, y}); + if (components == 3u) { + const auto z = inst.memory.image_address_components > first_component + 2u + ? EmitImageAddressFloatLoad(state, inst, inst.src[0], + first_component + 2u) + : EmitZeroF32(state); + state.builder.AddFunction({OpCompositeConstruct, state.vec3_float_type, grad, x, y, z}); + } else { + state.builder.AddFunction({OpCompositeConstruct, state.vec2_float_type, grad, x, y}); + } return grad; } -uint32_t EmitImagePackedOffset2I32(EmitterState& state, const IR::Instruction& inst, - const ImageSampleLayout& layout) { +uint32_t EmitImagePackedOffsetI32(EmitterState& state, const IR::Instruction& inst, + const ImageSampleLayout& layout, ImageViewKind view) { + const auto components = ImageViewSpatialComponents(view); if (layout.offset == NoImageComponent || inst.memory.image_address_components <= layout.offset) { const auto zero = ConstantI32(state, 0); - const auto ret = state.builder.AllocateId(); - state.builder.AddFunction({OpCompositeConstruct, state.vec2_int_type, ret, zero, zero}); + if (components == 1u) { + return zero; + } + const auto ret = state.builder.AllocateId(); + if (components == 3u) { + state.builder.AddFunction( + {OpCompositeConstruct, state.vec3_int_type, ret, zero, zero, zero}); + } else { + state.builder.AddFunction( + {OpCompositeConstruct, state.vec2_int_type, ret, zero, zero}); + } return ret; } const auto packed_bits = EmitImageAddressValueLoad(state, inst, inst.src[0], layout.offset); const auto packed_i32 = state.builder.AllocateId(); const auto offset_x = state.builder.AllocateId(); - const auto offset_y = state.builder.AllocateId(); - const auto offset = state.builder.AllocateId(); state.builder.AddFunction({OpBitcast, state.int_type, packed_i32, packed_bits}); state.builder.AddFunction({OpBitFieldSExtract, state.int_type, offset_x, packed_i32, ConstantI32(state, 0), ConstantI32(state, 6)}); + if (components == 1u) { + return offset_x; + } + const auto offset_y = state.builder.AllocateId(); + const auto offset = state.builder.AllocateId(); state.builder.AddFunction({OpBitFieldSExtract, state.int_type, offset_y, packed_i32, ConstantI32(state, 8), ConstantI32(state, 6)}); - state.builder.AddFunction( - {OpCompositeConstruct, state.vec2_int_type, offset, offset_x, offset_y}); + if (components == 3u) { + const auto offset_z = state.builder.AllocateId(); + state.builder.AddFunction({OpBitFieldSExtract, state.int_type, offset_z, packed_i32, + ConstantI32(state, 16), ConstantI32(state, 6)}); + state.builder.AddFunction( + {OpCompositeConstruct, state.vec3_int_type, offset, offset_x, offset_y, offset_z}); + } else { + state.builder.AddFunction( + {OpCompositeConstruct, state.vec2_int_type, offset, offset_x, offset_y}); + } return offset; } -uint32_t EmitImageOffsetCoordF32(EmitterState& state, const IR::Instruction& inst, - const ImageSampleLayout& layout, uint32_t sampled_image, - uint32_t coord, ImageViewKind view) { - if (!HasImageSampleFlag(inst, Decoder::ImageSampleFlagOffset)) { - return coord; - } - - const auto offset_i32 = EmitImagePackedOffset2I32(state, inst, layout); - const auto offset_f32 = state.builder.AllocateId(); - const auto image = state.builder.AllocateId(); - const auto size_u32 = state.builder.AllocateId(); - const auto size_xy_u32 = - ImageViewCoordinateComponents(view) == 3u ? state.builder.AllocateId() : size_u32; - const auto size_f32 = state.builder.AllocateId(); - const auto normalized = state.builder.AllocateId(); - const auto adjusted = state.builder.AllocateId(); - state.builder.AddFunction({OpConvertSToF, state.vec2_float_type, offset_f32, offset_i32}); - state.builder.AddFunction( - {OpImage, ImageViewImageType(state, view, inst.memory.kind == IR::ResourceKind::ImageUint), - image, sampled_image}); - state.builder.AddFunction({OpImageQuerySizeLod, ImageViewSizeType(state, view), size_u32, - image, ConstantU32(state, 0)}); - if (ImageViewCoordinateComponents(view) == 3u) { - const auto width = state.builder.AllocateId(); - const auto height = state.builder.AllocateId(); - state.builder.AddFunction({OpCompositeExtract, state.uint_type, width, size_u32, 0}); - state.builder.AddFunction({OpCompositeExtract, state.uint_type, height, size_u32, 1}); - state.builder.AddFunction( - {OpCompositeConstruct, state.vec2_uint_type, size_xy_u32, width, height}); - } - state.builder.AddFunction({OpConvertUToF, state.vec2_float_type, size_f32, size_xy_u32}); - state.builder.AddFunction({OpFDiv, state.vec2_float_type, normalized, offset_f32, size_f32}); - if (ImageViewCoordinateComponents(view) == 2u) { - state.builder.AddFunction({OpFAdd, state.vec2_float_type, adjusted, coord, normalized}); - return adjusted; - } - - const auto x = state.builder.AllocateId(); - const auto y = state.builder.AllocateId(); - const auto z = state.builder.AllocateId(); - const auto normalized_x = state.builder.AllocateId(); - const auto normalized_y = state.builder.AllocateId(); - const auto adjusted_x = state.builder.AllocateId(); - const auto adjusted_y = state.builder.AllocateId(); - state.builder.AddFunction({OpCompositeExtract, state.float_type, x, coord, 0}); - state.builder.AddFunction({OpCompositeExtract, state.float_type, y, coord, 1}); - state.builder.AddFunction({OpCompositeExtract, state.float_type, z, coord, 2}); - state.builder.AddFunction( - {OpCompositeExtract, state.float_type, normalized_x, normalized, 0}); - state.builder.AddFunction( - {OpCompositeExtract, state.float_type, normalized_y, normalized, 1}); - state.builder.AddFunction({OpFAdd, state.float_type, adjusted_x, x, normalized_x}); - state.builder.AddFunction({OpFAdd, state.float_type, adjusted_y, y, normalized_y}); - state.builder.AddFunction( - {OpCompositeConstruct, state.vec3_float_type, adjusted, adjusted_x, adjusted_y, z}); - return adjusted; -} - uint32_t EmitImageCoordU32(EmitterState& state, const IR::Instruction& inst, ImageViewKind view) { - const auto x = EmitImageAddressValueLoad(state, inst, inst.src[1], 0); - const auto y = inst.memory.image_address_components > 1u + const auto x = EmitImageAddressValueLoad(state, inst, inst.src[1], 0); + const auto components = ImageViewCoordinateComponents(view); + if (components == 1u) { + return x; + } + const auto y = inst.memory.image_address_components > 1u ? EmitImageAddressValueLoad(state, inst, inst.src[1], 1) : ConstantU32(state, 0); const auto coord = state.builder.AllocateId(); - if (ImageViewCoordinateComponents(view) == 3u) { + if (components == 3u) { const auto z = inst.memory.image_address_components > 2u ? EmitImageAddressValueLoad(state, inst, inst.src[1], 2) : ConstantU32(state, 0); @@ -191,12 +175,16 @@ uint32_t EmitImageCoordU32(EmitterState& state, const IR::Instruction& inst, Ima uint32_t EmitImageLoadCoordU32(EmitterState& state, const IR::Instruction& inst, ImageViewKind view) { - const auto x = EmitImageAddressValueLoad(state, inst, inst.src[0], 0); - const auto y = inst.memory.image_address_components > 1u + const auto x = EmitImageAddressValueLoad(state, inst, inst.src[0], 0); + const auto components = ImageViewCoordinateComponents(view); + if (components == 1u) { + return x; + } + const auto y = inst.memory.image_address_components > 1u ? EmitImageAddressValueLoad(state, inst, inst.src[0], 1) : ConstantU32(state, 0); const auto coord = state.builder.AllocateId(); - if (ImageViewCoordinateComponents(view) == 3u) { + if (components == 3u) { const auto z = inst.memory.image_address_components > 2u ? EmitImageAddressValueLoad(state, inst, inst.src[0], 2) : ConstantU32(state, 0); @@ -221,12 +209,16 @@ uint32_t EmitImageMipLodU32(EmitterState& state, const IR::Instruction& inst, uint32_t EmitImageQueryCoordF32(EmitterState& state, const IR::Instruction& inst, ImageViewKind view) { - const auto x = EmitImageAddressFloatLoad(state, inst, inst.src[0], 0); - const auto y = inst.memory.image_address_components > 1u + const auto x = EmitImageAddressFloatLoad(state, inst, inst.src[0], 0); + const auto components = ImageViewCoordinateComponents(view); + if (components == 1u) { + return x; + } + const auto y = inst.memory.image_address_components > 1u ? EmitImageAddressFloatLoad(state, inst, inst.src[0], 1) : EmitZeroF32(state); const auto coord = state.builder.AllocateId(); - if (ImageViewCoordinateComponents(view) == 3u) { + if (components == 3u) { const auto z = inst.memory.image_address_components > 2u ? EmitImageAddressFloatLoad(state, inst, inst.src[0], 2) : EmitZeroF32(state); diff --git a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterImageOps.cpp b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterImageOps.cpp index b7bc6f42d..6bbb63553 100644 --- a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterImageOps.cpp +++ b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterImageOps.cpp @@ -11,7 +11,16 @@ uint32_t ConstantVec2I32(EmitterState& state, int32_t x, int32_t y) { return value; } -uint32_t ConstantImageGatherHorizontalOffsets(EmitterState& state) { +uint32_t ConstantImageGatherHorizontalOffsets(EmitterState& state, ImageViewKind view) { + if (ImageViewSpatialComponents(view) == 1u) { + const auto type = state.builder.AllocateId(); + state.builder.AddType({OpTypeArray, type, state.int_type, ConstantU32(state, 4)}); + const auto value = state.builder.AllocateId(); + state.builder.AddType({OpConstantComposite, type, value, ConstantI32(state, -1), + ConstantI32(state, 0), ConstantI32(state, 1), + ConstantI32(state, 2)}); + return value; + } const auto offset0 = ConstantVec2I32(state, -1, 0); const auto offset1 = ConstantVec2I32(state, 0, 0); const auto offset2 = ConstantVec2I32(state, 1, 0); @@ -23,56 +32,16 @@ uint32_t ConstantImageGatherHorizontalOffsets(EmitterState& state) { return value; } -IR::DescriptorBindingKind StorageImageBindingKind(ImageViewKind view, bool uint_image) { - switch (view) { - case ImageViewKind::Dim2D: - return uint_image ? IR::DescriptorBindingKind::StorageUint2D - : IR::DescriptorBindingKind::Storage2D; - case ImageViewKind::Dim2DArray: - return uint_image ? IR::DescriptorBindingKind::StorageUint2DArray - : IR::DescriptorBindingKind::Storage2DArray; - case ImageViewKind::Dim3D: - return uint_image ? IR::DescriptorBindingKind::StorageUint3D - : IR::DescriptorBindingKind::Storage3D; - default: - return uint_image ? IR::DescriptorBindingKind::StorageUint2D - : IR::DescriptorBindingKind::Storage2D; - } -} - uint32_t LoadStorageImageDescriptorAtIndex(EmitterState& state, uint32_t resource, uint32_t array_index, bool uint_image, ImageViewKind view) { - uint32_t pointer_type = - uint_image ? state.ptr_uniform_storage_image_uint : state.ptr_uniform_storage_image; - uint32_t variable = - uint_image ? state.storage_image_uint_variable : state.storage_image_variable; - uint32_t image_type = uint_image ? state.storage_image_uint_type : state.storage_image_type; - const auto kind = StorageImageBindingKind(view, uint_image); - switch (view) { - case ImageViewKind::Dim2D: break; - case ImageViewKind::Dim2DArray: - pointer_type = uint_image ? state.ptr_uniform_storage_image_uint_2d_array - : state.ptr_uniform_storage_image_2d_array; - variable = uint_image ? state.storage_image_uint_2d_array_variable - : state.storage_image_2d_array_variable; - image_type = uint_image ? state.storage_image_uint_2d_array_type - : state.storage_image_2d_array_type; - break; - case ImageViewKind::Dim3D: - pointer_type = uint_image ? state.ptr_uniform_storage_image_uint_3d - : state.ptr_uniform_storage_image_3d; - variable = - uint_image ? state.storage_image_uint_3d_variable : state.storage_image_3d_variable; - image_type = - uint_image ? state.storage_image_uint_3d_type : state.storage_image_3d_type; - break; - } + const auto kind = StorageBindingKind(uint_image, view); + const auto& descriptors = state.storage_images[StorageImageIndex(uint_image, view)]; const auto pointer = - DescriptorElementPointer(state, pointer_type, variable, array_index, kind, resource, - "storage image descriptor array was not emitted"); + DescriptorElementPointer(state, descriptors.pointer_type, descriptors.variable, array_index, + kind, resource, "storage image descriptor array was not emitted"); const auto image = state.builder.AllocateId(); - state.builder.AddFunction({OpLoad, image_type, image, pointer}); + state.builder.AddFunction({OpLoad, descriptors.image_type, image, pointer}); return image; } @@ -80,24 +49,31 @@ uint32_t LoadStorageImageDescriptorAtIndex(EmitterState& state, uint32_t resourc uint32_t EmitImageGetResinfoComponent(EmitterState& state, uint32_t image, uint32_t size, uint32_t component_index, ImageViewKind view) { + const auto components = ImageViewCoordinateComponents(view); switch (component_index) { case 0: { + if (components == 1u) { + return size; + } const auto width = state.builder.AllocateId(); state.builder.AddFunction({OpCompositeExtract, state.uint_type, width, size, 0}); return width; } case 1: { + if (components < 2u) { + return ConstantU32(state, 0); + } const auto height = state.builder.AllocateId(); state.builder.AddFunction({OpCompositeExtract, state.uint_type, height, size, 1}); return height; } case 2: - if (ImageViewCoordinateComponents(view) == 3u) { + if (components == 3u) { const auto depth = state.builder.AllocateId(); state.builder.AddFunction({OpCompositeExtract, state.uint_type, depth, size, 2}); return depth; } - return ConstantU32(state, 1); + return ConstantU32(state, 0); case 3: { const auto levels = state.builder.AllocateId(); state.builder.AddFunction({OpImageQueryLevels, state.uint_type, levels, image}); @@ -182,7 +158,7 @@ void EmitImageLoad(EmitterState& state, const IR::Instruction& inst) { void EmitImageStore(EmitterState& state, const IR::Instruction& inst) { const auto uint_image = inst.memory.kind == IR::ResourceKind::StorageImageUint; const auto view = StorageImageViewKind(state, inst.memory, uint_image, inst.pc); - const auto binding = ResourceForDescriptor(state, StorageImageBindingKind(view, uint_image), + const auto binding = ResourceForDescriptor(state, StorageBindingKind(uint_image, view), inst.memory.resource); const auto image = LoadStorageImageDescriptorAtIndex(state, inst.memory.resource, binding.array_index, uint_image, view); @@ -244,8 +220,9 @@ void AddImageSampleOperands(EmitterState& state, const IR::Instruction& inst, // sampling because it modifies the hardware-computed LOD. if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagDerivative)) { mask |= ImageOperandsGradMask; - operands.push_back(EmitImageGradientF32(state, inst, layout.grad_x)); - operands.push_back(EmitImageGradientF32(state, inst, layout.grad_y)); + const auto view = SampledImageViewKind(state, inst.memory, inst.pc); + operands.push_back(EmitImageGradientF32(state, inst, layout.grad_x, view)); + operands.push_back(EmitImageGradientF32(state, inst, layout.grad_y, view)); } else if (explicit_lod) { mask |= ImageOperandsLodMask; operands.push_back(EmitImageLodF32(state, inst, layout)); @@ -286,10 +263,7 @@ void EmitImageSample(EmitterState& state, const IR::Instruction& inst) { } const auto explicit_lod = ImageSampleNeedsExplicitLod(state, inst); const auto opcode = ImageSampleOpcode(state, inst); - const auto coord = - EmitImageOffsetCoordF32(state, inst, layout, sampled_image, base_coord, view); - - std::vector words = {opcode, result_type, sample, sampled_image, coord}; + std::vector words = {opcode, result_type, sample, sampled_image, base_coord}; if (dref) { words.push_back(EmitImageDrefF32(state, inst, layout)); } @@ -308,15 +282,16 @@ uint32_t ImageGatherComponent(uint32_t dmask) { } void AddImageGatherOperands(EmitterState& state, const IR::Instruction& inst, - const ImageSampleLayout& layout, std::vector& words) { + const ImageSampleLayout& layout, ImageViewKind view, + std::vector& words) { if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagGatherHorizontal)) { words.push_back(ImageOperandsConstOffsetsMask); - words.push_back(ConstantImageGatherHorizontalOffsets(state)); + words.push_back(ConstantImageGatherHorizontalOffsets(state, view)); return; } if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagOffset)) { words.push_back(ImageOperandsOffsetMask); - words.push_back(EmitImagePackedOffset2I32(state, inst, layout)); + words.push_back(EmitImagePackedOffsetI32(state, inst, layout, view)); } } @@ -341,7 +316,7 @@ void EmitImageGather4(EmitterState& state, const IR::Instruction& inst) { OpImageGather, result_type, texels, sampled_image, coord, ConstantU32(state, ImageGatherComponent(inst.memory.dmask))}; - AddImageGatherOperands(state, inst, layout, words); + AddImageGatherOperands(state, inst, layout, view, words); state.builder.AddFunction(words); for (uint32_t i = 0; i < inst.memory.data_dwords; i++) { diff --git a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h index 23a4a587d..ed0673518 100644 --- a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h @@ -35,6 +35,8 @@ enum : uint32_t { MemoryModelGLSL450 = 1, CapabilityShader = 1, CapabilityImageGatherExtended = 25, + CapabilitySampled1D = 43, + CapabilityImage1D = 44, CapabilityImageQuery = 50, CapabilityStorageImageReadWithoutFormat = 55, CapabilityStorageImageWriteWithoutFormat = 56, @@ -83,6 +85,7 @@ enum : uint32_t { }; enum : uint32_t { + Dim1D = 0, Dim3D = 2, Dim2D = 1, ImageFormatUnknown = 0, @@ -297,6 +300,14 @@ struct SampledImageDescriptors { uint32_t variable = 0; }; +struct StorageImageDescriptors { + uint32_t image_type = 0; + uint32_t pointer_type = 0; + uint32_t array_type = 0; + uint32_t array_pointer_type = 0; + uint32_t variable = 0; +}; + struct EmitterState { EmitterState(const IR::Program& program_, const IR::ResourceSnapshot& resources_) : program(program_), resources(resources_) {} @@ -371,42 +382,13 @@ struct EmitterState { uint32_t ptr_workgroup_array = 0; uint32_t ptr_workgroup_uint = 0; uint32_t lds_variable = 0; - std::array sampled_images; + std::array sampled_images; + std::array storage_images; uint32_t sampler_type = 0; uint32_t sampler_array_type = 0; uint32_t ptr_uniform_sampler = 0; uint32_t ptr_uniform_sampler_array = 0; uint32_t sampler_variable = 0; - uint32_t storage_image_type = 0; - uint32_t ptr_uniform_storage_image = 0; - uint32_t storage_image_array_type = 0; - uint32_t ptr_uniform_storage_image_array = 0; - uint32_t storage_image_variable = 0; - uint32_t storage_image_2d_array_type = 0; - uint32_t ptr_uniform_storage_image_2d_array = 0; - uint32_t storage_image_2d_array_array_type = 0; - uint32_t ptr_uniform_storage_image_2d_array_array = 0; - uint32_t storage_image_2d_array_variable = 0; - uint32_t storage_image_3d_type = 0; - uint32_t ptr_uniform_storage_image_3d = 0; - uint32_t storage_image_3d_array_type = 0; - uint32_t ptr_uniform_storage_image_3d_array = 0; - uint32_t storage_image_3d_variable = 0; - uint32_t storage_image_uint_type = 0; - uint32_t ptr_uniform_storage_image_uint = 0; - uint32_t storage_image_uint_array_type = 0; - uint32_t ptr_uniform_storage_image_uint_array = 0; - uint32_t storage_image_uint_variable = 0; - uint32_t storage_image_uint_2d_array_type = 0; - uint32_t ptr_uniform_storage_image_uint_2d_array = 0; - uint32_t storage_image_uint_2d_array_array_type = 0; - uint32_t ptr_uniform_storage_image_uint_2d_array_array = 0; - uint32_t storage_image_uint_2d_array_variable = 0; - uint32_t storage_image_uint_3d_type = 0; - uint32_t ptr_uniform_storage_image_uint_3d = 0; - uint32_t storage_image_uint_3d_array_type = 0; - uint32_t ptr_uniform_storage_image_uint_3d_array = 0; - uint32_t storage_image_uint_3d_variable = 0; uint32_t ptr_image_uint = 0; uint32_t func_type = 0; uint32_t main_func = 0; @@ -466,28 +448,82 @@ struct ImageSampleLayout { }; enum class ImageViewKind { + Dim1D, + Dim1DArray, Dim2D, Dim2DArray, Dim3D, + Count, }; +constexpr uint32_t ImageViewKindCount = static_cast(ImageViewKind::Count); + constexpr uint32_t SampledImageIndex(bool integer, ImageViewKind view) { - return static_cast(view) + (integer ? 3u : 0u); + return static_cast(view) + (integer ? ImageViewKindCount : 0u); +} + +constexpr uint32_t StorageImageIndex(bool integer, ImageViewKind view) { + return static_cast(view) + (integer ? ImageViewKindCount : 0u); } constexpr IR::DescriptorBindingKind SampledBindingKind(bool integer, ImageViewKind view) { if (integer) { switch (view) { + case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::SampledUint1D; + case ImageViewKind::Dim1DArray: return IR::DescriptorBindingKind::SampledUint1DArray; + case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::SampledUint2D; case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::SampledUint2DArray; case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::SampledUint3D; - default: return IR::DescriptorBindingKind::SampledUint2D; + default: break; } } switch (view) { + case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::Sampled1D; + case ImageViewKind::Dim1DArray: return IR::DescriptorBindingKind::Sampled1DArray; + case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::Sampled2D; case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::Sampled2DArray; case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::Sampled3D; - default: return IR::DescriptorBindingKind::Sampled2D; + default: break; } + return IR::DescriptorBindingKind::Count; +} + +constexpr IR::DescriptorBindingKind StorageBindingKind(bool integer, ImageViewKind view) { + if (integer) { + switch (view) { + case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::StorageUint1D; + case ImageViewKind::Dim1DArray: return IR::DescriptorBindingKind::StorageUint1DArray; + case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::StorageUint2D; + case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::StorageUint2DArray; + case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::StorageUint3D; + default: break; + } + } + switch (view) { + case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::Storage1D; + case ImageViewKind::Dim1DArray: return IR::DescriptorBindingKind::Storage1DArray; + case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::Storage2D; + case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::Storage2DArray; + case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::Storage3D; + default: break; + } + return IR::DescriptorBindingKind::Count; +} + +constexpr uint32_t ImageSpirvDimension(ImageViewKind view) { + switch (view) { + case ImageViewKind::Dim1D: + case ImageViewKind::Dim1DArray: return Dim1D; + case ImageViewKind::Dim2D: + case ImageViewKind::Dim2DArray: + case ImageViewKind::Count: return Dim2D; + case ImageViewKind::Dim3D: return Dim3D; + } + return Dim2D; +} + +constexpr uint32_t ImageSpirvArrayed(ImageViewKind view) { + return view == ImageViewKind::Dim1DArray || view == ImageViewKind::Dim2DArray ? 1u : 0u; } struct AddCarryResult { @@ -632,6 +668,8 @@ ImageViewKind SampledImageViewKind(const EmitterState& state, const IR::MemoryIn uint32_t ImageViewCoordinateComponents(ImageViewKind view); +uint32_t ImageViewSpatialComponents(ImageViewKind view); + uint32_t ImageViewImageType(const EmitterState& state, ImageViewKind view, bool integer); uint32_t ImageViewSampledImageType(const EmitterState& state, ImageViewKind view, bool integer); @@ -792,14 +830,10 @@ uint32_t EmitImageBiasF32(EmitterState& state, const IR::Instruction& inst, const ImageSampleLayout& layout); uint32_t EmitImageGradientF32(EmitterState& state, const IR::Instruction& inst, - uint32_t first_component); + uint32_t first_component, ImageViewKind view); -uint32_t EmitImagePackedOffset2I32(EmitterState& state, const IR::Instruction& inst, - const ImageSampleLayout& layout); - -uint32_t EmitImageOffsetCoordF32(EmitterState& state, const IR::Instruction& inst, - const ImageSampleLayout& layout, uint32_t sampled_image, - uint32_t coord, ImageViewKind view); +uint32_t EmitImagePackedOffsetI32(EmitterState& state, const IR::Instruction& inst, + const ImageSampleLayout& layout, ImageViewKind view); uint32_t EmitImageCoordU32(EmitterState& state, const IR::Instruction& inst, ImageViewKind view = ImageViewKind::Dim2D); diff --git a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterModule.cpp index 7fa6bf22d..62f9ca4f4 100644 --- a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterModule.cpp @@ -324,36 +324,22 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) { IR::DescriptorBindingKind::AddressMemory); } constexpr const char* SampledNames[] = { - "sampled_2d", "sampled_2d_array", "sampled_3d", + "sampled_1d", "sampled_1d_array", "sampled_2d", "sampled_2d_array", + "sampled_3d", "sampled_uint_1d", "sampled_uint_1d_array", "sampled_uint_2d", "sampled_uint_2d_array", "sampled_uint_3d"}; for (uint32_t i = 0; i < state.sampled_images.size(); i++) { - const auto view = static_cast(i % 3u); + const auto view = static_cast(i % ImageViewKindCount); Decorate(state.sampled_images[i].variable, SampledNames[i], - SampledBindingKind(i >= 3u, view)); + SampledBindingKind(i >= ImageViewKindCount, view)); } - if (state.storage_image_variable != 0) { - Decorate(state.storage_image_variable, "textures2D_L", - IR::DescriptorBindingKind::Storage2D); - } - if (state.storage_image_uint_variable != 0) { - Decorate(state.storage_image_uint_variable, "textures2D_L_U", - IR::DescriptorBindingKind::StorageUint2D); - } - if (state.storage_image_2d_array_variable != 0) { - Decorate(state.storage_image_2d_array_variable, "textures2D_L_A", - IR::DescriptorBindingKind::Storage2DArray); - } - if (state.storage_image_uint_2d_array_variable != 0) { - Decorate(state.storage_image_uint_2d_array_variable, "textures2D_L_U_A", - IR::DescriptorBindingKind::StorageUint2DArray); - } - if (state.storage_image_3d_variable != 0) { - Decorate(state.storage_image_3d_variable, "textures2D_L_3D", - IR::DescriptorBindingKind::Storage3D); - } - if (state.storage_image_uint_3d_variable != 0) { - Decorate(state.storage_image_uint_3d_variable, "textures2D_L_U_3D", - IR::DescriptorBindingKind::StorageUint3D); + constexpr const char* StorageNames[] = { + "storage_1d", "storage_1d_array", "storage_2d", "storage_2d_array", + "storage_3d", "storage_uint_1d", "storage_uint_1d_array", + "storage_uint_2d", "storage_uint_2d_array", "storage_uint_3d"}; + for (uint32_t i = 0; i < state.storage_images.size(); i++) { + const auto view = static_cast(i % ImageViewKindCount); + Decorate(state.storage_images[i].variable, StorageNames[i], + StorageBindingKind(i >= ImageViewKindCount, view)); } if (state.sampler_variable != 0) { Decorate(state.sampler_variable, "samplers", IR::DescriptorBindingKind::Samplers); @@ -452,34 +438,16 @@ void EmitHeaderAndTypes(EmitterState& state) { image.array_type = state.builder.AllocateId(); image.array_pointer_type = state.builder.AllocateId(); } + for (auto& image: state.storage_images) { + image.image_type = state.builder.AllocateId(); + image.pointer_type = state.builder.AllocateId(); + image.array_type = state.builder.AllocateId(); + image.array_pointer_type = state.builder.AllocateId(); + } state.sampler_type = state.builder.AllocateId(); state.sampler_array_type = state.builder.AllocateId(); state.ptr_uniform_sampler = state.builder.AllocateId(); state.ptr_uniform_sampler_array = state.builder.AllocateId(); - state.storage_image_type = state.builder.AllocateId(); - state.ptr_uniform_storage_image = state.builder.AllocateId(); - state.storage_image_array_type = state.builder.AllocateId(); - state.ptr_uniform_storage_image_array = state.builder.AllocateId(); - state.storage_image_2d_array_type = state.builder.AllocateId(); - state.ptr_uniform_storage_image_2d_array = state.builder.AllocateId(); - state.storage_image_2d_array_array_type = state.builder.AllocateId(); - state.ptr_uniform_storage_image_2d_array_array = state.builder.AllocateId(); - state.storage_image_3d_type = state.builder.AllocateId(); - state.ptr_uniform_storage_image_3d = state.builder.AllocateId(); - state.storage_image_3d_array_type = state.builder.AllocateId(); - state.ptr_uniform_storage_image_3d_array = state.builder.AllocateId(); - state.storage_image_uint_type = state.builder.AllocateId(); - state.ptr_uniform_storage_image_uint = state.builder.AllocateId(); - state.storage_image_uint_array_type = state.builder.AllocateId(); - state.ptr_uniform_storage_image_uint_array = state.builder.AllocateId(); - state.storage_image_uint_2d_array_type = state.builder.AllocateId(); - state.ptr_uniform_storage_image_uint_2d_array = state.builder.AllocateId(); - state.storage_image_uint_2d_array_array_type = state.builder.AllocateId(); - state.ptr_uniform_storage_image_uint_2d_array_array = state.builder.AllocateId(); - state.storage_image_uint_3d_type = state.builder.AllocateId(); - state.ptr_uniform_storage_image_uint_3d = state.builder.AllocateId(); - state.storage_image_uint_3d_array_type = state.builder.AllocateId(); - state.ptr_uniform_storage_image_uint_3d_array = state.builder.AllocateId(); state.ptr_image_uint = state.builder.AllocateId(); state.func_type = state.builder.AllocateId(); state.main_func = state.builder.AllocateId(); @@ -487,12 +455,15 @@ void EmitHeaderAndTypes(EmitterState& state) { state.glsl_std450 = state.builder.AllocateId(); state.builder.AddCapability({CapabilityShader}); + state.builder.AddCapability({CapabilitySampled1D}); + state.builder.AddCapability({CapabilityImage1D}); state.builder.AddCapability({CapabilityImageQuery}); if (state.needs_image_gather_extended) { state.builder.AddCapability({CapabilityImageGatherExtended}); } - if (state.storage_image_variable != 0 || state.storage_image_2d_array_variable != 0 || - state.storage_image_3d_variable != 0) { + if (std::any_of(state.storage_images.begin(), + state.storage_images.begin() + ImageViewKindCount, + [](const auto& image) { return image.variable != 0; })) { state.builder.AddCapability({CapabilityStorageImageReadWithoutFormat}); state.builder.AddCapability({CapabilityStorageImageWriteWithoutFormat}); } @@ -729,17 +700,17 @@ void EmitHeaderAndTypes(EmitterState& state) { } for (uint32_t i = 0; i < state.sampled_images.size(); i++) { auto& image = state.sampled_images[i]; - const auto view = static_cast(i % 3u); - const auto component = i >= 3u ? state.uint_type : state.float_type; - const auto dimension = view == ImageViewKind::Dim3D ? Dim3D : Dim2D; - const auto arrayed = view == ImageViewKind::Dim2DArray ? 1u : 0u; - state.builder.AddType({OpTypeImage, image.image_type, component, dimension, 0, arrayed, 0, - 1, ImageFormatUnknown}); + const auto view = static_cast(i % ImageViewKindCount); + const bool integer = i >= ImageViewKindCount; + const auto component = integer ? state.uint_type : state.float_type; + state.builder.AddType({OpTypeImage, image.image_type, component, + ImageSpirvDimension(view), 0, ImageSpirvArrayed(view), 0, 1, + ImageFormatUnknown}); state.builder.AddType({OpTypeSampledImage, image.sampled_image_type, image.image_type}); state.builder.AddType( {OpTypePointer, image.pointer_type, StorageClassUniformConstant, image.image_type}); if (image.variable != 0) { - const auto kind = SampledBindingKind(i >= 3u, view); + const auto kind = SampledBindingKind(integer, view); const auto count = ConstantU32(state, DescriptorCount(state, kind)); state.builder.AddType({OpTypeArray, image.array_type, image.image_type, count}); state.builder.AddType({OpTypePointer, image.array_pointer_type, @@ -760,93 +731,26 @@ void EmitHeaderAndTypes(EmitterState& state) { state.builder.AddType({OpVariable, state.ptr_uniform_sampler_array, state.sampler_variable, StorageClassUniformConstant}); } - state.builder.AddType({OpTypeImage, state.storage_image_type, state.float_type, Dim2D, 0, 0, 0, - 2, ImageFormatUnknown}); - state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image, - StorageClassUniformConstant, state.storage_image_type}); - if (state.storage_image_variable != 0) { - const auto count = - ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::Storage2D)); + for (uint32_t i = 0; i < state.storage_images.size(); i++) { + auto& image = state.storage_images[i]; + const auto view = static_cast(i % ImageViewKindCount); + const bool integer = i >= ImageViewKindCount; + const auto component = integer ? state.uint_type : state.float_type; + const auto format = integer ? ImageFormatR32ui : ImageFormatUnknown; + state.builder.AddType({OpTypeImage, image.image_type, component, + ImageSpirvDimension(view), 0, ImageSpirvArrayed(view), 0, 2, + format}); state.builder.AddType( - {OpTypeArray, state.storage_image_array_type, state.storage_image_type, count}); - state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_array, - StorageClassUniformConstant, state.storage_image_array_type}); - state.builder.AddType({OpVariable, state.ptr_uniform_storage_image_array, - state.storage_image_variable, StorageClassUniformConstant}); - } - state.builder.AddType({OpTypeImage, state.storage_image_2d_array_type, state.float_type, Dim2D, - 0, 1, 0, 2, ImageFormatUnknown}); - state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_2d_array, - StorageClassUniformConstant, state.storage_image_2d_array_type}); - if (state.storage_image_2d_array_variable != 0) { - const auto count = - ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::Storage2DArray)); - state.builder.AddType({OpTypeArray, state.storage_image_2d_array_array_type, - state.storage_image_2d_array_type, count}); - state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_2d_array_array, - StorageClassUniformConstant, - state.storage_image_2d_array_array_type}); - state.builder.AddType({OpVariable, state.ptr_uniform_storage_image_2d_array_array, - state.storage_image_2d_array_variable, StorageClassUniformConstant}); - } - state.builder.AddType({OpTypeImage, state.storage_image_3d_type, state.float_type, Dim3D, 0, 0, - 0, 2, ImageFormatUnknown}); - state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_3d, - StorageClassUniformConstant, state.storage_image_3d_type}); - if (state.storage_image_3d_variable != 0) { - const auto count = - ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::Storage3D)); - state.builder.AddType( - {OpTypeArray, state.storage_image_3d_array_type, state.storage_image_3d_type, count}); - state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_3d_array, - StorageClassUniformConstant, state.storage_image_3d_array_type}); - state.builder.AddType({OpVariable, state.ptr_uniform_storage_image_3d_array, - state.storage_image_3d_variable, StorageClassUniformConstant}); - } - state.builder.AddType({OpTypeImage, state.storage_image_uint_type, state.uint_type, Dim2D, 0, 0, - 0, 2, ImageFormatR32ui}); - state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_uint, - StorageClassUniformConstant, state.storage_image_uint_type}); - if (state.storage_image_uint_variable != 0) { - const auto count = - ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::StorageUint2D)); - state.builder.AddType({OpTypeArray, state.storage_image_uint_array_type, - state.storage_image_uint_type, count}); - state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_uint_array, - StorageClassUniformConstant, state.storage_image_uint_array_type}); - state.builder.AddType({OpVariable, state.ptr_uniform_storage_image_uint_array, - state.storage_image_uint_variable, StorageClassUniformConstant}); - } - state.builder.AddType({OpTypeImage, state.storage_image_uint_2d_array_type, state.uint_type, - Dim2D, 0, 1, 0, 2, ImageFormatR32ui}); - state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_uint_2d_array, - StorageClassUniformConstant, state.storage_image_uint_2d_array_type}); - if (state.storage_image_uint_2d_array_variable != 0) { - const auto count = ConstantU32( - state, DescriptorCount(state, IR::DescriptorBindingKind::StorageUint2DArray)); - state.builder.AddType({OpTypeArray, state.storage_image_uint_2d_array_array_type, - state.storage_image_uint_2d_array_type, count}); - state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_uint_2d_array_array, - StorageClassUniformConstant, - state.storage_image_uint_2d_array_array_type}); - state.builder.AddType({OpVariable, state.ptr_uniform_storage_image_uint_2d_array_array, - state.storage_image_uint_2d_array_variable, - StorageClassUniformConstant}); - } - state.builder.AddType({OpTypeImage, state.storage_image_uint_3d_type, state.uint_type, Dim3D, 0, - 0, 0, 2, ImageFormatR32ui}); - state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_uint_3d, - StorageClassUniformConstant, state.storage_image_uint_3d_type}); - if (state.storage_image_uint_3d_variable != 0) { - const auto count = - ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::StorageUint3D)); - state.builder.AddType({OpTypeArray, state.storage_image_uint_3d_array_type, - state.storage_image_uint_3d_type, count}); - state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_uint_3d_array, - StorageClassUniformConstant, - state.storage_image_uint_3d_array_type}); - state.builder.AddType({OpVariable, state.ptr_uniform_storage_image_uint_3d_array, - state.storage_image_uint_3d_variable, StorageClassUniformConstant}); + {OpTypePointer, image.pointer_type, StorageClassUniformConstant, image.image_type}); + if (image.variable != 0) { + const auto kind = StorageBindingKind(integer, view); + const auto count = ConstantU32(state, DescriptorCount(state, kind)); + state.builder.AddType({OpTypeArray, image.array_type, image.image_type, count}); + state.builder.AddType({OpTypePointer, image.array_pointer_type, + StorageClassUniformConstant, image.array_type}); + state.builder.AddType({OpVariable, image.array_pointer_type, image.variable, + StorageClassUniformConstant}); + } } state.builder.AddType( {OpTypePointer, state.ptr_image_uint, StorageClassImage, state.uint_type}); @@ -882,28 +786,18 @@ void AllocateDescriptorVariables(EmitterState& state) { state.flattened_srt_variable = state.builder.AllocateId(); } for (uint32_t i = 0; i < state.sampled_images.size(); i++) { - const auto view = static_cast(i % 3u); - if (DescriptorBinding(state, SampledBindingKind(i >= 3u, view)) != nullptr) { + const auto view = static_cast(i % ImageViewKindCount); + if (DescriptorBinding(state, SampledBindingKind(i >= ImageViewKindCount, view)) != + nullptr) { state.sampled_images[i].variable = state.builder.AllocateId(); } } - if (DescriptorBinding(state, IR::DescriptorBindingKind::Storage2D) != nullptr) { - state.storage_image_variable = state.builder.AllocateId(); - } - if (DescriptorBinding(state, IR::DescriptorBindingKind::StorageUint2D) != nullptr) { - state.storage_image_uint_variable = state.builder.AllocateId(); - } - if (DescriptorBinding(state, IR::DescriptorBindingKind::Storage2DArray) != nullptr) { - state.storage_image_2d_array_variable = state.builder.AllocateId(); - } - if (DescriptorBinding(state, IR::DescriptorBindingKind::StorageUint2DArray) != nullptr) { - state.storage_image_uint_2d_array_variable = state.builder.AllocateId(); - } - if (DescriptorBinding(state, IR::DescriptorBindingKind::Storage3D) != nullptr) { - state.storage_image_3d_variable = state.builder.AllocateId(); - } - if (DescriptorBinding(state, IR::DescriptorBindingKind::StorageUint3D) != nullptr) { - state.storage_image_uint_3d_variable = state.builder.AllocateId(); + for (uint32_t i = 0; i < state.storage_images.size(); i++) { + const auto view = static_cast(i % ImageViewKindCount); + if (DescriptorBinding(state, StorageBindingKind(i >= ImageViewKindCount, view)) != + nullptr) { + state.storage_images[i].variable = state.builder.AllocateId(); + } } if (DescriptorBinding(state, IR::DescriptorBindingKind::Samplers) != nullptr) { state.sampler_variable = state.builder.AllocateId(); diff --git a/tests/ResourceTrackingTests.cpp b/tests/ResourceTrackingTests.cpp index 1a2da7e1f..8099dd1c7 100644 --- a/tests/ResourceTrackingTests.cpp +++ b/tests/ResourceTrackingTests.cpp @@ -1126,7 +1126,7 @@ void TestResourceSpecializationIsTypedAndTransactional() { null_program.stage = ShaderType::Compute; null_program.blocks.resize(1); null_program.blocks[0].instructions = { - ImageUse(0x10, Opcode::ImageLoad, ResourceKind::Image, + ImageUse(0x10, Opcode::ImageLoad, ResourceKind::ImageUint, Decoder::ImageDimension::Dim3D)}; Prepare(null_program); ResourceSnapshot null_snapshot; @@ -1141,8 +1141,81 @@ void TestResourceSpecializationIsTypedAndTransactional() { ValidateResourceSpecialization(null_program, null_snapshot, &error) && null_program.info.images[0].kind == ResourceKind::Image && null_program.info.images[0].dimension == - Decoder::ImageDimension::Dim3D, - "zero-base image descriptor did not preserve tracked null-image shape"); + Decoder::ImageDimension::Dim2D && + null_program.blocks[0].instructions[0].memory.image_dimension == + Decoder::ImageDimension::Dim2D, + "zero-base image descriptor did not use the canonical null-image shape"); + + Program null_atomic; + null_atomic.stage = ShaderType::Compute; + null_atomic.blocks.resize(1); + null_atomic.blocks[0].instructions = { + ImageUse(0x14, Opcode::AtomicAddU32, ResourceKind::StorageImageUint, + Decoder::ImageDimension::Dim1D)}; + Prepare(null_atomic); + Check(SpecializeResources(null_atomic, null_snapshot, &error) && + ValidateResourceSpecialization(null_atomic, null_snapshot, &error) && + null_atomic.info.images[0].kind == + ResourceKind::StorageImageUint && + null_atomic.info.images[0].dimension == + Decoder::ImageDimension::Dim2D, + "null image atomic did not preserve its required integer storage type"); + + Program array_view; + array_view.stage = ShaderType::Compute; + array_view.blocks.resize(1); + array_view.blocks[0].instructions = { + ImageUse(0x18, Opcode::ImageLoad, ResourceKind::Image, + Decoder::ImageDimension::Dim1D)}; + Prepare(array_view); + ResourceSnapshot array_snapshot; + array_snapshot.images.resize(1); + array_snapshot.images[0].dword_count = 8; + array_snapshot.images[0].dwords[0] = 0x1000; + array_snapshot.images[0].dwords[3] = + Prospero::GpuEnumValue(Prospero::ImageType::kColor1DArray) << 28u; + Check(SpecializeResources(array_view, array_snapshot, &error) && + array_view.info.images[0].dimension == + Decoder::ImageDimension::Dim1D && + array_view.blocks[0].instructions[0].memory.image_dimension == + Decoder::ImageDimension::Dim1D, + "non-array MIMG view did not narrow a 1D-array descriptor"); + auto null_after_1d = array_snapshot; + null_after_1d.images[0].dwords.fill(0); + Check(!ValidateResourceSpecialization(array_view, null_after_1d, &error) && + error.find("canonical null specialization") != std::string::npos, + "non-null 1D specialization accepted a canonical 2D null descriptor"); + + Program cross_family_array; + cross_family_array.stage = ShaderType::Compute; + cross_family_array.blocks.resize(1); + cross_family_array.blocks[0].instructions = { + ImageUse(0x1c, Opcode::ImageLoad, ResourceKind::Image, + Decoder::ImageDimension::Dim2DArray)}; + Prepare(cross_family_array); + Check(SpecializeResources(cross_family_array, array_snapshot, &error) && + cross_family_array.info.images[0].dimension == + Decoder::ImageDimension::Dim1DArray && + cross_family_array.blocks[0] + .instructions[0] + .memory.image_dimension == + Decoder::ImageDimension::Dim1DArray, + "array MIMG intent did not produce a 1D-array view"); + + Program cross_family_2d_array; + cross_family_2d_array.stage = ShaderType::Compute; + cross_family_2d_array.blocks.resize(1); + cross_family_2d_array.blocks[0].instructions = { + ImageUse(0x20, Opcode::ImageLoad, ResourceKind::Image, + Decoder::ImageDimension::Dim1DArray)}; + Prepare(cross_family_2d_array); + auto array_2d_snapshot = array_snapshot; + array_2d_snapshot.images[0].dwords[3] = + Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray) << 28u; + Check(SpecializeResources(cross_family_2d_array, array_2d_snapshot, &error) && + cross_family_2d_array.info.images[0].dimension == + Decoder::ImageDimension::Dim2DArray, + "array MIMG intent did not produce a 2D-array view"); Program program; program.stage = ShaderType::Compute; @@ -1182,6 +1255,11 @@ void TestResourceSpecializationIsTypedAndTransactional() { inst.memory.image_dimension == Decoder::ImageDimension::Dim2D, "runtime descriptor shape and integer format did not specialize dense " "IR"); + auto null_after_uint = snapshot; + null_after_uint.images[0].dwords.fill(0); + Check(!ValidateResourceSpecialization(program, null_after_uint, &error) && + error.find("canonical null specialization") != std::string::npos, + "non-null integer specialization accepted a non-integer null descriptor"); auto stale_swizzle = snapshot; stale_swizzle.images[0].dwords[3] = @@ -1438,6 +1516,45 @@ void TestNativeBindingLayout() { "native binding allocator did not preserve dense typed resource groups"); } +void TestNativeBindingLayoutOneDimensionalImages() { + Program program; + program.stage = ShaderType::Compute; + program.blocks.resize(1); + auto &insts = program.blocks[0].instructions; + insts.push_back(ImageUse(8, Opcode::ImageSample, ResourceKind::Image, + Decoder::ImageDimension::Dim1D, 0, 2)); + insts.push_back(ImageUse(12, Opcode::ImageSample, ResourceKind::Image, + Decoder::ImageDimension::Dim1DArray, 0, 2)); + insts.push_back(ImageUse(16, Opcode::ImageStore, ResourceKind::StorageImage, + Decoder::ImageDimension::Dim1D, 4)); + insts.push_back(ImageUse(20, Opcode::ImageStore, + ResourceKind::StorageImageUint, + Decoder::ImageDimension::Dim1DArray, 6)); + Prepare(program); + + ShaderComputeInputInfo compute; + compute.thread_ids_num = 1; + std::string error; + Check(CollectShaderInfo(program, {.compute = &compute}, &error) && + AllocateBindings(program, {}, &error), + error.c_str()); + const auto *sampled = + FindBinding(program.bindings, DescriptorBindingKind::Sampled1D); + const auto *sampled_array = + FindBinding(program.bindings, DescriptorBindingKind::Sampled1DArray); + const auto *storage = + FindBinding(program.bindings, DescriptorBindingKind::Storage1D); + const auto *storage_uint_array = + FindBinding(program.bindings, DescriptorBindingKind::StorageUint1DArray); + Check(sampled != nullptr && sampled_array != nullptr && storage != nullptr && + storage_uint_array != nullptr && + sampled->resources == std::vector{0} && + sampled_array->resources == std::vector{1} && + storage->resources == std::vector{2} && + storage_uint_array->resources == std::vector{3}, + "binding allocator did not preserve first-class 1D image groups"); +} + void TestNativeBindingLayoutSrtAndUserDataOverflow() { Program srt; srt.stage = ShaderType::Compute; @@ -1948,6 +2065,7 @@ int main() { RUN(TestRuntimeSpecializationCoversBakedBufferAndAddressFields); RUN(TestTrackedProgramIsImmutable); RUN(TestNativeBindingLayout); + RUN(TestNativeBindingLayoutOneDimensionalImages); RUN(TestNativeBindingLayoutSrtAndUserDataOverflow); RUN(TestNativeBindingLayoutGds); RUN(TestNativeBindingLayoutIsTransactional); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index a00beedb7..6379786cc 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -218,6 +218,13 @@ struct TextureCacheTestAccess { }; struct RenderExecutorTestAccess { + static DescriptorCache::TextureBinding + ResolveTexture(RenderExecutor &executor, + const ShaderRecompiler::IR::ImageResource &resource, + const ShaderRecompiler::IR::DescriptorValue &value) { + return executor.ResolveTexture(resource, value); + } + static auto PrepareGraphicsBindings(RenderExecutor &executor, CommandBuffer &buffer, const ShaderStageRuntime &vertex, @@ -706,6 +713,11 @@ struct TestCase { std::vector> sampled_image_rgba_mips; vk::Format sampled_image_format = vk::Format::eR32G32B32A32Sfloat; u32 sampled_image_dwords_per_pixel = 4; + vk::ImageType sampled_image_type = vk::ImageType::e2D; + vk::ImageViewType sampled_image_view_type = vk::ImageViewType::e2D; + u32 sampled_image_layers = 1; + u32 sampled_image_view_base_layer = 0; + u32 sampled_image_view_layers = 0; std::vector storage_image_rgba; std::vector expected_storage_image_rgba; vk::Format storage_image_format = vk::Format::eR32G32B32A32Sfloat; @@ -1111,6 +1123,7 @@ public: vk::ImageLayout layout = vk::ImageLayout::eUndefined; u32 width = 0; u32 height = 0; + u32 layers = 1; u32 mip_levels = 1; u32 dwords_per_pixel = 0; }; @@ -1836,6 +1849,50 @@ public: !ImageViewOps::FormatsCompatible(vk::Format::eD32Sfloat, vk::Format::eR32Sfloat), "role-free image backing or host format compatibility diverged"); + + auto line_info = color_info; + line_info.type = Prospero::ImageType::kColor1D; + line_info.extent = {8, 1, 1}; + line_info.resources = {2, 2}; + line_info.mip_layout[0] = {0, 64, 8, 1}; + line_info.mip_layout[1] = {64, 16, 4, 1}; + Libs::Graphics::Image line(m_runtime_context, scheduler, line_info); + auto line_view_info = sampled; + line_view_info.type = vk::ImageViewType::e1D; + const auto line_view = line.FindView(line_view_info); + auto line_array_info = line_view_info; + line_array_info.type = vk::ImageViewType::e1DArray; + line_array_info.layer_count = 2; + const auto line_array = line.FindView(line_array_info); + Require(name, "1D views", + line_view != nullptr && line_array != nullptr && + line_array != line_view && + line.backing.image_type == vk::ImageType::e1D && + line.backing.layers == 2, + "1D and 1D-array views did not use a first-class 1D backing"); + + auto volume_info = color_info; + volume_info.type = Prospero::ImageType::kColor3D; + volume_info.extent = {8, 8, 4}; + volume_info.resources = {2, 1}; + Libs::Graphics::Image volume(m_runtime_context, scheduler, volume_info); + auto slice_info = sampled; + slice_info.type = vk::ImageViewType::e2D; + slice_info.base_level = 1; + slice_info.base_layer = 1; + const auto slice = volume.FindView(slice_info); + auto slice_array_info = slice_info; + slice_array_info.type = vk::ImageViewType::e2DArray; + slice_array_info.base_layer = 0; + slice_array_info.layer_count = 2; + const auto slice_array = volume.FindView(slice_array_info); + Require(name, "3D slice views", + slice != nullptr && slice_array != nullptr && + slice_array != slice && + static_cast( + volume.backing.flags & + vk::ImageCreateFlagBits::e2DArrayCompatible), + "2D slice views of a compatible 3D backing were rejected"); std::printf("[host] %-32s ok\n", name); } @@ -2506,10 +2563,13 @@ public: Prospero::GpuEnumValue(Prospero::TileMode::kLinear); desc.info.mip_layout[0] = {0, size, extent.width, extent.height}; desc.view_info.format = format; - desc.view_info.type = type == Prospero::ImageType::kColor3D - ? vk::ImageViewType::e3D - : layers > 1 ? vk::ImageViewType::e2DArray - : vk::ImageViewType::e2D; + if (type == Prospero::ImageType::kColor3D) { + desc.view_info.type = vk::ImageViewType::e3D; + } else if (layers > 1) { + desc.view_info.type = vk::ImageViewType::e2DArray; + } else { + desc.view_info.type = vk::ImageViewType::e2D; + } desc.view_info.aspect = vk::ImageAspectFlagBits::eColor; desc.view_info.layer_count = layers; desc.view_info.usage = vk::ImageUsageFlagBits::eSampled; @@ -4939,6 +4999,11 @@ public: ShaderRecompiler::IR::DescriptorValue null_descriptor{}; null_descriptor.dword_count = 8; null_snapshot.images.assign(3, null_descriptor); + std::string null_error; + Require(name, "null specialization", + ShaderRecompiler::IR::SpecializeResources( + null_program, null_snapshot, &null_error), + null_error.c_str()); ShaderStageRuntime null_runtime{ std::make_shared( std::move(null_program)), @@ -5038,6 +5103,84 @@ public: std::copy(std::begin(storage.fields), std::end(storage.fields), storage_descriptor.dwords.begin()); storage_descriptor.dword_count = 8; + auto srgb_storage = storage; + constexpr auto srgb_format = + Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb); + constexpr uint64_t srgb_storage_address = base + 0xc0000; + const auto encoded_srgb_storage_address = srgb_storage_address >> 8u; + srgb_storage.fields[0] = + static_cast(encoded_srgb_storage_address); + srgb_storage.fields[1] = + static_cast(encoded_srgb_storage_address >> 32u) | + (srgb_format << 20u); + ShaderRecompiler::IR::DescriptorValue srgb_storage_descriptor{}; + std::copy(std::begin(srgb_storage.fields), std::end(srgb_storage.fields), + srgb_storage_descriptor.dwords.begin()); + srgb_storage_descriptor.dword_count = 8; + auto srgb_storage_resource = storage_resource; + srgb_storage_resource.kind = + ShaderRecompiler::IR::ResourceKind::StorageImage; + const auto srgb_storage_binding = + RenderExecutorTestAccess::ResolveTexture( + executor, srgb_storage_resource, srgb_storage_descriptor); + const auto srgb_storage_view = + texture_cache.FindTexture(srgb_storage_binding.image_id, + srgb_storage_binding.desc); + Require(name, "sRGB storage view", + srgb_storage_view != nullptr && + srgb_storage_binding.desc.info.pixel_format == + vk::Format::eR8G8B8A8Srgb && + srgb_storage_binding.desc.view_info.format == + vk::Format::eR8G8B8A8Unorm && + texture_cache.GetImage(srgb_storage_binding.image_id) + .backing.format == + vk::Format::eR8G8B8A8Srgb, + "storage descriptor did not preserve its sRGB backing and " + "select an UNORM Vulkan view"); + + auto narrowed_storage = storage; + constexpr uint64_t narrowed_storage_address = base + 0xd0000; + const auto encoded_narrowed_address = narrowed_storage_address >> 8u; + narrowed_storage.fields[0] = + static_cast(encoded_narrowed_address); + narrowed_storage.fields[1] = + static_cast(encoded_narrowed_address >> 32u) | + (stencil_format << 20u); + narrowed_storage.fields[3] = + DstSel(4, 5, 6, 7) | (linear << 20u) | + (Prospero::GpuEnumValue(Prospero::ImageType::kColor1DArray) << 28u); + narrowed_storage.fields[4] = 1u | (1u << 16u); + ShaderRecompiler::IR::DescriptorValue narrowed_storage_descriptor{}; + std::copy(std::begin(narrowed_storage.fields), + std::end(narrowed_storage.fields), + narrowed_storage_descriptor.dwords.begin()); + narrowed_storage_descriptor.dword_count = 8; + auto narrowed_storage_resource = storage_resource; + narrowed_storage_resource.dimension = + ShaderRecompiler::Decoder::ImageDimension::Dim1D; + const auto narrowed_storage_binding = + RenderExecutorTestAccess::ResolveTexture( + executor, narrowed_storage_resource, + narrowed_storage_descriptor); + const auto narrowed_storage_view = + texture_cache.FindTexture(narrowed_storage_binding.image_id, + narrowed_storage_binding.desc); + const auto &narrowed_storage_image = + texture_cache.GetImage(narrowed_storage_binding.image_id); + Require(name, "narrowed 1D array storage view", + narrowed_storage_view != nullptr && + narrowed_storage_binding.desc.info.type == + Prospero::ImageType::kColor1D && + narrowed_storage_binding.desc.info.resources.layers == 2 && + narrowed_storage_binding.desc.view_info.type == + vk::ImageViewType::e1D && + narrowed_storage_binding.desc.view_info.base_layer == 1 && + narrowed_storage_binding.desc.view_info.layer_count == 1 && + narrowed_storage_image.backing.image_type == + vk::ImageType::e1D, + "non-array 1D specialization did not select the descriptor " + "base layer from its 1D-array backing"); + ShaderRecompiler::IR::ResourceSnapshot storage_snapshot{}; storage_snapshot.images.push_back(storage_descriptor); ShaderStageRuntime storage_runtime{ @@ -6048,8 +6191,9 @@ public: if (!initial.empty()) { mips.push_back(initial); } - return CreateImage2DMips(shader_name, width, height, format, usage, mips, - dwords_per_pixel, final_layout); + return CreateImageMips(shader_name, width, height, format, usage, mips, + dwords_per_pixel, final_layout, + vk::ImageType::e2D, vk::ImageViewType::e2D, 1); } static u32 MipExtent(u32 value, u32 level) { @@ -6060,31 +6204,36 @@ public: } static size_t ImageMipDwordCount(u32 width, u32 height, u32 dwords_per_pixel, - u32 level) { + u32 level, u32 layers = 1) { return static_cast(MipExtent(width, level)) * - static_cast(MipExtent(height, level)) * dwords_per_pixel; + static_cast(MipExtent(height, level)) * layers * + dwords_per_pixel; } - Image CreateImage2DMips(const char *shader_name, u32 width, u32 height, - vk::Format format, vk::ImageUsageFlags usage, - const std::vector> &initial_mips, - u32 dwords_per_pixel, vk::ImageLayout final_layout) { + Image CreateImageMips(const char *shader_name, u32 width, u32 height, + vk::Format format, vk::ImageUsageFlags usage, + const std::vector> &initial_mips, + u32 dwords_per_pixel, vk::ImageLayout final_layout, + vk::ImageType image_type, vk::ImageViewType view_type, + u32 layers, u32 view_base_layer = 0, + u32 view_layers = 0) { Image ret; ret.format = format; ret.width = width; ret.height = height; + ret.layers = layers; ret.mip_levels = std::max(static_cast(initial_mips.size()), 1u); ret.dwords_per_pixel = dwords_per_pixel; vk::ImageCreateInfo image_info{}; image_info.sType = vk::StructureType::eImageCreateInfo; - image_info.imageType = vk::ImageType::e2D; + image_info.imageType = image_type; image_info.format = format; image_info.extent.width = width; image_info.extent.height = height; image_info.extent.depth = 1; image_info.mipLevels = ret.mip_levels; - image_info.arrayLayers = 1; + image_info.arrayLayers = layers; image_info.samples = vk::SampleCountFlagBits::e1; image_info.tiling = vk::ImageTiling::eOptimal; image_info.usage = usage | vk::ImageUsageFlagBits::eTransferDst | @@ -6120,13 +6269,20 @@ public: vk::ImageViewCreateInfo view_info{}; view_info.sType = vk::StructureType::eImageViewCreateInfo; view_info.image = ret.image; - view_info.viewType = vk::ImageViewType::e2D; + view_info.viewType = view_type; view_info.format = format; view_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; view_info.subresourceRange.baseMipLevel = 0; view_info.subresourceRange.levelCount = ret.mip_levels; - view_info.subresourceRange.baseArrayLayer = 0; - view_info.subresourceRange.layerCount = 1; + Require(shader_name, "dispatch", view_base_layer < layers, + "image view base layer is out of bounds"); + if (view_layers == 0) { + view_layers = layers - view_base_layer; + } + Require(shader_name, "dispatch", view_layers <= layers - view_base_layer, + "image view layer count is out of bounds"); + view_info.subresourceRange.baseArrayLayer = view_base_layer; + view_info.subresourceRange.layerCount = view_layers; RequireVk(shader_name, "dispatch", m_device.createImageView(&view_info, nullptr, &ret.view), "vkCreateImageView"); @@ -6135,13 +6291,13 @@ public: size_t total_dwords = 0; for (u32 level = 0; level < ret.mip_levels; level++) { total_dwords += - ImageMipDwordCount(width, height, dwords_per_pixel, level); + ImageMipDwordCount(width, height, dwords_per_pixel, level, layers); } std::vector contents(total_dwords, 0); size_t offset = 0; for (u32 level = 0; level < ret.mip_levels; level++) { const auto level_dwords = - ImageMipDwordCount(width, height, dwords_per_pixel, level); + ImageMipDwordCount(width, height, dwords_per_pixel, level, layers); const auto &src = initial_mips[level]; for (size_t i = 0; i < src.size() && i < level_dwords; i++) { contents[offset + i] = src[i]; @@ -6183,6 +6339,7 @@ public: std::vector ReadImage(const char *shader_name, Image *image) { const auto dword_count = static_cast(image->width) * static_cast(image->height) * + image->layers * image->dwords_per_pixel; auto staging = CreateHostBuffer(shader_name, dword_count * sizeof(u32), vk::BufferUsageFlagBits::eTransferDst, {}); @@ -6193,13 +6350,13 @@ public: vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, - vk::AccessFlagBits::eTransferRead); + vk::AccessFlagBits::eTransferRead, image->mip_levels, image->layers); vk::BufferImageCopy copy{}; copy.bufferOffset = 0; copy.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; copy.imageSubresource.mipLevel = 0; copy.imageSubresource.baseArrayLayer = 0; - copy.imageSubresource.layerCount = 1; + copy.imageSubresource.layerCount = image->layers; copy.imageExtent.width = image->width; copy.imageExtent.height = image->height; copy.imageExtent.depth = 1; @@ -6267,19 +6424,28 @@ public: }; auto Count = [&](Kind kind) { const auto *binding = Binding(kind); - return binding == nullptr ? 0u - : kind == Kind::Gds ? 1u - : static_cast(binding->resources.size()); + if (binding == nullptr) { + return 0u; + } + if (kind == Kind::Gds) { + return 1u; + } + return static_cast(binding->resources.size()); }; Require(test.name, "dispatch", - Count(Kind::Sampled2DArray) == 0 && Count(Kind::Sampled3D) == 0 && + Count(Kind::Sampled2DArray) == 0 && + Count(Kind::Sampled3D) == 0 && Count(Kind::SampledUint2DArray) == 0 && Count(Kind::SampledUint3D) == 0 && + Count(Kind::Storage1D) == 0 && + Count(Kind::Storage1DArray) == 0 && Count(Kind::Storage2DArray) == 0 && Count(Kind::Storage3D) == 0 && + Count(Kind::StorageUint1D) == 0 && + Count(Kind::StorageUint1DArray) == 0 && Count(Kind::StorageUint2DArray) == 0 && Count(Kind::StorageUint3D) == 0, - "array/3D image cases must provide matching Vulkan test views " + "unsupported array/3D image cases must provide matching Vulkan test views " "before dispatch"); vk::ShaderModuleCreateInfo module_info{}; @@ -6308,17 +6474,25 @@ public: vk::DescriptorType type = vk::DescriptorType::eStorageBuffer; u32 count = static_cast(binding.resources.size()); switch (binding.kind) { + case Kind::Sampled1D: + case Kind::Sampled1DArray: case Kind::Sampled2D: case Kind::Sampled2DArray: case Kind::Sampled3D: + case Kind::SampledUint1D: + case Kind::SampledUint1DArray: case Kind::SampledUint2D: case Kind::SampledUint2DArray: case Kind::SampledUint3D: type = vk::DescriptorType::eSampledImage; break; + case Kind::Storage1D: + case Kind::Storage1DArray: case Kind::Storage2D: case Kind::Storage2DArray: case Kind::Storage3D: + case Kind::StorageUint1D: + case Kind::StorageUint1DArray: case Kind::StorageUint2D: case Kind::StorageUint2DArray: case Kind::StorageUint3D: @@ -6402,13 +6576,19 @@ public: (Binding(Kind::FlattenedSrt) != nullptr ? 1u : 0u) + (Binding(Kind::UserData) != nullptr ? 1u : 0u)); add_pool_size(vk::DescriptorType::eSampledImage, - Count(Kind::Sampled2D) + Count(Kind::Sampled2DArray) + - Count(Kind::Sampled3D) + Count(Kind::SampledUint2D) + + Count(Kind::Sampled1D) + Count(Kind::Sampled1DArray) + + Count(Kind::Sampled2D) + Count(Kind::Sampled2DArray) + + Count(Kind::Sampled3D) + Count(Kind::SampledUint1D) + + Count(Kind::SampledUint1DArray) + + Count(Kind::SampledUint2D) + Count(Kind::SampledUint2DArray) + Count(Kind::SampledUint3D)); add_pool_size(vk::DescriptorType::eStorageImage, - Count(Kind::Storage2D) + Count(Kind::Storage2DArray) + - Count(Kind::Storage3D) + Count(Kind::StorageUint2D) + + Count(Kind::Storage1D) + Count(Kind::Storage1DArray) + + Count(Kind::Storage2D) + Count(Kind::Storage2DArray) + + Count(Kind::Storage3D) + Count(Kind::StorageUint1D) + + Count(Kind::StorageUint1DArray) + + Count(Kind::StorageUint2D) + Count(Kind::StorageUint2DArray) + Count(Kind::StorageUint3D)); add_pool_size(vk::DescriptorType::eSampler, Count(Kind::Samplers)); @@ -6529,13 +6709,18 @@ public: write.pBufferInfo = &gds_info; writes.push_back(write); } - const auto *sampled = Binding(Kind::Sampled2D); - const auto *sampled_uint = Binding(Kind::SampledUint2D); - Require(test.name, "dispatch", - sampled == nullptr || sampled_uint == nullptr, - "Vulkan test harness needs separate float and uint sampled images"); - if (sampled == nullptr) { - sampled = sampled_uint; + const ShaderRecompiler::IR::DescriptorBinding *sampled = nullptr; + constexpr std::array sampled_kinds { + Kind::Sampled1D, Kind::Sampled1DArray, Kind::Sampled2D, + Kind::SampledUint1D, Kind::SampledUint1DArray, Kind::SampledUint2D, + }; + for (const auto kind : sampled_kinds) { + if (const auto *candidate = Binding(kind); candidate != nullptr) { + Require(test.name, "dispatch", sampled == nullptr, + "Vulkan test harness needs separate sampled images for mixed " + "descriptor classes"); + sampled = candidate; + } } if (sampled != nullptr) { Require(test.name, "dispatch", sampled_image != nullptr, @@ -8138,7 +8323,7 @@ private: vk::PipelineStageFlags src_stage, vk::PipelineStageFlags dst_stage, vk::AccessFlags src_access, vk::AccessFlags dst_access, - u32 mip_levels = 1) { + u32 mip_levels = 1, u32 layers = 1) { vk::ImageMemoryBarrier barrier{}; barrier.sType = vk::StructureType::eImageMemoryBarrier; barrier.srcAccessMask = src_access; @@ -8152,7 +8337,7 @@ private: barrier.subresourceRange.baseMipLevel = 0; barrier.subresourceRange.levelCount = mip_levels; barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.layerCount = layers; cmd.pipelineBarrier(src_stage, dst_stage, {}, 0, nullptr, 0, nullptr, 1, &barrier); } @@ -8164,7 +8349,8 @@ private: vk::AccessFlags src_access, vk::AccessFlags dst_access) { vk::CommandBuffer cmd = BeginCommands(shader_name, "dispatch"); AddImageBarrier(cmd, image->image, image->layout, new_layout, src_stage, - dst_stage, src_access, dst_access, image->mip_levels); + dst_stage, src_access, dst_access, image->mip_levels, + image->layers); EndSubmitAndFree(shader_name, "dispatch", cmd); image->layout = new_layout; } @@ -8181,7 +8367,8 @@ private: vk::ImageLayout::eTransferDstOptimal, vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTransfer, {}, - vk::AccessFlagBits::eTransferWrite, image->mip_levels); + vk::AccessFlagBits::eTransferWrite, image->mip_levels, + image->layers); std::vector copies; copies.reserve(image->mip_levels); vk::DeviceSize offset = 0; @@ -8191,14 +8378,14 @@ private: copy.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; copy.imageSubresource.mipLevel = level; copy.imageSubresource.baseArrayLayer = 0; - copy.imageSubresource.layerCount = 1; + copy.imageSubresource.layerCount = image->layers; copy.imageExtent.width = MipExtent(image->width, level); copy.imageExtent.height = MipExtent(image->height, level); copy.imageExtent.depth = 1; copies.push_back(copy); offset += static_cast( ImageMipDwordCount(image->width, image->height, - image->dwords_per_pixel, level) * + image->dwords_per_pixel, level, image->layers) * sizeof(u32)); } cmd.copyBufferToImage(staging, image->image, @@ -8208,7 +8395,8 @@ private: final_layout, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eComputeShader, vk::AccessFlagBits::eTransferWrite, - AccessForLayout(final_layout), image->mip_levels); + AccessForLayout(final_layout), image->mip_levels, + image->layers); EndSubmitAndFree(shader_name, "dispatch", cmd); image->layout = final_layout; } @@ -8356,12 +8544,16 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) { VulkanHarness::Buffer gds_buffer; vk::Sampler sampler = nullptr; const bool needs_sampled_image = + Has(Kind::Sampled1D) || Has(Kind::Sampled1DArray) || Has(Kind::Sampled2D) || Has(Kind::Sampled2DArray) || Has(Kind::Sampled3D) || Has(Kind::SampledUint2D) || + Has(Kind::SampledUint1D) || Has(Kind::SampledUint1DArray) || Has(Kind::SampledUint2DArray) || Has(Kind::SampledUint3D); const bool needs_storage_image = + Has(Kind::Storage1D) || Has(Kind::Storage1DArray) || Has(Kind::Storage2D) || Has(Kind::Storage2DArray) || Has(Kind::Storage3D) || Has(Kind::StorageUint2D) || + Has(Kind::StorageUint1D) || Has(Kind::StorageUint1DArray) || Has(Kind::StorageUint2DArray) || Has(Kind::StorageUint3D); const bool needs_sampler = Has(Kind::Samplers); const bool needs_gds = Has(Kind::Gds); @@ -8372,19 +8564,22 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) { vulkan->CreateStorageBuffer(test.name, test.gds_initial, gds_dwords); } if (needs_sampled_image) { + auto sampled_mips = test.sampled_image_rgba_mips; + auto sampled_format = test.sampled_image_format; + auto sampled_dwords_per_pixel = test.sampled_image_dwords_per_pixel; if (!test.sampled_image_rgba_mips.empty()) { - sampled_image = vulkan->CreateImage2DMips( - test.name, test.image_width, test.image_height, - vk::Format::eR32G32B32A32Sfloat, vk::ImageUsageFlagBits::eSampled, - test.sampled_image_rgba_mips, 4, - vk::ImageLayout::eShaderReadOnlyOptimal); - } else { - sampled_image = vulkan->CreateImage2D( - test.name, test.image_width, test.image_height, - test.sampled_image_format, vk::ImageUsageFlagBits::eSampled, - test.sampled_image_rgba, test.sampled_image_dwords_per_pixel, - vk::ImageLayout::eShaderReadOnlyOptimal); + sampled_format = vk::Format::eR32G32B32A32Sfloat; + sampled_dwords_per_pixel = 4; + } else if (!test.sampled_image_rgba.empty()) { + sampled_mips.push_back(test.sampled_image_rgba); } + sampled_image = vulkan->CreateImageMips( + test.name, test.image_width, test.image_height, sampled_format, + vk::ImageUsageFlagBits::eSampled, sampled_mips, + sampled_dwords_per_pixel, vk::ImageLayout::eShaderReadOnlyOptimal, + test.sampled_image_type, test.sampled_image_view_type, + test.sampled_image_layers, test.sampled_image_view_base_layer, + test.sampled_image_view_layers); } if (needs_storage_image) { storage_image = vulkan->CreateImage2D( @@ -13465,6 +13660,105 @@ TestCase ImageLoadR32UintUsesIntegerSampledImage() { return test; } +TestCase ImageLoad1DUsesScalarCoordinate() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovU32(&code, 20, 2); + code.push_back(EncodeMimg0(0x00, 0x1, 0, false, 0)); + code.push_back(EncodeMimg1(0, 20)); + AppendStoreVgpr(&code, 0, 0); + AppendEnd(&code); + + TestCase test; + test.name = "ImageLoad1DUsesScalarCoordinate"; + test.code = code; + test.expected = {0xdeadbeefu}; + test.opcodes = {O::VMovB32, O::ImageLoad, O::BufferStoreDword, O::SEndpgm}; + test.image_width = 4; + test.image_height = 1; + test.sampled_image_rgba = {0, 0, 0xdeadbeefu, 0}; + test.sampled_image_format = vk::Format::eR32Uint; + test.sampled_image_dwords_per_pixel = 1; + test.sampled_image_type = vk::ImageType::e1D; + test.sampled_image_view_type = vk::ImageViewType::e1D; + test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32UInt); + test.user_data[3] = + Prospero::GpuEnumValue(Prospero::ImageType::kColor1D) << 28u; + test.has_user_data = true; + test.required_spirv = {"sampled_uint_1d"}; + return test; +} + +TestCase ImageLoad1DArrayUsesLayerCoordinate() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovU32(&code, 20, 2); + AppendVMovU32(&code, 21, 1); + code.push_back(EncodeMimg0(0x00, 0x1, 0, false, 4)); + code.push_back(EncodeMimg1(0, 20)); + AppendStoreVgpr(&code, 0, 0); + AppendEnd(&code); + + TestCase test; + test.name = "ImageLoad1DArrayUsesLayerCoordinate"; + test.code = code; + test.expected = {0xcafebabeu}; + test.opcodes = {O::VMovB32, O::ImageLoad, O::BufferStoreDword, O::SEndpgm}; + test.image_width = 4; + test.image_height = 1; + test.sampled_image_rgba = {0, 0, 0, 0, 0, 0, 0xcafebabeu, 0}; + test.sampled_image_format = vk::Format::eR32Uint; + test.sampled_image_dwords_per_pixel = 1; + test.sampled_image_type = vk::ImageType::e1D; + test.sampled_image_view_type = vk::ImageViewType::e1DArray; + test.sampled_image_layers = 2; + test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32UInt); + test.user_data[3] = + Prospero::GpuEnumValue(Prospero::ImageType::kColor1DArray) << 28u; + test.has_user_data = true; + test.required_spirv = {"sampled_uint_1d_array"}; + return test; +} + +TestCase ImageLoad1DArrayDescriptorUsesSelectedLayer() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovU32(&code, 20, 2); + code.push_back(EncodeMimg0(0x00, 0x1, 0, false, 0)); + code.push_back(EncodeMimg1(0, 20)); + AppendStoreVgpr(&code, 0, 0); + AppendEnd(&code); + + TestCase test; + test.name = "ImageLoad1DArrayDescriptorUsesSelectedLayer"; + test.code = code; + test.expected = {0xcafebabeu}; + test.opcodes = {O::VMovB32, O::ImageLoad, O::BufferStoreDword, O::SEndpgm}; + test.image_width = 4; + test.image_height = 1; + test.sampled_image_rgba = { + 0, 0, 0xdeadbeefu, 0, + 0, 0, 0xcafebabeu, 0, + }; + test.sampled_image_format = vk::Format::eR32Uint; + test.sampled_image_dwords_per_pixel = 1; + test.sampled_image_type = vk::ImageType::e1D; + test.sampled_image_view_type = vk::ImageViewType::e1D; + test.sampled_image_layers = 2; + test.sampled_image_view_base_layer = 1; + test.sampled_image_view_layers = 1; + test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32UInt); + test.user_data[3] = + Prospero::GpuEnumValue(Prospero::ImageType::kColor1DArray) << 28u; + test.user_data[4] = 1u | (1u << 16u); + test.has_user_data = true; + test.required_spirv = {"sampled_uint_1d"}; + return test; +} + TestCase ImageLoadMipUsesVaddr2Lod2D() { using O = ShaderOpcode; @@ -13707,7 +14001,7 @@ TestCase ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu() { using O = ShaderOpcode; std::vector code; - AppendVMovU32(&code, 20, 0); // Texel offset stays one packed 32-bit VGPR. + AppendVMovU32(&code, 20, 1); // Non-constant +1 X offset is not a SPIR-V ConstOffset. AppendVMovLiteral(&code, 21, 0x36003900u); // x=0.625, y=0.375 packed as f16. AppendVMovU32(&code, 22, 0); code.push_back(EncodeMimg0(0x30, 0xf)); @@ -13727,7 +14021,8 @@ TestCase ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu() { test.expected = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u}; test.opcodes = {O::VMovB32, O::ImageSample, O::BufferStoreDword, O::SEndpgm}; test.sampled_image_rgba = image; - test.required_spirv = {"UnpackHalf2x16", "OpBitFieldSExtract"}; + test.required_spirv = {"UnpackHalf2x16"}; + test.forbidden_spirv = {"OpBitFieldSExtract"}; return test; } @@ -13989,7 +14284,7 @@ TestCase ImageStoreR32UintUsesUintStorageImage() { test.storage_image_rgba = MakeRgbaImage(4, 4); test.storage_image_r32ui = std::vector(16, 0); test.expected_storage_image_r32ui = expected_image; - test.required_spirv = {"R32ui", "textures2D_L_U"}; + test.required_spirv = {"R32ui", "storage_uint_2d"}; return test; } @@ -14524,6 +14819,9 @@ std::vector MakeCases() { AddCase(BufferAtomicGlc0DoesNotReturnOldValue); AddCase(ImageLoadVariants); AddCase(ImageLoadR32UintUsesIntegerSampledImage); + AddCase(ImageLoad1DUsesScalarCoordinate); + AddCase(ImageLoad1DArrayUsesLayerCoordinate); + AddCase(ImageLoad1DArrayDescriptorUsesSelectedLayer); AddCase(ImageLoadMipUsesVaddr2Lod2D); AddCase(ImageLoadMipNsaUsesSelectedAddressVgprs); AddCase(ImageLoadA16UintCoordsOnGpu); @@ -14850,6 +15148,44 @@ void CheckReverseRenderTargetFormatContract() { } else if (std::strcmp(kind, "sampled-depth-swizzle") == 0) { (void)SelectSampledDepthView(vk::Format::eD32SfloatS8Uint, vk::Format::eR32Sfloat, DstSel(4, 5, 6, 7)); + } else if (std::strcmp(kind, "storage-incompatible-format") == 0) { + ValidateStorageColorView(vk::Format::eR8G8B8A8Srgb, + vk::Format::eR16G16B16A16Unorm, + DstSel(4, 5, 6, 7)); + } else if (std::strcmp(kind, "volume-mip-count") == 0 || + std::strcmp(kind, "volume-slice-range") == 0) { + VulkanHarness vulkan; + auto &graphics = vulkan.RuntimeContext(); + CommandScheduler scheduler(vulkan.RuntimeRenderer(), graphics); + ImageInfo volume_info{}; + volume_info.pixel_format = vk::Format::eR8G8B8A8Unorm; + volume_info.guest_format = + Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm); + volume_info.type = Prospero::ImageType::kColor3D; + volume_info.extent = {8, 8, 4}; + volume_info.resources = {2, 1}; + volume_info.pitch = 8; + volume_info.bytes_per_block = 4; + volume_info.samples = 1; + volume_info.tile_mode = + Prospero::GpuEnumValue(Prospero::TileMode::kLinear); + Libs::Graphics::Image volume(graphics, scheduler, volume_info); + ImageViewInfo view{}; + view.format = volume_info.pixel_format; + view.type = vk::ImageViewType::e2DArray; + view.aspect = vk::ImageAspectFlagBits::eColor; + view.base_level = 1; + view.level_count = 1; + view.layer_count = 1; + view.usage = vk::ImageUsageFlagBits::eSampled; + if (std::strcmp(kind, "volume-mip-count") == 0) { + view.base_level = 0; + view.level_count = 2; + } else { + view.base_layer = 1; + view.layer_count = 2; + } + (void)volume.FindView(view); } else { ShaderRecompiler::IR::ImageResource resource{}; resource.kind = ShaderRecompiler::IR::ResourceKind::StorageImage; @@ -15083,8 +15419,9 @@ void CheckSampledColorViews() { for (const char *kind : {"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"}) { + "storage-incompatible-format", "storage-kind", "storage-no-write", + "storage-atomic", "storage-compare", "storage-mip", "storage-dimension", + "volume-mip-count", "volume-slice-range"}) { std::string command = std::string("\"") + path + "\" --image-view-death " + kind; std::vector mutable_command(command.begin(), command.end()); @@ -16022,6 +16359,10 @@ void CheckBasicStorageTextureDescriptor() { 0x800); ValidateStorageColorView(vk::Format::eR16G16B16A16Sfloat, vk::Format::eR16G16B16A16Sfloat, swizzle); + ValidateStorageColorView(vk::Format::eR8G8B8A8Srgb, + vk::Format::eR8G8B8A8Unorm, swizzle); + ValidateStorageColorView(vk::Format::eB8G8R8A8Srgb, + vk::Format::eR8G8B8A8Unorm, swizzle); valid_storage_swizzles++; } Require("BasicStorageTexture", "all write swizzles", diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index a17261be6..8ca8c0fe8 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -640,6 +640,13 @@ void SetImageTestType(std::array* data, uint32_t srsrc, Prospero:: (*data)[type_dword] = Prospero::GpuEnumValue(type) << 28u; } +void SetImageTestFormat(std::array* data, uint32_t srsrc, + Prospero::BufferFormat format) { + const auto format_dword = srsrc * 4u + 1u; + Check(data != nullptr && format_dword < data->size(), "invalid image test descriptor source"); + (*data)[format_dword] = Prospero::GpuEnumValue(format) << 20u; +} + bool ReadZeroTestMemory(void*, uint64_t, uint32_t* value) { if (value == nullptr) { return false; @@ -3102,8 +3109,6 @@ void TestNewShaderRecompilerImageSampleVariants() { "SPIR-V binary does not contain shared OpImageSampleExplicitLod emission"); Check(SpirvContainsOpcode(result.spirv, 90), "SPIR-V binary does not contain shared OpImageSampleDrefExplicitLod emission"); - Check(SpirvContainsOpcode(result.spirv, 202), - "SPIR-V binary does not contain packed offset extraction"); Check(SpirvContainsOpcode(result.spirv, 80), "SPIR-V binary does not contain coordinate/gradient composite construction"); Check(SpirvContainsExtInst(result.spirv, 62), @@ -3400,8 +3405,11 @@ void TestNewShaderRecompilerPixelImageSampleLodSelection() { } void TestNewShaderRecompilerImageViewDimensions() { + constexpr uint32_t MimgDim1D = 0; constexpr uint32_t MimgDim3D = 2; + constexpr uint32_t MimgDim1DArray = 4; constexpr uint32_t MimgDim2DArray = 5; + constexpr uint32_t SpirvDim1D = 0; constexpr uint32_t SpirvDim2D = 1; constexpr uint32_t SpirvDim3D = 2; @@ -3414,6 +3422,18 @@ void TestNewShaderRecompilerImageViewDimensions() { EncodeMimg1(8, 2, 1, 8), // image_sample 3D EncodeMimg0(0x01, 0xf, false, MimgDim2DArray), EncodeMimg1(12, 3, 0, 12), // image_load_mip 2D array + EncodeMimg0(0x20, 0xf, false, MimgDim1D), + EncodeMimg1(16, 4, 1, 16), // image_sample 1D + EncodeMimg0(0x20, 0xf, false, MimgDim1DArray), + EncodeMimg1(20, 5, 1, 20), // image_sample 1D array + EncodeMimg0(0x22, 0xf, false, MimgDim1D), + EncodeMimg1(24, 6, 1, 24), // image_sample_d 1D + EncodeMimg0(0x01, 0xf, false, MimgDim1D), + EncodeMimg1(28, 7, 0, 28), // image_load_mip 1D + EncodeMimg0(0x0e, 0xf, false, MimgDim1D), + EncodeMimg1(32, 8, 0, 32), // image_get_resinfo 1D + EncodeMimg0(0x00, 0xf, false, MimgDim1D), + EncodeMimg1(36, 9, 0, 36), // integer image_load 1D 0xbf810000u, }; @@ -3423,6 +3443,13 @@ void TestNewShaderRecompilerImageViewDimensions() { SetImageTestType(&user_data, 1, Prospero::ImageType::kColor2DArray); SetImageTestType(&user_data, 2, Prospero::ImageType::kColor3D); SetImageTestType(&user_data, 3, Prospero::ImageType::kColor2DArray); + SetImageTestType(&user_data, 4, Prospero::ImageType::kColor1D); + SetImageTestType(&user_data, 5, Prospero::ImageType::kColor1DArray); + SetImageTestType(&user_data, 6, Prospero::ImageType::kColor1D); + SetImageTestType(&user_data, 7, Prospero::ImageType::kColor1D); + SetImageTestType(&user_data, 8, Prospero::ImageType::kColor1D); + SetImageTestType(&user_data, 9, Prospero::ImageType::kColor1D); + SetImageTestFormat(&user_data, 9, Prospero::BufferFormat::k32UInt); ShaderRecompiler::CompileOptions options; options.stage = ShaderType::Pixel; @@ -3438,6 +3465,15 @@ void TestNewShaderRecompilerImageViewDimensions() { "MIMG DIM did not decode 2D-array image view"); Check(Common::ContainsStr(result.decoded_dump, "image_dim=3d"), "MIMG DIM did not decode 3D image view"); + Check(Common::ContainsStr(result.decoded_dump, + "image_dim=1d sample_flags=none addr_components=1"), + "1D sample did not preserve its scalar coordinate"); + Check(Common::ContainsStr(result.decoded_dump, + "image_dim=1d_array sample_flags=none addr_components=2"), + "1D-array sample did not preserve coordinate plus layer"); + Check(Common::ContainsStr(result.decoded_dump, + "image_dim=1d sample_flags=derivative addr_components=3"), + "1D derivative sample did not preserve scalar gradients"); Check(Common::ContainsStr(result.decoded_dump, "image_sample_l"), "2D-array LOD sample did not decode"); Check(Common::ContainsStr(result.decoded_dump, @@ -3447,13 +3483,121 @@ void TestNewShaderRecompilerImageViewDimensions() { "2D-array image view did not survive into IR metadata"); Check(Common::ContainsStr(result.ir_dump, "image_dim=3d"), "3D image view did not survive into IR metadata"); + Check(Common::ContainsStr(result.ir_dump, "image_dim=1d"), + "1D image view did not survive into IR metadata"); + Check(Common::ContainsStr(result.ir_dump, "image_dim=1d_array"), + "1D-array image view did not survive into IR metadata"); + Check(SpirvContainsTypeImage(result.spirv, SpirvDim1D, 0, 1), + "SPIR-V binary does not contain sampled 1D image type"); + Check(SpirvContainsTypeImage(result.spirv, SpirvDim1D, 1, 1), + "SPIR-V binary does not contain sampled 1D-array image type"); Check(SpirvContainsTypeImage(result.spirv, SpirvDim2D, 1, 1), "SPIR-V binary does not contain sampled 2D-array image type"); Check(SpirvContainsTypeImage(result.spirv, SpirvDim3D, 0, 1), "SPIR-V binary does not contain sampled 3D image type"); + Check(SpirvContainsCapability(result.spirv, 43), + "SPIR-V binary does not request Sampled1D"); + Check(SpirvContainsCapability(result.spirv, 44), + "SPIR-V binary does not request Image1D"); Check(SpirvContainsOpcode(result.spirv, 95), "SPIR-V binary does not contain array image fetch"); CheckSpirvBinaryValidates(result.spirv); + const auto source = DisassembleSpirvBinary(result.spirv); + Check(Common::ContainsStr(source, " Grad "), + "1D derivative sample did not emit scalar SPIR-V gradients"); + Check(Common::ContainsStr(source, "OpImageQuerySizeLod"), + "1D resource query did not emit a scalar size query"); + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "sampled_uint_1d"), + "integer 1D load did not access the uint 1D descriptor binding"); +} + +void TestNewShaderRecompilerStorageImage1DDescriptorVariants() { + constexpr uint32_t MimgDim1D = 0; + constexpr uint32_t MimgDim1DArray = 4; + constexpr uint32_t SpirvDim1D = 0; + + const uint32_t shader[] = { + EncodeMimg0(0x08, 0xf, false, MimgDim1D), + EncodeMimg1(20, 4, 0, 4), // image_store 1D + EncodeMimg0(0x08, 0xf, false, MimgDim1DArray), + EncodeMimg1(24, 5, 0, 8), // image_store 1D array + 0xbf810000u, + }; + + auto user_data = ImageTestUserData(); + SetImageTestType(&user_data, 4, Prospero::ImageType::kColor1D); + SetImageTestType(&user_data, 5, Prospero::ImageType::kColor1DArray); + + ShaderRecompiler::CompileOptions options; + options.stage = ShaderType::Compute; + options.dump_ir = true; + options.user_data = user_data.data(); + + ShaderRecompiler::CompileResult result; + std::string error; + Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str()); + Check(Common::ContainsStr(result.ir_dump, "image_dim=1d"), + "1D storage dimension did not survive into IR metadata"); + Check(Common::ContainsStr(result.ir_dump, "image_dim=1d_array"), + "1D-array storage dimension did not survive into IR metadata"); + Check(SpirvContainsTypeImage(result.spirv, SpirvDim1D, 0, 2), + "SPIR-V binary does not contain storage 1D image type"); + Check(SpirvContainsTypeImage(result.spirv, SpirvDim1D, 1, 2), + "SPIR-V binary does not contain storage 1D-array image type"); + CheckSpirvBinaryValidates(result.spirv); + + const auto source = DisassembleSpirvBinary(result.spirv); + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "storage_1d "), + "1D store did not access the 1D storage descriptor binding"); + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "storage_1d_array"), + "1D-array store did not access the 1D-array storage descriptor binding"); +} + +void TestNewShaderRecompilerNullImageUsesCanonical2DView() { + const uint32_t shader[] = { + EncodeMimg0(0x20, 0xf, false, 0), + EncodeMimg1(0, 0, 1, 0), // 1D instruction with a null image descriptor + 0xbf810000u, + }; + std::array user_data {}; + + ShaderRecompiler::CompileOptions options; + options.stage = ShaderType::Compute; + options.user_data = user_data.data(); + + ShaderRecompiler::CompileResult result; + std::string error; + Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str()); + CheckSpirvBinaryValidates(result.spirv); + const auto source = DisassembleSpirvBinary(result.spirv); + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "sampled_2d "), + "null image did not use the canonical 2D sampled descriptor binding"); + Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "sampled_1d "), + "null image retained the decoded 1D descriptor binding"); +} + +void TestNewShaderRecompilerRejectsOneDimensionalGather() { + constexpr std::array cases { + std::pair {0u, Prospero::ImageType::kColor1D}, + std::pair {4u, Prospero::ImageType::kColor1DArray}, + }; + for (const auto& [dimension, type]: cases) { + const uint32_t shader[] = { + EncodeMimg0(0x47, 0x1, false, dimension), + EncodeMimg1(0, 0, 1, 0), + 0xbf810000u, + }; + auto user_data = ImageTestUserData(type); + ShaderRecompiler::CompileOptions options; + options.stage = ShaderType::Compute; + options.user_data = user_data.data(); + + ShaderRecompiler::CompileResult result; + std::string error; + Check(!ShaderRecompiler::TryRecompile(shader, options, result, &error) && + Common::ContainsStr(error, "1D image gather is not supported by SPIR-V"), + "1D image gather escaped as invalid SPIR-V"); + } } void TestNewShaderRecompilerImageGatherVariants() { @@ -3680,7 +3824,7 @@ void TestNewShaderRecompilerStorageImage3DDescriptorVariant() { CheckSpirvBinaryValidates(result.spirv); const auto source = DisassembleSpirvBinary(result.spirv); - Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "textures2D_L_3D"), + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "storage_3d"), "storage image store did not access the 3D storage descriptor binding"); } @@ -3719,11 +3863,11 @@ void TestNewShaderRecompilerStorageImage2DDescriptorOverridesMimg3D() { CheckSpirvBinaryValidates(result.spirv); const auto source = DisassembleSpirvBinary(result.spirv); - Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "textures2D_L"), + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "storage_2d"), "2D descriptor storage image store did not access the base storage binding"); - Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "textures2D_L_A"), + Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "storage_2d_array"), "2D descriptor storage image store unexpectedly used the array storage binding"); - Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "textures2D_L_3D"), + Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "storage_3d"), "2D descriptor storage image store unexpectedly used the 3D storage binding"); } @@ -6811,6 +6955,9 @@ int main() { TestNewShaderRecompilerImageSampleA16Rdna2AddressOrder(); TestNewShaderRecompilerPixelImageSampleLodSelection(); TestNewShaderRecompilerImageViewDimensions(); + TestNewShaderRecompilerStorageImage1DDescriptorVariants(); + TestNewShaderRecompilerNullImageUsesCanonical2DView(); + TestNewShaderRecompilerRejectsOneDimensionalGather(); TestNewShaderRecompilerImageGatherVariants(); TestNewShaderRecompilerImageLoadA16UintCoords(); TestNewShaderRecompilerImageLoadVariants();