diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptorCache.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptorCache.cpp index 089fc8805..b56031cf0 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptorCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptorCache.cpp @@ -51,7 +51,12 @@ bool IsStorageImage(BindingKind kind) { case BindingKind::StorageUint1DArray: case BindingKind::StorageUint2D: case BindingKind::StorageUint2DArray: - case BindingKind::StorageUint3D: return true; + case BindingKind::StorageUint3D: + case BindingKind::StorageAtomic1D: + case BindingKind::StorageAtomic1DArray: + case BindingKind::StorageAtomic2D: + case BindingKind::StorageAtomic2DArray: + case BindingKind::StorageAtomic3D: return true; default: return false; } } diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index 9beee808b..923cded17 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -286,10 +286,6 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa LOGF("shaderStorageImageWriteWithoutFormat is not supported\n"); skip_device = true; } - if (device_features2.features.shaderStorageImageReadWithoutFormat != VK_TRUE) { - LOGF("shaderStorageImageReadWithoutFormat is not supported\n"); - skip_device = true; - } if (device_features2.features.shaderImageGatherExtended != VK_TRUE) { LOGF("shaderImageGatherExtended is not supported\n"); @@ -570,7 +566,6 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V device_features.depthBounds = VK_TRUE; // unsupported by MoltenVK #endif device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE; - device_features.shaderStorageImageReadWithoutFormat = VK_TRUE; device_features.shaderImageGatherExtended = VK_TRUE; device_features.independentBlend = VK_TRUE; device_features.tessellationShader = VK_TRUE; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp index a92099732..e8c2d87be 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp @@ -134,11 +134,7 @@ ImageViewKind SampledImageViewKind(const EmitterState& state, const IR::MemoryIn return ImageViewKindFromDimension(mem.image_dimension); } -ImageViewKind StorageImageViewKind(const EmitterState& state, const IR::MemoryInfo& mem, - bool uint_image, uint32_t use_pc) { - (void)state; - (void)uint_image; - (void)use_pc; +ImageViewKind StorageImageViewKind(const IR::MemoryInfo& mem) { return ImageViewKindFromDimension(mem.image_dimension); } @@ -188,20 +184,36 @@ uint32_t ImageViewSizeType(EmitterState& state, ImageViewKind view) { } } -uint32_t StorageImageType(EmitterState& state, bool uint_image, ImageViewKind view) { - const auto component = uint_image ? TypeU32(state) : TypeF32(state); - const auto format = uint_image ? ImageFormatR32ui : ImageFormatUnknown; +uint32_t StorageImageType(EmitterState& state, StorageImageClass image_class, ImageViewKind view) { + uint32_t component = TypeF32(state); + uint32_t format = ImageFormatUnknown; + if (image_class == StorageImageClass::FormatlessUint) { + component = TypeU32(state); + } else if (image_class == StorageImageClass::AtomicUint) { + component = TypeU32(state); + format = ImageFormatR32ui; + } return state.builder.Type(OpTypeImage, {component, ImageSpirvDimension(view), 0, ImageSpirvArrayed(view), 0, 2, format}); } -uint32_t StorageImagePointerType(EmitterState& state, bool uint_image, ImageViewKind view) { +uint32_t StorageImagePointerType(EmitterState& state, StorageImageClass image_class, + ImageViewKind view) { return state.builder.Type( - OpTypePointer, {StorageClassUniformConstant, StorageImageType(state, uint_image, view)}); + OpTypePointer, {StorageClassUniformConstant, StorageImageType(state, image_class, view)}); } -uint32_t StorageImageVariable(const EmitterState& state, bool uint_image, ImageViewKind view) { - return state.storage_image_variables[StorageImageIndex(uint_image, view)]; +StorageImageClass StorageImageClassForResource(const EmitterState& state, uint32_t resource) { + const auto& image = state.program.info.images.at(resource); + const bool uint_image = image.kind == IR::ResourceKind::StorageImageUint; + EXIT_IF(image.kind != IR::ResourceKind::StorageImage && !uint_image); + EXIT_IF(image.atomic && !uint_image); + return StorageImageClassFor(uint_image, image.atomic); +} + +uint32_t StorageImageVariable(const EmitterState& state, StorageImageClass image_class, + ImageViewKind view) { + return state.storage_image_variables[StorageImageIndex(image_class, view)]; } uint32_t LoadSampledImageDescriptor(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc, @@ -259,46 +271,33 @@ uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32 return MakeSampledImage(state, selected, use_pc, view); } -uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource, bool uint_image, - uint32_t use_pc, ImageViewKind view) { - (void)use_pc; - const auto kind = StorageBindingKind(uint_image, view); +uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource, ImageViewKind view) { + const auto image_class = StorageImageClassForResource(state, resource); + const auto kind = StorageBindingKind(image_class, view); const auto binding = ResourceForDescriptor(state, kind, resource); - const auto ptr_type = StorageImagePointerType(state, uint_image, view); - const auto variable = StorageImageVariable(state, uint_image, view); + const auto ptr_type = StorageImagePointerType(state, image_class, view); + const auto variable = StorageImageVariable(state, image_class, view); return DescriptorElementPointer(state, ptr_type, variable, binding.array_index, kind, resource, "storage image descriptor array was not emitted"); } -uint32_t LoadStorageImageDescriptor(EmitterState& state, uint32_t resource, bool uint_image, - uint32_t use_pc, ImageViewKind view) { - const auto pointer = StorageImageDescriptorPointer(state, resource, uint_image, use_pc, view); - if (pointer == 0) { - ExitDescriptorBindingFailure(state, StorageBindingKind(uint_image, view), resource, - "storage image descriptor pointer creation failed"); - } - const auto type = StorageImageType(state, uint_image, view); - const auto image = state.builder.AllocateId(); - state.builder.AddFunction({OpLoad, type, image, pointer}); - return image; -} - -void EmitStorageImageWrite(EmitterState& state, uint32_t resource, bool uint_image, - ImageViewKind view, uint32_t mip_lod, uint32_t coord, uint32_t texel) { - if (!uint_image) { +void EmitStorageImageWrite(EmitterState& state, uint32_t resource, ImageViewKind view, + uint32_t mip_lod, uint32_t coord, uint32_t texel) { + const auto image_class = StorageImageClassForResource(state, resource); + if (image_class != StorageImageClass::AtomicUint) { state.builder.RequireCapability(CapabilityStorageImageWriteWithoutFormat); } - const auto kind = StorageBindingKind(uint_image, view); + const auto kind = StorageBindingKind(image_class, view); const auto binding = ResourceForDescriptor(state, kind, resource); const auto& image = state.program.info.images.at(resource); const auto LoadAt = [&](uint32_t array_index) { const auto pointer = DescriptorElementPointer( - state, StorageImagePointerType(state, uint_image, view), - StorageImageVariable(state, uint_image, view), array_index, kind, resource, + state, StorageImagePointerType(state, image_class, view), + StorageImageVariable(state, image_class, view), array_index, kind, resource, "storage image descriptor array was not emitted"); const auto descriptor = state.builder.AllocateId(); state.builder.AddFunction( - {OpLoad, StorageImageType(state, uint_image, view), descriptor, pointer}); + {OpLoad, StorageImageType(state, image_class, view), descriptor, pointer}); return descriptor; }; if (image.mip_mode != IR::ImageMipMode::DynamicStorage) { diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp index 353429f9d..0418f7d87 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp @@ -588,7 +588,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { } if (op == IR::ValueOpcode::ImageWrite) { const auto uint_image = mem.kind == IR::ResourceKind::StorageImageUint; - const auto view = StorageImageViewKind(state, mem, uint_image, pc); + const auto view = StorageImageViewKind(mem); EmitIfCondition(state, ctx.Arg(inst, 3), [&]() { const auto mip_lod = state.program.info.images[mem.resource].mip_mode == IR::ImageMipMode::DynamicStorage @@ -596,7 +596,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { : 0u; const auto coord = CoordU32(ctx, mem, *address, view); const auto texel = StoreTexel(ctx, mem, ctx.Arg(inst, 2), uint_image); - EmitStorageImageWrite(state, mem.resource, uint_image, view, mip_lod, coord, texel); + EmitStorageImageWrite(state, mem.resource, view, mip_lod, coord, texel); }); return true; } @@ -793,14 +793,14 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { } const auto atomic_opcode = ImageAtomicOpcode(op); if (atomic_opcode != 0u) { - const auto view = StorageImageViewKind(state, mem, true, pc); + const auto view = StorageImageViewKind(mem); ctx.Define(inst, EmitValueOrZeroIfCondition(state, ctx.Arg(inst, 3), [&]() { const auto pointer = state.builder.AllocateId(); const auto pointer_type = state.builder.Type(OpTypePointer, {StorageClassImage, TypeU32(state)}); state.builder.AddFunction( {OpImageTexelPointer, pointer_type, pointer, - StorageImageDescriptorPointer(state, mem.resource, true, pc, view), + StorageImageDescriptorPointer(state, mem.resource, view), CoordU32(ctx, mem, *address, view), ConstantU32(state, 0)}); const auto old = state.builder.AllocateId(); state.builder.AddFunction({atomic_opcode, TypeU32(state), old, pointer, diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index 874c9318f..dd58ca86f 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -42,7 +42,6 @@ enum : uint32_t { CapabilitySampled1D = 43, CapabilityImage1D = 44, CapabilityImageQuery = 50, - CapabilityStorageImageReadWithoutFormat = 55, CapabilityStorageImageWriteWithoutFormat = 56, CapabilityGroupNonUniform = 61, CapabilityGroupNonUniformBallot = 64, @@ -298,6 +297,27 @@ struct DescriptorResourceBinding { uint32_t array_index = 0; }; +enum class ImageViewKind { + Dim1D, + Dim1DArray, + Dim2D, + Dim2DArray, + Dim3D, + Dim2DMsaa, + Dim2DMsaaArray, + Count, +}; + +constexpr uint32_t SampledImageViewKindCount = static_cast(ImageViewKind::Count); +constexpr uint32_t StorageImageViewKindCount = static_cast(ImageViewKind::Dim2DMsaa); + +enum class StorageImageClass : uint32_t { + FormatlessFloat, + FormatlessUint, + AtomicUint, + Count, +}; + struct EmitterState { EmitterState(const IR::Program& program_, const IR::ResourceSnapshot& resources_, ShaderStageInputInfo input_info_) @@ -322,8 +342,10 @@ struct EmitterState { uint32_t flattened_srt_variable = 0; uint32_t lds_variable = 0; uint32_t scratch_variable = 0; - std::array sampled_image_variables {}; - std::array storage_image_variables {}; + std::array sampled_image_variables {}; + std::array(StorageImageClass::Count) * StorageImageViewKindCount> + storage_image_variables {}; uint32_t sampler_variable = 0; uint32_t main_func = 0; uint32_t entry_label = 0; @@ -414,26 +436,23 @@ struct ImageSampleLayout { uint32_t grad_y = NoImageComponent; }; -enum class ImageViewKind { - Dim1D, - Dim1DArray, - Dim2D, - Dim2DArray, - Dim3D, - Dim2DMsaa, - Dim2DMsaaArray, - Count, -}; - -constexpr uint32_t SampledImageViewKindCount = static_cast(ImageViewKind::Count); -constexpr uint32_t StorageImageViewKindCount = static_cast(ImageViewKind::Dim2DMsaa); - constexpr uint32_t SampledImageIndex(bool integer, ImageViewKind view) { return static_cast(view) + (integer ? SampledImageViewKindCount : 0u); } -constexpr uint32_t StorageImageIndex(bool integer, ImageViewKind view) { - return static_cast(view) + (integer ? StorageImageViewKindCount : 0u); +constexpr StorageImageClass StorageImageClassFor(bool uint_image, bool atomic) { + if (atomic) { + return StorageImageClass::AtomicUint; + } + if (uint_image) { + return StorageImageClass::FormatlessUint; + } + return StorageImageClass::FormatlessFloat; +} + +constexpr uint32_t StorageImageIndex(StorageImageClass image_class, ImageViewKind view) { + return static_cast(view) + + static_cast(image_class) * StorageImageViewKindCount; } constexpr IR::DescriptorBindingKind SampledBindingKind(bool integer, ImageViewKind view) { @@ -463,24 +482,44 @@ constexpr IR::DescriptorBindingKind SampledBindingKind(bool integer, ImageViewKi 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; +constexpr IR::DescriptorBindingKind StorageBindingKind(StorageImageClass image_class, + ImageViewKind view) { + switch (image_class) { + case StorageImageClass::FormatlessFloat: + 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; + } + break; + case StorageImageClass::FormatlessUint: + 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; + } + break; + case StorageImageClass::AtomicUint: + switch (view) { + case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::StorageAtomic1D; + case ImageViewKind::Dim1DArray: + return IR::DescriptorBindingKind::StorageAtomic1DArray; + case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::StorageAtomic2D; + case ImageViewKind::Dim2DArray: + return IR::DescriptorBindingKind::StorageAtomic2DArray; + case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::StorageAtomic3D; + default: break; + } + break; + case StorageImageClass::Count: break; } return IR::DescriptorBindingKind::Count; } @@ -575,9 +614,10 @@ uint32_t ImageViewSampledImageType(EmitterState& state, ImageViewKind view, bool uint32_t ImageViewSizeType(EmitterState& state, ImageViewKind view); -uint32_t StorageImageType(EmitterState& state, bool uint_image, ImageViewKind view); +uint32_t StorageImageType(EmitterState& state, StorageImageClass image_class, ImageViewKind view); -uint32_t StorageImagePointerType(EmitterState& state, bool uint_image, ImageViewKind view); +uint32_t StorageImagePointerType(EmitterState& state, StorageImageClass image_class, + ImageViewKind view); uint32_t LoadSampledImageDescriptor(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc, ImageViewKind view); @@ -589,18 +629,12 @@ uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32 uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc, ImageViewKind view, uint32_t image_resource); -ImageViewKind StorageImageViewKind(const EmitterState& state, const IR::MemoryInfo& mem, - bool uint_image, uint32_t use_pc); +ImageViewKind StorageImageViewKind(const IR::MemoryInfo& mem); -uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource, bool uint_image, - uint32_t use_pc = UINT32_MAX, - ImageViewKind view = ImageViewKind::Dim2D); +uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource, ImageViewKind view); -uint32_t LoadStorageImageDescriptor(EmitterState& state, uint32_t resource, bool uint_image, - uint32_t use_pc, ImageViewKind view = ImageViewKind::Dim2D); - -void EmitStorageImageWrite(EmitterState& state, uint32_t resource, bool uint_image, - ImageViewKind view, uint32_t mip_lod, uint32_t coord, uint32_t texel); +void EmitStorageImageWrite(EmitterState& state, uint32_t resource, ImageViewKind view, + uint32_t mip_lod, uint32_t coord, uint32_t texel); uint32_t ExecutionModelForStage(ShaderType stage); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index b9b257359..0ccef0c59 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -179,14 +179,14 @@ void DefineDescriptorVariables(EmitterState& state) { } } for (uint32_t i = 0; i < state.storage_image_variables.size(); i++) { - const auto view = static_cast(i % StorageImageViewKindCount); - const bool integer = i >= StorageImageViewKindCount; - const auto kind = StorageBindingKind(integer, view); + const auto view = static_cast(i % StorageImageViewKindCount); + const auto image_class = static_cast(i / StorageImageViewKindCount); + const auto kind = StorageBindingKind(image_class, view); if (DescriptorBinding(state, kind) == nullptr) { continue; } const auto count = ConstantU32(state, DescriptorCount(state, kind)); - const auto image_type = StorageImageType(state, integer, view); + const auto image_type = StorageImageType(state, image_class, view); const auto array_type = state.builder.Type(OpTypeArray, {image_type, count}); const auto pointer_type = TypePointer(state, StorageClassUniformConstant, array_type); state.storage_image_variables[i] = @@ -563,11 +563,17 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) { "storage_uint_1d_array", "storage_uint_2d", "storage_uint_2d_array", - "storage_uint_3d"}; + "storage_uint_3d", + "storage_atomic_1d", + "storage_atomic_1d_array", + "storage_atomic_2d", + "storage_atomic_2d_array", + "storage_atomic_3d"}; for (uint32_t i = 0; i < state.storage_image_variables.size(); i++) { const auto view = static_cast(i % StorageImageViewKindCount); + const auto image_class = static_cast(i / StorageImageViewKindCount); Decorate(state.storage_image_variables[i], StorageNames[i], - StorageBindingKind(i >= StorageImageViewKindCount, view)); + StorageBindingKind(image_class, view)); } if (state.sampler_variable != 0) { Decorate(state.sampler_variable, "samplers", IR::DescriptorBindingKind::Samplers); diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 0b3836d24..b85b7331a 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -325,6 +325,11 @@ enum class DescriptorBindingKind { StorageUint2D, StorageUint2DArray, StorageUint3D, + StorageAtomic1D, + StorageAtomic1DArray, + StorageAtomic2D, + StorageAtomic2DArray, + StorageAtomic3D, Samplers, Gds, AddressMemory, @@ -362,6 +367,9 @@ DescriptorBindingForImage(const ImageResource& image) { default: return std::nullopt; } case ResourceKind::StorageImage: + if (image.atomic) { + return std::nullopt; + } switch (image.dimension) { case Dimension::Dim1D: return Kind::Storage1D; case Dimension::Dim1DArray: return Kind::Storage1DArray; @@ -372,11 +380,31 @@ DescriptorBindingForImage(const ImageResource& image) { } case ResourceKind::StorageImageUint: switch (image.dimension) { - case Dimension::Dim1D: return Kind::StorageUint1D; - case Dimension::Dim1DArray: return Kind::StorageUint1DArray; - case Dimension::Dim2D: return Kind::StorageUint2D; - case Dimension::Dim2DArray: return Kind::StorageUint2DArray; - case Dimension::Dim3D: return Kind::StorageUint3D; + case Dimension::Dim1D: + if (image.atomic) { + return Kind::StorageAtomic1D; + } + return Kind::StorageUint1D; + case Dimension::Dim1DArray: + if (image.atomic) { + return Kind::StorageAtomic1DArray; + } + return Kind::StorageUint1DArray; + case Dimension::Dim2D: + if (image.atomic) { + return Kind::StorageAtomic2D; + } + return Kind::StorageUint2D; + case Dimension::Dim2DArray: + if (image.atomic) { + return Kind::StorageAtomic2DArray; + } + return Kind::StorageUint2DArray; + case Dimension::Dim3D: + if (image.atomic) { + return Kind::StorageAtomic3D; + } + return Kind::StorageUint3D; default: return std::nullopt; } default: return std::nullopt; diff --git a/src/graphics/shader/recompiler/ir/passes/BindingLayout.cpp b/src/graphics/shader/recompiler/ir/passes/BindingLayout.cpp index 69c373005..2b10633f2 100644 --- a/src/graphics/shader/recompiler/ir/passes/BindingLayout.cpp +++ b/src/graphics/shader/recompiler/ir/passes/BindingLayout.cpp @@ -37,6 +37,11 @@ constexpr std::array ImageBindingKinds = { DescriptorBindingKind::StorageUint2D, DescriptorBindingKind::StorageUint2DArray, DescriptorBindingKind::StorageUint3D, + DescriptorBindingKind::StorageAtomic1D, + DescriptorBindingKind::StorageAtomic1DArray, + DescriptorBindingKind::StorageAtomic2D, + DescriptorBindingKind::StorageAtomic2DArray, + DescriptorBindingKind::StorageAtomic3D, }; bool CollectUserData(const Program& program, std::vector& result) { diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp index 70835bdb3..a9e62693e 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp @@ -81,10 +81,15 @@ uint32_t ByteExtent(const MemoryInfo& memory) { return end > UINT32_MAX ? UINT32_MAX : static_cast(end); } +bool IsStorageImage(ResourceKind kind) { + return kind == ResourceKind::StorageImage || kind == ResourceKind::StorageImageUint; +} + ImageMipMode MipMode(const MemoryInfo& memory) { - const bool storage = - memory.kind == ResourceKind::StorageImage || memory.kind == ResourceKind::StorageImageUint; - return storage && memory.image_has_mip ? ImageMipMode::DynamicStorage : ImageMipMode::None; + if (IsStorageImage(memory.kind) && memory.image_has_mip) { + return ImageMipMode::DynamicStorage; + } + return ImageMipMode::None; } class Tracker { @@ -650,9 +655,14 @@ private: const bool depth = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0; for (uint32_t i = 0; i < m_info.images.size(); i++) { auto& image = m_info.images[i]; - if (image.source == source && image.kind == memory.kind && + const bool compatible_kind = image.kind == memory.kind || (IsStorageImage(image.kind) && + IsStorageImage(memory.kind)); + if (image.source == source && compatible_kind && image.dimension == memory.image_dimension && image.mip_mode == mip && image.depth_compare == depth && image.r128 == memory.image_r128) { + if (memory.kind == ResourceKind::StorageImageUint) { + image.kind = ResourceKind::StorageImageUint; + } Merge(image, op, pc); return i; } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 99a2f49f5..244982a6f 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -8781,7 +8781,11 @@ public: Count(Kind::StorageUint1D) == 0 && Count(Kind::StorageUint1DArray) == 0 && Count(Kind::StorageUint2DArray) == 0 && - Count(Kind::StorageUint3D) == 0, + Count(Kind::StorageUint3D) == 0 && + Count(Kind::StorageAtomic1D) == 0 && + Count(Kind::StorageAtomic1DArray) == 0 && + Count(Kind::StorageAtomic2DArray) == 0 && + Count(Kind::StorageAtomic3D) == 0, "unsupported array/3D image cases must provide matching Vulkan test " "views " "before dispatch"); @@ -8834,6 +8838,11 @@ public: case Kind::StorageUint2D: case Kind::StorageUint2DArray: case Kind::StorageUint3D: + case Kind::StorageAtomic1D: + case Kind::StorageAtomic1DArray: + case Kind::StorageAtomic2D: + case Kind::StorageAtomic2DArray: + case Kind::StorageAtomic3D: type = vk::DescriptorType::eStorageImage; break; case Kind::Samplers: @@ -8926,7 +8935,10 @@ public: Count(Kind::Storage2D) + Count(Kind::Storage2DArray) + Count(Kind::Storage3D) + Count(Kind::StorageUint1D) + Count(Kind::StorageUint1DArray) + Count(Kind::StorageUint2D) + - Count(Kind::StorageUint2DArray) + Count(Kind::StorageUint3D)); + Count(Kind::StorageUint2DArray) + Count(Kind::StorageUint3D) + + Count(Kind::StorageAtomic1D) + Count(Kind::StorageAtomic1DArray) + + Count(Kind::StorageAtomic2D) + Count(Kind::StorageAtomic2DArray) + + Count(Kind::StorageAtomic3D)); add_pool_size(vk::DescriptorType::eSampler, Count(Kind::Samplers)); vk::DescriptorPoolCreateInfo pool_info{}; pool_info.sType = vk::StructureType::eDescriptorPoolCreateInfo; @@ -8955,6 +8967,7 @@ public: std::vector sampled_infos; std::vector storage_infos; std::vector storage_uint_infos; + std::vector storage_atomic_infos; std::vector sampler_infos; Buffer flattened_buffer; Buffer user_data_buffer; @@ -9078,45 +9091,34 @@ public: } const auto *storage = Binding(Kind::Storage2D); const auto *storage_uint = Binding(Kind::StorageUint2D); - if (storage != nullptr || storage_uint != nullptr) { - Require(test.name, "dispatch", storage_image != nullptr, - "storage image descriptor requested but no storage image was " + const auto *storage_atomic = Binding(Kind::StorageAtomic2D); + const auto BindStorage = + [&](const ShaderRecompiler::IR::DescriptorBinding *binding, + const Image *image, std::vector *infos) { + if (binding == nullptr) { + return; + } + Require( + test.name, "dispatch", image != nullptr, + "storage image descriptor requested but no matching image was " "provided"); - Require(test.name, "dispatch", storage_image_uint != nullptr, - "uint storage image descriptor requested but no uint storage " - "image was provided"); - storage_infos.resize(storage != nullptr ? storage->resources.size() : 0u); - storage_uint_infos.resize( - storage_uint != nullptr ? storage_uint->resources.size() : 0u); - for (auto &info : storage_infos) { - info.imageView = storage_image->view; - info.imageLayout = storage_image->layout; - } - for (auto &info : storage_uint_infos) { - info.imageView = storage_image_uint->view; - info.imageLayout = storage_image_uint->layout; - } - if (storage != nullptr) { - vk::WriteDescriptorSet write{}; - write.sType = vk::StructureType::eWriteDescriptorSet; - write.dstSet = descriptor_set; - write.dstBinding = storage->binding; - write.descriptorCount = static_cast(storage_infos.size()); - write.descriptorType = vk::DescriptorType::eStorageImage; - write.pImageInfo = storage_infos.data(); - writes.push_back(write); - } - if (storage_uint != nullptr) { - vk::WriteDescriptorSet write{}; - write.sType = vk::StructureType::eWriteDescriptorSet; - write.dstSet = descriptor_set; - write.dstBinding = storage_uint->binding; - write.descriptorCount = static_cast(storage_uint_infos.size()); - write.descriptorType = vk::DescriptorType::eStorageImage; - write.pImageInfo = storage_uint_infos.data(); - writes.push_back(write); - } - } + infos->resize(binding->resources.size()); + for (auto &info : *infos) { + info.imageView = image->view; + info.imageLayout = image->layout; + } + vk::WriteDescriptorSet write{}; + write.sType = vk::StructureType::eWriteDescriptorSet; + write.dstSet = descriptor_set; + write.dstBinding = binding->binding; + write.descriptorCount = static_cast(infos->size()); + write.descriptorType = vk::DescriptorType::eStorageImage; + write.pImageInfo = infos->data(); + writes.push_back(write); + }; + BindStorage(storage, storage_image, &storage_infos); + BindStorage(storage_uint, storage_image_uint, &storage_uint_infos); + BindStorage(storage_atomic, storage_image_uint, &storage_atomic_infos); const auto *samplers = Binding(Kind::Samplers); if (samplers != nullptr) { Require(test.name, "dispatch", sampler != nullptr, @@ -10582,9 +10584,6 @@ private: Require("VulkanHarness", "dispatch", available_features.shaderStorageImageWriteWithoutFormat == true, "shaderStorageImageWriteWithoutFormat is not supported"); - Require("VulkanHarness", "dispatch", - available_features.shaderStorageImageReadWithoutFormat == true, - "shaderStorageImageReadWithoutFormat is not supported"); Require("VulkanHarness", "dispatch", available_features12.timelineSemaphore == true, "timeline semaphores are not supported"); @@ -10622,7 +10621,6 @@ private: device_info.pNext = &device_features13; vk::PhysicalDeviceFeatures device_features{}; device_features.shaderStorageImageWriteWithoutFormat = true; - device_features.shaderStorageImageReadWithoutFormat = true; device_features.sampleRateShading = true; device_info.pEnabledFeatures = &device_features; constexpr const char *device_extensions[] = { @@ -10977,7 +10975,10 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) { Has(Kind::Storage2D) || Has(Kind::Storage2DArray) || Has(Kind::Storage3D) || Has(Kind::StorageUint2D) || Has(Kind::StorageUint1D) || Has(Kind::StorageUint1DArray) || - Has(Kind::StorageUint2DArray) || Has(Kind::StorageUint3D); + Has(Kind::StorageUint2DArray) || Has(Kind::StorageUint3D) || + Has(Kind::StorageAtomic1D) || Has(Kind::StorageAtomic1DArray) || + Has(Kind::StorageAtomic2D) || Has(Kind::StorageAtomic2DArray) || + Has(Kind::StorageAtomic3D); const bool needs_sampler = Has(Kind::Samplers); const bool needs_gds = Has(Kind::Gds); if (needs_gds) { @@ -18632,11 +18633,13 @@ TestCase ImageStoreR32SintUsesRawUintView() { test.has_user_data = true; test.storage_image_r32ui = std::vector(16, 0); test.expected_storage_image_r32ui = expected_image; - test.required_spirv = {"storage_uint_2d"}; + test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat", + "storage_uint_2d"}; + test.forbidden_spirv = {"R32ui"}; return test; } -TestCase ImageStoreR32UintUsesUintStorageImage() { +TestCase ImageStoreR32UintUsesFormatlessStorageImage() { using O = ShaderOpcode; std::vector code; @@ -18652,7 +18655,7 @@ TestCase ImageStoreR32UintUsesUintStorageImage() { expected_image[1 * 4 + 2] = 0x12345678u; TestCase test; - test.name = "ImageStoreR32UintUsesUintStorageImage"; + test.name = "ImageStoreR32UintUsesFormatlessStorageImage"; test.code = code; test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::S_ENDPGM}; test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt); @@ -18660,7 +18663,9 @@ 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", "storage_uint_2d"}; + test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat", + "storage_uint_2d"}; + test.forbidden_spirv = {"R32ui"}; return test; } @@ -18692,7 +18697,9 @@ TestCase ImageStorePackedUintSaturatesChannels() { test.image_descriptor_swizzle = DstSel(4, 5, 6, 0); test.storage_image_r32ui = std::vector(16, 0); test.expected_storage_image_r32ui = std::move(expected_image); - test.required_spirv = {"R32ui", "OpULessThan", "OpSelect"}; + test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat", + "storage_uint_2d", "OpULessThan", "OpSelect"}; + test.forbidden_spirv = {"R32ui"}; return test; } @@ -18722,6 +18729,9 @@ TestCase ImageStorePackedUintHonorsSparseDmask() { test.image_descriptor_swizzle = DstSel(4, 5, 6, 0); test.storage_image_r32ui = std::vector(16, 0); test.expected_storage_image_r32ui = std::move(expected_image); + test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat", + "storage_uint_2d"}; + test.forbidden_spirv = {"R32ui"}; return test; } @@ -18751,6 +18761,82 @@ TestCase ComputeTgSizeSgprUsesWaveMetadata() { return test; } +TestCase ImageStoreAndAtomicShareTypedBinding() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovU32(&code, 20, 2); + AppendVMovU32(&code, 21, 1); + AppendVMovU32(&code, 22, 0); + AppendVMovU32(&code, 0, 5); + code.push_back(EncodeMimg0(0x08, 0x1)); + code.push_back(EncodeMimg1(0, 20)); + AppendVMovU32(&code, 0, 3); + code.push_back(EncodeMimg0(0x11, 0x1, 0, true)); + code.push_back(EncodeMimg1(0, 20)); + AppendStoreVgpr(&code, 0, 0); + AppendEnd(&code); + + std::vector expected_image(16, 0); + expected_image[1 * 4 + 2] = 8; + + TestCase test; + test.name = "ImageStoreAndAtomicShareTypedBinding"; + test.code = std::move(code); + test.expected = {5}; + test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::IMAGE_ATOMIC_ADD, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt); + test.has_user_data = true; + test.storage_image_r32ui = std::vector(16, 0); + test.expected_storage_image_r32ui = std::move(expected_image); + test.required_spirv = {"OpImageWrite", "OpImageTexelPointer", "R32ui", + "storage_atomic_2d"}; + test.forbidden_spirv = {"StorageImageReadWithoutFormat", + "StorageImageWriteWithoutFormat", "storage_uint_2d"}; + return test; +} + +TestCase ImageStoreAndAtomicUseSeparateBindings() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovU32(&code, 20, 2); + AppendVMovU32(&code, 21, 1); + AppendVMovU32(&code, 22, 0); + AppendVMovU32(&code, 0, 5); + code.push_back(EncodeMimg0(0x08, 0x1)); + code.push_back(EncodeMimg1(0, 20, 0)); + AppendVMovU32(&code, 0, 3); + code.push_back(EncodeMimg0(0x11, 0x1, 0, true)); + code.push_back(EncodeMimg1(0, 20, 2)); + AppendStoreVgpr(&code, 0, 0); + AppendEnd(&code); + + auto user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt); + std::copy_n(user_data.begin(), 8, user_data.begin() + 8); + std::vector expected_image(16, 0); + expected_image[1 * 4 + 2] = 8; + + TestCase test; + test.name = "ImageStoreAndAtomicUseSeparateBindings"; + test.code = std::move(code); + test.expected = {5}; + test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::IMAGE_ATOMIC_ADD, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.user_data = user_data; + test.has_user_data = true; + test.storage_image_r32ui = std::vector(16, 0); + test.expected_storage_image_r32ui = std::move(expected_image); + test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat", + "OpImageWrite", + "OpImageTexelPointer", + "R32ui", + "storage_uint_2d", + "storage_atomic_2d"}; + return test; +} + TestCase ImageAtomicVariants() { using O = ShaderOpcode; @@ -18778,10 +18864,10 @@ TestCase ImageAtomicVariants() { O::IMAGE_ATOMIC_UMIN, O::IMAGE_ATOMIC_AND, O::IMAGE_ATOMIC_OR, O::IMAGE_ATOMIC_XOR, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; - test.required_spirv = {"OpImageTexelPointer", "R32ui"}; + test.required_spirv = {"OpImageTexelPointer", "R32ui", "storage_atomic_2d"}; test.forbidden_spirv = {"OpTypeSampler", "OpTypeSampledImage", "StorageImageReadWithoutFormat", - "StorageImageWriteWithoutFormat"}; + "StorageImageWriteWithoutFormat", "storage_uint_2d"}; test.storage_image_rgba = MakeRgbaImage(4, 4); test.storage_image_r32ui = std::vector(16, 0); for (u32 i = 0; i < static_cast(std::size(initial)); i++) { @@ -19340,10 +19426,12 @@ std::vector MakeCases() { AddCase(ImageStoreYzwxUsesInverseSwizzle); AddCase(ImageStoreR32FloatUsesFormatlessStorageImage); AddCase(ImageStoreR32SintUsesRawUintView); - AddCase(ImageStoreR32UintUsesUintStorageImage); + AddCase(ImageStoreR32UintUsesFormatlessStorageImage); AddCase(ImageStorePackedUintSaturatesChannels); AddCase(ImageStorePackedUintHonorsSparseDmask); AddCase(ComputeTgSizeSgprUsesWaveMetadata); + AddCase(ImageStoreAndAtomicShareTypedBinding); + AddCase(ImageStoreAndAtomicUseSeparateBindings); AddCase(ImageAtomicVariants); AddCase(ImageAtomicGlc0DoesNotReturnOldValue); AddCase(MultipleWorkitemsGlobalId); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 2912e07a9..25c6d3e77 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -8625,8 +8625,10 @@ void TestDemandDrivenSpirvDeclarations() { SpirvInstructionOpcodeCount(declaration_binary, 59u) == 2u, "canonical builder emitted duplicate declarations"); - const auto compile = [](std::span shader) { + const auto compile = [](std::span shader, + Prospero::BufferFormat format) { auto user_data = ImageTestUserData(); + SetImageTestFormat(&user_data, 0, format); auto options = MakeCompileOptions(ShaderType::Compute); options.user_data = user_data.data(); ShaderRecompiler::CompileResult result; @@ -8664,7 +8666,7 @@ void TestDemandDrivenSpirvDeclarations() { const uint32_t store[] = {EncodeMimg0(0x08, 0xf), EncodeMimg1(0, 0, 0, 20), EncodeSopp(0x01)}; - const auto store_spirv = compile(store); + const auto store_spirv = compile(store, Prospero::BufferFormat::k8UNorm); Check(SpirvInstructionOpcodeCount(store_spirv, 99u) == 1u, "storage image fixture did not reach OpImageWrite"); const auto store_metrics = MeasureSpirv(store_spirv); @@ -8676,7 +8678,7 @@ void TestDemandDrivenSpirvDeclarations() { const uint32_t atomic[] = {EncodeMimg0(0x11, 0x1, true), EncodeMimg1(0, 0, 0, 20), EncodeSopp(0x01)}; - const auto atomic_spirv = compile(atomic); + const auto atomic_spirv = compile(atomic, Prospero::BufferFormat::k32UInt); Check(SpirvInstructionOpcodeCount(atomic_spirv, 60u) == 1u, "image atomic fixture did not reach OpImageTexelPointer"); const auto atomic_metrics = MeasureSpirv(atomic_spirv); @@ -10170,6 +10172,7 @@ void TestNewShaderRecompilerNativeBindingPlan() { }; auto user_data = ImageTestUserData(); + SetImageTestFormat(&user_data, 6, Prospero::BufferFormat::k32UInt); auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; options.user_data = user_data.data(); @@ -10185,8 +10188,8 @@ void TestNewShaderRecompilerNativeBindingPlan() { result.program.bindings, BindingKind::Sampled2D); const auto *storage = ShaderRecompiler::IR::FindBinding( result.program.bindings, BindingKind::Storage2D); - const auto *uint_storage = ShaderRecompiler::IR::FindBinding( - result.program.bindings, BindingKind::StorageUint2D); + const auto *atomic_storage = ShaderRecompiler::IR::FindBinding( + result.program.bindings, BindingKind::StorageAtomic2D); const auto *samplers = ShaderRecompiler::IR::FindBinding( result.program.bindings, BindingKind::Samplers); Check(buffers != nullptr && buffers->resources.size() == 2, @@ -10195,8 +10198,8 @@ void TestNewShaderRecompilerNativeBindingPlan() { "native binding plan did not allocate the sampled image"); Check(storage != nullptr && storage->resources.size() == 1, "native binding plan did not allocate the float storage image"); - Check(uint_storage != nullptr && uint_storage->resources.size() == 1, - "native binding plan did not allocate the uint storage image"); + Check(atomic_storage != nullptr && atomic_storage->resources.size() == 1, + "native binding plan did not allocate the atomic storage image"); Check(samplers != nullptr && samplers->resources.size() == 1, "native binding plan did not allocate the sampler"); Check(SpirvContainsOpcode(result.spirv, 86),