From 5c86f894beb7b5d52ab417cfe089509e3afaca87 Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 2 Sep 2026 12:44:30 +0400 Subject: [PATCH] shader: simplify typed image bindings --- .../renderer/pipeline/descriptors.cpp | 154 +++++-------- .../backend/spirv/spirvEmitterAnalysis.cpp | 170 ++++++-------- .../backend/spirv/spirvEmitterImage.cpp | 166 ++++++++------ .../backend/spirv/spirvEmitterInternal.h | 159 +------------ .../backend/spirv/spirvEmitterModule.cpp | 97 ++------ src/graphics/shader/recompiler/ir/ShaderIR.h | 202 +++++++---------- .../recompiler/ir/passes/BindingLayout.cpp | 13 +- tests/ResourceTrackingTests.cpp | 112 ++++++++- tests/ShaderRecompilerComputeTests.cpp | 213 +++++++++--------- tests/shaderCfgTests.cpp | 58 +++-- 10 files changed, 597 insertions(+), 747 deletions(-) diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index 71be0ed8b..5ae9c72f9 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -48,67 +48,27 @@ namespace { using BindingKind = ShaderRecompiler::IR::DescriptorBindingKind; -bool IsSampledImage(BindingKind kind) { - switch (kind) { - case BindingKind::Sampled1D: - case BindingKind::Sampled1DArray: - case BindingKind::Sampled2D: - case BindingKind::Sampled2DArray: - case BindingKind::Sampled2DMsaa: - case BindingKind::Sampled2DMsaaArray: - case BindingKind::Sampled3D: - case BindingKind::SampledUint1D: - case BindingKind::SampledUint1DArray: - case BindingKind::SampledUint2D: - case BindingKind::SampledUint2DArray: - case BindingKind::SampledUint2DMsaa: - case BindingKind::SampledUint2DMsaaArray: - case BindingKind::SampledUint3D: - case BindingKind::SampledSint1D: - case BindingKind::SampledSint1DArray: - case BindingKind::SampledSint2D: - case BindingKind::SampledSint2DArray: - case BindingKind::SampledSint2DMsaa: - case BindingKind::SampledSint2DMsaaArray: - case BindingKind::SampledSint3D: return true; - default: return false; - } -} - -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: - case BindingKind::StorageAtomic1D: - case BindingKind::StorageAtomic1DArray: - case BindingKind::StorageAtomic2D: - case BindingKind::StorageAtomic2DArray: - case BindingKind::StorageAtomic3D: return true; - default: return false; - } -} - } // namespace vk::DescriptorType NativeDescriptorType(BindingKind kind) { - if (kind == BindingKind::Samplers) { - return vk::DescriptorType::eSampler; - } - if (IsSampledImage(kind)) { + const auto image_class = ShaderRecompiler::IR::ImageBindingResourceClass(kind); + if (image_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) { return vk::DescriptorType::eSampledImage; } - if (IsStorageImage(kind)) { + if (image_class == ShaderRecompiler::IR::ImageResourceClass::Storage) { return vk::DescriptorType::eStorageImage; } - return vk::DescriptorType::eStorageBuffer; + switch (kind) { + case BindingKind::Samplers: return vk::DescriptorType::eSampler; + case BindingKind::Buffers: + case BindingKind::Gds: + case BindingKind::BdaPagetable: + case BindingKind::FaultBuffer: + case BindingKind::FlattenedSrt: + case BindingKind::UserData: return vk::DescriptorType::eStorageBuffer; + case BindingKind::Count: EXIT("invalid native descriptor binding kind"); + } + EXIT("invalid native descriptor binding kind"); } uint32_t NativeDescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding) { @@ -1181,48 +1141,54 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer, write.descriptorCount = NativeDescriptorCount(binding); const auto buffer_start = m_descriptor_buffers.size(); const auto image_start = m_descriptor_images.size(); - switch (binding.kind) { - case BindingKind::Buffers: - for (const auto resource: binding.resources) { - const auto& view = descriptors.buffers.at(resource); - EXIT_IF(view.buffer == nullptr); - m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range); - } - break; - case BindingKind::BdaPagetable: - case BindingKind::FaultBuffer: { - auto& cache = m_context.GetBufferCache(); - const auto* bda_buffer = binding.kind == BindingKind::BdaPagetable - ? cache.GetBdaPageTableBuffer() - : cache.GetFaultBuffer(); - m_descriptor_buffers.emplace_back(bda_buffer->Handle(), 0, bda_buffer->Size()); - break; + if (ShaderRecompiler::IR::ImageBindingResourceClass(binding.kind) != + ShaderRecompiler::IR::ImageResourceClass::None) { + for (const auto resource: binding.resources) { + m_descriptor_images.push_back(MakeImageInfo( + descriptors.images.at(resource), m_image_occurrences.at(resource)++)); } - case BindingKind::FlattenedSrt: - case BindingKind::UserData: - case BindingKind::Gds: { - const auto& view = - binding.kind == BindingKind::FlattenedSrt ? descriptors.flattened_srt - : binding.kind == BindingKind::UserData ? descriptors.user_data - : descriptors.gds; - EXIT_IF(view.buffer == nullptr); - m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range); - break; + } else { + switch (binding.kind) { + case BindingKind::Buffers: + for (const auto resource: binding.resources) { + const auto& view = descriptors.buffers.at(resource); + EXIT_IF(view.buffer == nullptr); + m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range); + } + break; + case BindingKind::BdaPagetable: + case BindingKind::FaultBuffer: { + auto& cache = m_context.GetBufferCache(); + const auto* bda_buffer = binding.kind == BindingKind::BdaPagetable + ? cache.GetBdaPageTableBuffer() + : cache.GetFaultBuffer(); + m_descriptor_buffers.emplace_back(bda_buffer->Handle(), 0, + bda_buffer->Size()); + break; + } + case BindingKind::FlattenedSrt: + case BindingKind::UserData: + case BindingKind::Gds: { + const BufferView* view = &descriptors.gds; + if (binding.kind == BindingKind::FlattenedSrt) { + view = &descriptors.flattened_srt; + } else if (binding.kind == BindingKind::UserData) { + view = &descriptors.user_data; + } + EXIT_IF(view->buffer == nullptr); + m_descriptor_buffers.emplace_back(view->buffer, view->offset, view->range); + break; + } + case BindingKind::Samplers: + for (const auto resource: binding.resources) { + const auto sampler = descriptors.samplers.at(resource); + EXIT_IF(sampler == nullptr); + m_descriptor_images.emplace_back(sampler, nullptr, + vk::ImageLayout::eUndefined); + } + break; + case BindingKind::Count: EXIT("invalid descriptor binding kind"); } - case BindingKind::Samplers: - for (const auto resource: binding.resources) { - const auto sampler = descriptors.samplers.at(resource); - EXIT_IF(sampler == nullptr); - m_descriptor_images.emplace_back(sampler, nullptr, - vk::ImageLayout::eUndefined); - } - break; - default: - for (const auto resource: binding.resources) { - m_descriptor_images.push_back(MakeImageInfo( - descriptors.images.at(resource), m_image_occurrences.at(resource)++)); - } - break; } if (m_descriptor_buffers.size() != buffer_start) { write.pBufferInfo = m_descriptor_buffers.data() + buffer_start; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp index f039c8a59..d0555440a 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp @@ -124,34 +124,42 @@ const ImageDimensionInfo& ImageDimensionInfoFor(ImageDimension dimension) { std::abort(); } -uint32_t SampledImageScalarType(EmitterState& state, SampledImageClass image_class) { - switch (image_class) { - case SampledImageClass::Float: return TypeF32(state); - case SampledImageClass::Uint: return TypeU32(state); - case SampledImageClass::Sint: return TypeI32(state); - case SampledImageClass::Count: break; +uint32_t ImageScalarType(EmitterState& state, Prospero::TextureNumericClass numeric_class) { + switch (numeric_class) { + case Prospero::TextureNumericClass::Float: return TypeF32(state); + case Prospero::TextureNumericClass::Uint: return TypeU32(state); + case Prospero::TextureNumericClass::Sint: return TypeI32(state); + case Prospero::TextureNumericClass::Unsupported: break; } - return 0; + EXIT("invalid image numeric class"); } -uint32_t SampledImageVectorType(EmitterState& state, SampledImageClass image_class, - uint32_t components) { - return state.builder.Type(OpTypeVector, - {SampledImageScalarType(state, image_class), components}); +uint32_t ImageVectorType(EmitterState& state, Prospero::TextureNumericClass numeric_class, + uint32_t components) { + return state.builder.Type(OpTypeVector, {ImageScalarType(state, numeric_class), components}); } -uint32_t ImageViewImageType(EmitterState& state, ImageDimension dimension, - SampledImageClass image_class) { - const auto& info = ImageDimensionInfoFor(dimension); +uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) { + uint32_t sampled = 0; + uint32_t format = ImageFormatUnknown; + if (image.resource_class == IR::ImageResourceClass::Sampled) { + EXIT_IF(image.atomic); + sampled = 1; + } else if (image.resource_class == IR::ImageResourceClass::Storage) { + EXIT_IF(image.numeric_class == Prospero::TextureNumericClass::Sint || + image.numeric_class == Prospero::TextureNumericClass::Unsupported); + sampled = 2; + if (image.atomic) { + EXIT_IF(image.numeric_class != Prospero::TextureNumericClass::Uint); + format = ImageFormatR32ui; + } + } else { + EXIT("invalid image resource class"); + } + const auto& info = ImageDimensionInfoFor(image.dimension); return state.builder.Type(OpTypeImage, - {SampledImageScalarType(state, image_class), info.spirv_dimension, 0, - info.arrayed, info.multisampled, 1, ImageFormatUnknown}); -} - -uint32_t ImageViewSampledImageType(EmitterState& state, ImageDimension dimension, - SampledImageClass image_class) { - return state.builder.Type(OpTypeSampledImage, - {ImageViewImageType(state, dimension, image_class)}); + {ImageScalarType(state, image.numeric_class), info.spirv_dimension, 0, + info.arrayed, info.multisampled, sampled, format}); } uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension) { @@ -163,62 +171,20 @@ uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension) { } } -uint32_t StorageImageType(EmitterState& state, StorageImageClass image_class, - ImageDimension dimension) { - 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; - } - const auto& info = ImageDimensionInfoFor(dimension); - return state.builder.Type(OpTypeImage, - {component, info.spirv_dimension, 0, info.arrayed, 0, 2, format}); -} - -uint32_t StorageImagePointerType(EmitterState& state, StorageImageClass image_class, - ImageDimension dimension) { - return state.builder.Type(OpTypePointer, {StorageClassUniformConstant, - StorageImageType(state, image_class, dimension)}); -} - -StorageImageClass StorageImageClassForResource(const EmitterState& state, uint32_t resource) { - const auto& image = state.program.info.images.at(resource); - EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage); - if (image.atomic) { - EXIT_IF(image.numeric_class != Prospero::TextureNumericClass::Uint); - return StorageImageClass::AtomicUint; - } - switch (image.numeric_class) { - case Prospero::TextureNumericClass::Float: return StorageImageClass::FormatlessFloat; - case Prospero::TextureNumericClass::Uint: return StorageImageClass::FormatlessUint; - case Prospero::TextureNumericClass::Sint: - case Prospero::TextureNumericClass::Unsupported: - EXIT("invalid storage image numeric class"); - } - EXIT("invalid storage image numeric class"); -} - uint32_t LoadSampledImageDescriptor(EmitterState& state, uint32_t resource) { const auto& image_resource = state.program.info.images.at(resource); EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled); - const auto dimension = image_resource.dimension; - const auto image_class = SampledImageClassFor(image_resource.numeric_class); - EXIT_IF(image_class == SampledImageClass::Count); - const auto kind = SampledBindingKind(image_class, dimension); - const auto array_index = ResourceForDescriptor(state, kind, resource); - const auto variable = state.sampled_image_variables[SampledImageIndex(image_class, dimension)]; - const auto pointer_type = - state.builder.Type(OpTypePointer, {StorageClassUniformConstant, - ImageViewImageType(state, dimension, image_class)}); + const auto kind = IR::DescriptorBindingForImage(image_resource); + EXIT_IF(!kind.has_value()); + const auto array_index = ResourceForDescriptor(state, *kind, resource); + const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)]; + const auto pointer_type = state.builder.Type( + OpTypePointer, {StorageClassUniformConstant, ImageType(state, image_resource)}); const auto pointer = - DescriptorElementPointer(state, pointer_type, variable, array_index, kind, resource, + DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource, "sampled image descriptor array was not emitted"); const auto image = state.builder.AllocateId(); - state.builder.AddFunction( - {OpLoad, ImageViewImageType(state, dimension, image_class), image, pointer}); + state.builder.AddFunction({OpLoad, ImageType(state, image_resource), image, pointer}); return image; } @@ -238,50 +204,48 @@ uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler) { uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler) { const auto& image_resource = state.program.info.images.at(resource); - const auto dimension = image_resource.dimension; - const auto image_class = SampledImageClassFor(image_resource.numeric_class); const auto image = LoadSampledImageDescriptor(state, resource); const auto sampler_id = LoadSamplerDescriptor(state, sampler); - if (image == 0 || sampler_id == 0) { - ExitDescriptorBindingFailure(state, SampledBindingKind(image_class, dimension), resource, - "sampled image or sampler descriptor load failed"); - } - const auto sampled_image = state.builder.AllocateId(); - state.builder.AddFunction({OpSampledImage, - ImageViewSampledImageType(state, dimension, image_class), - sampled_image, image, sampler_id}); + const auto sampled_image = state.builder.AllocateId(); + const auto sampled_type = + state.builder.Type(OpTypeSampledImage, {ImageType(state, image_resource)}); + state.builder.AddFunction({OpSampledImage, sampled_type, sampled_image, image, sampler_id}); return sampled_image; } uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource) { - const auto dimension = state.program.info.images.at(resource).dimension; - const auto image_class = StorageImageClassForResource(state, resource); - const auto kind = StorageBindingKind(image_class, dimension); - const auto array_index = ResourceForDescriptor(state, kind, resource); - const auto ptr_type = StorageImagePointerType(state, image_class, dimension); - const auto variable = state.storage_image_variables[StorageImageIndex(image_class, dimension)]; - return DescriptorElementPointer(state, ptr_type, variable, array_index, kind, resource, + const auto& image = state.program.info.images.at(resource); + EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage); + const auto kind = IR::DescriptorBindingForImage(image); + EXIT_IF(!kind.has_value()); + const auto array_index = ResourceForDescriptor(state, *kind, resource); + const auto pointer_type = + state.builder.Type(OpTypePointer, {StorageClassUniformConstant, ImageType(state, image)}); + const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)]; + return DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource, "storage image descriptor array was not emitted"); } void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord, uint32_t texel) { - const auto dimension = state.program.info.images.at(resource).dimension; - const auto image_class = StorageImageClassForResource(state, resource); - if (image_class != StorageImageClass::AtomicUint) { + const auto& image = state.program.info.images.at(resource); + EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage); + if (!image.atomic) { state.builder.RequireCapability(CapabilityStorageImageWriteWithoutFormat); } - const auto kind = StorageBindingKind(image_class, dimension); - const auto array_index = 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, image_class, dimension), - state.storage_image_variables[StorageImageIndex(image_class, dimension)], array_index, - kind, resource, "storage image descriptor array was not emitted"); + const auto kind = IR::DescriptorBindingForImage(image); + EXIT_IF(!kind.has_value()); + const auto array_index = ResourceForDescriptor(state, *kind, resource); + const auto image_type = ImageType(state, image); + const auto pointer_type = + state.builder.Type(OpTypePointer, {StorageClassUniformConstant, image_type}); + const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)]; + const auto LoadAt = [&](uint32_t array_index) { + const auto pointer = + DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource, + "storage image descriptor array was not emitted"); const auto descriptor = state.builder.AllocateId(); - state.builder.AddFunction( - {OpLoad, StorageImageType(state, image_class, dimension), descriptor, pointer}); + state.builder.AddFunction({OpLoad, image_type, descriptor, pointer}); return descriptor; }; if (image.mip_mode != IR::ImageMipMode::DynamicStorage) { @@ -289,7 +253,7 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_ return; } if (image.mip_count == 0u) { - ExitDescriptorBindingFailure(state, kind, resource, + ExitDescriptorBindingFailure(state, *kind, resource, "dynamic storage image has no mip descriptors"); } diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp index a6f4718ad..9790501cb 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp @@ -202,26 +202,32 @@ uint32_t FloatBits(ValueEmitContext& ctx, uint32_t value) { } uint32_t SampledComponentBits(ValueEmitContext& ctx, uint32_t value, - SampledImageClass image_class) { - return image_class == SampledImageClass::Uint - ? value - : Unary(ctx.state, OpBitcast, TypeU32(ctx.state), value); -} - -uint32_t SampledComponentZero(EmitterState& state, SampledImageClass image_class) { - switch (image_class) { - case SampledImageClass::Float: return ZeroF32(state); - case SampledImageClass::Uint: return ConstantU32(state, 0); - case SampledImageClass::Sint: return ConstantI32(state, 0); - case SampledImageClass::Count: return 0; + Prospero::TextureNumericClass numeric_class) { + if (numeric_class == Prospero::TextureNumericClass::Uint) { + return value; } - return 0; + return Unary(ctx.state, OpBitcast, TypeU32(ctx.state), value); } -uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, SampledImageClass image_class, - bool dref, const IR::MemoryInfo& mem, bool gather = false) { - const auto value_class = dref ? SampledImageClass::Float : image_class; - const bool integer = IsIntegerSampledImage(value_class); +uint32_t SampledComponentZero(EmitterState& state, Prospero::TextureNumericClass numeric_class) { + switch (numeric_class) { + case Prospero::TextureNumericClass::Float: return ZeroF32(state); + case Prospero::TextureNumericClass::Uint: return ConstantU32(state, 0); + case Prospero::TextureNumericClass::Sint: return ConstantI32(state, 0); + case Prospero::TextureNumericClass::Unsupported: break; + } + EXIT("invalid sampled image numeric class"); +} + +uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, + Prospero::TextureNumericClass numeric_class, bool dref, + const IR::MemoryInfo& mem, bool gather = false) { + auto value_class = numeric_class; + if (dref) { + value_class = Prospero::TextureNumericClass::Float; + } + const bool integer = value_class == Prospero::TextureNumericClass::Uint || + value_class == Prospero::TextureNumericClass::Sint; if (mem.data_bits == 16u) { uint32_t packed[4] = {ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0)}; @@ -230,8 +236,8 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, SampledImageClass i const auto component = gather ? index : DmaskComponent(mem.dmask, index); const auto result = ctx.state.builder.AllocateId(); ctx.state.builder.AddFunction({OpCompositeExtract, - SampledImageScalarType(ctx.state, value_class), result, - value, component}); + ImageScalarType(ctx.state, value_class), result, value, + component}); return result; }; for (uint32_t word = 0; word < mem.data_dwords; word++) { @@ -272,9 +278,8 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, SampledImageClass i continue; } const auto scalar = ctx.state.builder.AllocateId(); - ctx.state.builder.AddFunction({OpCompositeExtract, - SampledImageScalarType(ctx.state, value_class), scalar, - value, index}); + ctx.state.builder.AddFunction( + {OpCompositeExtract, ImageScalarType(ctx.state, value_class), scalar, value, index}); component[index] = SampledComponentBits(ctx, scalar, value_class); } const auto result = ctx.state.builder.AllocateId(); @@ -462,7 +467,7 @@ uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uin } uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo& mem, - uint32_t coord, SampledImageClass image_class) { + uint32_t coord, Prospero::TextureNumericClass numeric_class) { auto& state = ctx.state; state.builder.RequireCapability(CapabilityImageQuery); const auto image = LoadSampledImageDescriptor(state, mem.resource); @@ -478,8 +483,8 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo& ConstantF32(state, 0x3f000000u))}); const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler); - const auto vector_type = SampledImageVectorType(state, image_class, 4); - const auto scalar_type = SampledImageScalarType(state, image_class); + const auto vector_type = ImageVectorType(state, numeric_class, 4); + const auto scalar_type = ImageScalarType(state, numeric_class); const auto component = ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid ? ImageGatherComponent(mem.dmask) @@ -625,7 +630,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { if (op == IR::ValueOpcode::ImageRead) { const auto dimension = image.dimension; const auto& dimension_info = ImageDimensionInfoFor(dimension); - const auto image_class = SampledImageClassFor(image.numeric_class); + const auto numeric_class = image.numeric_class; const auto condition = ctx.Arg(inst, 2); ctx.Define( inst, @@ -637,17 +642,17 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { const auto coord = CoordU32(ctx, mem, *address, dimension); if (dimension_info.multisampled != 0u) { state.builder.AddFunction( - {OpImageFetch, SampledImageVectorType(state, image_class, 4), color, + {OpImageFetch, ImageVectorType(state, numeric_class, 4), color, descriptor, coord, ImageOperandsSampleMask, AddressU32(ctx, mem, *address, dimension_info.coordinate_components)}); } else { state.builder.AddFunction({OpImageFetch, - SampledImageVectorType(state, image_class, 4), - color, descriptor, coord, ImageOperandsLodMask, + ImageVectorType(state, numeric_class, 4), color, + descriptor, coord, ImageOperandsLodMask, LodU32(ctx, mem, *address, dimension)}); } - return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), image_class, false, - mem); + return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), numeric_class, + false, mem); })); return true; } @@ -669,7 +674,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { const auto dimension = image.dimension; const auto& dimension_info = ImageDimensionInfoFor(dimension); const auto layout = Layout(mem, dimension); - const auto image_class = SampledImageClassFor(image.numeric_class); + const auto numeric_class = image.numeric_class; const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare); if (dref && state.program.info.images[mem.resource].conversion_format != Prospero::BufferFormat::kInvalid) { @@ -686,9 +691,9 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { ctx.Fail(inst, "has an unsupported 1D gather variant"); return true; } - const auto sample = EmitOneDimensionalGatherLz(ctx, mem, coord, image_class); - ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample), image_class, - false, mem, true)); + const auto sample = EmitOneDimensionalGatherLz(ctx, mem, coord, numeric_class); + ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample), + numeric_class, false, mem, true)); return true; } if (dimension == ImageDimension::Dim1DArray) { @@ -697,25 +702,23 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { } const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler); const auto sample = state.builder.AllocateId(); - std::vector words = - dref ? std::vector {OpImageDrefGather, - TypeF32Vector(state, 4), - sample, - sampled, - coord, - layout.dref != NoImageComponent - ? AddressF32(ctx, mem, *address, layout.dref) - : ZeroF32(state)} - : std::vector { - OpImageGather, - SampledImageVectorType(state, image_class, 4), - sample, - sampled, - coord, - ConstantU32(state, ImageConversionFormat(state, mem).format == - Prospero::BufferFormat::kInvalid - ? ImageGatherComponent(mem.dmask) - : 0u)}; + std::vector words; + if (dref) { + auto dref_value = ZeroF32(state); + if (layout.dref != NoImageComponent) { + dref_value = AddressF32(ctx, mem, *address, layout.dref); + } + words = {OpImageDrefGather, TypeF32Vector(state, 4), sample, sampled, coord, + dref_value}; + } else { + uint32_t component = 0; + if (ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid) { + component = ImageGatherComponent(mem.dmask); + } + words = {OpImageGather, ImageVectorType(state, numeric_class, 4), + sample, sampled, + coord, ConstantU32(state, component)}; + } if (HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) { words.push_back(ImageOperandsConstOffsetsMask); words.push_back(HorizontalOffsets(state, dimension)); @@ -724,24 +727,33 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { words.push_back(PackedOffset(ctx, mem, *address, layout, dimension)); } state.builder.AddFunction(words); + auto result_numeric_class = numeric_class; + if (dref) { + result_numeric_class = Prospero::TextureNumericClass::Float; + } ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample), - dref ? SampledImageClass::Float : image_class, false, mem, - true)); + result_numeric_class, false, mem, true)); return true; } const bool explicit_lod = HasFlag(mem, Decoder::ImageSampleFlagDerivative) || HasFlag(mem, Decoder::ImageSampleFlagLod) || HasFlag(mem, Decoder::ImageSampleFlagLevelZero) || state.stage != ShaderType::Pixel; - const auto opcode = explicit_lod - ? (dref ? OpImageSampleDrefExplicitLod : OpImageSampleExplicitLod) - : (dref ? OpImageSampleDrefImplicitLod : OpImageSampleImplicitLod); - const auto result_type = - dref ? TypeF32(state) : SampledImageVectorType(state, image_class, 4); - const auto dref_value = - dref ? (layout.dref != NoImageComponent ? AddressF32(ctx, mem, *address, layout.dref) - : ZeroF32(state)) - : 0u; + uint32_t opcode = OpImageSampleImplicitLod; + if (explicit_lod) { + opcode = dref ? OpImageSampleDrefExplicitLod : OpImageSampleExplicitLod; + } else if (dref) { + opcode = OpImageSampleDrefImplicitLod; + } + uint32_t result_type = ImageVectorType(state, numeric_class, 4); + uint32_t dref_value = 0; + if (dref) { + result_type = TypeF32(state); + dref_value = ZeroF32(state); + if (layout.dref != NoImageComponent) { + dref_value = AddressF32(ctx, mem, *address, layout.dref); + } + } uint32_t operand_mask = 0; std::vector operands; if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) { @@ -752,10 +764,11 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { CoordF32(ctx, mem, *address, layout.grad_y, dimension_info.spatial_components)); } else if (explicit_lod) { operand_mask |= ImageOperandsLodMask; - operands.push_back(HasFlag(mem, Decoder::ImageSampleFlagLod) && - layout.lod != NoImageComponent - ? AddressF32(ctx, mem, *address, layout.lod) - : ZeroF32(state)); + auto lod = ZeroF32(state); + if (HasFlag(mem, Decoder::ImageSampleFlagLod) && layout.lod != NoImageComponent) { + lod = AddressF32(ctx, mem, *address, layout.lod); + } + operands.push_back(lod); } else if (layout.bias != NoImageComponent) { operand_mask |= ImageOperandsBiasMask; operands.push_back(AddressF32(ctx, mem, *address, layout.bias)); @@ -776,8 +789,11 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { }; if (image.indirect_root != mem.resource) { const auto sample = EmitSample(mem.resource); - ctx.Define(inst, ResultVector(ctx, dref ? sample : UnpackImageTexel(ctx, mem, sample), - image_class, dref, mem)); + auto result = sample; + if (!dref) { + result = UnpackImageTexel(ctx, mem, sample); + } + ctx.Define(inst, ResultVector(ctx, result, numeric_class, dref, mem)); return true; } const auto* handle = image_arg.ResolveInstruction(); @@ -868,9 +884,11 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { } EmitLabel(state, merge_label); state.builder.AddFunction(phi_words); - ctx.Define(inst, - ResultVector(ctx, dref ? phi_words[2] : UnpackImageTexel(ctx, mem, phi_words[2]), - image_class, dref, mem)); + auto result = phi_words[2]; + if (!dref) { + result = UnpackImageTexel(ctx, mem, result); + } + ctx.Define(inst, ResultVector(ctx, result, numeric_class, dref, mem)); return true; } const auto atomic_opcode = ImageAtomicOpcode(op); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index 0baa67bbd..bcfc01017 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -321,7 +321,6 @@ using ImageDimension = Decoder::ImageDimension; struct ImageDimensionInfo { ImageDimension dimension; - uint32_t ordinal; uint32_t spirv_dimension; uint32_t coordinate_components; uint32_t spatial_components; @@ -330,29 +329,17 @@ struct ImageDimensionInfo { }; constexpr std::array ImageDimensions {{ - {ImageDimension::Dim1D, 0, Dim1D, 1, 1, 0, 0}, - {ImageDimension::Dim1DArray, 1, Dim1D, 2, 1, 1, 0}, - {ImageDimension::Dim2D, 2, Dim2D, 2, 2, 0, 0}, - {ImageDimension::Dim2DArray, 3, Dim2D, 3, 2, 1, 0}, - {ImageDimension::Dim3D, 4, Dim3D, 3, 3, 0, 0}, - {ImageDimension::Dim2DMsaa, 5, Dim2D, 2, 2, 0, 1}, - {ImageDimension::Dim2DMsaaArray, 6, Dim2D, 3, 2, 1, 1}, + {ImageDimension::Dim1D, Dim1D, 1, 1, 0, 0}, + {ImageDimension::Dim1DArray, Dim1D, 2, 1, 1, 0}, + {ImageDimension::Dim2D, Dim2D, 2, 2, 0, 0}, + {ImageDimension::Dim2DArray, Dim2D, 3, 2, 1, 0}, + {ImageDimension::Dim3D, Dim3D, 3, 3, 0, 0}, + {ImageDimension::Dim2DMsaa, Dim2D, 2, 2, 0, 1}, + {ImageDimension::Dim2DMsaaArray, Dim2D, 3, 2, 1, 1}, }}; -constexpr uint32_t SampledImageDimensionCount = ImageDimensions.size(); -constexpr uint32_t StorageImageDimensionCount = 5; - const ImageDimensionInfo& ImageDimensionInfoFor(ImageDimension dimension); -enum class SampledImageClass : uint32_t { Float, Uint, Sint, Count }; - -enum class StorageImageClass : uint32_t { - FormatlessFloat, - FormatlessUint, - AtomicUint, - Count, -}; - struct EmitterState { EmitterState(const IR::Program& program_, const IR::ResourceSnapshot& resources_, ShaderStageInputInfo input_info_) @@ -378,12 +365,7 @@ struct EmitterState { uint32_t flattened_srt_variable = 0; uint32_t lds_variable = 0; uint32_t scratch_variable = 0; - std::array(SampledImageClass::Count) * SampledImageDimensionCount> - sampled_image_variables {}; - std::array(StorageImageClass::Count) * StorageImageDimensionCount> - storage_image_variables {}; + std::array image_variables {}; uint32_t sampler_variable = 0; uint32_t main_func = 0; uint32_t entry_label = 0; @@ -482,113 +464,6 @@ struct ImageSampleLayout { uint32_t grad_y = NoImageComponent; }; -constexpr SampledImageClass SampledImageClassFor(Prospero::TextureNumericClass numeric_class) { - switch (numeric_class) { - case Prospero::TextureNumericClass::Float: return SampledImageClass::Float; - case Prospero::TextureNumericClass::Uint: return SampledImageClass::Uint; - case Prospero::TextureNumericClass::Sint: return SampledImageClass::Sint; - case Prospero::TextureNumericClass::Unsupported: return SampledImageClass::Count; - } - return SampledImageClass::Count; -} - -constexpr bool IsIntegerSampledImage(SampledImageClass image_class) { - return image_class == SampledImageClass::Uint || image_class == SampledImageClass::Sint; -} - -inline uint32_t SampledImageIndex(SampledImageClass image_class, ImageDimension dimension) { - return ImageDimensionInfoFor(dimension).ordinal + - static_cast(image_class) * SampledImageDimensionCount; -} - -inline uint32_t StorageImageIndex(StorageImageClass image_class, ImageDimension dimension) { - return ImageDimensionInfoFor(dimension).ordinal + - static_cast(image_class) * StorageImageDimensionCount; -} - -constexpr IR::DescriptorBindingKind SampledBindingKind(SampledImageClass image_class, - ImageDimension dimension) { - if (image_class == SampledImageClass::Uint) { - switch (dimension) { - case ImageDimension::Dim1D: return IR::DescriptorBindingKind::SampledUint1D; - case ImageDimension::Dim1DArray: return IR::DescriptorBindingKind::SampledUint1DArray; - case ImageDimension::Dim2D: return IR::DescriptorBindingKind::SampledUint2D; - case ImageDimension::Dim2DArray: return IR::DescriptorBindingKind::SampledUint2DArray; - case ImageDimension::Dim3D: return IR::DescriptorBindingKind::SampledUint3D; - case ImageDimension::Dim2DMsaa: return IR::DescriptorBindingKind::SampledUint2DMsaa; - case ImageDimension::Dim2DMsaaArray: - return IR::DescriptorBindingKind::SampledUint2DMsaaArray; - default: break; - } - } - if (image_class == SampledImageClass::Sint) { - switch (dimension) { - case ImageDimension::Dim1D: return IR::DescriptorBindingKind::SampledSint1D; - case ImageDimension::Dim1DArray: return IR::DescriptorBindingKind::SampledSint1DArray; - case ImageDimension::Dim2D: return IR::DescriptorBindingKind::SampledSint2D; - case ImageDimension::Dim2DArray: return IR::DescriptorBindingKind::SampledSint2DArray; - case ImageDimension::Dim3D: return IR::DescriptorBindingKind::SampledSint3D; - case ImageDimension::Dim2DMsaa: return IR::DescriptorBindingKind::SampledSint2DMsaa; - case ImageDimension::Dim2DMsaaArray: - return IR::DescriptorBindingKind::SampledSint2DMsaaArray; - default: break; - } - } - switch (dimension) { - case ImageDimension::Dim1D: return IR::DescriptorBindingKind::Sampled1D; - case ImageDimension::Dim1DArray: return IR::DescriptorBindingKind::Sampled1DArray; - case ImageDimension::Dim2D: return IR::DescriptorBindingKind::Sampled2D; - case ImageDimension::Dim2DArray: return IR::DescriptorBindingKind::Sampled2DArray; - case ImageDimension::Dim3D: return IR::DescriptorBindingKind::Sampled3D; - case ImageDimension::Dim2DMsaa: return IR::DescriptorBindingKind::Sampled2DMsaa; - case ImageDimension::Dim2DMsaaArray: return IR::DescriptorBindingKind::Sampled2DMsaaArray; - default: break; - } - return IR::DescriptorBindingKind::Count; -} - -constexpr IR::DescriptorBindingKind StorageBindingKind(StorageImageClass image_class, - ImageDimension dimension) { - switch (image_class) { - case StorageImageClass::FormatlessFloat: - switch (dimension) { - case ImageDimension::Dim1D: return IR::DescriptorBindingKind::Storage1D; - case ImageDimension::Dim1DArray: return IR::DescriptorBindingKind::Storage1DArray; - case ImageDimension::Dim2D: return IR::DescriptorBindingKind::Storage2D; - case ImageDimension::Dim2DArray: return IR::DescriptorBindingKind::Storage2DArray; - case ImageDimension::Dim3D: return IR::DescriptorBindingKind::Storage3D; - default: break; - } - break; - case StorageImageClass::FormatlessUint: - switch (dimension) { - case ImageDimension::Dim1D: return IR::DescriptorBindingKind::StorageUint1D; - case ImageDimension::Dim1DArray: - return IR::DescriptorBindingKind::StorageUint1DArray; - case ImageDimension::Dim2D: return IR::DescriptorBindingKind::StorageUint2D; - case ImageDimension::Dim2DArray: - return IR::DescriptorBindingKind::StorageUint2DArray; - case ImageDimension::Dim3D: return IR::DescriptorBindingKind::StorageUint3D; - default: break; - } - break; - case StorageImageClass::AtomicUint: - switch (dimension) { - case ImageDimension::Dim1D: return IR::DescriptorBindingKind::StorageAtomic1D; - case ImageDimension::Dim1DArray: - return IR::DescriptorBindingKind::StorageAtomic1DArray; - case ImageDimension::Dim2D: return IR::DescriptorBindingKind::StorageAtomic2D; - case ImageDimension::Dim2DArray: - return IR::DescriptorBindingKind::StorageAtomic2DArray; - case ImageDimension::Dim3D: return IR::DescriptorBindingKind::StorageAtomic3D; - default: break; - } - break; - case StorageImageClass::Count: break; - } - return IR::DescriptorBindingKind::Count; -} - struct F32Class { uint32_t bits = 0; uint32_t nan = 0; @@ -639,25 +514,15 @@ uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type, IR::DescriptorBindingKind kind, uint32_t resource, const char* variable_name); -uint32_t SampledImageScalarType(EmitterState& state, SampledImageClass image_class); +uint32_t ImageScalarType(EmitterState& state, Prospero::TextureNumericClass numeric_class); -uint32_t SampledImageVectorType(EmitterState& state, SampledImageClass image_class, - uint32_t components); +uint32_t ImageVectorType(EmitterState& state, Prospero::TextureNumericClass numeric_class, + uint32_t components); -uint32_t ImageViewImageType(EmitterState& state, ImageDimension dimension, - SampledImageClass image_class); - -uint32_t ImageViewSampledImageType(EmitterState& state, ImageDimension dimension, - SampledImageClass image_class); +uint32_t ImageType(EmitterState& state, const IR::ImageResource& image); uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension); -uint32_t StorageImageType(EmitterState& state, StorageImageClass image_class, - ImageDimension dimension); - -uint32_t StorageImagePointerType(EmitterState& state, StorageImageClass image_class, - ImageDimension dimension); - uint32_t LoadSampledImageDescriptor(EmitterState& state, uint32_t resource); uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index 8cce6e202..fb0544b57 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -187,38 +187,23 @@ void DefineDescriptorVariables(EmitterState& state) { state.flattened_srt_variable = state.builder.DefineGlobalVariable( TypeStorageBufferPointer(state), StorageClassStorageBuffer); } - for (uint32_t i = 0; i < state.sampled_image_variables.size(); i++) { - const auto dimension = ImageDimensions[i % SampledImageDimensionCount].dimension; - const auto image_class = static_cast(i / SampledImageDimensionCount); - const auto kind = SampledBindingKind(image_class, dimension); - if (DescriptorBinding(state, kind) == nullptr) { + for (const auto& binding: state.program.bindings.descriptors) { + if (IR::ImageBindingResourceClass(binding.kind) == IR::ImageResourceClass::None) { continue; } - const auto count = ConstantU32(state, DescriptorCount(state, kind)); - const auto image_type = ImageViewImageType(state, dimension, image_class); - const auto array_type = state.builder.Type(OpTypeArray, {image_type, count}); - const auto pointer_type = TypePointer(state, StorageClassUniformConstant, array_type); - state.sampled_image_variables[i] = + const auto& image = state.program.info.images.at(binding.resources.front()); + const auto count = ConstantU32(state, DescriptorCount(state, binding.kind)); + const auto image_type = ImageType(state, image); + const auto array_type = state.builder.Type(OpTypeArray, {image_type, count}); + const auto pointer_type = TypePointer(state, StorageClassUniformConstant, array_type); + state.image_variables[IR::ImageBindingIndex(binding.kind)] = state.builder.DefineGlobalVariable(pointer_type, StorageClassUniformConstant); - if (dimension == ImageDimension::Dim1D || dimension == ImageDimension::Dim1DArray) { - state.builder.RequireCapability(CapabilitySampled1D); - } - } - for (uint32_t i = 0; i < state.storage_image_variables.size(); i++) { - const auto dimension = ImageDimensions[i % StorageImageDimensionCount].dimension; - const auto image_class = static_cast(i / StorageImageDimensionCount); - const auto kind = StorageBindingKind(image_class, dimension); - if (DescriptorBinding(state, kind) == nullptr) { - continue; - } - const auto count = ConstantU32(state, DescriptorCount(state, kind)); - const auto image_type = StorageImageType(state, image_class, dimension); - 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] = - state.builder.DefineGlobalVariable(pointer_type, StorageClassUniformConstant); - if (dimension == ImageDimension::Dim1D || dimension == ImageDimension::Dim1DArray) { - state.builder.RequireCapability(CapabilityImage1D); + if (image.dimension == ImageDimension::Dim1D || + image.dimension == ImageDimension::Dim1DArray) { + const auto capability = image.resource_class == IR::ImageResourceClass::Sampled + ? CapabilitySampled1D + : CapabilityImage1D; + state.builder.RequireCapability(capability); } } if (DescriptorBinding(state, IR::DescriptorBindingKind::Samplers) != nullptr) { @@ -594,53 +579,13 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) { Decorate(state.fault_buffer_variable, "fault_buffer", IR::DescriptorBindingKind::FaultBuffer); } - constexpr const char* SampledNames[] = {"sampled_1d", - "sampled_1d_array", - "sampled_2d", - "sampled_2d_array", - "sampled_3d", - "sampled_2d_msaa", - "sampled_2d_msaa_array", - "sampled_uint_1d", - "sampled_uint_1d_array", - "sampled_uint_2d", - "sampled_uint_2d_array", - "sampled_uint_3d", - "sampled_uint_2d_msaa", - "sampled_uint_2d_msaa_array", - "sampled_sint_1d", - "sampled_sint_1d_array", - "sampled_sint_2d", - "sampled_sint_2d_array", - "sampled_sint_3d", - "sampled_sint_2d_msaa", - "sampled_sint_2d_msaa_array"}; - for (uint32_t i = 0; i < state.sampled_image_variables.size(); i++) { - const auto dimension = ImageDimensions[i % SampledImageDimensionCount].dimension; - const auto image_class = static_cast(i / SampledImageDimensionCount); - Decorate(state.sampled_image_variables[i], SampledNames[i], - SampledBindingKind(image_class, dimension)); - } - 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", - "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 dimension = ImageDimensions[i % StorageImageDimensionCount].dimension; - const auto image_class = static_cast(i / StorageImageDimensionCount); - Decorate(state.storage_image_variables[i], StorageNames[i], - StorageBindingKind(image_class, dimension)); + for (const auto& binding: state.program.bindings.descriptors) { + if (IR::ImageBindingResourceClass(binding.kind) == IR::ImageResourceClass::None) { + continue; + } + const auto name = "image_" + std::to_string(static_cast(binding.kind)); + Decorate(state.image_variables[IR::ImageBindingIndex(binding.kind)], name.c_str(), + binding.kind); } 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 6a3883773..a910ed559 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -259,45 +259,13 @@ struct StageOutput { bool operator==(const StageOutput& other) const = default; }; -enum class DescriptorBindingKind { - Buffers, - Sampled1D, - Sampled1DArray, - Sampled2D, - Sampled2DArray, - Sampled2DMsaa, - Sampled2DMsaaArray, - Sampled3D, - SampledUint1D, - SampledUint1DArray, - SampledUint2D, - SampledUint2DArray, - SampledUint2DMsaa, - SampledUint2DMsaaArray, - SampledUint3D, - SampledSint1D, - SampledSint1DArray, - SampledSint2D, - SampledSint2DArray, - SampledSint2DMsaa, - SampledSint2DMsaaArray, - SampledSint3D, - Storage1D, - Storage1DArray, - Storage2D, - Storage2DArray, - Storage3D, - StorageUint1D, - StorageUint1DArray, - StorageUint2D, - StorageUint2DArray, - StorageUint3D, - StorageAtomic1D, - StorageAtomic1DArray, - StorageAtomic2D, - StorageAtomic2DArray, - StorageAtomic3D, - Samplers, +inline constexpr uint32_t FirstImageBinding = 1u; +inline constexpr uint32_t FirstStorageImageBinding = 22u; +inline constexpr uint32_t ImageBindingCount = 36u; + +enum class DescriptorBindingKind : uint32_t { + Buffers = 0u, + Samplers = FirstImageBinding + ImageBindingCount, Gds, BdaPagetable, FaultBuffer, @@ -306,6 +274,9 @@ enum class DescriptorBindingKind { Count, }; +static_assert(static_cast(DescriptorBindingKind::Samplers) == 37u); +static_assert(static_cast(DescriptorBindingKind::Count) == 43u); + constexpr uint32_t NativePushConstantSize = 128; [[nodiscard]] constexpr uint32_t NativeBinding(ShaderType stage, DescriptorBindingKind kind) { @@ -313,88 +284,87 @@ constexpr uint32_t NativePushConstantSize = 128; (stage == ShaderType::Pixel ? static_cast(DescriptorBindingKind::Count) : 0u); } -[[nodiscard]] inline std::optional -DescriptorBindingForImage(const ImageResource& image) { - using Dimension = Decoder::ImageDimension; - using Kind = DescriptorBindingKind; +[[nodiscard]] constexpr ImageResourceClass ImageBindingResourceClass(DescriptorBindingKind kind) { + const auto value = static_cast(kind); + if (value >= FirstImageBinding && value < FirstStorageImageBinding) { + return ImageResourceClass::Sampled; + } + if (value >= FirstStorageImageBinding && + value < static_cast(DescriptorBindingKind::Samplers)) { + return ImageResourceClass::Storage; + } + return ImageResourceClass::None; +} - switch (image.resource_class) { - case ImageResourceClass::Sampled: - switch (image.numeric_class) { - case Prospero::TextureNumericClass::Float: break; - case Prospero::TextureNumericClass::Uint: - switch (image.dimension) { - case Dimension::Dim1D: return Kind::SampledUint1D; - case Dimension::Dim1DArray: return Kind::SampledUint1DArray; - case Dimension::Dim2D: return Kind::SampledUint2D; - case Dimension::Dim2DArray: return Kind::SampledUint2DArray; - case Dimension::Dim2DMsaa: return Kind::SampledUint2DMsaa; - case Dimension::Dim2DMsaaArray: return Kind::SampledUint2DMsaaArray; - case Dimension::Dim3D: return Kind::SampledUint3D; - default: return std::nullopt; - } - case Prospero::TextureNumericClass::Sint: - switch (image.dimension) { - case Dimension::Dim1D: return Kind::SampledSint1D; - case Dimension::Dim1DArray: return Kind::SampledSint1DArray; - case Dimension::Dim2D: return Kind::SampledSint2D; - case Dimension::Dim2DArray: return Kind::SampledSint2DArray; - case Dimension::Dim2DMsaa: return Kind::SampledSint2DMsaa; - case Dimension::Dim2DMsaaArray: return Kind::SampledSint2DMsaaArray; - case Dimension::Dim3D: return Kind::SampledSint3D; - default: return std::nullopt; - } - case Prospero::TextureNumericClass::Unsupported: return std::nullopt; +[[nodiscard]] constexpr uint32_t ImageBindingIndex(DescriptorBindingKind kind) { + return static_cast(kind) - FirstImageBinding; +} + +[[nodiscard]] constexpr std::optional +DescriptorBindingForImage(const ImageResource& image) { + constexpr uint32_t SampledFloatBinding = 1u; + constexpr uint32_t SampledUintBinding = 8u; + constexpr uint32_t SampledSintBinding = 15u; + constexpr uint32_t StorageFloatBinding = 22u; + constexpr uint32_t StorageUintBinding = 27u; + constexpr uint32_t AtomicUintBinding = 32u; + + uint32_t base = 0; + bool sampled = false; + if (image.resource_class == ImageResourceClass::Sampled) { + if (image.atomic) { + return std::nullopt; + } + sampled = true; + switch (image.numeric_class) { + case Prospero::TextureNumericClass::Float: base = SampledFloatBinding; break; + case Prospero::TextureNumericClass::Uint: base = SampledUintBinding; break; + case Prospero::TextureNumericClass::Sint: base = SampledSintBinding; break; + case Prospero::TextureNumericClass::Unsupported: return std::nullopt; + default: return std::nullopt; + } + } else if (image.resource_class == ImageResourceClass::Storage) { + if (image.atomic) { + if (image.numeric_class != Prospero::TextureNumericClass::Uint) { + return std::nullopt; } - switch (image.dimension) { - case Dimension::Dim1D: return Kind::Sampled1D; - case Dimension::Dim1DArray: return Kind::Sampled1DArray; - case Dimension::Dim2D: return Kind::Sampled2D; - case Dimension::Dim2DArray: return Kind::Sampled2DArray; - case Dimension::Dim2DMsaa: return Kind::Sampled2DMsaa; - case Dimension::Dim2DMsaaArray: return Kind::Sampled2DMsaaArray; - case Dimension::Dim3D: return Kind::Sampled3D; + base = AtomicUintBinding; + } else { + switch (image.numeric_class) { + case Prospero::TextureNumericClass::Float: base = StorageFloatBinding; break; + case Prospero::TextureNumericClass::Uint: base = StorageUintBinding; break; + case Prospero::TextureNumericClass::Sint: + case Prospero::TextureNumericClass::Unsupported: return std::nullopt; default: return std::nullopt; } - case ImageResourceClass::Storage: - if (image.atomic) { - if (image.numeric_class != Prospero::TextureNumericClass::Uint) { - return std::nullopt; - } - switch (image.dimension) { - case Dimension::Dim1D: return Kind::StorageAtomic1D; - case Dimension::Dim1DArray: return Kind::StorageAtomic1DArray; - case Dimension::Dim2D: return Kind::StorageAtomic2D; - case Dimension::Dim2DArray: return Kind::StorageAtomic2DArray; - case Dimension::Dim3D: return Kind::StorageAtomic3D; - default: return std::nullopt; - } - } - switch (image.numeric_class) { - case Prospero::TextureNumericClass::Float: - switch (image.dimension) { - case Dimension::Dim1D: return Kind::Storage1D; - case Dimension::Dim1DArray: return Kind::Storage1DArray; - case Dimension::Dim2D: return Kind::Storage2D; - case Dimension::Dim2DArray: return Kind::Storage2DArray; - case Dimension::Dim3D: return Kind::Storage3D; - default: return std::nullopt; - } - case Prospero::TextureNumericClass::Uint: - 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; - default: return std::nullopt; - } - case Prospero::TextureNumericClass::Sint: - case Prospero::TextureNumericClass::Unsupported: return std::nullopt; - } - case ImageResourceClass::None: return std::nullopt; + } + } else { + return std::nullopt; } - return std::nullopt; + + uint32_t dimension = 0; + switch (image.dimension) { + case Decoder::ImageDimension::Dim1D: break; + case Decoder::ImageDimension::Dim1DArray: dimension = 1u; break; + case Decoder::ImageDimension::Dim2D: dimension = 2u; break; + case Decoder::ImageDimension::Dim2DArray: dimension = 3u; break; + case Decoder::ImageDimension::Dim2DMsaa: + if (!sampled) { + return std::nullopt; + } + dimension = 4u; + break; + case Decoder::ImageDimension::Dim2DMsaaArray: + if (!sampled) { + return std::nullopt; + } + dimension = 5u; + break; + case Decoder::ImageDimension::Dim3D: dimension = sampled ? 6u : 4u; break; + case Decoder::ImageDimension::Unknown: return std::nullopt; + default: return std::nullopt; + } + return static_cast(base + dimension); } struct DescriptorBinding { diff --git a/src/graphics/shader/recompiler/ir/passes/BindingLayout.cpp b/src/graphics/shader/recompiler/ir/passes/BindingLayout.cpp index c62a6ef9b..7a4608ca8 100644 --- a/src/graphics/shader/recompiler/ir/passes/BindingLayout.cpp +++ b/src/graphics/shader/recompiler/ir/passes/BindingLayout.cpp @@ -10,11 +10,6 @@ namespace Libs::Graphics::ShaderRecompiler::IR { namespace { -constexpr auto FirstImageBinding = DescriptorBindingKind::Sampled1D; -constexpr uint32_t ImageBindingCount = - static_cast(DescriptorBindingKind::StorageAtomic3D) - - static_cast(FirstImageBinding) + 1u; - [[noreturn]] void BindingFail(const char* message) { EXIT("shader binding layout failed: %s", message); std::abort(); @@ -107,7 +102,7 @@ void AllocateBindings(Program& program, uint32_t push_constant_offset) { if (!kind.has_value()) { EXIT("shader binding layout failed: image %u has an invalid binding class", i); } - const auto group = static_cast(*kind) - static_cast(FirstImageBinding); + const auto group = ImageBindingIndex(*kind); if (group >= image_groups.size()) { EXIT("shader binding layout failed: image %u has an unmapped binding class", i); } @@ -122,10 +117,8 @@ void AllocateBindings(Program& program, uint32_t push_constant_offset) { } for (uint32_t i = 0; i < image_groups.size(); i++) { if (!image_groups[i].empty()) { - AddBinding( - next, - static_cast(static_cast(FirstImageBinding) + i), - std::move(image_groups[i])); + AddBinding(next, static_cast(FirstImageBinding + i), + std::move(image_groups[i])); } } diff --git a/tests/ResourceTrackingTests.cpp b/tests/ResourceTrackingTests.cpp index 7eaac0947..e7e283020 100644 --- a/tests/ResourceTrackingTests.cpp +++ b/tests/ResourceTrackingTests.cpp @@ -767,8 +767,10 @@ void TestDynamicStorageMipTracking() { ShaderComputeInputInfo compute{}; CollectShaderInfo(fixture.program, {.compute = &compute}); AllocateBindings(fixture.program, 0); + const auto storage_kind = DescriptorBindingForImage(images[0]); + Check(storage_kind.has_value(), "storage image has no descriptor binding"); const auto *storage_binding = - FindBinding(fixture.program.bindings, DescriptorBindingKind::Storage2D); + FindBinding(fixture.program.bindings, *storage_kind); Check(storage_binding != nullptr && storage_binding->resources == std::vector({0, 1, 1, 1}), "dynamic storage mip descriptors were not expanded consecutively"); @@ -1168,6 +1170,113 @@ void TestShaderInfoAndBindingLayout() { "binding layout did not collect live typed user-data values"); } +void TestImageBindingAbi() { + using NumericClass = Libs::Graphics::Prospero::TextureNumericClass; + + Check(ImageBindingCount == 36u && + static_cast(DescriptorBindingKind::Buffers) == 0u && + static_cast(DescriptorBindingKind::Samplers) == 37u && + static_cast(DescriptorBindingKind::Gds) == 38u && + static_cast(DescriptorBindingKind::BdaPagetable) == 39u && + static_cast(DescriptorBindingKind::FaultBuffer) == 40u && + static_cast(DescriptorBindingKind::FlattenedSrt) == 41u && + static_cast(DescriptorBindingKind::UserData) == 42u && + static_cast(DescriptorBindingKind::Count) == 43u, + "native descriptor binding anchors changed"); + + const std::array sampled_dimensions{ + Decoder::ImageDimension::Dim1D, + Decoder::ImageDimension::Dim1DArray, + Decoder::ImageDimension::Dim2D, + Decoder::ImageDimension::Dim2DArray, + Decoder::ImageDimension::Dim2DMsaa, + Decoder::ImageDimension::Dim2DMsaaArray, + Decoder::ImageDimension::Dim3D, + }; + const std::array storage_dimensions{ + Decoder::ImageDimension::Dim1D, Decoder::ImageDimension::Dim1DArray, + Decoder::ImageDimension::Dim2D, Decoder::ImageDimension::Dim2DArray, + Decoder::ImageDimension::Dim3D, + }; + const std::array sampled_classes{NumericClass::Float, NumericClass::Uint, + NumericClass::Sint}; + const std::array storage_classes{NumericClass::Float, NumericClass::Uint}; + uint32_t index = 0; + const auto CheckBinding = + [&](ImageResourceClass resource_class, NumericClass numeric_class, + Decoder::ImageDimension dimension, bool atomic) { + ImageResource image; + image.resource_class = resource_class; + image.numeric_class = numeric_class; + image.dimension = dimension; + image.atomic = atomic; + const auto kind = DescriptorBindingForImage(image); + Check(kind.has_value() && + static_cast(*kind) == FirstImageBinding + index && + ImageBindingIndex(*kind) == index && + ImageBindingResourceClass(*kind) == resource_class && + NativeBinding(ShaderType::Compute, *kind) == + FirstImageBinding + index && + NativeBinding(ShaderType::Pixel, *kind) == + static_cast(DescriptorBindingKind::Count) + + FirstImageBinding + index, + "generated image descriptor binding changed ABI"); + index++; + }; + for (const auto numeric_class : sampled_classes) { + for (const auto dimension : sampled_dimensions) { + CheckBinding(ImageResourceClass::Sampled, numeric_class, dimension, + false); + } + } + for (const auto numeric_class : storage_classes) { + for (const auto dimension : storage_dimensions) { + CheckBinding(ImageResourceClass::Storage, numeric_class, dimension, + false); + } + } + for (const auto dimension : storage_dimensions) { + CheckBinding(ImageResourceClass::Storage, NumericClass::Uint, dimension, + true); + } + Check(index == ImageBindingCount, "image descriptor ABI case count changed"); + + const auto Invalid = [](ImageResource image) { + return !DescriptorBindingForImage(image).has_value(); + }; + ImageResource image; + Check(Invalid(image), "untyped image received a descriptor binding"); + image.resource_class = ImageResourceClass::Sampled; + image.numeric_class = NumericClass::Float; + image.dimension = Decoder::ImageDimension::Unknown; + Check(Invalid(image), + "unknown sampled dimension received a descriptor binding"); + image.dimension = Decoder::ImageDimension::Dim2D; + image.numeric_class = NumericClass::Unsupported; + Check(Invalid(image), + "unsupported sampled class received a descriptor binding"); + image.numeric_class = static_cast(UINT32_MAX); + Check(Invalid(image), "invalid sampled class received a descriptor binding"); + image.numeric_class = NumericClass::Float; + image.dimension = static_cast(UINT32_MAX); + Check(Invalid(image), + "invalid sampled dimension received a descriptor binding"); + image.dimension = Decoder::ImageDimension::Dim2D; + image.atomic = true; + Check(Invalid(image), "atomic sampled image received a descriptor binding"); + image.resource_class = ImageResourceClass::Storage; + image.atomic = false; + image.numeric_class = NumericClass::Sint; + Check(Invalid(image), "signed storage image received a descriptor binding"); + image.numeric_class = NumericClass::Float; + image.dimension = Decoder::ImageDimension::Dim2DMsaa; + Check(Invalid(image), + "multisampled storage image received a descriptor binding"); + image.dimension = Decoder::ImageDimension::Dim2D; + image.atomic = true; + Check(Invalid(image), "float atomic image received a descriptor binding"); +} + void TestGraphicsPushConstantLayout() { const auto AddUserData = [](Fixture &fixture, uint32_t count) { for (uint32_t index = 0; index < count; index++) { @@ -1312,6 +1421,7 @@ int main() { Run("dynamic FLAT address", TestDynamicFlatAddressesUseDma); Run("buffer swizzle specialization", TestBufferSwizzleSpecialization); Run("shader info and bindings", TestShaderInfoAndBindingLayout); + Run("image binding ABI", TestImageBindingAbi); Run("graphics push constants", TestGraphicsPushConstantLayout); Run("resource limit", TestResourceLimitIsTransactional); Run("malformed memory kinds", TestMalformedMemoryKindsRejected); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 289ab407d..03435ed98 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -9534,36 +9534,26 @@ public: auto Native = [](Kind kind) { return ShaderRecompiler::IR::NativeBinding(ShaderType::Compute, kind); }; - auto Count = [&](Kind kind) { - const auto *binding = Binding(kind); - if (binding == nullptr) { - return 0u; + for (const auto &binding : layout.descriptors) { + const auto resource_class = + ShaderRecompiler::IR::ImageBindingResourceClass(binding.kind); + if (resource_class == ShaderRecompiler::IR::ImageResourceClass::None) { + continue; } - if (kind == Kind::Gds) { - return 1u; + const auto &image = + compiled.program.info.images.at(binding.resources.front()); + bool supported = + image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D; + if (resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) { + supported = supported || + image.dimension == + ShaderRecompiler::Decoder::ImageDimension::Dim1D || + image.dimension == + ShaderRecompiler::Decoder::ImageDimension::Dim1DArray; } - return static_cast(binding->resources.size()); - }; - Require( - test.name, "dispatch", - Count(Kind::Sampled2DArray) == 0 && Count(Kind::Sampled3D) == 0 && - Count(Kind::SampledUint2DArray) == 0 && - Count(Kind::SampledUint3D) == 0 && - Count(Kind::SampledSint2DArray) == 0 && - Count(Kind::SampledSint3D) == 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 && - 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"); + Require(test.name, "dispatch", supported, + "unsupported image dimension needs a matching Vulkan test view"); + } vk::ShaderModuleCreateInfo module_info{}; module_info.sType = vk::StructureType::eShaderModuleCreateInfo; @@ -9652,34 +9642,10 @@ public: } pool_sizes.push_back({type, count}); }; - add_pool_size(vk::DescriptorType::eStorageBuffer, - Count(Kind::Buffers) + - (Binding(Kind::BdaPagetable) != nullptr ? 1u : 0u) + - (Binding(Kind::FaultBuffer) != nullptr ? 1u : 0u) + - Count(Kind::Gds) + - (Binding(Kind::FlattenedSrt) != nullptr ? 1u : 0u) + - (Binding(Kind::UserData) != nullptr ? 1u : 0u)); - add_pool_size( - vk::DescriptorType::eSampledImage, - 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) + - Count(Kind::SampledSint1D) + Count(Kind::SampledSint1DArray) + - Count(Kind::SampledSint2D) + Count(Kind::SampledSint2DArray) + - Count(Kind::SampledSint3D)); - add_pool_size( - vk::DescriptorType::eStorageImage, - 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) + - Count(Kind::StorageAtomic1D) + Count(Kind::StorageAtomic1DArray) + - Count(Kind::StorageAtomic2D) + Count(Kind::StorageAtomic2DArray) + - Count(Kind::StorageAtomic3D)); - add_pool_size(vk::DescriptorType::eSampler, Count(Kind::Samplers)); + for (const auto &binding : layout.descriptors) { + add_pool_size(NativeDescriptorType(binding.kind), + NativeDescriptorCount(binding)); + } vk::DescriptorPoolCreateInfo pool_info{}; pool_info.sType = vk::StructureType::eDescriptorPoolCreateInfo; pool_info.maxSets = 1; @@ -9810,17 +9776,28 @@ public: writes.push_back(write); } const ShaderRecompiler::IR::DescriptorBinding *sampled = nullptr; - constexpr std::array sampled_kinds{ - Kind::Sampled1D, Kind::Sampled1DArray, Kind::Sampled2D, - Kind::SampledUint1D, Kind::SampledUint1DArray, Kind::SampledUint2D, - Kind::SampledSint1D, Kind::SampledSint1DArray, Kind::SampledSint2D, - }; - for (const auto kind : sampled_kinds) { - if (const auto *candidate = Binding(kind); candidate != nullptr) { + const ShaderRecompiler::IR::DescriptorBinding *storage = nullptr; + const ShaderRecompiler::IR::DescriptorBinding *storage_uint = nullptr; + const ShaderRecompiler::IR::DescriptorBinding *storage_atomic = nullptr; + for (const auto &binding : layout.descriptors) { + const auto resource_class = + ShaderRecompiler::IR::ImageBindingResourceClass(binding.kind); + if (resource_class == ShaderRecompiler::IR::ImageResourceClass::None) { + continue; + } + const auto &image = + compiled.program.info.images.at(binding.resources.front()); + if (resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) { Require(test.name, "dispatch", sampled == nullptr, "Vulkan test harness needs separate sampled images for mixed " "descriptor classes"); - sampled = candidate; + sampled = &binding; + } else if (image.atomic) { + storage_atomic = &binding; + } else if (image.numeric_class == Prospero::TextureNumericClass::Float) { + storage = &binding; + } else { + storage_uint = &binding; } } if (sampled != nullptr) { @@ -9841,9 +9818,6 @@ public: write.pImageInfo = sampled_infos.data(); writes.push_back(write); } - const auto *storage = Binding(Kind::Storage2D); - const auto *storage_uint = Binding(Kind::StorageUint2D); - const auto *storage_atomic = Binding(Kind::StorageAtomic2D); const auto BindStorage = [&](const ShaderRecompiler::IR::DescriptorBinding *binding, const Image *image, std::vector *infos) { @@ -11837,24 +11811,18 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) { VulkanHarness::Image storage_image_uint; 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) || - Has(Kind::SampledSint2D) || Has(Kind::SampledSint1D) || - Has(Kind::SampledSint1DArray) || Has(Kind::SampledSint2DArray) || - Has(Kind::SampledSint3D); - 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) || - Has(Kind::StorageAtomic1D) || Has(Kind::StorageAtomic1DArray) || - Has(Kind::StorageAtomic2D) || Has(Kind::StorageAtomic2DArray) || - Has(Kind::StorageAtomic3D); + bool needs_sampled_image = false; + bool needs_storage_image = false; + for (const auto &binding : compiled.program.bindings.descriptors) { + const auto resource_class = + ShaderRecompiler::IR::ImageBindingResourceClass(binding.kind); + if (resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) { + needs_sampled_image = true; + } else if (resource_class == + ShaderRecompiler::IR::ImageResourceClass::Storage) { + needs_storage_image = true; + } + } const bool needs_sampler = Has(Kind::Samplers); const bool needs_gds = Has(Kind::Gds); if (needs_gds) { @@ -19760,7 +19728,7 @@ TestCase ImageLoadR32UintUsesIntegerSampledImage() { test.sampled_image_dwords_per_pixel = 1; test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32UInt); test.has_user_data = true; - test.required_spirv = {"sampled_uint_2d", "OpTypeImage %uint", + test.required_spirv = {"image_10", "OpTypeImage %uint", "OpImageFetch %v4uint"}; return test; } @@ -19788,8 +19756,8 @@ TestCase ImageLoadR32SintUsesSignedSampledImage() { test.sampled_image_dwords_per_pixel = 1; test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32SInt); test.has_user_data = true; - test.required_spirv = {"sampled_sint_2d", "OpTypeImage %int", - "OpImageFetch %v4int", "OpBitcast %uint"}; + test.required_spirv = {"image_17", "OpTypeImage %int", "OpImageFetch %v4int", + "OpBitcast %uint"}; return test; } @@ -19914,7 +19882,7 @@ TestCase ImageLoad1DUsesScalarCoordinate() { test.user_data[3] = static_cast(Prospero::ImageType::kColor1D) << 28u; test.has_user_data = true; - test.required_spirv = {"sampled_uint_1d"}; + test.required_spirv = {"image_8"}; return test; } @@ -19950,7 +19918,7 @@ TestCase ImageGather2DInstructionWith1DDescriptor() { test.user_data[3] = static_cast(Prospero::ImageType::kColor1D) << 28u; test.has_user_data = true; - test.required_spirv = {"sampled_1d", "OpImageQuerySizeLod", + test.required_spirv = {"image_1", "OpImageQuerySizeLod", "OpImageSampleExplicitLod", "Floor"}; test.forbidden_spirv = {"OpImageGather"}; return test; @@ -19985,7 +19953,7 @@ TestCase ImageLoad1DArrayUsesLayerCoordinate() { test.user_data[3] = static_cast(Prospero::ImageType::kColor1DArray) << 28u; test.has_user_data = true; - test.required_spirv = {"sampled_uint_1d_array"}; + test.required_spirv = {"image_9"}; return test; } @@ -20022,7 +19990,7 @@ TestCase ImageLoad1DArrayDescriptorUsesSelectedLayer() { << 28u; test.user_data[4] = 1u | (1u << 16u); test.has_user_data = true; - test.required_spirv = {"sampled_uint_1d"}; + test.required_spirv = {"image_8"}; return test; } @@ -20767,7 +20735,7 @@ TestCase ImageStoreR32SintUsesRawUintView() { test.storage_image_r32ui = std::vector(16, 0); test.expected_storage_image_r32ui = expected_image; test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat", - "storage_uint_2d"}; + "image_29"}; test.forbidden_spirv = {"R32ui"}; return test; } @@ -20797,7 +20765,7 @@ TestCase ImageStoreR32UintUsesFormatlessStorageImage() { test.storage_image_r32ui = std::vector(16, 0); test.expected_storage_image_r32ui = expected_image; test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat", - "storage_uint_2d"}; + "image_29"}; test.forbidden_spirv = {"R32ui"}; return test; } @@ -20831,7 +20799,7 @@ TestCase ImageStorePackedUintSaturatesChannels() { 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", "OpULessThan", "OpSelect"}; + "image_29", "OpULessThan", "OpSelect"}; test.forbidden_spirv = {"R32ui"}; return test; } @@ -20863,7 +20831,7 @@ TestCase ImageStorePackedUintHonorsSparseDmask() { 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"}; + "image_29"}; test.forbidden_spirv = {"R32ui"}; return test; } @@ -20924,9 +20892,9 @@ TestCase ImageStoreAndAtomicShareTypedBinding() { 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"}; + "image_34"}; test.forbidden_spirv = {"StorageImageReadWithoutFormat", - "StorageImageWriteWithoutFormat", "storage_uint_2d"}; + "StorageImageWriteWithoutFormat", "image_29"}; return test; } @@ -20965,8 +20933,8 @@ TestCase ImageStoreAndAtomicUseSeparateBindings() { "OpImageWrite", "OpImageTexelPointer", "R32ui", - "storage_uint_2d", - "storage_atomic_2d"}; + "image_29", + "image_34"}; return test; } @@ -20997,10 +20965,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", "storage_atomic_2d"}; + test.required_spirv = {"OpImageTexelPointer", "R32ui", "image_34"}; test.forbidden_spirv = {"OpTypeSampler", "OpTypeSampledImage", "StorageImageReadWithoutFormat", - "StorageImageWriteWithoutFormat", "storage_uint_2d"}; + "StorageImageWriteWithoutFormat", "image_29"}; 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++) { @@ -23756,8 +23724,48 @@ void CheckImageSamplerSpecialization() { std::printf("[host] %-32s ok\n", "ImageSpecializationPipelineId"); } +void CheckNativeImageDescriptorTypes() { + using ShaderRecompiler::IR::DescriptorBindingKind; + using ShaderRecompiler::IR::FirstImageBinding; + using ShaderRecompiler::IR::FirstStorageImageBinding; + + for (uint32_t value = FirstImageBinding; + value < static_cast(DescriptorBindingKind::Samplers); + value++) { + const auto expected = value < FirstStorageImageBinding + ? vk::DescriptorType::eSampledImage + : vk::DescriptorType::eStorageImage; + Require("NativeImageDescriptorTypes", "image binding", + NativeDescriptorType(static_cast(value)) == + expected, + "generated image binding has the wrong Vulkan descriptor type"); + } + Require("NativeImageDescriptorTypes", "non-image bindings", + NativeDescriptorType(DescriptorBindingKind::Samplers) == + vk::DescriptorType::eSampler && + NativeDescriptorType(DescriptorBindingKind::Buffers) == + vk::DescriptorType::eStorageBuffer && + NativeDescriptorType(DescriptorBindingKind::Gds) == + vk::DescriptorType::eStorageBuffer && + NativeDescriptorType(DescriptorBindingKind::BdaPagetable) == + vk::DescriptorType::eStorageBuffer && + NativeDescriptorType(DescriptorBindingKind::FaultBuffer) == + vk::DescriptorType::eStorageBuffer && + NativeDescriptorType(DescriptorBindingKind::FlattenedSrt) == + vk::DescriptorType::eStorageBuffer && + NativeDescriptorType(DescriptorBindingKind::UserData) == + vk::DescriptorType::eStorageBuffer, + "non-image binding has the wrong Vulkan descriptor type"); + std::printf("[host] %-32s ok\n", "NativeImageDescriptorTypes"); +} + #if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS void CheckShaderRecompilerFatalContracts() { + ExpectFatal("InvalidDescriptorBindingRejection", [] { + (void)NativeDescriptorType( + ShaderRecompiler::IR::DescriptorBindingKind::Count); + }); + ExpectFatal("WritableFlatStoreRejection", [] { const auto test = FlatStoreVariants(); (void)CompileCase(test); @@ -25827,6 +25835,7 @@ int main(int argc, char **argv) { VulkanHarness vulkan; #endif CheckImageSamplerSpecialization(); + CheckNativeImageDescriptorTypes(); CheckClipControlDepthClipState(); CheckReferenceClockScale(); CheckVulkan13FeatureRequirements(); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index d0d88ace5..e0f5d3b00 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -5086,8 +5086,7 @@ void TestNewShaderRecompilerImageViewDimensions() { "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"), + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_8 "), "integer 1D load did not access the uint 1D descriptor binding"); } @@ -5124,11 +5123,10 @@ void TestNewShaderRecompilerStorageImage1DDescriptorVariants() { CheckSpirvBinaryValidates(result.spirv); const auto source = DisassembleSpirvBinary(result.spirv); - Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "storage_1d "), + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_22 "), "1D store did not access the 1D storage descriptor binding"); Check( - SpirvSourceHasInstructionUsing(source, "OpAccessChain", - "storage_1d_array"), + SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_23 "), "1D-array store did not access the 1D-array storage descriptor binding"); } @@ -5146,9 +5144,9 @@ void TestNewShaderRecompilerNullImageUsesCanonical2DView() { auto result = ShaderRecompiler::Recompile(shader, options); CheckSpirvBinaryValidates(result.spirv); const auto source = DisassembleSpirvBinary(result.spirv); - Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "sampled_2d "), + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_3 "), "null image did not use the canonical 2D sampled descriptor binding"); - Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "sampled_1d "), + Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_1 "), "null image retained the decoded 1D descriptor binding"); } @@ -5315,17 +5313,17 @@ void TestNewShaderRecompilerImageLoad2DMsaa() { result.program.info.images[0].dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa, "2D-MSAA descriptor specialization lost the multisample dimension"); - Check(ShaderRecompiler::IR::FindBinding( - result.program.bindings, - ShaderRecompiler::IR::DescriptorBindingKind::Sampled2DMsaa) != - nullptr, + const auto binding_kind = ShaderRecompiler::IR::DescriptorBindingForImage( + result.program.info.images[0]); + Check(binding_kind.has_value() && + ShaderRecompiler::IR::FindBinding(result.program.bindings, + *binding_kind) != nullptr, "2D-MSAA image did not receive a multisampled descriptor binding"); Check(SpirvContainsTypeImage(result.spirv, 1, 0, 1, 1), "SPIR-V binary does not contain a multisampled 2D image type"); CheckSpirvBinaryValidates(result.spirv); const auto source = DisassembleSpirvBinary(result.spirv); - Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", - "sampled_2d_msaa"), + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_5 "), "2D-MSAA load did not access its multisampled descriptor"); Check(SpirvSourceHasInstructionUsing(source, "OpImageFetch", " Sample "), "2D-MSAA load did not emit the fragment ID as a SPIR-V Sample operand"); @@ -5424,7 +5422,7 @@ void TestNewShaderRecompilerStorageImage3DDescriptorVariant() { CheckSpirvBinaryValidates(result.spirv); const auto source = DisassembleSpirvBinary(result.spirv); - Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "storage_3d"), + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_26 "), "storage image store did not access the 3D storage descriptor binding"); } @@ -5460,14 +5458,13 @@ void TestNewShaderRecompilerStorageImage2DDescriptorOverridesMimg3D() { CheckSpirvBinaryValidates(result.spirv); const auto source = DisassembleSpirvBinary(result.spirv); - Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "storage_2d"), + Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_24 "), "2D descriptor storage image store did not access the base storage " "binding"); - Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", - "storage_2d_array"), + Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_25 "), "2D descriptor storage image store unexpectedly used the array storage " "binding"); - Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "storage_3d"), + Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "image_26 "), "2D descriptor storage image store unexpectedly used the 3D storage " "binding"); } @@ -10342,12 +10339,25 @@ void TestNewShaderRecompilerNativeBindingPlan() { auto result = ShaderRecompiler::Recompile(shader, options); const auto *buffers = ShaderRecompiler::IR::FindBinding( result.program.bindings, BindingKind::Buffers); - const auto *sampled = ShaderRecompiler::IR::FindBinding( - result.program.bindings, BindingKind::Sampled2D); - const auto *storage = ShaderRecompiler::IR::FindBinding( - result.program.bindings, BindingKind::Storage2D); - const auto *atomic_storage = ShaderRecompiler::IR::FindBinding( - result.program.bindings, BindingKind::StorageAtomic2D); + const ShaderRecompiler::IR::DescriptorBinding *sampled = nullptr; + const ShaderRecompiler::IR::DescriptorBinding *storage = nullptr; + const ShaderRecompiler::IR::DescriptorBinding *atomic_storage = nullptr; + for (const auto &image : result.program.info.images) { + const auto kind = ShaderRecompiler::IR::DescriptorBindingForImage(image); + if (!kind.has_value()) { + continue; + } + const auto *binding = + ShaderRecompiler::IR::FindBinding(result.program.bindings, *kind); + if (image.resource_class == + ShaderRecompiler::IR::ImageResourceClass::Sampled) { + sampled = binding; + } else if (image.atomic) { + atomic_storage = binding; + } else { + storage = binding; + } + } const auto *samplers = ShaderRecompiler::IR::FindBinding( result.program.bindings, BindingKind::Samplers); Check(buffers != nullptr && buffers->resources.size() == 2,