diff --git a/Graphics/ShaderTools/include/SPIRVShaderResources.hpp b/Graphics/ShaderTools/include/SPIRVShaderResources.hpp index 891cd8a5e2..51d07f7904 100644 --- a/Graphics/ShaderTools/include/SPIRVShaderResources.hpp +++ b/Graphics/ShaderTools/include/SPIRVShaderResources.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2024 Diligent Graphics LLC + * Copyright 2019-2025 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,9 +32,19 @@ // SPIRVShaderResources class uses continuous chunk of memory to store all resources, as follows: // -// m_MemoryBuffer m_TotalResources -// | | | -// | Uniform Buffers | Storage Buffers | Storage Images | Sampled Images | Atomic Counters | Separate Samplers | Separate Images | Stage Inputs | Resource Names | +// _ _ _ m_MemoryBuffer +// | Uniform Buffers | +// | Storage Buffers | | +// | Storage Images | V +// | Sampled Images | +// | Atomic Counters | +// | Separate Samplers | +// | Separate Images | +// | Input Attachments | +// | Accel Structs | +// | Push Constants | _ _ _ m_TotalResources +// | Stage Inputs | +// | Resource Names | #include #include @@ -79,11 +89,13 @@ struct SPIRVShaderResourceAttribs SeparateSampler, InputAttachment, AccelerationStructure, + PushConstant, NumResourceTypes }; static SHADER_RESOURCE_TYPE GetShaderResourceType(ResourceType Type); static PIPELINE_RESOURCE_FLAGS GetPipelineResourceFlags(ResourceType Type); + static const char* ResourceTypeToString(ResourceType Type); // clang-format off @@ -111,6 +123,11 @@ struct SPIRVShaderResourceAttribs Uint32 _BufferStaticSize = 0, Uint32 _BufferStride = 0) noexcept; + // Constructor for push constants (no binding or descriptor set decoration) + SPIRVShaderResourceAttribs(const char* _Name, + ResourceType _Type, + Uint32 _BufferStaticSize) noexcept; + ShaderResourceDesc GetResourceDesc() const { return ShaderResourceDesc{Name, GetShaderResourceType(Type), ArraySize}; @@ -125,6 +142,39 @@ struct SPIRVShaderResourceAttribs { return IsMS != 0; } + + Uint32 GetConstantBufferSize() const + { + VERIFY((Type == PushConstant || Type == UniformBuffer), "Invalid resource type: PushConstant or UniformBuffer expected"); + return BufferStaticSize; + } + + Uint32 GetInlineConstantCountOrThrow(const char* ShaderName) const + { + VERIFY_EXPR(Type == PushConstant || Type == UniformBuffer); + + if (ArraySize != 1) + { + LOG_ERROR_AND_THROW("Inline constants resource '", Name, "' in shader '", ShaderName, "' can not be an array."); + } + const Uint32 NumConstants = GetConstantBufferSize() / sizeof(Uint32); + + constexpr Uint32 kMaxInlineConstants = 64; + if (NumConstants > kMaxInlineConstants) + { + LOG_ERROR_AND_THROW("Inline constants resource '", Name, "' in shader '", ShaderName, "' has ", + NumConstants, " constants. The maximum supported number of inline constants is ", + kMaxInlineConstants, '.'); + } + + if (NumConstants == 0) + { + LOG_ERROR_AND_THROW("Resource '", Name, "' in shader '", ShaderName, + "' is marked as inline constants, but has zero buffer size. "); + } + + return NumConstants; + } }; static_assert(sizeof(SPIRVShaderResourceAttribs) % sizeof(void*) == 0, "Size of SPIRVShaderResourceAttribs struct must be multiple of sizeof(void*)"); @@ -166,28 +216,30 @@ class SPIRVShaderResources // clang-format off - Uint32 GetNumUBs ()const noexcept{ return (m_StorageBufferOffset - 0); } - Uint32 GetNumSBs ()const noexcept{ return (m_StorageImageOffset - m_StorageBufferOffset); } - Uint32 GetNumImgs ()const noexcept{ return (m_SampledImageOffset - m_StorageImageOffset); } - Uint32 GetNumSmpldImgs ()const noexcept{ return (m_AtomicCounterOffset - m_SampledImageOffset); } - Uint32 GetNumACs ()const noexcept{ return (m_SeparateSamplerOffset - m_AtomicCounterOffset); } - Uint32 GetNumSepSmplrs ()const noexcept{ return (m_SeparateImageOffset - m_SeparateSamplerOffset);} - Uint32 GetNumSepImgs ()const noexcept{ return (m_InputAttachmentOffset - m_SeparateImageOffset); } - Uint32 GetNumInptAtts ()const noexcept{ return (m_AccelStructOffset - m_InputAttachmentOffset);} - Uint32 GetNumAccelStructs()const noexcept{ return (m_TotalResources - m_AccelStructOffset); } + Uint32 GetNumUBs ()const noexcept{ return (m_StorageBufferOffset - 0); } + Uint32 GetNumSBs ()const noexcept{ return (m_StorageImageOffset - m_StorageBufferOffset); } + Uint32 GetNumImgs ()const noexcept{ return (m_SampledImageOffset - m_StorageImageOffset); } + Uint32 GetNumSmpldImgs ()const noexcept{ return (m_AtomicCounterOffset - m_SampledImageOffset); } + Uint32 GetNumACs ()const noexcept{ return (m_SeparateSamplerOffset - m_AtomicCounterOffset); } + Uint32 GetNumSepSmplrs ()const noexcept{ return (m_SeparateImageOffset - m_SeparateSamplerOffset); } + Uint32 GetNumSepImgs ()const noexcept{ return (m_InputAttachmentOffset - m_SeparateImageOffset); } + Uint32 GetNumInptAtts ()const noexcept{ return (m_AccelStructOffset - m_InputAttachmentOffset); } + Uint32 GetNumAccelStructs ()const noexcept{ return (m_PushConstantOffset - m_AccelStructOffset); } + Uint32 GetNumPushConstants()const noexcept{ return (m_TotalResources - m_PushConstantOffset); } Uint32 GetTotalResources () const noexcept { return m_TotalResources; } Uint32 GetNumShaderStageInputs()const noexcept { return m_NumShaderStageInputs; } - const SPIRVShaderResourceAttribs& GetUB (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumUBs(), 0 ); } - const SPIRVShaderResourceAttribs& GetSB (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumSBs(), m_StorageBufferOffset ); } - const SPIRVShaderResourceAttribs& GetImg (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumImgs(), m_StorageImageOffset ); } - const SPIRVShaderResourceAttribs& GetSmpldImg (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumSmpldImgs(), m_SampledImageOffset ); } - const SPIRVShaderResourceAttribs& GetAC (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumACs(), m_AtomicCounterOffset ); } - const SPIRVShaderResourceAttribs& GetSepSmplr (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumSepSmplrs(), m_SeparateSamplerOffset); } - const SPIRVShaderResourceAttribs& GetSepImg (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumSepImgs(), m_SeparateImageOffset ); } - const SPIRVShaderResourceAttribs& GetInptAtt (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumInptAtts(), m_InputAttachmentOffset); } - const SPIRVShaderResourceAttribs& GetAccelStruct(Uint32 n)const noexcept{ return GetResAttribs(n, GetNumAccelStructs(), m_AccelStructOffset ); } - const SPIRVShaderResourceAttribs& GetResource (Uint32 n)const noexcept{ return GetResAttribs(n, GetTotalResources(), 0 ); } + const SPIRVShaderResourceAttribs& GetUB (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumUBs(), 0 ); } + const SPIRVShaderResourceAttribs& GetSB (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumSBs(), m_StorageBufferOffset ); } + const SPIRVShaderResourceAttribs& GetImg (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumImgs(), m_StorageImageOffset ); } + const SPIRVShaderResourceAttribs& GetSmpldImg (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumSmpldImgs(), m_SampledImageOffset ); } + const SPIRVShaderResourceAttribs& GetAC (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumACs(), m_AtomicCounterOffset ); } + const SPIRVShaderResourceAttribs& GetSepSmplr (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumSepSmplrs(), m_SeparateSamplerOffset ); } + const SPIRVShaderResourceAttribs& GetSepImg (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumSepImgs(), m_SeparateImageOffset ); } + const SPIRVShaderResourceAttribs& GetInptAtt (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumInptAtts(), m_InputAttachmentOffset ); } + const SPIRVShaderResourceAttribs& GetAccelStruct (Uint32 n)const noexcept{ return GetResAttribs(n, GetNumAccelStructs(), m_AccelStructOffset ); } + const SPIRVShaderResourceAttribs& GetPushConstant(Uint32 n)const noexcept{ return GetResAttribs(n, GetNumPushConstants(), m_PushConstantOffset ); } + const SPIRVShaderResourceAttribs& GetResource (Uint32 n)const noexcept{ return GetResAttribs(n, GetTotalResources(), 0 ); } // clang-format on @@ -217,15 +269,16 @@ class SPIRVShaderResources struct ResourceCounters { - Uint32 NumUBs = 0; - Uint32 NumSBs = 0; - Uint32 NumImgs = 0; - Uint32 NumSmpldImgs = 0; - Uint32 NumACs = 0; - Uint32 NumSepSmplrs = 0; - Uint32 NumSepImgs = 0; - Uint32 NumInptAtts = 0; - Uint32 NumAccelStructs = 0; + Uint32 NumUBs = 0; + Uint32 NumSBs = 0; + Uint32 NumImgs = 0; + Uint32 NumSmpldImgs = 0; + Uint32 NumACs = 0; + Uint32 NumSepSmplrs = 0; + Uint32 NumSepImgs = 0; + Uint32 NumInptAtts = 0; + Uint32 NumAccelStructs = 0; + Uint32 NumPushConstants = 0; // Vulkan push constant buffers (max 1 per Vulkan spec) }; SHADER_TYPE GetShaderType() const noexcept { return m_ShaderType; } @@ -243,16 +296,18 @@ class SPIRVShaderResources typename THandleSepSmpl, typename THandleSepImg, typename THandleInptAtt, - typename THandleAccelStruct> - void ProcessResources(THandleUB HandleUB, - THandleSB HandleSB, - THandleImg HandleImg, - THandleSmplImg HandleSmplImg, - THandleAC HandleAC, - THandleSepSmpl HandleSepSmpl, - THandleSepImg HandleSepImg, - THandleInptAtt HandleInptAtt, - THandleAccelStruct HandleAccelStruct) const + typename THandleAccelStruct, + typename THandlePushConstant> + void ProcessResources(THandleUB HandleUB, + THandleSB HandleSB, + THandleImg HandleImg, + THandleSmplImg HandleSmplImg, + THandleAC HandleAC, + THandleSepSmpl HandleSepSmpl, + THandleSepImg HandleSepImg, + THandleInptAtt HandleInptAtt, + THandleAccelStruct HandleAccelStruct, + THandlePushConstant HandlePushConstant) const { for (Uint32 n = 0; n < GetNumUBs(); ++n) { @@ -308,7 +363,13 @@ class SPIRVShaderResources HandleAccelStruct(AccelStruct, n); } - static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 12, "Please handle the new resource type here, if needed"); + for (Uint32 n = 0; n < GetNumPushConstants(); ++n) + { + const SPIRVShaderResourceAttribs& PushConstant = GetPushConstant(n); + HandlePushConstant(PushConstant, n); + } + + static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please handle the new resource type here, if needed"); } template @@ -359,16 +420,17 @@ class SPIRVShaderResources // clang-format off - SPIRVShaderResourceAttribs& GetUB (Uint32 n)noexcept{ return GetResAttribs(n, GetNumUBs(), 0 ); } - SPIRVShaderResourceAttribs& GetSB (Uint32 n)noexcept{ return GetResAttribs(n, GetNumSBs(), m_StorageBufferOffset ); } - SPIRVShaderResourceAttribs& GetImg (Uint32 n)noexcept{ return GetResAttribs(n, GetNumImgs(), m_StorageImageOffset ); } - SPIRVShaderResourceAttribs& GetSmpldImg (Uint32 n)noexcept{ return GetResAttribs(n, GetNumSmpldImgs(), m_SampledImageOffset ); } - SPIRVShaderResourceAttribs& GetAC (Uint32 n)noexcept{ return GetResAttribs(n, GetNumACs(), m_AtomicCounterOffset ); } - SPIRVShaderResourceAttribs& GetSepSmplr (Uint32 n)noexcept{ return GetResAttribs(n, GetNumSepSmplrs(), m_SeparateSamplerOffset); } - SPIRVShaderResourceAttribs& GetSepImg (Uint32 n)noexcept{ return GetResAttribs(n, GetNumSepImgs(), m_SeparateImageOffset ); } - SPIRVShaderResourceAttribs& GetInptAtt (Uint32 n)noexcept{ return GetResAttribs(n, GetNumInptAtts(), m_InputAttachmentOffset); } - SPIRVShaderResourceAttribs& GetAccelStruct(Uint32 n)noexcept{ return GetResAttribs(n, GetNumAccelStructs(), m_AccelStructOffset ); } - SPIRVShaderResourceAttribs& GetResource (Uint32 n)noexcept{ return GetResAttribs(n, GetTotalResources(), 0 ); } + SPIRVShaderResourceAttribs& GetUB (Uint32 n)noexcept{ return GetResAttribs(n, GetNumUBs(), 0 ); } + SPIRVShaderResourceAttribs& GetSB (Uint32 n)noexcept{ return GetResAttribs(n, GetNumSBs(), m_StorageBufferOffset ); } + SPIRVShaderResourceAttribs& GetImg (Uint32 n)noexcept{ return GetResAttribs(n, GetNumImgs(), m_StorageImageOffset ); } + SPIRVShaderResourceAttribs& GetSmpldImg (Uint32 n)noexcept{ return GetResAttribs(n, GetNumSmpldImgs(), m_SampledImageOffset ); } + SPIRVShaderResourceAttribs& GetAC (Uint32 n)noexcept{ return GetResAttribs(n, GetNumACs(), m_AtomicCounterOffset ); } + SPIRVShaderResourceAttribs& GetSepSmplr (Uint32 n)noexcept{ return GetResAttribs(n, GetNumSepSmplrs(), m_SeparateSamplerOffset ); } + SPIRVShaderResourceAttribs& GetSepImg (Uint32 n)noexcept{ return GetResAttribs(n, GetNumSepImgs(), m_SeparateImageOffset ); } + SPIRVShaderResourceAttribs& GetInptAtt (Uint32 n)noexcept{ return GetResAttribs(n, GetNumInptAtts(), m_InputAttachmentOffset ); } + SPIRVShaderResourceAttribs& GetAccelStruct (Uint32 n)noexcept{ return GetResAttribs(n, GetNumAccelStructs(), m_AccelStructOffset ); } + SPIRVShaderResourceAttribs& GetPushConstant(Uint32 n)noexcept{ return GetResAttribs(n, GetNumPushConstants(), m_PushConstantOffset ); } + SPIRVShaderResourceAttribs& GetResource (Uint32 n)noexcept{ return GetResAttribs(n, GetTotalResources(), 0 ); } // clang-format on @@ -394,6 +456,7 @@ class SPIRVShaderResources OffsetType m_SeparateImageOffset = 0; OffsetType m_InputAttachmentOffset = 0; OffsetType m_AccelStructOffset = 0; + OffsetType m_PushConstantOffset = 0; OffsetType m_TotalResources = 0; OffsetType m_NumShaderStageInputs = 0; diff --git a/Graphics/ShaderTools/src/SPIRVShaderResources.cpp b/Graphics/ShaderTools/src/SPIRVShaderResources.cpp index dc65e596bb..8ef61ab0bd 100644 --- a/Graphics/ShaderTools/src/SPIRVShaderResources.cpp +++ b/Graphics/ShaderTools/src/SPIRVShaderResources.cpp @@ -74,6 +74,11 @@ static RESOURCE_DIMENSION GetResourceDimension(const diligent_spirv_cross::Compi default: return RESOURCE_DIM_UNDEFINED; } } + else if (type.basetype == diligent_spirv_cross::SPIRType::BaseType::Struct) + { + // Uniform buffers and storage buffers are Struct types + return RESOURCE_DIM_BUFFER; + } else { return RESOURCE_DIM_UNDEFINED; @@ -126,10 +131,28 @@ SPIRVShaderResourceAttribs::SPIRVShaderResourceAttribs(const diligent_spirv_cros // clang-format on {} +// Constructor for push constants (no binding or descriptor set decoration) +SPIRVShaderResourceAttribs::SPIRVShaderResourceAttribs(const char* _Name, + ResourceType _Type, + Uint32 _BufferStaticSize) noexcept : + // clang-format off + Name {_Name}, + // For push constants, ArraySize is always 1 + // This is consistent with how inline constants work in the pipeline resource signature + ArraySize {1}, + Type {_Type}, + ResourceDim {RESOURCE_DIM_BUFFER}, + IsMS {0}, + BindingDecorationOffset {0}, // Push constants have no binding decoration + DescriptorSetDecorationOffset {0}, // Push constants have no descriptor set decoration + BufferStaticSize {_BufferStaticSize}, + BufferStride {0} +// clang-format on +{} SHADER_RESOURCE_TYPE SPIRVShaderResourceAttribs::GetShaderResourceType(ResourceType Type) { - static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 12, "Please handle the new resource type below"); + static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please handle the new resource type below"); switch (Type) { case SPIRVShaderResourceAttribs::ResourceType::UniformBuffer: @@ -171,6 +194,10 @@ SHADER_RESOURCE_TYPE SPIRVShaderResourceAttribs::GetShaderResourceType(ResourceT case SPIRVShaderResourceAttribs::ResourceType::AccelerationStructure: return SHADER_RESOURCE_TYPE_ACCEL_STRUCT; + case SPIRVShaderResourceAttribs::ResourceType::PushConstant: + // Push constants map to constant buffer type with special inline constants flag + return SHADER_RESOURCE_TYPE_CONSTANT_BUFFER; + default: UNEXPECTED("Unknown SPIRV resource type"); return SHADER_RESOURCE_TYPE_UNKNOWN; @@ -179,7 +206,7 @@ SHADER_RESOURCE_TYPE SPIRVShaderResourceAttribs::GetShaderResourceType(ResourceT PIPELINE_RESOURCE_FLAGS SPIRVShaderResourceAttribs::GetPipelineResourceFlags(ResourceType Type) { - static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 12, "Please handle the new resource type below"); + static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please handle the new resource type below"); switch (Type) { case SPIRVShaderResourceAttribs::ResourceType::UniformTexelBuffer: @@ -189,11 +216,50 @@ PIPELINE_RESOURCE_FLAGS SPIRVShaderResourceAttribs::GetPipelineResourceFlags(Res case SPIRVShaderResourceAttribs::ResourceType::SampledImage: return PIPELINE_RESOURCE_FLAG_COMBINED_SAMPLER; + case SPIRVShaderResourceAttribs::ResourceType::PushConstant: + // Push constants map to constant buffer with the inline constants flag + return PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS; + default: return PIPELINE_RESOURCE_FLAG_NONE; } } +const char* SPIRVShaderResourceAttribs::ResourceTypeToString(SPIRVShaderResourceAttribs::ResourceType Type) +{ + switch (Type) + { + case SPIRVShaderResourceAttribs::ResourceType::UniformBuffer: + return "UniformBuffer"; + case SPIRVShaderResourceAttribs::ResourceType::ROStorageBuffer: + return "ROStorageBuffer"; + case SPIRVShaderResourceAttribs::ResourceType::RWStorageBuffer: + return "RWStorageBuffer"; + case SPIRVShaderResourceAttribs::ResourceType::UniformTexelBuffer: + return "UniformTexelBuffer"; + case SPIRVShaderResourceAttribs::ResourceType::StorageTexelBuffer: + return "StorageTexelBuffer"; + case SPIRVShaderResourceAttribs::ResourceType::StorageImage: + return "StorageImage"; + case SPIRVShaderResourceAttribs::ResourceType::SampledImage: + return "SampledImage"; + case SPIRVShaderResourceAttribs::ResourceType::AtomicCounter: + return "AtomicCounter"; + case SPIRVShaderResourceAttribs::ResourceType::SeparateImage: + return "SeparateImage"; + case SPIRVShaderResourceAttribs::ResourceType::SeparateSampler: + return "SeparateSampler"; + case SPIRVShaderResourceAttribs::ResourceType::InputAttachment: + return "InputAttachment"; + case SPIRVShaderResourceAttribs::ResourceType::AccelerationStructure: + return "AccelerationStructure"; + case SPIRVShaderResourceAttribs::ResourceType::PushConstant: + return "PushConstant"; + default: + return "Unknown"; + } +} + spv::ExecutionModel ShaderTypeToSpvExecutionModel(SHADER_TYPE ShaderType) { static_assert(SHADER_TYPE_LAST == 0x4000, "Please handle the new shader type in the switch below"); @@ -262,6 +328,33 @@ const std::string& GetUBOrSBName(diligent_spirv_cross::Compiler& C return ((IRSource.hlsl || IRSource.lang == spv::SourceLanguageSlang) && !instance_name.empty()) ? instance_name : UB.name; } +// Get name for push constant buffer +// Push constants may have their variable name empty (e.g., when declared as [[vk::push_constant]] cbuffer in HLSL) +// In that case, we try to get the name from the base type (the struct type) +const std::string& GetPushConstantName(diligent_spirv_cross::Compiler& Compiler, + const diligent_spirv_cross::Resource& PC, + const diligent_spirv_cross::ParsedIR::Source& IRSource) +{ + // First, try the standard method used for UBs/SBs + const std::string& Name = GetUBOrSBName(Compiler, PC, IRSource); + if (!Name.empty()) + return Name; + + // If the name is empty, try to get the name from the base type + // This handles cases like: + // [[vk::push_constant]] + // cbuffer Constants { ... }; + // where the variable has no name but the struct type does + const std::string& base_type_name = Compiler.get_name(PC.base_type_id); + if (!base_type_name.empty()) + return base_type_name; + + // As a last resort, try to get the fallback name + // Note: get_fallback_name returns by value, so we can't return a reference to it + // Return the empty name and let the caller handle it + return Name; +} + static SHADER_CODE_BASIC_TYPE SpirvBaseTypeToShaderCodeBasicType(diligent_spirv_cross::SPIRType::BaseType SpvBaseType) { switch (SpvBaseType) @@ -423,7 +516,18 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, ResourceNamesPoolSize += GetUBOrSBName(Compiler, ub, ParsedIRSource).length() + 1; for (const diligent_spirv_cross::Resource& sb : resources.storage_buffers) ResourceNamesPoolSize += GetUBOrSBName(Compiler, sb, ParsedIRSource).length() + 1; - static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 12, "Please account for the new resource type below"); + static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please account for the new resource type below"); + + // Process push constant buffers - Vulkan spec allows only one push_constant buffer per pipeline + if (resources.push_constant_buffers.size() > 1) + { + LOG_ERROR_AND_THROW("Shader '", shaderDesc.Name, "' contains ", resources.push_constant_buffers.size(), + " push constant buffers, but Vulkan spec allows only one push_constant buffer per pipeline."); + } + // For push constants, use GetPushConstantName which also tries to get the name from the base type + for (const diligent_spirv_cross::Resource& pc : resources.push_constant_buffers) + ResourceNamesPoolSize += GetPushConstantName(Compiler, pc, ParsedIRSource).length() + 1; + for (auto* pResType : { &resources.storage_images, @@ -493,16 +597,17 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, } ResourceCounters ResCounters; - ResCounters.NumUBs = static_cast(resources.uniform_buffers.size()); - ResCounters.NumSBs = static_cast(resources.storage_buffers.size()); - ResCounters.NumImgs = static_cast(resources.storage_images.size()); - ResCounters.NumSmpldImgs = static_cast(resources.sampled_images.size()); - ResCounters.NumACs = static_cast(resources.atomic_counters.size()); - ResCounters.NumSepSmplrs = static_cast(resources.separate_samplers.size()); - ResCounters.NumSepImgs = static_cast(resources.separate_images.size()); - ResCounters.NumInptAtts = static_cast(resources.subpass_inputs.size()); - ResCounters.NumAccelStructs = static_cast(resources.acceleration_structures.size()); - static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 12, "Please set the new resource type counter here"); + ResCounters.NumUBs = static_cast(resources.uniform_buffers.size()); + ResCounters.NumSBs = static_cast(resources.storage_buffers.size()); + ResCounters.NumImgs = static_cast(resources.storage_images.size()); + ResCounters.NumSmpldImgs = static_cast(resources.sampled_images.size()); + ResCounters.NumACs = static_cast(resources.atomic_counters.size()); + ResCounters.NumSepSmplrs = static_cast(resources.separate_samplers.size()); + ResCounters.NumSepImgs = static_cast(resources.separate_images.size()); + ResCounters.NumInptAtts = static_cast(resources.subpass_inputs.size()); + ResCounters.NumAccelStructs = static_cast(resources.acceleration_structures.size()); + ResCounters.NumPushConstants = static_cast(resources.push_constant_buffers.size()); + static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please set the new resource type counter here"); // Resource names pool is only needed to facilitate string allocation. StringPool ResourceNamesPool; @@ -687,7 +792,26 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, VERIFY_EXPR(CurrAccelStruct == GetNumAccelStructs()); } - static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 12, "Please initialize SPIRVShaderResourceAttribs for the new resource type here"); + { + Uint32 CurrPushConstant = 0; + for (const diligent_spirv_cross::Resource& PushConst : resources.push_constant_buffers) + { + const std::string& pcName = GetPushConstantName(Compiler, PushConst, ParsedIRSource); + const diligent_spirv_cross::SPIRType& Type = Compiler.get_type(PushConst.type_id); + const size_t Size = Compiler.get_declared_struct_size(Type); + + // Push constants use a special constructor without binding/descriptor set decorations + new (&GetPushConstant(CurrPushConstant++)) SPIRVShaderResourceAttribs // + { + ResourceNamesPool.CopyString(pcName), + SPIRVShaderResourceAttribs::ResourceType::PushConstant, + static_cast(Size) // + }; + } + VERIFY_EXPR(CurrPushConstant == GetNumPushConstants()); + } + + static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please initialize SPIRVShaderResourceAttribs for the new resource type here"); if (CombinedSamplerSuffix != nullptr) { @@ -757,8 +881,9 @@ void SPIRVShaderResources::Initialize(IMemoryAllocator& Allocator, m_SeparateImageOffset = AdvanceOffset(Counters.NumSepImgs); m_InputAttachmentOffset = AdvanceOffset(Counters.NumInptAtts); m_AccelStructOffset = AdvanceOffset(Counters.NumAccelStructs); + m_PushConstantOffset = AdvanceOffset(Counters.NumPushConstants); m_TotalResources = AdvanceOffset(0); - static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 12, "Please update the new resource type offset"); + static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please update the new resource type offset"); VERIFY(NumShaderStageInputs <= MaxOffset, "Max offset exceeded"); m_NumShaderStageInputs = static_cast(NumShaderStageInputs); @@ -771,16 +896,17 @@ void SPIRVShaderResources::Initialize(IMemoryAllocator& Allocator, m_NumShaderStageInputs * sizeof(SPIRVShaderStageInputAttribs) + AlignedResourceNamesPoolSize * sizeof(char); - VERIFY_EXPR(GetNumUBs() == Counters.NumUBs); - VERIFY_EXPR(GetNumSBs() == Counters.NumSBs); - VERIFY_EXPR(GetNumImgs() == Counters.NumImgs); - VERIFY_EXPR(GetNumSmpldImgs() == Counters.NumSmpldImgs); - VERIFY_EXPR(GetNumACs() == Counters.NumACs); - VERIFY_EXPR(GetNumSepSmplrs() == Counters.NumSepSmplrs); - VERIFY_EXPR(GetNumSepImgs() == Counters.NumSepImgs); - VERIFY_EXPR(GetNumInptAtts() == Counters.NumInptAtts); - VERIFY_EXPR(GetNumAccelStructs() == Counters.NumAccelStructs); - static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 12, "Please update the new resource count verification"); + VERIFY_EXPR(GetNumUBs() == Counters.NumUBs); + VERIFY_EXPR(GetNumSBs() == Counters.NumSBs); + VERIFY_EXPR(GetNumImgs() == Counters.NumImgs); + VERIFY_EXPR(GetNumSmpldImgs() == Counters.NumSmpldImgs); + VERIFY_EXPR(GetNumACs() == Counters.NumACs); + VERIFY_EXPR(GetNumSepSmplrs() == Counters.NumSepSmplrs); + VERIFY_EXPR(GetNumSepImgs() == Counters.NumSepImgs); + VERIFY_EXPR(GetNumInptAtts() == Counters.NumInptAtts); + VERIFY_EXPR(GetNumAccelStructs() == Counters.NumAccelStructs); + VERIFY_EXPR(GetNumPushConstants() == Counters.NumPushConstants); + static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please update the new resource count verification"); // clang-format on if (MemorySize) @@ -826,7 +952,10 @@ SPIRVShaderResources::~SPIRVShaderResources() for (Uint32 n = 0; n < GetNumAccelStructs(); ++n) GetAccelStruct(n).~SPIRVShaderResourceAttribs(); - static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 12, "Please add destructor for the new resource"); + for (Uint32 n = 0; n < GetNumPushConstants(); ++n) + GetPushConstant(n).~SPIRVShaderResourceAttribs(); + + static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please add destructor for the new resource"); } void SPIRVShaderResources::MapHLSLVertexShaderInputs(std::vector& SPIRV) const @@ -948,9 +1077,19 @@ std::string SPIRVShaderResources::DumpResources() const }, [&](const SPIRVShaderResourceAttribs& SepImg, Uint32) // { - VERIFY(SepImg.Type == SPIRVShaderResourceAttribs::ResourceType::SeparateImage, "Unexpected resource type"); - ss << std::endl - << std::setw(3) << ResNum << " Separate Img "; + VERIFY((SepImg.Type == SPIRVShaderResourceAttribs::ResourceType::SeparateImage || + SepImg.Type == SPIRVShaderResourceAttribs::ResourceType::UniformTexelBuffer), + "Unexpected resource type"); + if (SepImg.Type == SPIRVShaderResourceAttribs::ResourceType::UniformTexelBuffer) + { + ss << std::endl + << std::setw(3) << ResNum << " Uniform Txl Buff "; + } + else + { + ss << std::endl + << std::setw(3) << ResNum << " Separate Img "; + } DumpResource(SepImg); }, [&](const SPIRVShaderResourceAttribs& InptAtt, Uint32) // @@ -966,6 +1105,13 @@ std::string SPIRVShaderResources::DumpResources() const ss << std::endl << std::setw(3) << ResNum << " Accel Struct "; DumpResource(AccelStruct); + }, + [&](const SPIRVShaderResourceAttribs& PushConst, Uint32) // + { + VERIFY(PushConst.Type == SPIRVShaderResourceAttribs::ResourceType::PushConstant, "Unexpected resource type"); + ss << std::endl + << std::setw(3) << ResNum << " Push Constant "; + DumpResource(PushConst); } // ); VERIFY_EXPR(ResNum == GetTotalResources()); diff --git a/Tests/DiligentCoreTest/CMakeLists.txt b/Tests/DiligentCoreTest/CMakeLists.txt index d6fed71511..478a3f0559 100644 --- a/Tests/DiligentCoreTest/CMakeLists.txt +++ b/Tests/DiligentCoreTest/CMakeLists.txt @@ -20,6 +20,12 @@ if(NOT WEBGPU_SUPPORTED) ) endif() +if(NOT ${DILIGENT_USE_SPIRV_TOOLCHAIN} OR ${DILIGENT_NO_GLSLANG}) + list(REMOVE_ITEM SOURCE + ${CMAKE_CURRENT_SOURCE_DIR}/src/ShaderTools/SPIRVShaderResourcesTest.cpp + ) +endif() + set_source_files_properties(${SHADERS} PROPERTIES VS_TOOL_OVERRIDE "None") if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/AccelerationStructures.glsl b/Tests/DiligentCoreTest/assets/shaders/SPIRV/AccelerationStructures.glsl new file mode 100644 index 0000000000..42f5e1787a --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/AccelerationStructures.glsl @@ -0,0 +1,30 @@ +// Test for acceleration structures +// GLSL ray gen shader for testing acceleration structure resource reflection +#version 460 +#extension GL_EXT_ray_tracing : require + +layout(set = 0, binding = 0) uniform accelerationStructureEXT g_AccelStruct; + +layout(location = 0) rayPayloadEXT vec4 payload; + +void main() +{ + // Use traceRayEXT to ensure g_AccelStruct is actually used and not optimized away + // This is the proper way to use acceleration structures in ray gen shaders + const vec2 uv = vec2(gl_LaunchIDEXT.xy + 0.5) / vec2(gl_LaunchSizeEXT.xy); + const vec3 origin = vec3(uv.x, 1.0 - uv.y, -1.0); + const vec3 direction = vec3(0.0, 0.0, 1.0); + + payload = vec4(0.0); + traceRayEXT(g_AccelStruct, // acceleration structure (first parameter) + gl_RayFlagsNoneEXT, // ray flags + 0xFF, // cullMask + 0, // sbtRecordOffset + 1, // sbtRecordStride + 0, // missIndex + origin, // ray origin + 0.01, // ray min range + direction, // ray direction + 10.0, // ray max range + 0); // payload location +} diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/AtomicCounters.glsl b/Tests/DiligentCoreTest/assets/shaders/SPIRV/AtomicCounters.glsl new file mode 100644 index 0000000000..efcf907ab3 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/AtomicCounters.glsl @@ -0,0 +1,30 @@ +// Test for atomic counters +// GLSL fragment shader for testing atomic counter resource reflection +// Note: Vulkan does not support atomic_uint (AtomicCounter storage class). +// We use a storage buffer with atomic operations to simulate atomic counters. +// However, this will be reflected as RWStorageBuffer, not AtomicCounter. +#version 450 + +layout(binding = 0) coherent buffer AtomicCounterBuffer +{ + uint counter; +} g_AtomicCounter; + +layout(location = 0) out vec4 FragColor; + +void main() +{ + // Increment atomic counter using atomicAdd + // atomicAdd returns the value before the addition + uint counterValue = atomicAdd(g_AtomicCounter.counter, 1u); + + // Create a color based on counter value + vec4 color = vec4( + float(counterValue & 0xFFu) / 255.0, + float((counterValue >> 8u) & 0xFFu) / 255.0, + float((counterValue >> 16u) & 0xFFu) / 255.0, + 1.0 + ); + + FragColor = color; +} diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/InputAttachments.psh b/Tests/DiligentCoreTest/assets/shaders/SPIRV/InputAttachments.psh new file mode 100644 index 0000000000..1691087c1b --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/InputAttachments.psh @@ -0,0 +1,14 @@ +// Test for input attachments (subpass inputs) +// In HLSL, subpass inputs are declared using SubpassInput type with vk::input_attachment_index attribute +[[vk::input_attachment_index(0)]] +[[vk::binding(0)]] +SubpassInput g_InputAttachment; + +float4 main() : SV_Target +{ + // Read from input attachment (subpass input) + float4 color = g_InputAttachment.SubpassLoad(); + + // Process the input color + return color * 2.0; +} diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/MixedResources.psh b/Tests/DiligentCoreTest/assets/shaders/SPIRV/MixedResources.psh new file mode 100644 index 0000000000..ac86013752 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/MixedResources.psh @@ -0,0 +1,66 @@ +// Test for mixed resources to verify all resource types work together + +struct BufferData +{ + float4 data[4]; +}; + +// Uniform buffer +cbuffer UniformBuff : register(b0) +{ + float4x4 g_WorldViewProj; + float4 g_MaterialParams; +}; + +// Read-only structured buffer +StructuredBuffer ROStorageBuff : register(t0); + +// Read-write structured buffer +RWStructuredBuffer RWStorageBuff : register(u0); + +// Sampled texture +Texture2D SampledTex : register(t1); + +// Storage texture +RWTexture2D StorageTex : register(u1); + +// Sampler +SamplerState Sampler : register(s0); + +// Test for push constants +struct PushConstants_t +{ + float2 Offset; + float Time; + uint FrameCount; +}; + +[[vk::push_constant]] +cbuffer PushConstants +{ + PushConstants_t g_PushConstants; +}; + +float4 main() : SV_Target +{ + // Use uniform buffer data + float4 color = g_MaterialParams; + + // Read from RO structured buffer + color += ROStorageBuff[0].data[0]; + + // Write to RW structured buffer + RWStorageBuff[1].data[1] = color * 2.0; + + // Sample texture + color += SampledTex.Sample(Sampler, float2(0.5, 0.5)); + + // Write to storage texture + StorageTex[uint2(0, 0)] = color; + + // Use push constant data + color.xy += g_PushConstants.Offset; + color.rgb *= sin(g_PushConstants.Time) * 0.5 + 0.5; + + return color; +} diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/PushConstants.psh b/Tests/DiligentCoreTest/assets/shaders/SPIRV/PushConstants.psh new file mode 100644 index 0000000000..9020ed7444 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/PushConstants.psh @@ -0,0 +1,26 @@ +// Test for push constants +struct PushConstants_t +{ + float4x4 g_MVPMatrix; + float4 g_Color; + float2 g_Offset; + float g_Scale; + uint g_Padding; +}; + +[[vk::push_constant]] +cbuffer PushConstants +{ + PushConstants_t g_PushConstants; +}; + +float4 main() : SV_Target +{ + // Use push constant data through the structure instance + float4 result = g_PushConstants.g_Color * g_PushConstants.g_Scale; + + // Apply offset to result (simplified example) + result.xy += g_PushConstants.g_Offset; + + return result; +} diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/StorageBuffers.psh b/Tests/DiligentCoreTest/assets/shaders/SPIRV/StorageBuffers.psh new file mode 100644 index 0000000000..c47aebf8e2 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/StorageBuffers.psh @@ -0,0 +1,43 @@ +// Test for storage buffers (structured buffers and byte address buffers) +struct BufferData0 +{ + float4 data0; + float4 data1; +}; + +struct BufferData1 +{ + float4 data0; + float4 data1; + float4 data2; + float4 data3; +}; + +// Read-only structured buffer +StructuredBuffer g_ROBuffer : register(t0); + +// Read-write structured buffer +RWStructuredBuffer g_RWBuffer : register(u0); + +// Atomic counter buffers +ByteAddressBuffer g_ROAtomicBuffer : register(t1); +RWByteAddressBuffer g_RWAtomicBuffer : register(u1); + +float4 main() : SV_Target +{ + float4 result = float4(0.0, 0.0, 0.0, 0.0); + + // Read from RO buffer + result += g_ROBuffer[0].data0; + result += g_ROBuffer[1].data1; + + // Write to RW buffer + g_RWBuffer[0].data0 = result; + g_RWBuffer[1].data1 = result * 2.0; + + // Atomic operations + uint atomicValue = g_ROAtomicBuffer.Load(0); + g_RWAtomicBuffer.Store(0, atomicValue + 1); + + return result; +} diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/StorageImages.psh b/Tests/DiligentCoreTest/assets/shaders/SPIRV/StorageImages.psh new file mode 100644 index 0000000000..3a5537e0a9 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/StorageImages.psh @@ -0,0 +1,20 @@ +// Test for storage images (read-write textures) +// Note: HLSL does not support RWTextureCube, so we only test 2D, 2DArray, and 3D storage images +RWTexture2D g_RWImage2D : register(u0); +RWTexture2DArray g_RWImage2DArray : register(u1); +RWTexture3D g_RWImage3D : register(u2); + +float4 main() : SV_Target +{ + // Write to storage images + g_RWImage2D[uint2(0, 0)] = float4(1.0, 0.0, 0.0, 1.0); + g_RWImage2DArray[uint3(0, 0, 0)] = float4(0.0, 1.0, 0.0, 1.0); + g_RWImage3D[uint3(0, 0, 0)] = float4(0.0, 0.0, 1.0, 1.0); + + // Read back and combine + float4 color = g_RWImage2D[uint2(0, 0)]; + color += g_RWImage2DArray[uint3(0, 0, 0)]; + color += g_RWImage3D[uint3(0, 0, 0)]; + + return color; +} diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/TexelBuffers.psh b/Tests/DiligentCoreTest/assets/shaders/SPIRV/TexelBuffers.psh new file mode 100644 index 0000000000..737cfdc044 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/TexelBuffers.psh @@ -0,0 +1,15 @@ +// Test for texel buffers (buffer textures) +Buffer g_UniformTexelBuffer : register(t0); // Uniform texel buffer (read-only) +RWBuffer g_StorageTexelBuffer : register(u0); // Storage texel buffer (read-write) + +float4 main() : SV_Target +{ + // Read from uniform texel buffer + float4 value = g_UniformTexelBuffer[0]; + + // Write to storage texel buffer + g_StorageTexelBuffer[0] = value * 2.0; + + // Read back from storage texel buffer + return g_StorageTexelBuffer[0]; +} diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/Textures.psh b/Tests/DiligentCoreTest/assets/shaders/SPIRV/Textures.psh new file mode 100644 index 0000000000..525244ce25 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/Textures.psh @@ -0,0 +1,30 @@ +// Test for textures and samplers +Texture2D g_SampledImage : register(t0); // 2D texture (sampled image) +Texture2DMS g_SampledImageMS : register(t1); // 2D multisample texture +Texture3D g_SampledImage3D : register(t2); // 3D texture +TextureCube g_SampledImageCube : register(t3); // Cubemap texture + +SamplerState g_Sampler : register(s0); // Separate sampler +Texture2D g_SeparateImage : register(t4); // Separate image (used with separate sampler) + +// Combined sampler test - when texture and sampler follow the naming convention, +// they should be recognized as SampledImage instead of SeparateImage +Texture2D g_Texture : register(t5); +SamplerState g_Texture_sampler : register(s1); + +float4 main() : SV_Target +{ + // Sample textures + float4 color1 = g_SampledImage.Sample(g_Sampler, float2(0.5, 0.5)); + float4 color2 = g_SampledImageMS.Load(int2(0, 0), 0); // Texture2DMS uses Load instead of Sample + float4 color3 = g_SampledImage3D.Sample(g_Sampler, float3(0.5, 0.5, 0.5)); + float4 color4 = g_SampledImageCube.Sample(g_Sampler, float3(0.0, 1.0, 0.0)); + + // Sample separate image with separate sampler + float4 color5 = g_SeparateImage.Sample(g_Sampler, float2(0.25, 0.25)); + + // Sample combined sampler texture + float4 color6 = g_Texture.Sample(g_Texture_sampler, float2(0.5, 0.5)); + + return color1 + color2 + color3 + color4 + color5 + color6; +} diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/UniformBuffers.psh b/Tests/DiligentCoreTest/assets/shaders/SPIRV/UniformBuffers.psh new file mode 100644 index 0000000000..ae8e697849 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/UniformBuffers.psh @@ -0,0 +1,24 @@ +// Test for uniform buffers (constant buffers) +cbuffer CB0 : register(b0) +{ + float4x4 g_WorldViewProj; +}; + +cbuffer CB1 : register(b1) +{ + float4 g_Color; + float3 g_LightDir; + float g_LightIntensity; + float2 g_TexScale; + uint2 g_Padding; +}; + +cbuffer CB2 : register(b2) +{ + float4 g_MaterialParams; +}; + +float4 main() : SV_Target +{ + return g_Color * g_MaterialParams; +} diff --git a/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp b/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp new file mode 100644 index 0000000000..827b76e289 --- /dev/null +++ b/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp @@ -0,0 +1,320 @@ +/* + * Copyright 2025 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#include "SPIRVShaderResources.hpp" +#include "GLSLangUtils.hpp" +#include "DefaultShaderSourceStreamFactory.h" +#include "RefCntAutoPtr.hpp" +#include "EngineMemory.h" +#include "BasicFileSystem.hpp" + +#include +#include +#include + +#include "TestingEnvironment.hpp" +#include "gtest/gtest.h" + +using namespace Diligent; +using namespace Diligent::Testing; + +namespace +{ + +class SPIRVShaderResourcesTest : public ::testing::Test +{ +protected: + static void SetUpTestSuite() + { + GLSLangUtils::InitializeGlslang(); + } + + static void TearDownTestSuite() + { + GLSLangUtils::FinalizeGlslang(); + } +}; + + +struct SPIRVShaderResourceRefAttribs +{ + const char* const Name; + const Uint16 ArraySize; + const SPIRVShaderResourceAttribs::ResourceType Type; + const RESOURCE_DIMENSION ResourceDim; + const Uint8 IsMS; + const Uint32 BufferStaticSize; + const Uint32 BufferStride; +}; + +std::vector LoadSPIRVFromHLSL(const char* FilePath, SHADER_TYPE ShaderType = SHADER_TYPE_PIXEL) +{ + ShaderCreateInfo ShaderCI; + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL; + ShaderCI.FilePath = FilePath; + ShaderCI.Desc = {"SPIRV test shader", ShaderType}; + ShaderCI.EntryPoint = "main"; + + RefCntAutoPtr pShaderSourceStreamFactory; + CreateDefaultShaderSourceStreamFactory("shaders/SPIRV", &pShaderSourceStreamFactory); + if (!pShaderSourceStreamFactory) + return {}; + + ShaderCI.pShaderSourceStreamFactory = pShaderSourceStreamFactory; + + return GLSLangUtils::HLSLtoSPIRV(ShaderCI, GLSLangUtils::SpirvVersion::Vk100, nullptr, nullptr); +} + +std::vector LoadSPIRVFromGLSL(const char* FilePath, SHADER_TYPE ShaderType = SHADER_TYPE_PIXEL) +{ + RefCntAutoPtr pShaderSourceStreamFactory; + CreateDefaultShaderSourceStreamFactory("shaders/SPIRV", &pShaderSourceStreamFactory); + if (!pShaderSourceStreamFactory) + return {}; + + RefCntAutoPtr pShaderSourceStream; + pShaderSourceStreamFactory->CreateInputStream(FilePath, &pShaderSourceStream); + if (!pShaderSourceStream) + return {}; + + size_t ShaderSourceSize = pShaderSourceStream->GetSize(); + if (ShaderSourceSize == 0) + return {}; + + std::vector ShaderSource(ShaderSourceSize); + pShaderSourceStream->Read(ShaderSource.data(), ShaderSourceSize); + + // Ray tracing shaders require Vulkan 1.1 or higher SPIR-V version + GLSLangUtils::SpirvVersion Version = GLSLangUtils::SpirvVersion::Vk100; + if (ShaderType == SHADER_TYPE_RAY_GEN || + ShaderType == SHADER_TYPE_RAY_MISS || + ShaderType == SHADER_TYPE_RAY_CLOSEST_HIT || + ShaderType == SHADER_TYPE_RAY_ANY_HIT || + ShaderType == SHADER_TYPE_RAY_INTERSECTION || + ShaderType == SHADER_TYPE_CALLABLE) + { + Version = GLSLangUtils::SpirvVersion::Vk110_Spirv14; + } + + GLSLangUtils::GLSLtoSPIRVAttribs Attribs; + Attribs.ShaderType = ShaderType; + Attribs.ShaderSource = ShaderSource.data(); + Attribs.SourceCodeLen = static_cast(ShaderSourceSize); + Attribs.pShaderSourceStreamFactory = pShaderSourceStreamFactory; + Attribs.Version = Version; + Attribs.AssignBindings = true; + + return GLSLangUtils::GLSLtoSPIRV(Attribs); +} + +void TestSPIRVResources(const char* FilePath, + const std::vector& RefResources, + SHADER_TYPE ShaderType = SHADER_TYPE_PIXEL, + const char* CombinedSamplerSuffix = nullptr, + bool IsGLSL = false) +{ + const auto SPIRV = IsGLSL ? LoadSPIRVFromGLSL(FilePath, ShaderType) : LoadSPIRVFromHLSL(FilePath, ShaderType); + ASSERT_FALSE(SPIRV.empty()) << "Failed to compile HLSL to SPIRV: " << FilePath; + + ShaderDesc ShaderDesc; + ShaderDesc.Name = "SPIRVResources test"; + ShaderDesc.ShaderType = ShaderType; + + std::string EntryPoint; + SPIRVShaderResources Resources{ + GetRawAllocator(), + SPIRV, + ShaderDesc, + CombinedSamplerSuffix, + false, // LoadShaderStageInputs + false, // LoadUniformBufferReflection + EntryPoint}; + + LOG_INFO_MESSAGE("SPIRV Resources:\n", Resources.DumpResources()); + + EXPECT_EQ(size_t{Resources.GetTotalResources()}, RefResources.size()); + + std::unordered_map RefResourcesMap; + for (const SPIRVShaderResourceRefAttribs& RefRes : RefResources) + { + RefResourcesMap[RefRes.Name] = &RefRes; + } + + for (Uint32 i = 0; i < Resources.GetTotalResources(); ++i) + { + const auto& Res = const_cast(Resources).GetResource(i); + const auto* pRefRes = RefResourcesMap[Res.Name]; + ASSERT_NE(pRefRes, nullptr) << "Resource '" << Res.Name << "' is not found in the reference list"; + + EXPECT_STREQ(SPIRVShaderResourceAttribs::ResourceTypeToString(Res.Type), SPIRVShaderResourceAttribs::ResourceTypeToString(pRefRes->Type)) << Res.Name; + EXPECT_EQ(Res.ArraySize, pRefRes->ArraySize) << Res.Name; + EXPECT_EQ(Res.ResourceDim, pRefRes->ResourceDim) << Res.Name; + EXPECT_EQ(Res.IsMS, pRefRes->IsMS) << Res.Name; + EXPECT_EQ(Res.BufferStaticSize, pRefRes->BufferStaticSize) << Res.Name; + EXPECT_EQ(Res.BufferStride, pRefRes->BufferStride) << Res.Name; + + if (Res.Type == SPIRVShaderResourceAttribs::ResourceType::UniformBuffer || + Res.Type == SPIRVShaderResourceAttribs::ResourceType::PushConstant) + { + EXPECT_EQ(Res.GetInlineConstantCountOrThrow(FilePath), pRefRes->BufferStaticSize / 4) << Res.Name; + } + } +} + +using SPIRVResourceType = SPIRVShaderResourceAttribs::ResourceType; + +TEST_F(SPIRVShaderResourcesTest, UniformBuffers) +{ + TestSPIRVResources("UniformBuffers.psh", + { + // CB0 is optimized away as it's not used in the shader + SPIRVShaderResourceRefAttribs{"CB1", 1, SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0, 48, 0}, + SPIRVShaderResourceRefAttribs{"CB2", 1, SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0, 16, 0}, + }); +} + +TEST_F(SPIRVShaderResourcesTest, StorageBuffers) +{ + TestSPIRVResources("StorageBuffers.psh", + { + // StructuredBuffers have BufferStaticSize=0 (runtime array) and BufferStride is the element size + SPIRVShaderResourceRefAttribs{"g_ROBuffer", 1, SPIRVResourceType::ROStorageBuffer, RESOURCE_DIM_BUFFER, 0, 0, 32}, + SPIRVShaderResourceRefAttribs{"g_RWBuffer", 1, SPIRVResourceType::RWStorageBuffer, RESOURCE_DIM_BUFFER, 0, 0, 64}, + // ByteAddressBuffers also have BufferStaticSize=0 and BufferStride=4 (uint size) + SPIRVShaderResourceRefAttribs{"g_ROAtomicBuffer", 1, SPIRVResourceType::ROStorageBuffer, RESOURCE_DIM_BUFFER, 0, 0, 4}, + SPIRVShaderResourceRefAttribs{"g_RWAtomicBuffer", 1, SPIRVResourceType::RWStorageBuffer, RESOURCE_DIM_BUFFER, 0, 0, 4}, + }); +} + +TEST_F(SPIRVShaderResourcesTest, TexelBuffers) +{ + TestSPIRVResources("TexelBuffers.psh", + { + SPIRVShaderResourceRefAttribs{"g_UniformTexelBuffer", 1, SPIRVResourceType::UniformTexelBuffer, RESOURCE_DIM_BUFFER, 0, 0, 0}, + SPIRVShaderResourceRefAttribs{"g_StorageTexelBuffer", 1, SPIRVResourceType::StorageTexelBuffer, RESOURCE_DIM_BUFFER, 0, 0, 0}, + }); +} + +TEST_F(SPIRVShaderResourcesTest, Textures) +{ + TestSPIRVResources("Textures.psh", + { + // When textures and samplers are declared separately in HLSL, they are compiled as separate_images + // instead of sampled_images. This is the correct behavior for separate sampler/texture declarations. + SPIRVShaderResourceRefAttribs{"g_SampledImage", 1, SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_2D, 0, 0, 0}, + SPIRVShaderResourceRefAttribs{"g_SampledImageMS", 1, SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_2D, 1, 0, 0}, + SPIRVShaderResourceRefAttribs{"g_SampledImage3D", 1, SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_3D, 0, 0, 0}, + SPIRVShaderResourceRefAttribs{"g_SampledImageCube", 1, SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_CUBE, 0, 0, 0}, + SPIRVShaderResourceRefAttribs{"g_Sampler", 1, SPIRVResourceType::SeparateSampler, RESOURCE_DIM_UNDEFINED, 0, 0, 0}, + SPIRVShaderResourceRefAttribs{"g_SeparateImage", 1, SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_2D, 0, 0, 0}, + // Combined sampler: g_Texture and g_Texture_sampler + // Note: Even with CombinedSamplerSuffix, SPIRV may still classify them as separate_images + // if they are declared separately. The CombinedSamplerSuffix is mainly used for naming convention. + SPIRVShaderResourceRefAttribs{"g_Texture", 1, SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_2D, 0, 0, 0}, + SPIRVShaderResourceRefAttribs{"g_Texture_sampler", 1, SPIRVResourceType::SeparateSampler, RESOURCE_DIM_UNDEFINED, 0, 0, 0}, + }); +} + +TEST_F(SPIRVShaderResourcesTest, StorageImages) +{ + TestSPIRVResources("StorageImages.psh", + { + // Note: HLSL does not support RWTextureCube, so we only test 2D, 2DArray, and 3D storage images + SPIRVShaderResourceRefAttribs{"g_RWImage2D", 1, SPIRVResourceType::StorageImage, RESOURCE_DIM_TEX_2D, 0, 0, 0}, + SPIRVShaderResourceRefAttribs{"g_RWImage2DArray", 1, SPIRVResourceType::StorageImage, RESOURCE_DIM_TEX_2D_ARRAY, 0, 0, 0}, + SPIRVShaderResourceRefAttribs{"g_RWImage3D", 1, SPIRVResourceType::StorageImage, RESOURCE_DIM_TEX_3D, 0, 0, 0}, + }); +} + +TEST_F(SPIRVShaderResourcesTest, AtomicCounters) +{ + // Use GLSL for atomic counters. Note: Vulkan does not support atomic_uint (AtomicCounter storage class). + // We use a storage buffer with atomic operations to simulate atomic counters. + // This will be reflected as RWStorageBuffer, not AtomicCounter. + // The resource name is the buffer block name (AtomicCounterBuffer), not the instance name (g_AtomicCounter). + TestSPIRVResources("AtomicCounters.glsl", + { + SPIRVShaderResourceRefAttribs{"AtomicCounterBuffer", 1, SPIRVResourceType::RWStorageBuffer, RESOURCE_DIM_BUFFER, 0, 4, 0}, + }, + SHADER_TYPE_PIXEL, + nullptr, + true); // IsGLSL = true +} + +TEST_F(SPIRVShaderResourcesTest, InputAttachments) +{ + TestSPIRVResources("InputAttachments.psh", + { + SPIRVShaderResourceRefAttribs{"g_InputAttachment", 1, SPIRVResourceType::InputAttachment, RESOURCE_DIM_UNDEFINED, 0, 0, 0}, + }); +} + +TEST_F(SPIRVShaderResourcesTest, AccelerationStructures) +{ + // Use GLSL for acceleration structures since HLSLtoSPIRV doesn't support raytracing shaders + // Acceleration structures are used in raytracing shaders, so we use SHADER_TYPE_RAY_GEN + // The ray gen shader uses traceRayEXT with g_AccelStruct to ensure it's not optimized away + TestSPIRVResources("AccelerationStructures.glsl", + { + SPIRVShaderResourceRefAttribs{"g_AccelStruct", 1, SPIRVResourceType::AccelerationStructure, RESOURCE_DIM_UNDEFINED, 0, 0, 0}, + }, + SHADER_TYPE_RAY_GEN, + nullptr, + true); // IsGLSL = true +} + +TEST_F(SPIRVShaderResourcesTest, PushConstants) +{ + // Push constant ArraySize represents the number of 32-bit words, not array elements + // PushConstants struct: float4x4 (16 floats) + float4 (4 floats) + float2 (2 floats) + float (1 float) + uint (1 uint) + // Total: 16 + 4 + 2 + 1 + 1 = 24 floats/uints = 24 * 4 bytes = 96 bytes = 24 words + TestSPIRVResources("PushConstants.psh", + { + SPIRVShaderResourceRefAttribs{"PushConstants", 1, SPIRVResourceType::PushConstant, RESOURCE_DIM_BUFFER, 0, 96, 0}, + }); +} + +TEST_F(SPIRVShaderResourcesTest, MixedResources) +{ + TestSPIRVResources("MixedResources.psh", + { + // UniformBuff: float4x4 (64 bytes) + float4 (16 bytes) = 80 bytes + SPIRVShaderResourceRefAttribs{"UniformBuff", 1, SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0, 80, 0}, + // ROStorageBuff: StructuredBuffer where BufferData = float4[4] = 64 bytes + // StructuredBuffers have BufferStaticSize=0 (runtime array) and BufferStride is the element size + SPIRVShaderResourceRefAttribs{"ROStorageBuff", 1, SPIRVResourceType::ROStorageBuffer, RESOURCE_DIM_BUFFER, 0, 0, 64}, + // RWStorageBuff: same as ROStorageBuff + SPIRVShaderResourceRefAttribs{"RWStorageBuff", 1, SPIRVResourceType::RWStorageBuffer, RESOURCE_DIM_BUFFER, 0, 0, 64}, + // SampledTex: When Texture2D and SamplerState are declared separately, they are compiled as SeparateImage + SPIRVShaderResourceRefAttribs{"SampledTex", 1, SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_2D, 0, 0, 0}, + SPIRVShaderResourceRefAttribs{"StorageTex", 1, SPIRVResourceType::StorageImage, RESOURCE_DIM_TEX_2D, 0, 0, 0}, + SPIRVShaderResourceRefAttribs{"Sampler", 1, SPIRVResourceType::SeparateSampler, RESOURCE_DIM_UNDEFINED, 0, 0, 0}, + // PushConstants: float2 (2 floats) + float (1 float) + uint (1 uint) = 4 words = 16 bytes + SPIRVShaderResourceRefAttribs{"PushConstants", 1, SPIRVResourceType::PushConstant, RESOURCE_DIM_BUFFER, 0, 16, 0}, + }); +} + +} // namespace