diff --git a/Graphics/Archiver/src/Archiver_Vk.cpp b/Graphics/Archiver/src/Archiver_Vk.cpp index 0f53a74b9b..312ed32d79 100644 --- a/Graphics/Archiver/src/Archiver_Vk.cpp +++ b/Graphics/Archiver/src/Archiver_Vk.cpp @@ -128,9 +128,10 @@ void SerializedPipelineStateImpl::PatchShadersVk(const CreateInfoType& CreateInf { ShaderStageInfoVk& Src{ShaderStages[i]}; PipelineStateVkImpl::ShaderStageInfo& Dst{ShaderStagesVk[i]}; - Dst.Type = Src.Type; - Dst.Shaders = std::move(Src.Shaders); - Dst.SPIRVs = std::move(Src.SPIRVs); + Dst.Type = Src.Type; + Dst.Shaders = std::move(Src.Shaders); + Dst.SPIRVs = std::move(Src.SPIRVs); + Dst.ShaderResources = std::move(Src.ShaderResources); } IPipelineResourceSignature** ppSignatures = CreateInfo.ppResourceSignatures; diff --git a/Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp b/Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp index e1321b2853..9988f3b9f8 100644 --- a/Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp +++ b/Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp @@ -1637,13 +1637,17 @@ String GetPipelineResourceFlagsString(PIPELINE_RESOURCE_FLAGS Flags, bool GetFul PIPELINE_RESOURCE_FLAGS Flag = ExtractLSB(Flags); - static_assert(PIPELINE_RESOURCE_FLAG_LAST == (1u << 4), "Please update the switch below to handle the new pipeline resource flag."); + static_assert(PIPELINE_RESOURCE_FLAG_LAST == (1u << 5), "Please update the switch below to handle the new pipeline resource flag."); switch (Flag) { case PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS: Str.append(GetFullName ? "PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS" : "NO_DYNAMIC_BUFFERS"); break; + case PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS: + Str.append(GetFullName ? "PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS" : "INLINE_CONSTANTS"); + break; + case PIPELINE_RESOURCE_FLAG_COMBINED_SAMPLER: Str.append(GetFullName ? "PIPELINE_RESOURCE_FLAG_COMBINED_SAMPLER" : "COMBINED_SAMPLER"); break; @@ -1808,7 +1812,7 @@ PIPELINE_RESOURCE_FLAGS GetValidPipelineResourceFlags(SHADER_RESOURCE_TYPE Resou switch (ResourceType) { case SHADER_RESOURCE_TYPE_CONSTANT_BUFFER: - return PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS | PIPELINE_RESOURCE_FLAG_RUNTIME_ARRAY; + return PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS | PIPELINE_RESOURCE_FLAG_RUNTIME_ARRAY | PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS; case SHADER_RESOURCE_TYPE_TEXTURE_SRV: return PIPELINE_RESOURCE_FLAG_COMBINED_SAMPLER | PIPELINE_RESOURCE_FLAG_RUNTIME_ARRAY; @@ -1839,7 +1843,7 @@ PIPELINE_RESOURCE_FLAGS GetValidPipelineResourceFlags(SHADER_RESOURCE_TYPE Resou PIPELINE_RESOURCE_FLAGS ShaderVariableFlagsToPipelineResourceFlags(SHADER_VARIABLE_FLAGS Flags) { - static_assert(SHADER_VARIABLE_FLAG_LAST == (1 << 3), "Please update the switch below to handle the new shader variable flags"); + static_assert(SHADER_VARIABLE_FLAG_LAST == (1 << 4), "Please update the switch below to handle the new shader variable flags"); switch (Flags) { case SHADER_VARIABLE_FLAG_NONE: @@ -1848,6 +1852,9 @@ PIPELINE_RESOURCE_FLAGS ShaderVariableFlagsToPipelineResourceFlags(SHADER_VARIAB case SHADER_VARIABLE_FLAG_NO_DYNAMIC_BUFFERS: return PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS; + case SHADER_VARIABLE_FLAG_INLINE_CONSTANTS: + return PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS; + case SHADER_VARIABLE_FLAG_GENERAL_INPUT_ATTACHMENT_VK: return PIPELINE_RESOURCE_FLAG_GENERAL_INPUT_ATTACHMENT; diff --git a/Graphics/GraphicsEngine/include/DeviceContextBase.hpp b/Graphics/GraphicsEngine/include/DeviceContextBase.hpp index 972e92d641..e4ee3d13b3 100644 --- a/Graphics/GraphicsEngine/include/DeviceContextBase.hpp +++ b/Graphics/GraphicsEngine/include/DeviceContextBase.hpp @@ -381,6 +381,9 @@ class DeviceContextBase : public ObjectBaseHasInlineConstants()) + InlineConstantsSRBMask |= SRBBit; + else + InlineConstantsSRBMask &= ~SRBBit; + #ifdef DILIGENT_DEVELOPMENT SRBs[Index] = pSRB; if (pSRB != nullptr) @@ -412,7 +420,8 @@ class DeviceContextBase : public ObjectBase ResDesc.ArraySize) + { + RESOURCE_VALIDATION_FAILURE("Inline constant range (", FirstConstant, " .. ", FirstConstant + NumConstants - 1, + ") is out of bounds for variable '", ResDesc.Name, "' of size ", ResDesc.ArraySize, " constants."); + ParamsOK = false; + } + + return ParamsOK; +} + #undef RESOURCE_VALIDATION_FAILURE template @@ -713,6 +761,27 @@ struct ShaderVariableBase : public ResourceVariableBaseInterface } + virtual void DILIGENT_CALL_TYPE SetInlineConstants(const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants) override final + { +#ifdef DILIGENT_DEVELOPMENT + { + const PipelineResourceDesc& Desc = GetDesc(); + DEV_CHECK_ERR(Desc.ResourceType == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, + "SetInlineConstants() is only allowed for constant buffer resources."); + DEV_CHECK_ERR(Desc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS, + "SetInlineConstants() is only allowed for inline constant buffers."); + DEV_CHECK_ERR(pConstants != nullptr, "Pointer to inline constants is null."); + DEV_CHECK_ERR(FirstConstant + NumConstants <= Desc.ArraySize, + "Inline constant range (", FirstConstant, " .. ", FirstConstant + NumConstants - 1, + ") is out of bounds for variable '", Desc.Name, "' of size ", Desc.ArraySize, " constants."); + } +#endif + + static_cast(this)->SetConstants(pConstants, FirstConstant, NumConstants); + } + virtual SHADER_RESOURCE_VARIABLE_TYPE DILIGENT_CALL_TYPE GetType() const override final { return GetDesc().VarType; diff --git a/Graphics/GraphicsEngine/interface/Constants.h b/Graphics/GraphicsEngine/interface/Constants.h index ce3bc5564d..a54a336cd0 100644 --- a/Graphics/GraphicsEngine/interface/Constants.h +++ b/Graphics/GraphicsEngine/interface/Constants.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,6 +62,9 @@ DILIGENT_BEGIN_NAMESPACE(Diligent) /// Bit shift for the the shading X-axis rate. #define DILIGENT_SHADING_RATE_X_SHIFT 2 +/// The maximum number of 4-byte inline constants in a pipeline state. +#define DILIGENT_MAX_INLINE_CONSTANTS 64 + static DILIGENT_CONSTEXPR Uint32 MAX_BUFFER_SLOTS = DILIGENT_MAX_BUFFER_SLOTS; static DILIGENT_CONSTEXPR Uint32 MAX_RENDER_TARGETS = DILIGENT_MAX_RENDER_TARGETS; static DILIGENT_CONSTEXPR Uint32 MAX_VIEWPORTS = DILIGENT_MAX_VIEWPORTS; @@ -71,5 +74,6 @@ static DILIGENT_CONSTEXPR Uint32 DEFAULT_ADAPTER_ID = DILIGENT_DEFAULT_ADAP static DILIGENT_CONSTEXPR Uint8 DEFAULT_QUEUE_ID = DILIGENT_DEFAULT_QUEUE_ID; static DILIGENT_CONSTEXPR Uint32 MAX_SHADING_RATES = DILIGENT_MAX_SHADING_RATES; static DILIGENT_CONSTEXPR Uint32 SHADING_RATE_X_SHIFT = DILIGENT_SHADING_RATE_X_SHIFT; +static DILIGENT_CONSTEXPR Uint32 MAX_INLINE_CONSTANTS = DILIGENT_MAX_INLINE_CONSTANTS; DILIGENT_END_NAMESPACE // namespace Diligent diff --git a/Graphics/GraphicsEngine/interface/DeviceContext.h b/Graphics/GraphicsEngine/interface/DeviceContext.h index d7ee0c448b..263d1da145 100644 --- a/Graphics/GraphicsEngine/interface/DeviceContext.h +++ b/Graphics/GraphicsEngine/interface/DeviceContext.h @@ -213,7 +213,27 @@ DILIGENT_TYPED_ENUM(DRAW_FLAGS, Uint8) /// (see RootSignature::CommitRootViews). When `DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT` is set, root views are only bound /// by the first draw command that uses the PSO + SRB pair. The flag avoids setting the same GPU virtual addresses when /// they stay unchanged. - DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT = 1u << 2u + /// + /// \see DRAW_FLAG_INLINE_CONSTANTS_INTACT, DRAW_FLAG_DYNAMIC_BUFFERS_AND_INLINE_CONSTANTS_INTACT + DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT = 1u << 2u, + + /// Indicates that none of the inline constants used by the draw command + /// have been modified by the CPU since the last command. + /// + /// This flag should be used to improve performance when an application issues a + /// series of draw commands that use the same pipeline state and shader resources and + /// no inline constants are updated between the commands. + /// + /// \see DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT, DRAW_FLAG_DYNAMIC_BUFFERS_AND_INLINE_CONSTANTS_INTACT + DRAW_FLAG_INLINE_CONSTANTS_INTACT = 1u << 3u, + + /// Indicates that neither the dynamic resource buffers nor the inline constants used by + /// the draw command have been modified by the CPU since the previous command. + /// + /// \see DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT, DRAW_FLAG_INLINE_CONSTANTS_INTACT + DRAW_FLAG_DYNAMIC_BUFFERS_AND_INLINE_CONSTANTS_INTACT = + DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT | + DRAW_FLAG_INLINE_CONSTANTS_INTACT }; DEFINE_FLAG_ENUM_OPERATORS(DRAW_FLAGS) diff --git a/Graphics/GraphicsEngine/interface/PipelineResourceSignature.h b/Graphics/GraphicsEngine/interface/PipelineResourceSignature.h index 83ee6293a4..1e2fb02465 100644 --- a/Graphics/GraphicsEngine/interface/PipelineResourceSignature.h +++ b/Graphics/GraphicsEngine/interface/PipelineResourceSignature.h @@ -105,9 +105,28 @@ DILIGENT_TYPED_ENUM(PIPELINE_RESOURCE_FLAGS, Uint8) /// PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS flag. PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS = 1u << 0, + /// Indicates that the resource consists of inline constants (also + /// known as push constants in Vulkan or root constants in Direct3D12). + /// + /// Applies to SHADER_RESOURCE_TYPE_CONSTANT_BUFFER only. + /// + /// Use this flag if you have a buffer of frequently changing constants + /// - that are small in size (typically up to 128 bytes) and + /// - change often (e.g. per-draw or per-dispatch). + /// + /// Inline constants are set directly using IShaderResourceVariable::SetInlineConstants. + /// + /// This flag cannot be combined with any other flags. + /// + /// In Vulkan and Direct3D12, inline constants are not bound via descriptor sets or root + /// signatures, but are set directly in command buffers or command lists and are very cheap. + /// In legacy APIs (Direct3D11 and OpenGL), inline constants are emulated using regular + /// constant buffers and thus have higher overhead. + PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS = 1u << 1, + /// Indicates that a texture SRV will be combined with a sampler. /// Applies to SHADER_RESOURCE_TYPE_TEXTURE_SRV resources. - PIPELINE_RESOURCE_FLAG_COMBINED_SAMPLER = 1u << 1, + PIPELINE_RESOURCE_FLAG_COMBINED_SAMPLER = 1u << 2, /// Indicates that this variable will be used to bind formatted buffers. /// Applies to SHADER_RESOURCE_TYPE_BUFFER_UAV and SHADER_RESOURCE_TYPE_BUFFER_SRV @@ -117,19 +136,19 @@ DILIGENT_TYPED_ENUM(PIPELINE_RESOURCE_FLAGS, Uint8) /// as opposed to structured buffers. If an application will be using /// formatted buffers with buffer UAVs and SRVs, it must specify the /// PIPELINE_RESOURCE_FLAG_FORMATTED_BUFFER flag. - PIPELINE_RESOURCE_FLAG_FORMATTED_BUFFER = 1u << 2, + PIPELINE_RESOURCE_FLAG_FORMATTED_BUFFER = 1u << 3, /// Indicates that resource is a run-time sized shader array (e.g. an array without a specific size). - PIPELINE_RESOURCE_FLAG_RUNTIME_ARRAY = 1u << 3, + PIPELINE_RESOURCE_FLAG_RUNTIME_ARRAY = 1u << 4, /// Indicates that the resource is an input attachment in general layout, which allows simultaneously /// reading from the resource through the input attachment and writing to it via color or depth-stencil /// attachment. /// /// \note This flag is only valid in Vulkan. - PIPELINE_RESOURCE_FLAG_GENERAL_INPUT_ATTACHMENT = 1u << 4, + PIPELINE_RESOURCE_FLAG_GENERAL_INPUT_ATTACHMENT = 1u << 5, - PIPELINE_RESOURCE_FLAG_LAST = PIPELINE_RESOURCE_FLAG_GENERAL_INPUT_ATTACHMENT + PIPELINE_RESOURCE_FLAG_LAST = PIPELINE_RESOURCE_FLAG_GENERAL_INPUT_ATTACHMENT }; DEFINE_FLAG_ENUM_OPERATORS(PIPELINE_RESOURCE_FLAGS); @@ -269,6 +288,9 @@ struct PipelineResourceDesc SHADER_TYPE ShaderStages DEFAULT_INITIALIZER(SHADER_TYPE_UNKNOWN); /// Resource array size (must be 1 for non-array resources). + /// + /// For inline constants (see PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS), + /// this member specifies the number of 4-byte values. Uint32 ArraySize DEFAULT_INITIALIZER(1); /// Resource type, see Diligent::SHADER_RESOURCE_TYPE. @@ -334,6 +356,13 @@ struct PipelineResourceDesc { return !(*this == Rhs); } + + Uint32 GetArraySize() const noexcept + { + return (Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0 ? + 1 : // For inline constants, ArraySize is the number of 4-byte constants. + ArraySize; + } #endif }; typedef struct PipelineResourceDesc PipelineResourceDesc; diff --git a/Graphics/GraphicsEngine/interface/PipelineState.h b/Graphics/GraphicsEngine/interface/PipelineState.h index b7419a98dc..0246eb8897 100644 --- a/Graphics/GraphicsEngine/interface/PipelineState.h +++ b/Graphics/GraphicsEngine/interface/PipelineState.h @@ -103,22 +103,32 @@ DILIGENT_TYPED_ENUM(SHADER_VARIABLE_FLAGS, Uint8) /// flag in the internal pipeline resource signature. SHADER_VARIABLE_FLAG_NO_DYNAMIC_BUFFERS = 1u << 0, + /// Indicates that the resource consists of inline constants + /// (also known as push constants in Vulkan or root constants in Direct3D12). + /// Applies to Diligent::SHADER_RESOURCE_TYPE_CONSTANT_BUFFER only. + /// + /// \remarks This flag directly translates to the Diligent::PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS + /// flag in the internal pipeline resource signature. + /// + /// See Diligent::PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS for more details. + SHADER_VARIABLE_FLAG_INLINE_CONSTANTS = 1u << 1, + /// Indicates that the resource is an input attachment in general layout, which allows simultaneously /// reading from the resource through the input attachment and writing to it via color or depth-stencil /// attachment. /// /// \note This flag is only valid in Vulkan. - SHADER_VARIABLE_FLAG_GENERAL_INPUT_ATTACHMENT_VK = 1u << 1, + SHADER_VARIABLE_FLAG_GENERAL_INPUT_ATTACHMENT_VK = 1u << 2, /// Indicates that the resource is an unfilterable-float texture. /// /// \note This flag is only valid in WebGPU and ignored in other backends. - SHADER_VARIABLE_FLAG_UNFILTERABLE_FLOAT_TEXTURE_WEBGPU = 1u << 2, + SHADER_VARIABLE_FLAG_UNFILTERABLE_FLOAT_TEXTURE_WEBGPU = 1u << 3, /// Indicates that the resource is a non-filtering sampler. /// /// \note This flag is only valid in WebGPU and ignored in other backends. - SHADER_VARIABLE_FLAG_NON_FILTERING_SAMPLER_WEBGPU = 1u << 3, + SHADER_VARIABLE_FLAG_NON_FILTERING_SAMPLER_WEBGPU = 1u << 4, /// Special value that indicates the last flag in the enumeration. SHADER_VARIABLE_FLAG_LAST = SHADER_VARIABLE_FLAG_NON_FILTERING_SAMPLER_WEBGPU diff --git a/Graphics/GraphicsEngine/interface/ShaderResourceVariable.h b/Graphics/GraphicsEngine/interface/ShaderResourceVariable.h index 50e6d94b35..eeef1683b6 100644 --- a/Graphics/GraphicsEngine/interface/ShaderResourceVariable.h +++ b/Graphics/GraphicsEngine/interface/ShaderResourceVariable.h @@ -250,6 +250,20 @@ DILIGENT_BEGIN_INTERFACE(IShaderResourceVariable, IObject) Uint32 ArrayIndex DEFAULT_VALUE(0)) PURE; + /// For inline constant variables, sets the constant values + + /// Inline constant variables are defined using Diligent::SHADER_RESOURCE_VARIABLE_FLAG_INLINE_CONSTANTS + /// or Diligent::PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS flags. + /// + /// \param [in] pConstants - pointer to the array of 32-bit constant values. + /// \param [in] FirstConstant - index of the first 32-bit constant to set. + /// \param [in] NumConstants - number of 32-bit constants to set. + VIRTUAL void METHOD(SetInlineConstants)(THIS_ + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants) PURE; + + /// Returns the shader resource variable type VIRTUAL SHADER_RESOURCE_VARIABLE_TYPE METHOD(GetType)(THIS) CONST PURE; @@ -278,14 +292,15 @@ DILIGENT_END_INTERFACE // clang-format off -# define IShaderResourceVariable_Set(This, ...) CALL_IFACE_METHOD(ShaderResourceVariable, Set, This, __VA_ARGS__) -# define IShaderResourceVariable_SetArray(This, ...) CALL_IFACE_METHOD(ShaderResourceVariable, SetArray, This, __VA_ARGS__) -# define IShaderResourceVariable_SetBufferRange(This, ...) CALL_IFACE_METHOD(ShaderResourceVariable, SetBufferRange, This, __VA_ARGS__) -# define IShaderResourceVariable_SetBufferOffset(This, ...) CALL_IFACE_METHOD(ShaderResourceVariable, SetBufferOffset, This, __VA_ARGS__) -# define IShaderResourceVariable_GetType(This) CALL_IFACE_METHOD(ShaderResourceVariable, GetType, This) -# define IShaderResourceVariable_GetResourceDesc(This, ...) CALL_IFACE_METHOD(ShaderResourceVariable, GetResourceDesc, This, __VA_ARGS__) -# define IShaderResourceVariable_GetIndex(This) CALL_IFACE_METHOD(ShaderResourceVariable, GetIndex, This) -# define IShaderResourceVariable_Get(This, ...) CALL_IFACE_METHOD(ShaderResourceVariable, Get, This, __VA_ARGS__) +# define IShaderResourceVariable_Set(This, ...) CALL_IFACE_METHOD(ShaderResourceVariable, Set, This, __VA_ARGS__) +# define IShaderResourceVariable_SetArray(This, ...) CALL_IFACE_METHOD(ShaderResourceVariable, SetArray, This, __VA_ARGS__) +# define IShaderResourceVariable_SetBufferRange(This, ...) CALL_IFACE_METHOD(ShaderResourceVariable, SetBufferRange, This, __VA_ARGS__) +# define IShaderResourceVariable_SetBufferOffset(This, ...) CALL_IFACE_METHOD(ShaderResourceVariable, SetBufferOffset, This, __VA_ARGS__) +# define IShaderResourceVariable_SetInlineConstants(This,...)CALL_IFACE_METHOD(ShaderResourceVariable, SetInlineConstants, This, __VA_ARGS__) +# define IShaderResourceVariable_GetType(This) CALL_IFACE_METHOD(ShaderResourceVariable, GetType, This) +# define IShaderResourceVariable_GetResourceDesc(This, ...) CALL_IFACE_METHOD(ShaderResourceVariable, GetResourceDesc, This, __VA_ARGS__) +# define IShaderResourceVariable_GetIndex(This) CALL_IFACE_METHOD(ShaderResourceVariable, GetIndex, This) +# define IShaderResourceVariable_Get(This, ...) CALL_IFACE_METHOD(ShaderResourceVariable, Get, This, __VA_ARGS__) // clang-format on diff --git a/Graphics/GraphicsEngine/src/PipelineResourceSignatureBase.cpp b/Graphics/GraphicsEngine/src/PipelineResourceSignatureBase.cpp index cc5ecfd543..3d54a8844e 100644 --- a/Graphics/GraphicsEngine/src/PipelineResourceSignatureBase.cpp +++ b/Graphics/GraphicsEngine/src/PipelineResourceSignatureBase.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -111,6 +111,21 @@ void ValidatePipelineResourceSignatureDesc(const PipelineResourceSignatureDesc& ": ", GetPipelineResourceFlagsString(AllowedResourceFlags, false, ", "), "."); } + if ((Res.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0) + { + if (Res.Flags != PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + { + LOG_PRS_ERROR_AND_THROW("Incorrect Desc.Resources[", i, "].Flags (", GetPipelineResourceFlagsString(Res.Flags), + "). INLINE_CONSTANTS flag cannot be combined with other flags."); + } + + if (Res.ArraySize > MAX_INLINE_CONSTANTS) + { + LOG_PRS_ERROR_AND_THROW("Desc.Resources[", i, "].ArraySize (", Res.ArraySize, + ") exceeds the maximum allowed value (", MAX_INLINE_CONSTANTS, ") for inline constants."); + } + } + if ((Res.Flags & PIPELINE_RESOURCE_FLAG_FORMATTED_BUFFER) != 0 && Features.FormattedBuffers == DEVICE_FEATURE_STATE_DISABLED) { LOG_PRS_ERROR_AND_THROW("Incorrect Desc.Resources[", i, "].Flags (FORMATTED_BUFFER). The flag can only be used if FormattedBuffers device feature is enabled."); diff --git a/Graphics/GraphicsEngine/src/PipelineStateBase.cpp b/Graphics/GraphicsEngine/src/PipelineStateBase.cpp index 7d495c55b5..376d4a54ca 100644 --- a/Graphics/GraphicsEngine/src/PipelineStateBase.cpp +++ b/Graphics/GraphicsEngine/src/PipelineStateBase.cpp @@ -478,6 +478,9 @@ void ValidatePipelineResourceLayoutDesc(const PipelineStateDesc& PSODesc, const if (Var.ShaderStages == SHADER_TYPE_UNKNOWN) LOG_PSO_ERROR_AND_THROW("ResourceLayout.Variables[", i, "].ShaderStages must not be SHADER_TYPE_UNKNOWN."); + if ((Var.Flags & SHADER_VARIABLE_FLAG_INLINE_CONSTANTS) != 0 && (Var.Flags != SHADER_VARIABLE_FLAG_INLINE_CONSTANTS)) + LOG_PSO_ERROR_AND_THROW("ResourceLayout.Variables[", i, "].Flags: INLINE_CONSTANTS flag cannot be combined with other flags."); + auto range = UniqueVariables.equal_range(Var.Name); for (auto it = range.first; it != range.second; ++it) { diff --git a/Graphics/GraphicsEngineD3D11/include/DeviceContextD3D11Impl.hpp b/Graphics/GraphicsEngineD3D11/include/DeviceContextD3D11Impl.hpp index 9bffad5fc1..0432485d3f 100644 --- a/Graphics/GraphicsEngineD3D11/include/DeviceContextD3D11Impl.hpp +++ b/Graphics/GraphicsEngineD3D11/include/DeviceContextD3D11Impl.hpp @@ -360,7 +360,9 @@ class DeviceContextD3D11Impl final : public DeviceContextBase pBuffer; +}; + struct PipelineResourceSignatureInternalDataD3D11 : PipelineResourceSignatureInternalData { @@ -106,6 +113,8 @@ class PipelineResourceSignatureD3D11Impl final : public PipelineResourceSignatur // Make the base class method visible using TPipelineResourceSignatureBase::CopyStaticResources; + void UpdateInlineConstantBuffers(const ShaderResourceCacheD3D11& ResourceCache, ID3D11DeviceContext* pd3d11Ctx) const; + #ifdef DILIGENT_DEVELOPMENT /// Verifies committed resource using the D3D resource attributes from the PSO. bool DvpValidateCommittedResource(const D3DShaderResourceAttribs& D3DAttribs, @@ -128,6 +137,9 @@ class PipelineResourceSignatureD3D11Impl final : public PipelineResourceSignatur // Indicates which constant buffer slots are allowed to contain buffers with dynamic offsets. std::array m_DynamicCBSlotsMask{}; static_assert(sizeof(m_DynamicCBSlotsMask[0]) * 8 >= D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT, "Not enough bits for all dynamic buffer slots"); + + Uint32 m_NumInlineConstantBuffers = 0; + std::unique_ptr m_InlineConstantBuffers; }; } // namespace Diligent diff --git a/Graphics/GraphicsEngineD3D11/include/ShaderResourceCacheD3D11.hpp b/Graphics/GraphicsEngineD3D11/include/ShaderResourceCacheD3D11.hpp index 277a16d981..d716a99a55 100644 --- a/Graphics/GraphicsEngineD3D11/include/ShaderResourceCacheD3D11.hpp +++ b/Graphics/GraphicsEngineD3D11/include/ShaderResourceCacheD3D11.hpp @@ -49,13 +49,15 @@ namespace Diligent { +struct InlineConstantBufferAttribsD3D11; + /// The class implements a cache that holds resources bound to all shader stages. // All resources are stored in the continuous memory using the following layout: // -// | CachedCB | ID3D11Buffer* || CachedResource | ID3D11ShaderResourceView* || CachedSampler | ID3D11SamplerState* || CachedResource | ID3D11UnorderedAccessView*|| -// |--------------------------|------------------------||--------------------------|---------------------------||------------------------------|-----------------------------||-------------------------|---------------------------|| -// | 0 | 1 | ... | CBCount-1 | 0 | 1 | ...| CBCount-1 || 0 | 1 | ... | SRVCount-1 | 0 | 1 | ... | SRVCount-1 || 0 | 1 | ... | SamplerCount-1 | 0 | 1 | ...| SamplerCount-1 ||0 | 1 | ... | UAVCount-1 | 0 | 1 | ... | UAVCount-1 || -// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// | CachedCB | ID3D11Buffer* || CachedResource | ID3D11ShaderResourceView* || CachedSampler | ID3D11SamplerState* || CachedResource | ID3D11UnorderedAccessView*| Inline Constants | +// |--------------------------|------------------------||--------------------------|---------------------------||------------------------------|-----------------------------||-------------------------|---------------------------|------------------| +// | 0 | 1 | ... | CBCount-1 | 0 | 1 | ...| CBCount-1 || 0 | 1 | ... | SRVCount-1 | 0 | 1 | ... | SRVCount-1 || 0 | 1 | ... | SamplerCount-1 | 0 | 1 | ...| SamplerCount-1 ||0 | 1 | ... | UAVCount-1 | 0 | 1 | ... | UAVCount-1 | 0 | 1 | ... | +// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // class ShaderResourceCacheD3D11 : public ShaderResourceCacheBase { @@ -89,6 +91,9 @@ class ShaderResourceCacheD3D11 : public ShaderResourceCacheBase // Dynamic offset in bytes Uint32 DynamicOffset = 0; + // Pointer to inline constant data + void* pInlineConstantData = nullptr; + explicit operator bool() const noexcept { return pBuff; @@ -138,6 +143,17 @@ class ShaderResourceCacheD3D11 : public ShaderResourceCacheBase return pBuff && RangeSize != 0 && RangeSize < pBuff->GetDesc().Size; } + void SetInlineConstants(const void* pSrcConstants, Uint32 FirstConstant, Uint32 NumConstants) + { + VERIFY(pSrcConstants != nullptr, "Source constant data pointer is null"); + VERIFY(FirstConstant + NumConstants <= RangeSize / sizeof(Uint32), + "Too many constants (", FirstConstant + NumConstants, ") for the allocated space (", RangeSize / sizeof(Uint32), ")"); + VERIFY(pInlineConstantData != nullptr, "Inline constant data pointer is null"); + memcpy(reinterpret_cast(pInlineConstantData) + FirstConstant * sizeof(Uint32), + pSrcConstants, + NumConstants * sizeof(Uint32)); + } + // Returns ID3D11Buffer template typename CachedResourceTraits::D3D11ResourceType* GetD3D11Resource(); @@ -247,7 +263,9 @@ class ShaderResourceCacheD3D11 : public ShaderResourceCacheBase // Static resource cache does not allow dynamic buffers (pDynamicCBSlotsMask == null). void Initialize(const D3D11ShaderResourceCounters& ResCount, IMemoryAllocator& MemAllocator, - const std::array* pDynamicCBSlotsMask); + const std::array* pDynamicCBSlotsMask, + const InlineConstantBufferAttribsD3D11* pInlineCBs, + Uint32 NumInlineCBs); template inline void SetResource(const D3D11ResourceBindPoints& BindPoints, @@ -256,9 +274,15 @@ class ShaderResourceCacheD3D11 : public ShaderResourceCacheBase __forceinline void SetDynamicCBOffset(const D3D11ResourceBindPoints& BindPoints, Uint32 DynamicOffset); + __forceinline void SetInlineConstants(const D3D11ResourceBindPoints& BindPoints, + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants); template - __forceinline const typename CachedResourceTraits::CachedResourceType& GetResource(const D3D11ResourceBindPoints& BindPoints) const + __forceinline const typename CachedResourceTraits::CachedResourceType& GetResource( + const D3D11ResourceBindPoints& BindPoints, + typename CachedResourceTraits::D3D11ResourceType** ppd3d11Resource = nullptr) const { VERIFY(BindPoints.GetActiveStages() != SHADER_TYPE_UNKNOWN, "No active shader stage"); const Int32 FirstStageInd = GetFirstShaderStageIndex(BindPoints.GetActiveStages()); @@ -266,9 +290,9 @@ class ShaderResourceCacheD3D11 : public ShaderResourceCacheBase VERIFY(FirstStageBinding < GetResourceCount(FirstStageInd), "Resource slot is out of range"); const auto FirstStageResArrays = GetConstResourceArrays(FirstStageInd); const auto& CachedRes = FirstStageResArrays.first[FirstStageBinding]; + auto* pd3d11Res = FirstStageResArrays.second[FirstStageBinding]; #ifdef DILIGENT_DEBUG { - const auto* pd3d11Res = FirstStageResArrays.second[FirstStageBinding]; for (SHADER_TYPE ActiveStages = BindPoints.GetActiveStages(); ActiveStages != SHADER_TYPE_UNKNOWN;) { const Int32 ShaderInd = ExtractFirstShaderStageIndex(ActiveStages); @@ -278,6 +302,8 @@ class ShaderResourceCacheD3D11 : public ShaderResourceCacheBase } } #endif + if (ppd3d11Resource != nullptr) + *ppd3d11Resource = pd3d11Res; return CachedRes; } @@ -289,6 +315,8 @@ class ShaderResourceCacheD3D11 : public ShaderResourceCacheBase template bool CopyResource(const ShaderResourceCacheD3D11& SrcCache, const D3D11ResourceBindPoints& BindPoints); + inline void CopyInlineConstants(const ShaderResourceCacheD3D11& SrcCache, const D3D11ResourceBindPoints& BindPoints, Uint32 NumConstants); + template __forceinline bool IsResourceBound(const D3D11ResourceBindPoints& BindPoints) const { @@ -320,7 +348,7 @@ class ShaderResourceCacheD3D11 : public ShaderResourceCacheBase template __forceinline Uint32 GetResourceCount(Uint32 ShaderInd) const; - bool IsInitialized() const { return m_IsInitialized; } + bool IsInitialized() const { return m_Flags & FLAG_IS_INITIALIZED; } ResourceCacheContentType GetContentType() const { return m_ContentType; } @@ -392,11 +420,21 @@ class ShaderResourceCacheD3D11 : public ShaderResourceCacheBase return false; } + bool HasInlineConstants() const + { + return m_Flags & FLAG_HAS_INLINE_CONSTANTS; + } + #ifdef DILIGENT_DEBUG void DbgVerifyDynamicBufferMasks() const; #endif private: + void InitInlineConstantBuffer(const D3D11ResourceBindPoints& BindPoints, + RefCntAutoPtr pBuffer, + Uint32 NumConstants, + void* pInlineConstantData); + template __forceinline Uint32 GetResourceDataOffset(Uint32 ShaderInd) const; @@ -466,7 +504,14 @@ class ShaderResourceCacheD3D11 : public ShaderResourceCacheBase std::array m_Offsets = {}; - bool m_IsInitialized = false; + enum FLAGS : Uint8 + { + FLAG_NONE = 0, + FLAG_IS_INITIALIZED = 1u << 0u, + FLAG_HAS_INLINE_CONSTANTS = 1u << 1u, + }; + DECLARE_FRIEND_FLAG_ENUM_OPERATORS(FLAGS) + FLAGS m_Flags = FLAG_NONE; // Indicates what types of resources are stored in the cache const ResourceCacheContentType m_ContentType; @@ -480,6 +525,7 @@ class ShaderResourceCacheD3D11 : public ShaderResourceCacheBase std::unique_ptr> m_pResourceData; }; +DEFINE_FLAG_ENUM_OPERATORS(ShaderResourceCacheD3D11::FLAGS) template <> struct ShaderResourceCacheD3D11::CachedResourceTraits @@ -641,7 +687,7 @@ inline ShaderResourceCacheD3D11::MinMaxSlot ShaderResourceCacheD3D11::BindCBs( // Offsets in Direct3D11 are measure in float4 constants. const UINT FirstCBConstant = StaticCast((ResArrays.first[res].BaseOffset + ResArrays.first[res].DynamicOffset) / 16u); // The number of constants must be a multiple of 16 constants. It is OK if it is past the end of the buffer. - const UINT NumCBConstants = StaticCast(AlignUp(ResArrays.first[res].RangeSize / 16u, 16u)); + const UINT NumCBConstants = StaticCast(AlignUp((ResArrays.first[res].RangeSize + 15u) / 16u, 16u)); // clang-format off if (CommittedD3D11Resources[Slot] != pd3d11CB || FirstConstants[Slot] != FirstCBConstant || @@ -688,7 +734,7 @@ inline void ShaderResourceCacheD3D11::BindDynamicCBs(Uint32 // Offsets in Direct3D11 are measure in float4 constants. const UINT FirstCBConstant = StaticCast((CB.BaseOffset + CB.DynamicOffset) / 16u); // The number of constants must be a multiple of 16 constants. It is OK if it is past the end of the buffer. - const UINT NumCBConstants = StaticCast(AlignUp(CB.RangeSize / 16u, 16u)); + const UINT NumCBConstants = StaticCast(AlignUp((CB.RangeSize + 15u) / 16u, 16u)); // clang-format off if (CommittedD3D11Resources[Slot] != pd3d11CB || FirstConstants[Slot] != FirstCBConstant || @@ -725,6 +771,36 @@ __forceinline void ShaderResourceCacheD3D11::SetDynamicCBOffset(const D3D11Resou } } +__forceinline void ShaderResourceCacheD3D11::SetInlineConstants(const D3D11ResourceBindPoints& BindPoints, + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants) +{ + // Since all shader stages share the same inline constant data, we can just set it for one stage + SHADER_TYPE ActiveStages = BindPoints.GetActiveStages(); + VERIFY_EXPR(ActiveStages != SHADER_TYPE_UNKNOWN); + const Uint32 ShaderInd0 = ExtractFirstShaderStageIndex(ActiveStages); + const Uint32 Binding0 = BindPoints[ShaderInd0]; + VERIFY(Binding0 < GetResourceCount(ShaderInd0), "Cache offset is out of range"); + const auto ResArrays0 = GetResourceArrays(ShaderInd0); + ResArrays0.first[Binding0].SetInlineConstants(pConstants, FirstConstant, NumConstants); + +#ifdef DILIGENT_DEBUG + while (ActiveStages != SHADER_TYPE_UNKNOWN) + { + const Uint32 ShaderInd = ExtractFirstShaderStageIndex(ActiveStages); + const Uint32 Binding = BindPoints[ShaderInd]; + VERIFY(Binding < GetResourceCount(ShaderInd), "Cache offset is out of range"); + + const auto ResArrays = GetResourceArrays(ShaderInd); + VERIFY(ResArrays.first[Binding].pInlineConstantData == ResArrays0.first[Binding0].pInlineConstantData, + "All shader stages must share the same inline constant data (ensured by InitInlineConstantBuffer)"); + VERIFY(ResArrays.first[Binding] == ResArrays0.first[Binding0], + "All shader stages must share the same inline constant data attributes (ensured by InitInlineConstantBuffer)"); + } +#endif +} + template <> inline void ShaderResourceCacheD3D11::UpdateDynamicCBOffsetFlag( const ShaderResourceCacheD3D11::CachedCB& CB, @@ -782,6 +858,51 @@ bool ShaderResourceCacheD3D11::CopyResource(const ShaderResourceCacheD3D11& SrcC return IsBound; } +inline void ShaderResourceCacheD3D11::CopyInlineConstants(const ShaderResourceCacheD3D11& SrcCache, const D3D11ResourceBindPoints& BindPoints, Uint32 NumConstants) +{ + // Since all shader stages share the same inline constant data, we can just copy from one stage + SHADER_TYPE ActiveStages = BindPoints.GetActiveStages(); + VERIFY_EXPR(ActiveStages != SHADER_TYPE_UNKNOWN); + + const Int32 ShaderInd0 = ExtractFirstShaderStageIndex(ActiveStages); + + const auto SrcResArrays0 = SrcCache.GetConstResourceArrays(ShaderInd0); + const auto DstResArrays0 = GetResourceArrays(ShaderInd0); + + const Uint32 Binding0 = BindPoints[ShaderInd0]; + VERIFY(Binding0 < GetResourceCount(ShaderInd0), "Destination index is out of range"); + VERIFY(Binding0 < SrcCache.GetResourceCount(ShaderInd0), "Source index is out of range"); + VERIFY(SrcResArrays0.first[Binding0].pInlineConstantData != nullptr, "Source inline constant data is null"); + VERIFY(DstResArrays0.first[Binding0].pInlineConstantData != nullptr, "Destination inline constant data is null"); + VERIFY(SrcResArrays0.first[Binding0].RangeSize == NumConstants * sizeof(Uint32), "Source inline constant buffer size mismatch"); + VERIFY(DstResArrays0.first[Binding0].RangeSize == NumConstants * sizeof(Uint32), "Destination inline constant buffer size mismatch"); + memcpy(DstResArrays0.first[Binding0].pInlineConstantData, + SrcResArrays0.first[Binding0].pInlineConstantData, + NumConstants * sizeof(Uint32)); + +#ifdef DILIGENT_DEBUG + while (ActiveStages != SHADER_TYPE_UNKNOWN) + { + const Int32 ShaderInd = ExtractFirstShaderStageIndex(ActiveStages); + + const auto SrcResArrays = SrcCache.GetConstResourceArrays(ShaderInd); + const auto DstResArrays = GetResourceArrays(ShaderInd); + + const Uint32 Binding = BindPoints[ShaderInd]; + VERIFY(Binding < GetResourceCount(ShaderInd), "Index is out of range"); + VERIFY(Binding < SrcCache.GetResourceCount(ShaderInd), "Index is out of range"); + VERIFY(SrcResArrays0.first[Binding0] == SrcResArrays.first[Binding], + "All shader stages must share the same inline constant data attributes (ensured by InitInlineConstantBuffer)"); + VERIFY(DstResArrays0.first[Binding0] == DstResArrays.first[Binding], + "All shader stages must share the same inline constant data attributes (ensured by InitInlineConstantBuffer)"); + VERIFY(SrcResArrays0.first[Binding0].pInlineConstantData == SrcResArrays.first[Binding].pInlineConstantData, + "All shader stages must share the same inline constant data (ensured by InitInlineConstantBuffer)"); + VERIFY(DstResArrays0.first[Binding0].pInlineConstantData == DstResArrays.first[Binding].pInlineConstantData, + "All shader stages must share the same inline constant data (ensured by InitInlineConstantBuffer)"); + } +#endif +} + template <> __forceinline ID3D11Buffer* ShaderResourceCacheD3D11::CachedCB::GetD3D11Resource() { diff --git a/Graphics/GraphicsEngineD3D11/include/ShaderVariableManagerD3D11.hpp b/Graphics/GraphicsEngineD3D11/include/ShaderVariableManagerD3D11.hpp index 58e95400cc..588768df52 100644 --- a/Graphics/GraphicsEngineD3D11/include/ShaderVariableManagerD3D11.hpp +++ b/Graphics/GraphicsEngineD3D11/include/ShaderVariableManagerD3D11.hpp @@ -132,6 +132,11 @@ class ShaderVariableManagerD3D11 : ShaderVariableManagerBase @@ -143,6 +148,8 @@ class ShaderVariableManagerD3D11 : ShaderVariableManagerBase diff --git a/Graphics/GraphicsEngineD3D11/src/DeviceContextD3D11Impl.cpp b/Graphics/GraphicsEngineD3D11/src/DeviceContextD3D11Impl.cpp index 6312e12e1f..b5bcf72d62 100755 --- a/Graphics/GraphicsEngineD3D11/src/DeviceContextD3D11Impl.cpp +++ b/Graphics/GraphicsEngineD3D11/src/DeviceContextD3D11Impl.cpp @@ -423,7 +423,9 @@ void DeviceContextD3D11Impl::BindDynamicCBs(const ShaderResourceCacheD3D11& R } -void DeviceContextD3D11Impl::BindShaderResources(Uint32 BindSRBMask) +void DeviceContextD3D11Impl::BindShaderResources(Uint32 BindSRBMask, + bool DynamicBuffersIntact, + bool InlineConstantsIntact) { VERIFY_EXPR(BindSRBMask != 0); @@ -449,7 +451,8 @@ void DeviceContextD3D11Impl::BindShaderResources(Uint32 BindSRBMask) continue; } - if (m_BindInfo.StaleSRBMask & SignBit) + const bool SRBStale = (m_BindInfo.StaleSRBMask & SignBit) != 0; + if (SRBStale) { // Bind all cache resources BindCacheResources(*pResourceCache, BaseBindings, PsUavBindMode); @@ -457,17 +460,57 @@ void DeviceContextD3D11Impl::BindShaderResources(Uint32 BindSRBMask) else { // Bind constant buffers with dynamic offsets. In Direct3D11 only those buffers are counted as dynamic. - VERIFY((m_BindInfo.DynamicSRBMask & SignBit) != 0, - "When bit in StaleSRBMask is not set, the same bit in DynamicSRBMask must be set. Check GetCommitMask()."); - DEV_CHECK_ERR(pResourceCache->HasDynamicResources(), - "Bit in DynamicSRBMask is set, but the cache does not contain dynamic resources. This may indicate that resources " - "in the cache have changed, but the SRB has not been committed before the draw/dispatch command."); - if (pResourceCache->GetUAVCount(PSInd) > 0) + VERIFY(((m_BindInfo.DynamicSRBMask | m_BindInfo.InlineConstantsSRBMask) & SignBit) != 0, + "When bit in StaleSRBMask is not set, the same bit in either DynamicSRBMask or InlineConstantsSRBMask must be set. Check GetCommitMask()."); + + if ((m_BindInfo.DynamicSRBMask & SignBit) != 0) + { + DEV_CHECK_ERR(pResourceCache->HasDynamicResources(), + "Shader resource cache does not contain dynamic resources, but the corresponding bit in DynamicSRBMask is set. " + "This may indicate that resources in the cache have changed, but the SRB has not been committed before the draw/dispatch command."); + if (pResourceCache->GetUAVCount(PSInd) > 0) + { + if (PsUavBindMode != PixelShaderUAVBindMode::Bind) + PsUavBindMode = PixelShaderUAVBindMode::Keep; + } + if (!DynamicBuffersIntact) + { + BindDynamicCBs(*pResourceCache, BaseBindings); + } + } + else { - if (PsUavBindMode != PixelShaderUAVBindMode::Bind) - PsUavBindMode = PixelShaderUAVBindMode::Keep; + DEV_CHECK_ERR(!pResourceCache->HasDynamicResources(), + "Shader resource cache contains dynamic resources, but the corresponding bit in DynamicSRBMask is not set. " + "This may indicate that resources in the cache have changed, but the SRB has not been committed before the draw/dispatch command."); } - BindDynamicCBs(*pResourceCache, BaseBindings); + } + + if ((m_BindInfo.InlineConstantsSRBMask & SignBit) != 0) + { + VERIFY(pResourceCache->HasInlineConstants(), + "Shader resource cache does not contain inline constants, but the corresponding bit in InlineConstantsSRBMask is set. " + "This may be a bug because inline constants flag in the cache never changes after SRB creation, " + "while m_BindInfo.InlineConstantsSRBMask is initialized when SRB is committed."); + // Always update inline constant buffers if the SRB is stale + if (SRBStale || !InlineConstantsIntact) + { + if (PipelineResourceSignatureD3D11Impl* pSign = m_pPipelineState->GetResourceSignature(SignIdx)) + { + pSign->UpdateInlineConstantBuffers(*pResourceCache, m_pd3d11DeviceContext); + } + else + { + UNEXPECTED("Pipeline resource signature is null for signature index ", SignIdx); + } + } + } + else + { + VERIFY(!pResourceCache->HasInlineConstants(), + "Shader resource cache contains inline constants, but the corresponding bit in InlineConstantsSRBMask is not set. " + "This may be a bug because inline constants flag in the cache never changes after SRB creation, " + "while m_BindInfo.InlineConstantsSRBMask is initialized when SRB is committed."); } } @@ -669,9 +712,11 @@ void DeviceContextD3D11Impl::PrepareForDraw(DRAW_FLAGS Flags) CommitD3D11VertexBuffers(m_pPipelineState); } - if (Uint32 BindSRBMask = m_BindInfo.GetCommitMask(Flags & DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT)) + const bool DynamicBuffersIntact = (Flags & DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT) != 0; + const bool InlineConstantsIntact = (Flags & DRAW_FLAG_INLINE_CONSTANTS_INTACT) != 0; + if (Uint32 BindSRBMask = m_BindInfo.GetCommitMask(DynamicBuffersIntact, InlineConstantsIntact)) { - BindShaderResources(BindSRBMask); + BindShaderResources(BindSRBMask, DynamicBuffersIntact, InlineConstantsIntact); } #ifdef DILIGENT_DEVELOPMENT diff --git a/Graphics/GraphicsEngineD3D11/src/PipelineResourceSignatureD3D11Impl.cpp b/Graphics/GraphicsEngineD3D11/src/PipelineResourceSignatureD3D11Impl.cpp index edd339f46b..c02c8e3233 100644 --- a/Graphics/GraphicsEngineD3D11/src/PipelineResourceSignatureD3D11Impl.cpp +++ b/Graphics/GraphicsEngineD3D11/src/PipelineResourceSignatureD3D11Impl.cpp @@ -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"); @@ -93,14 +93,14 @@ D3D11_RESOURCE_RANGE PipelineResourceSignatureD3D11Impl::ShaderResourceTypeToRan static_assert(SHADER_RESOURCE_TYPE_LAST == 8, "Please update the switch below to handle the new shader resource type"); switch (Type) { - // clang-format off - case SHADER_RESOURCE_TYPE_CONSTANT_BUFFER: return D3D11_RESOURCE_RANGE_CBV; - case SHADER_RESOURCE_TYPE_TEXTURE_SRV: return D3D11_RESOURCE_RANGE_SRV; - case SHADER_RESOURCE_TYPE_BUFFER_SRV: return D3D11_RESOURCE_RANGE_SRV; - case SHADER_RESOURCE_TYPE_TEXTURE_UAV: return D3D11_RESOURCE_RANGE_UAV; - case SHADER_RESOURCE_TYPE_BUFFER_UAV: return D3D11_RESOURCE_RANGE_UAV; - case SHADER_RESOURCE_TYPE_SAMPLER: return D3D11_RESOURCE_RANGE_SAMPLER; - case SHADER_RESOURCE_TYPE_INPUT_ATTACHMENT: return D3D11_RESOURCE_RANGE_SRV; + // clang-format off + case SHADER_RESOURCE_TYPE_CONSTANT_BUFFER: return D3D11_RESOURCE_RANGE_CBV; + case SHADER_RESOURCE_TYPE_TEXTURE_SRV: return D3D11_RESOURCE_RANGE_SRV; + case SHADER_RESOURCE_TYPE_BUFFER_SRV: return D3D11_RESOURCE_RANGE_SRV; + case SHADER_RESOURCE_TYPE_TEXTURE_UAV: return D3D11_RESOURCE_RANGE_UAV; + case SHADER_RESOURCE_TYPE_BUFFER_UAV: return D3D11_RESOURCE_RANGE_UAV; + case SHADER_RESOURCE_TYPE_SAMPLER: return D3D11_RESOURCE_RANGE_SAMPLER; + case SHADER_RESOURCE_TYPE_INPUT_ATTACHMENT: return D3D11_RESOURCE_RANGE_SRV; // clang-format on default: UNEXPECTED("Unsupported resource type"); @@ -153,6 +153,17 @@ void PipelineResourceSignatureD3D11Impl::CreateLayout(const bool IsSerialized) DstImtblSampAttribs.ArraySize = std::max(DstImtblSampAttribs.ArraySize, ResDesc.ArraySize); } } + + if (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + { + VERIFY(ResDesc.ResourceType == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, "Only constant buffers can have INLINE_CONSTANTS flag"); + ++m_NumInlineConstantBuffers; + } + } + + if (m_NumInlineConstantBuffers > 0) + { + m_InlineConstantBuffers = std::make_unique(m_NumInlineConstantBuffers); } // Allocate registers for immutable samplers first @@ -174,6 +185,7 @@ void PipelineResourceSignatureD3D11Impl::CreateLayout(const bool IsSerialized) D3D11ShaderResourceCounters StaticResCounters; + Uint32 InlineConstantBufferIdx = 0; for (Uint32 i = 0; i < m_Desc.NumResources; ++i) { const PipelineResourceDesc& ResDesc = GetResourceDesc(i); @@ -198,7 +210,12 @@ void PipelineResourceSignatureD3D11Impl::CreateLayout(const bool IsSerialized) { const D3D11_RESOURCE_RANGE Range = ShaderResourceTypeToRange(ResDesc.ResourceType); - AllocBindPoints(m_ResourceCounters, BindPoints, ResDesc.ShaderStages, ResDesc.ArraySize, Range); + VERIFY((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) == 0 || ResDesc.ResourceType == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, + "Only constant buffers can have inline constants flag"); + // For inline constants, ArraySize holds the number of 4-byte constants, while + // the resource occupies a single constant buffer slot. + const Uint32 ArraySize = ResDesc.GetArraySize(); + AllocBindPoints(m_ResourceCounters, BindPoints, ResDesc.ShaderStages, ArraySize, Range); if (ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) { // Since resources in the static cache are indexed by the same bindings, we need to @@ -212,20 +229,51 @@ void PipelineResourceSignatureD3D11Impl::CreateLayout(const bool IsSerialized) } } - if (Range == D3D11_RESOURCE_RANGE_CBV && (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS) == 0) + if (Range == D3D11_RESOURCE_RANGE_CBV && + (ResDesc.Flags & (PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS | PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS)) == 0) { // Set corresponding bits in m_DynamicCBSlotsMask for (SHADER_TYPE ShaderStages = ResDesc.ShaderStages; ShaderStages != SHADER_TYPE_UNKNOWN;) { const Int32 ShaderInd = ExtractFirstShaderStageIndex(ShaderStages); const Uint16 BindPoint = Uint16{BindPoints[ShaderInd]}; - for (Uint32 elem = 0; elem < ResDesc.ArraySize; ++elem) + for (Uint32 elem = 0; elem < ArraySize; ++elem) { VERIFY_EXPR(BindPoint + elem < Uint32{sizeof(m_DynamicCBSlotsMask[0]) * 8}); m_DynamicCBSlotsMask[ShaderInd] |= 1u << (BindPoint + elem); } } } + + if (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + { + // Inline constant buffers are handled mostly like regular constant buffers. The only + // difference is that the buffer is created internally here and is not expected to be bound. + // It is updated by UpdateInlineConstantBuffers() method. + + VERIFY(ResDesc.ResourceType == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, "Only constant buffers can have INLINE_CONSTANTS flag"); + InlineConstantBufferAttribsD3D11& InlineCBAttribs{m_InlineConstantBuffers[InlineConstantBufferIdx++]}; + InlineCBAttribs.BindPoints = BindPoints; + InlineCBAttribs.NumConstants = ResDesc.ArraySize; + + // All SRBs created from this signature will share the same inline constant buffer. + // An alternative design is to have a separate inline constant buffer for each SRB, + // which will allow skipping buffer update if the inline constants are not changed. + // However, this will increase memory consumption as each SRB will have its own copy of the inline CB. + // Besides, inline constants are expected to change frequently, so skipping updates is unlikely. + if (m_pDevice) + { + std::string Name = m_Desc.Name; + Name += " - "; + Name += ResDesc.Name; + BufferDesc CBDesc{Name.c_str(), ResDesc.ArraySize * sizeof(Uint32), BIND_UNIFORM_BUFFER, USAGE_DYNAMIC, CPU_ACCESS_WRITE}; + + RefCntAutoPtr pBuffer; + m_pDevice->CreateBuffer(CBDesc, nullptr, &pBuffer); + VERIFY_EXPR(pBuffer); + InlineCBAttribs.pBuffer = pBuffer.RawPtr(); + } + } } else { @@ -253,10 +301,11 @@ void PipelineResourceSignatureD3D11Impl::CreateLayout(const bool IsSerialized) "Deserialized immutable sampler flag is invalid"); } } + VERIFY_EXPR(InlineConstantBufferIdx == m_NumInlineConstantBuffers); if (m_pStaticResCache) { - m_pStaticResCache->Initialize(StaticResCounters, GetRawAllocator(), nullptr); + m_pStaticResCache->Initialize(StaticResCounters, GetRawAllocator(), nullptr, m_InlineConstantBuffers.get(), m_NumInlineConstantBuffers); VERIFY_EXPR(m_pStaticResCache->IsInitialized()); } } @@ -289,12 +338,20 @@ void PipelineResourceSignatureD3D11Impl::CopyStaticResources(ShaderResourceCache switch (ShaderResourceTypeToRange(ResDesc.ResourceType)) { case D3D11_RESOURCE_RANGE_CBV: - for (Uint32 ArrInd = 0; ArrInd < ResDesc.ArraySize; ++ArrInd) + if (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) { - if (!DstResourceCache.CopyResource(SrcResourceCache, ResAttr.BindPoints + ArrInd)) + VERIFY(ResDesc.Flags == PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS, "INLINE_CONSTANTS flag is not compatible with other flags"); + DstResourceCache.CopyInlineConstants(SrcResourceCache, ResAttr.BindPoints, ResDesc.ArraySize); + } + else + { + for (Uint32 ArrInd = 0; ArrInd < ResDesc.ArraySize; ++ArrInd) { - if (DstCacheType == ResourceCacheContentType::SRB) - LOG_ERROR_MESSAGE("No resource is assigned to static shader variable '", GetShaderResourcePrintName(ResDesc, ArrInd), "' in pipeline resource signature '", m_Desc.Name, "'."); + if (!DstResourceCache.CopyResource(SrcResourceCache, ResAttr.BindPoints + ArrInd)) + { + if (DstCacheType == ResourceCacheContentType::SRB) + LOG_ERROR_MESSAGE("No resource is assigned to static shader variable '", GetShaderResourcePrintName(ResDesc, ArrInd), "' in pipeline resource signature '", m_Desc.Name, "'."); + } } } break; @@ -353,7 +410,8 @@ void PipelineResourceSignatureD3D11Impl::CopyStaticResources(ShaderResourceCache void PipelineResourceSignatureD3D11Impl::InitSRBResourceCache(ShaderResourceCacheD3D11& ResourceCache) { - ResourceCache.Initialize(m_ResourceCounters, m_SRBMemAllocator.GetResourceCacheDataAllocator(0), &m_DynamicCBSlotsMask); + ResourceCache.Initialize(m_ResourceCounters, m_SRBMemAllocator.GetResourceCacheDataAllocator(0), &m_DynamicCBSlotsMask, + m_InlineConstantBuffers.get(), m_NumInlineConstantBuffers); VERIFY_EXPR(ResourceCache.IsInitialized()); // Copy immutable samplers. @@ -389,9 +447,10 @@ void PipelineResourceSignatureD3D11Impl::UpdateShaderResourceBindingMap(Resource ResourceBinding::BindInfo BindInfo // { Uint32{BaseBindings[Range][ShaderInd]} + Uint32{ResAttr.BindPoints[ShaderInd]}, - 0u, // register space is not supported - ResDesc.ArraySize, - ResDesc.ResourceType // + 0u, // register space is not supported + ResDesc.GetArraySize(), // For inline constants, ArraySize holds the number of 4-byte constants, + // while the resource occupies a single constant buffer slot. + ResDesc.ResourceType, }; bool IsUnique = ResourceMap.emplace(HashMapStringKey{ResDesc.Name}, BindInfo).second; VERIFY(IsUnique, "Shader resource '", ResDesc.Name, @@ -445,6 +504,30 @@ void PipelineResourceSignatureD3D11Impl::UpdateShaderResourceBindingMap(Resource } } +void PipelineResourceSignatureD3D11Impl::UpdateInlineConstantBuffers(const ShaderResourceCacheD3D11& ResourceCache, ID3D11DeviceContext* pd3d11Ctx) const +{ + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) + { + const InlineConstantBufferAttribsD3D11& InlineCBAttr = m_InlineConstantBuffers[i]; + + ID3D11Buffer* pd3d11CB = nullptr; + const ShaderResourceCacheD3D11::CachedCB& InlineCB = ResourceCache.GetResource(InlineCBAttr.BindPoints, &pd3d11CB); + VERIFY(InlineCBAttr.NumConstants * sizeof(Uint32) == InlineCB.RangeSize, "Inline constant buffer size mismatch"); + VERIFY(InlineCB.pInlineConstantData != nullptr, "Inline constant data pointer is null"); + + D3D11_MAPPED_SUBRESOURCE MappedData{}; + if (SUCCEEDED(pd3d11Ctx->Map(pd3d11CB, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedData))) + { + memcpy(MappedData.pData, InlineCB.pInlineConstantData, InlineCBAttr.NumConstants * sizeof(Uint32)); + pd3d11Ctx->Unmap(pd3d11CB, 0); + } + else + { + DEV_ERROR("Failed to map inline constant buffer"); + } + } +} + #ifdef DILIGENT_DEVELOPMENT bool PipelineResourceSignatureD3D11Impl::DvpValidateCommittedResource(const D3DShaderResourceAttribs& D3DAttribs, Uint32 ResIndex, @@ -457,7 +540,7 @@ bool PipelineResourceSignatureD3D11Impl::DvpValidateCommittedResource(const D3DS const PipelineResourceAttribsD3D11& ResAttr = m_pResourceAttribs[ResIndex]; VERIFY(strcmp(ResDesc.Name, D3DAttribs.Name) == 0, "Inconsistent resource names"); - VERIFY_EXPR(D3DAttribs.BindCount <= ResDesc.ArraySize); + VERIFY_EXPR(D3DAttribs.BindCount <= ResDesc.GetArraySize()); bool BindingsOK = true; switch (ShaderResourceTypeToRange(ResDesc.ResourceType)) diff --git a/Graphics/GraphicsEngineD3D11/src/PipelineStateD3D11Impl.cpp b/Graphics/GraphicsEngineD3D11/src/PipelineStateD3D11Impl.cpp index f10c728e73..d3e39f00e6 100644 --- a/Graphics/GraphicsEngineD3D11/src/PipelineStateD3D11Impl.cpp +++ b/Graphics/GraphicsEngineD3D11/src/PipelineStateD3D11Impl.cpp @@ -91,7 +91,14 @@ PipelineResourceSignatureDescWrapper PipelineStateD3D11Impl::GetDefaultResourceS const SHADER_RESOURCE_TYPE ResType = Attribs.GetShaderResourceType(); const PIPELINE_RESOURCE_FLAGS ResFlags = Attribs.GetPipelineResourceFlags() | ShaderVariableFlagsToPipelineResourceFlags(VarDesc.Flags); - SignDesc.AddResource(VarDesc.ShaderStages, Attribs.Name, Attribs.BindCount, ResType, VarDesc.Type, ResFlags); + + Uint32 ArraySize = Attribs.BindCount; + if (ResFlags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + { + VERIFY(ResFlags == PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS, "INLINE_CONSTANTS flag cannot be combined with other flags."); + ArraySize = Attribs.GetInlineConstantCountOrThrow(pShader->GetDesc().Name); + } + SignDesc.AddResource(VarDesc.ShaderStages, Attribs.Name, ArraySize, ResType, VarDesc.Type, ResFlags); } else { diff --git a/Graphics/GraphicsEngineD3D11/src/ShaderResourceCacheD3D11.cpp b/Graphics/GraphicsEngineD3D11/src/ShaderResourceCacheD3D11.cpp index adc17e70ed..a5df147fb7 100755 --- a/Graphics/GraphicsEngineD3D11/src/ShaderResourceCacheD3D11.cpp +++ b/Graphics/GraphicsEngineD3D11/src/ShaderResourceCacheD3D11.cpp @@ -95,7 +95,9 @@ void ShaderResourceCacheD3D11::DestructResources(Uint32 ShaderInd) void ShaderResourceCacheD3D11::Initialize(const D3D11ShaderResourceCounters& ResCount, IMemoryAllocator& MemAllocator, - const std::array* pDynamicCBSlotsMask) + const std::array* pDynamicCBSlotsMask, + const InlineConstantBufferAttribsD3D11* pInlineCBs, + Uint32 NumInlineCBs) { // http://diligentgraphics.com/diligent-engine/architecture/d3d11/shader-resource-cache/ VERIFY(!IsInitialized(), "Resource cache has already been initialized!"); @@ -130,11 +132,51 @@ void ShaderResourceCacheD3D11::Initialize(const D3D11ShaderResourceCounters& } m_Offsets[MaxOffsets - 1] = static_cast(MemOffset); - const size_t BufferSize = MemOffset; + size_t BufferSize = MemOffset; VERIFY_EXPR(m_pResourceData == nullptr); VERIFY_EXPR(BufferSize == GetRequiredMemorySize(ResCount)); + auto ProcessInlineCBs = [pInlineCBs, NumInlineCBs, this](auto Handler) { + for (Uint32 i = 0; i < NumInlineCBs; ++i) + { + const InlineConstantBufferAttribsD3D11& InlineCBAttr = pInlineCBs[i]; + + SHADER_TYPE ActiveStages = InlineCBAttr.BindPoints.GetActiveStages(); + const Uint32 ShaderInd0 = ExtractFirstShaderStageIndex(ActiveStages); + const Uint32 Binding0 = InlineCBAttr.BindPoints[ShaderInd0]; + // Static resource cache may not contain all inline constant buffers. + // Skip those that are out of range. + const bool IsInRange = Binding0 < GetResourceCount(ShaderInd0); + +#ifdef DILIGENT_DEBUG + while (ActiveStages != SHADER_TYPE_UNKNOWN) + { + const Uint32 ShaderInd = ExtractFirstShaderStageIndex(ActiveStages); + const Uint32 Binding = InlineCBAttr.BindPoints[ShaderInd]; + VERIFY(IsInRange == (Binding < GetResourceCount(ShaderInd)), + "All binding points for inline constant buffer must be in range or out of range simultaneously."); + } +#endif + + if (IsInRange) + { + Handler(InlineCBAttr); + } + } + }; + + Uint32 TotalInlineConstants = 0; + ProcessInlineCBs([&TotalInlineConstants](const InlineConstantBufferAttribsD3D11& InlineCBAttr) { + TotalInlineConstants += InlineCBAttr.NumConstants; + }); + + if (TotalInlineConstants > 0) + { + m_Flags |= FLAG_HAS_INLINE_CONSTANTS; + BufferSize += TotalInlineConstants * sizeof(Uint32); + } + if (BufferSize > 0) { m_pResourceData = decltype(m_pResourceData){ @@ -153,7 +195,21 @@ void ShaderResourceCacheD3D11::Initialize(const D3D11ShaderResourceCounters& ConstructResources(ShaderInd); } - m_IsInitialized = true; + if (TotalInlineConstants > 0) + { + Uint32* pInlineCBData = reinterpret_cast(m_pResourceData.get() + MemOffset); + // Initialize inline constant buffers. + ProcessInlineCBs([&pInlineCBData, this](const InlineConstantBufferAttribsD3D11& InlineCBAttr) { + VERIFY_EXPR(InlineCBAttr.NumConstants > 0); + VERIFY_EXPR(InlineCBAttr.pBuffer != nullptr); + // Use the same buffer and data pointer for all active shader stages. + InitInlineConstantBuffer(InlineCBAttr.BindPoints, InlineCBAttr.pBuffer, InlineCBAttr.NumConstants, pInlineCBData); + pInlineCBData += InlineCBAttr.NumConstants; + }); + VERIFY_EXPR(pInlineCBData == reinterpret_cast(m_pResourceData.get() + BufferSize)); + } + + m_Flags |= FLAG_IS_INITIALIZED; } ShaderResourceCacheD3D11::~ShaderResourceCacheD3D11() @@ -168,13 +224,40 @@ ShaderResourceCacheD3D11::~ShaderResourceCacheD3D11() DestructResources(ShaderInd); DestructResources(ShaderInd); } - m_Offsets = {}; - m_IsInitialized = false; + m_Offsets = {}; + m_Flags = FLAG_NONE; m_pResourceData.reset(); } } +void ShaderResourceCacheD3D11::InitInlineConstantBuffer(const D3D11ResourceBindPoints& BindPoints, + RefCntAutoPtr pBuffer, + Uint32 NumConstants, + void* pInlineConstantData) +{ + // Use the same buffer and data pointer for all shader stages. + VERIFY_EXPR(pBuffer); + VERIFY_EXPR(pInlineConstantData); + ID3D11Buffer* pd3d11Buffer = pBuffer->GetD3D11Buffer(); + VERIFY_EXPR(pd3d11Buffer); + for (SHADER_TYPE ActiveStages = BindPoints.GetActiveStages(); ActiveStages != SHADER_TYPE_UNKNOWN;) + { + const Uint32 ShaderInd = ExtractFirstShaderStageIndex(ActiveStages); + const Uint32 Binding = BindPoints[ShaderInd]; + VERIFY(Binding < GetResourceCount(ShaderInd), "Cache offset is out of range"); + + auto ResArrays = GetResourceArrays(ShaderInd); + auto& CachedRes = ResArrays.first[Binding]; + auto& pd3d11Res = ResArrays.second[Binding]; + + CachedRes.pBuff = pBuffer; + CachedRes.RangeSize = NumConstants * sizeof(Uint32); + CachedRes.pInlineConstantData = pInlineConstantData; + pd3d11Res = pd3d11Buffer; + } +} + template void ShaderResourceCacheD3D11::TransitionResourceStates(DeviceContextD3D11Impl& Ctx) { diff --git a/Graphics/GraphicsEngineD3D11/src/ShaderVariableManagerD3D11.cpp b/Graphics/GraphicsEngineD3D11/src/ShaderVariableManagerD3D11.cpp index 87762b641b..6a8df98181 100644 --- a/Graphics/GraphicsEngineD3D11/src/ShaderVariableManagerD3D11.cpp +++ b/Graphics/GraphicsEngineD3D11/src/ShaderVariableManagerD3D11.cpp @@ -300,6 +300,19 @@ void ShaderVariableManagerD3D11::ConstBuffBindInfo::SetDynamicOffset(Uint32 Arra m_ParentManager.m_ResourceCache.SetDynamicCBOffset(Attr.BindPoints + ArrayIndex, Offset); } +void ShaderVariableManagerD3D11::ConstBuffBindInfo::SetConstants(const void* pConstants, Uint32 FirstConstant, Uint32 NumConstants) +{ + const PipelineResourceAttribsD3D11& Attr = GetAttribs(); + const PipelineResourceDesc& Desc = GetDesc(); + VERIFY_EXPR(Desc.ResourceType == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER); +#ifdef DILIGENT_DEVELOPMENT + { + VerifyInlineConstants(Desc, pConstants, FirstConstant, NumConstants); + } +#endif + m_ParentManager.m_ResourceCache.SetInlineConstants(Attr.BindPoints, pConstants, FirstConstant, NumConstants); +} + void ShaderVariableManagerD3D11::TexSRVBindInfo::BindResource(const BindResourceInfo& BindInfo) { const PipelineResourceDesc& Desc = GetDesc(); diff --git a/Graphics/GraphicsEngineD3D12/include/DeviceContextD3D12Impl.hpp b/Graphics/GraphicsEngineD3D12/include/DeviceContextD3D12Impl.hpp index d359d0a469..ef1eb8d329 100644 --- a/Graphics/GraphicsEngineD3D12/include/DeviceContextD3D12Impl.hpp +++ b/Graphics/GraphicsEngineD3D12/include/DeviceContextD3D12Impl.hpp @@ -426,7 +426,11 @@ class DeviceContextD3D12Impl final : public DeviceContextNextGenBase - __forceinline void CommitRootTablesAndViews(RootTableInfo& RootInfo, Uint32 CommitSRBMask, CommandContext& CmdCtx) const; + __forceinline void CommitRootTablesAndViews(RootTableInfo& RootInfo, + Uint32 CommitSRBMask, + CommandContext& CmdCtx, + bool DynamicBuffersIntact = false, + bool InlineConstantsIntact = false) const; #ifdef DILIGENT_DEVELOPMENT void DvpValidateCommittedShaderResources(RootTableInfo& RootInfo) const; diff --git a/Graphics/GraphicsEngineD3D12/include/PipelineResourceSignatureD3D12Impl.hpp b/Graphics/GraphicsEngineD3D12/include/PipelineResourceSignatureD3D12Impl.hpp index e76c7beef5..fc6a8bf72b 100644 --- a/Graphics/GraphicsEngineD3D12/include/PipelineResourceSignatureD3D12Impl.hpp +++ b/Graphics/GraphicsEngineD3D12/include/PipelineResourceSignatureD3D12Impl.hpp @@ -125,7 +125,7 @@ class PipelineResourceSignatureD3D12Impl final : public PipelineResourceSignatur Uint32 GetTotalRootParamsCount() const { - return m_RootParams.GetNumRootTables() + m_RootParams.GetNumRootViews(); + return m_RootParams.GetNumRootTables() + m_RootParams.GetNumRootViews() + m_RootParams.GetNumRootConstants(); } Uint32 GetNumRootTables() const @@ -138,6 +138,11 @@ class PipelineResourceSignatureD3D12Impl final : public PipelineResourceSignatur return m_RootParams.GetNumRootViews(); } + Uint32 GetNumRootConstants() const + { + return m_RootParams.GetNumRootConstants(); + } + void InitSRBResourceCache(ShaderResourceCacheD3D12& ResourceCache); void CopyStaticResources(ShaderResourceCacheD3D12& ResourceCache) const; @@ -158,6 +163,9 @@ class PipelineResourceSignatureD3D12Impl final : public PipelineResourceSignatur void CommitRootViews(const CommitCacheResourcesAttribs& CommitAttribs, Uint64 BuffersMask) const; + void CommitRootConstants(const CommitCacheResourcesAttribs& CommitAttribs, + Uint64 ConstantsMask) const; + const RootParamsManager& GetRootParams() const { return m_RootParams; } // Adds resources and immutable samplers from this signature to the diff --git a/Graphics/GraphicsEngineD3D12/include/RootParamsManager.hpp b/Graphics/GraphicsEngineD3D12/include/RootParamsManager.hpp index 379838a6d0..368c476e17 100644 --- a/Graphics/GraphicsEngineD3D12/include/RootParamsManager.hpp +++ b/Graphics/GraphicsEngineD3D12/include/RootParamsManager.hpp @@ -139,6 +139,7 @@ class RootParamsManager Uint32 GetNumRootTables() const { return m_NumRootTables; } Uint32 GetNumRootViews() const { return m_NumRootViews; } + Uint32 GetNumRootConstants() const { return m_NumRootConstants; } const RootParameter& GetRootTable(Uint32 TableInd) const { @@ -152,6 +153,12 @@ class RootParamsManager return m_pRootViews[ViewInd]; } + const RootParameter& GetRootConstants(Uint32 ConstInd) const + { + VERIFY_EXPR(ConstInd < m_NumRootConstants); + return m_pRootConstants[ConstInd]; + } + // Returns the total number of resources in a given parameter group and descriptor heap type Uint32 GetParameterGroupSize(D3D12_DESCRIPTOR_HEAP_TYPE d3d12HeapType, ROOT_PARAMETER_GROUP Group) const { @@ -169,11 +176,18 @@ class RootParamsManager std::unique_ptr> m_pMemory; + // The number of root tables Uint32 m_NumRootTables = 0; - Uint32 m_NumRootViews = 0; - const RootParameter* m_pRootTables = nullptr; - const RootParameter* m_pRootViews = nullptr; + // The number of root views + Uint32 m_NumRootViews = 0; + + // The number of root constant parameters (not the number of 32-bit constants) + Uint32 m_NumRootConstants = 0; + + const RootParameter* m_pRootTables = nullptr; + const RootParameter* m_pRootViews = nullptr; + const RootParameter* m_pRootConstants = nullptr; // The total number of resources placed in descriptor tables for each heap type and parameter group type std::array, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER + 1> m_ParameterGroupSizes{}; @@ -206,6 +220,14 @@ class RootParamsBuilder D3D12_SHADER_VISIBILITY Visibility, ROOT_PARAMETER_GROUP RootType); + // Adds a new root constants parameter and returns the reference to it. + RootParameter& AddRootConstants(Uint32 RootIndex, + UINT Register, + UINT RegisterSpace, + UINT Num32BitValues, + D3D12_SHADER_VISIBILITY Visibility, + ROOT_PARAMETER_GROUP RootType); + struct RootTableData; // Adds a new root table parameter and returns the reference to it. RootTableData& AddRootTable(Uint32 RootIndex, @@ -214,6 +236,10 @@ class RootParamsBuilder Uint32 NumRangesInNewTable = 1); +#ifdef DILIGENT_DEBUG + void DbgCheckRootIndexUniqueness(Uint32 RootIndex) const; +#endif + private: struct RootTableData { @@ -231,6 +257,7 @@ class RootParamsBuilder }; std::vector m_RootTables; std::vector m_RootViews; + std::vector m_RootConstants; static constexpr int InvalidRootTableIndex = -1; diff --git a/Graphics/GraphicsEngineD3D12/include/ShaderResourceCacheD3D12.hpp b/Graphics/GraphicsEngineD3D12/include/ShaderResourceCacheD3D12.hpp index 5f6ba25026..173146b639 100644 --- a/Graphics/GraphicsEngineD3D12/include/ShaderResourceCacheD3D12.hpp +++ b/Graphics/GraphicsEngineD3D12/include/ShaderResourceCacheD3D12.hpp @@ -38,7 +38,7 @@ // m_pMemory | m_pResources, m_NumResources == m | // | | | // V | V -// | RootTable[0] | .... | RootTable[Nrt-1] | Res[0] | ... | Res[n-1] | .... | Res[0] | ... | Res[m-1] | DescriptorHeapAllocation[0] | ... +// | RootTable[0] | .... | RootTable[Nrt-1] | Res[0] | ... | Res[n-1] | .... | Res[0] | ... | Res[m-1] | Descriptor Heap Allocations | Inline constant values | // | A \ // | | \ // |________________________________________________| \RefCntAutoPtr @@ -70,6 +70,7 @@ // DESCRIPTOR_HEAP_TYPE_SAMPLER // // +// Root constant parameters use CPUDescriptorHandle.ptr to store pointer to the raw data. // // The allocation is inexed by the offset from the beginning of the root table. // Each root table is assigned the space to store exactly m_NumResources resources. @@ -138,18 +139,29 @@ class ShaderResourceCacheD3D12 : public ShaderResourceCacheBase struct MemoryRequirements { - Uint32 NumTables = 0; - Uint32 TotalResources = 0; - Uint32 NumDescriptorAllocations = 0; - size_t TotalSize = 0; + Uint32 NumTables = 0; + Uint32 TotalResources = 0; + Uint32 NumDescriptorAllocations = 0; + Uint32 TotalInlineConstantValues = 0; + size_t TotalSize = 0; }; static MemoryRequirements GetMemoryRequirements(const RootParamsManager& RootParams); + + struct InlineConstantParamInfo + { + Uint32 RootIndex = 0; + Uint32 OffsetFromTableStart = 0; + Uint32 NumValues = 0; + }; + using InlineConstantInfoVectorType = std::vector; + // Initializes resource cache to hold the given number of root tables, no descriptor space // is allocated (this is used to initialize the cache for a pipeline resource signature). - void Initialize(IMemoryAllocator& MemAllocator, - Uint32 NumTables, - const Uint32 TableSizes[]); + void Initialize(IMemoryAllocator& MemAllocator, + Uint32 NumTables, + const Uint32 TableSizes[], + const InlineConstantInfoVectorType& InlineConstants); // Initializes resource cache to hold the resources of a root parameters manager // (this is used to initialize the cache for an SRB). @@ -192,6 +204,7 @@ class ShaderResourceCacheD3D12 : public ShaderResourceCacheBase // CPU descriptor handle of a cached resource in CPU-only descriptor heap. // This handle may be null for CBVs that address the buffer range. + // For root constant parameters, ptr stores pointer to the raw data. D3D12_CPU_DESCRIPTOR_HANDLE CPUDescriptorHandle = {}; RefCntAutoPtr pObject; @@ -281,10 +294,16 @@ class ShaderResourceCacheD3D12 : public ShaderResourceCacheBase Uint32 OffsetFromTableStart, Uint32 BufferDynamicOffset); + void SetInlineConstants(Uint32 RootIndex, + Uint32 OffsetFromTableStart, + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants); + const RootTable& GetRootTable(Uint32 RootIndex) const { VERIFY_EXPR(RootIndex < m_NumTables); - return reinterpret_cast(m_pMemory.get())[RootIndex]; + return GetRootTableStorage()[RootIndex]; } Uint32 GetNumRootTables() const { return m_NumTables; } @@ -339,10 +358,16 @@ class ShaderResourceCacheD3D12 : public ShaderResourceCacheBase // Returns the bitmask indicating root views with bound non-dynamic buffers Uint64 GetNonDynamicRootBuffersMask() const { return m_NonDynamicRootBuffersMask; } + // Returns the bitmask indicating constants parameters + Uint64 GetRootConstantsMask() const { return m_RootConstantsMask; } + // Returns true if the cache contains at least one dynamic resource, i.e. // dynamic buffer or a buffer range. bool HasDynamicResources() const { return GetDynamicRootBuffersMask() != 0; } + // Returns true if the cache contains at least one inline constants parameter. + bool HasInlineConstants() const { return GetRootConstantsMask() != 0; } + #ifdef DILIGENT_DEBUG void DbgValidateDynamicBuffersMask() const; #endif @@ -351,7 +376,7 @@ class ShaderResourceCacheD3D12 : public ShaderResourceCacheBase RootTable& GetRootTable(Uint32 RootIndex) { VERIFY_EXPR(RootIndex < m_NumTables); - return reinterpret_cast(m_pMemory.get())[RootIndex]; + return GetRootTableStorage()[RootIndex]; } Resource& GetResource(Uint32 Idx) @@ -360,7 +385,33 @@ class ShaderResourceCacheD3D12 : public ShaderResourceCacheBase return reinterpret_cast(reinterpret_cast(m_pMemory.get()) + m_NumTables)[Idx]; } - size_t AllocateMemory(IMemoryAllocator& MemAllocator); + size_t AllocateMemory(IMemoryAllocator& MemAllocator, + Uint32 TotalInlineConstantValues); + + // Memory layout: + // + // | Root tables | Resources | DescriptorHeapAllocations | Inline constant values | + // + RootTable* GetRootTableStorage() + { + return reinterpret_cast(m_pMemory.get()); + } + const RootTable* GetRootTableStorage() const + { + return reinterpret_cast(m_pMemory.get()); + } + Resource* GetResourceStorage() + { + return reinterpret_cast(GetRootTableStorage() + m_NumTables); + } + DescriptorHeapAllocation* GetDescriptorAllocationStorage() + { + return reinterpret_cast(GetResourceStorage() + m_TotalResourceCount); + } + Uint32* GetInlineConstantStorage() + { + return reinterpret_cast(GetDescriptorAllocationStorage() + m_NumDescriptorAllocations); + } private: static constexpr Uint32 MaxRootTables = 64; @@ -392,6 +443,9 @@ class ShaderResourceCacheD3D12 : public ShaderResourceCacheBase // The bitmask indicating root views with bound non-dynamic buffers Uint64 m_NonDynamicRootBuffersMask = Uint64{0}; + + // The bitmask indicating root constants parameters + Uint64 m_RootConstantsMask = Uint64{0}; }; } // namespace Diligent diff --git a/Graphics/GraphicsEngineD3D12/include/ShaderVariableManagerD3D12.hpp b/Graphics/GraphicsEngineD3D12/include/ShaderVariableManagerD3D12.hpp index 7d28d04deb..4ea84db39d 100644 --- a/Graphics/GraphicsEngineD3D12/include/ShaderVariableManagerD3D12.hpp +++ b/Graphics/GraphicsEngineD3D12/include/ShaderVariableManagerD3D12.hpp @@ -104,6 +104,11 @@ class ShaderVariableManagerD3D12 : public ShaderVariableManagerBase(-1); diff --git a/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp index 13565aedfd..e8e840dc93 100644 --- a/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp @@ -370,7 +370,11 @@ void DeviceContextD3D12Impl::SetPipelineState(IPipelineState* pPipelineState) } template -void DeviceContextD3D12Impl::CommitRootTablesAndViews(RootTableInfo& RootInfo, Uint32 CommitSRBMask, CommandContext& CmdCtx) const +void DeviceContextD3D12Impl::CommitRootTablesAndViews(RootTableInfo& RootInfo, + Uint32 CommitSRBMask, + CommandContext& CmdCtx, + bool DynamicBuffersIntact, + bool InlineConstantsIntact) const { const RootSignatureD3D12& RootSig = m_pPipelineState->GetRootSignature(); @@ -397,7 +401,9 @@ void DeviceContextD3D12Impl::CommitRootTablesAndViews(RootTableInfo& RootInfo, U CommitAttribs.pResourceCache = pResourceCache; CommitAttribs.BaseRootIndex = RootSig.GetBaseRootIndex(sign); - if ((RootInfo.StaleSRBMask & SignBit) != 0) + + const bool SRBStale = (RootInfo.StaleSRBMask & SignBit) != 0; + if (SRBStale) { // Commit root tables for stale SRBs only pSignature->CommitRootTables(CommitAttribs); @@ -410,7 +416,10 @@ void DeviceContextD3D12Impl::CommitRootTablesAndViews(RootTableInfo& RootInfo, U DEV_CHECK_ERR((RootInfo.DynamicSRBMask & SignBit) != 0, "There are dynamic root buffers in the cache, but the bit in DynamicSRBMask is not set. This may indicate that resources " "in the cache have changed, but the SRB has not been committed before the draw/dispatch command."); - pSignature->CommitRootViews(CommitAttribs, DynamicRootBuffersMask); + if (SRBStale || !DynamicBuffersIntact) + { + pSignature->CommitRootViews(CommitAttribs, DynamicRootBuffersMask); + } } else { @@ -418,6 +427,25 @@ void DeviceContextD3D12Impl::CommitRootTablesAndViews(RootTableInfo& RootInfo, U "There are no dynamic root buffers in the cache, but the bit in DynamicSRBMask is set. This may indicate that resources " "in the cache have changed, but the SRB has not been committed before the draw/dispatch command."); } + + if (Uint64 RootConstantsMask = pResourceCache->GetRootConstantsMask()) + { + VERIFY((RootInfo.InlineConstantsSRBMask & SignBit) != 0, + "There are root constants in the cache, but the bit in InlineConstantsSRBMask is not set. " + "This may be a bug because root constants mask in the cache never changes after SRB creation, " + "while RootInfo.InlineConstantsSRBMask is initialized when SRB is committed."); + if (SRBStale || !InlineConstantsIntact) + { + pSignature->CommitRootConstants(CommitAttribs, RootConstantsMask); + } + } + else + { + VERIFY((RootInfo.InlineConstantsSRBMask & SignBit) == 0, + "There are no root constants in the cache, but the bit in InlineConstantsSRBMask is set. " + "This may be a bug because root constants mask in the cache never changes after SRB creation, " + "while RootInfo.InlineConstantsSRBMask is initialized when SRB is committed."); + } } VERIFY_EXPR((CommitSRBMask & RootInfo.ActiveSRBMask) == 0); @@ -629,9 +657,11 @@ void DeviceContextD3D12Impl::PrepareForDraw(GraphicsContext& GraphCtx, DRAW_FLAG #ifdef DILIGENT_DEVELOPMENT DvpValidateCommittedShaderResources(RootInfo); #endif - if (Uint32 CommitSRBMask = RootInfo.GetCommitMask(Flags & DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT)) + const bool DynamicBuffersIntact = (Flags & DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT) != 0; + const bool InlineConstantsIntact = (Flags & DRAW_FLAG_INLINE_CONSTANTS_INTACT) != 0; + if (Uint32 CommitSRBMask = RootInfo.GetCommitMask(DynamicBuffersIntact, InlineConstantsIntact)) { - CommitRootTablesAndViews(RootInfo, CommitSRBMask, GraphCtx); + CommitRootTablesAndViews(RootInfo, CommitSRBMask, GraphCtx, DynamicBuffersIntact, InlineConstantsIntact); } #ifdef NTDDI_WIN10_19H1 diff --git a/Graphics/GraphicsEngineD3D12/src/PipelineResourceSignatureD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/PipelineResourceSignatureD3D12Impl.cpp index c61bc1bee6..9d06cb95aa 100644 --- a/Graphics/GraphicsEngineD3D12/src/PipelineResourceSignatureD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/PipelineResourceSignatureD3D12Impl.cpp @@ -193,6 +193,8 @@ void PipelineResourceSignatureD3D12Impl::AllocateRootParameters(const bool IsSer // Cache table sizes for static resources std::array StaticResCacheTblSizes{}; + ShaderResourceCacheD3D12::InlineConstantInfoVectorType StaticResInlineConstInfo; + // Allocate registers for immutable samplers first for (Uint32 i = 0; i < m_Desc.NumImmutableSamplers; ++i) { @@ -244,6 +246,10 @@ void PipelineResourceSignatureD3D12Impl::AllocateRootParameters(const bool IsSer // Do not allocate resource slot for immutable samplers that are also defined as resource if (!(ResDesc.ResourceType == SHADER_RESOURCE_TYPE_SAMPLER && SrcImmutableSamplerInd != InvalidImmutableSamplerIndex)) { + // For inline constants, ArraySize holds the number of 4-byte constants, + // while the resource occupies only one register slot. + const Uint32 ArraySize = ResDesc.GetArraySize(); + if (ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) { // Use artificial root signature: @@ -253,7 +259,18 @@ void PipelineResourceSignatureD3D12Impl::AllocateRootParameters(const bool IsSer // Samplers at root index D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER (3) SigRootIndex = d3d12DescriptorRangeType; SigOffsetFromTableStart = StaticResCacheTblSizes[SigRootIndex]; - StaticResCacheTblSizes[SigRootIndex] += ResDesc.ArraySize; + + if ((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0) + { + ShaderResourceCacheD3D12::InlineConstantParamInfo InlineConstInfo; + InlineConstInfo.RootIndex = SigRootIndex; + InlineConstInfo.OffsetFromTableStart = SigOffsetFromTableStart; + // For inline constants, ArraySize holds the number of 4-byte constants. + InlineConstInfo.NumValues = ResDesc.ArraySize; + StaticResInlineConstInfo.push_back(InlineConstInfo); + } + + StaticResCacheTblSizes[SigRootIndex] += ArraySize; } if (IsRTSizedArray) @@ -267,7 +284,7 @@ void PipelineResourceSignatureD3D12Impl::AllocateRootParameters(const bool IsSer // Normal resources go into space 0. Space = 0; Register = NumResources[d3d12DescriptorRangeType]; - NumResources[d3d12DescriptorRangeType] += ResDesc.ArraySize; + NumResources[d3d12DescriptorRangeType] += ArraySize; } const PIPELINE_RESOURCE_FLAGS dbgValidResourceFlags = GetValidPipelineResourceFlags(ResDesc.ResourceType); @@ -275,7 +292,7 @@ void PipelineResourceSignatureD3D12Impl::AllocateRootParameters(const bool IsSer const bool UseDynamicOffset = (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS) == 0; const bool IsFormattedBuffer = (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_FORMATTED_BUFFER) != 0; - const bool IsArray = ResDesc.ArraySize != 1; + const bool IsArray = ArraySize != 1; d3d12RootParamType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; static_assert(SHADER_RESOURCE_TYPE_LAST == SHADER_RESOURCE_TYPE_ACCEL_STRUCT, "Please update the switch below to handle the new shader resource type"); @@ -283,7 +300,15 @@ void PipelineResourceSignatureD3D12Impl::AllocateRootParameters(const bool IsSer { case SHADER_RESOURCE_TYPE_CONSTANT_BUFFER: VERIFY(!IsFormattedBuffer, "Constant buffers can't be labeled as formatted. This error should've been caught by ValidatePipelineResourceSignatureDesc()."); - d3d12RootParamType = UseDynamicOffset && !IsArray ? D3D12_ROOT_PARAMETER_TYPE_CBV : D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + if ((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0) + { + VERIFY(UseDynamicOffset, "NO_DYNAMIC_BUFFERS flag is not compatible with INLINE_CONSTANTS. This error should've been caught by ValidatePipelineResourceSignatureDesc()."); + d3d12RootParamType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + } + else + { + d3d12RootParamType = UseDynamicOffset && !IsArray ? D3D12_ROOT_PARAMETER_TYPE_CBV : D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + } break; case SHADER_RESOURCE_TYPE_BUFFER_SRV: @@ -299,8 +324,10 @@ void PipelineResourceSignatureD3D12Impl::AllocateRootParameters(const bool IsSer d3d12RootParamType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; } - ParamsBuilder.AllocateResourceSlot(ResDesc.ShaderStages, ResDesc.VarType, d3d12RootParamType, - d3d12DescriptorRangeType, ResDesc.ArraySize, Register, Space, + ParamsBuilder.AllocateResourceSlot(ResDesc.ShaderStages, ResDesc.VarType, + d3d12RootParamType, d3d12DescriptorRangeType, + ResDesc.ArraySize, // Array size or the number of inline constants + Register, Space, SRBRootIndex, SRBOffsetFromTableStart); } else @@ -327,7 +354,7 @@ void PipelineResourceSignatureD3D12Impl::AllocateRootParameters(const bool IsSer SigRootIndex, SigOffsetFromTableStart, SrcImmutableSamplerInd != InvalidImmutableSamplerIndex, - d3d12RootParamType // + d3d12RootParamType, }; } else @@ -356,7 +383,7 @@ void PipelineResourceSignatureD3D12Impl::AllocateRootParameters(const bool IsSer if (GetNumStaticResStages() > 0) { - m_pStaticResCache->Initialize(GetRawAllocator(), static_cast(StaticResCacheTblSizes.size()), StaticResCacheTblSizes.data()); + m_pStaticResCache->Initialize(GetRawAllocator(), static_cast(StaticResCacheTblSizes.size()), StaticResCacheTblSizes.data(), StaticResInlineConstInfo); } else { @@ -416,27 +443,54 @@ void PipelineResourceSignatureD3D12Impl::CopyStaticResources(ShaderResourceCache Uint32 SrcCacheOffset = Attr.OffsetFromTableStart(SrcCacheType); Uint32 DstCacheOffset = Attr.OffsetFromTableStart(DstCacheType); - for (Uint32 ArrInd = 0; ArrInd < ResDesc.ArraySize; ++ArrInd, ++SrcCacheOffset, ++DstCacheOffset) + if (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) { - const ShaderResourceCacheD3D12::Resource& SrcRes = SrcRootTable.GetResource(SrcCacheOffset); - if (!SrcRes.pObject) - { - if (DstCacheType == ResourceCacheContentType::SRB) - LOG_ERROR_MESSAGE("No resource is assigned to static shader variable '", GetShaderResourcePrintName(ResDesc, ArrInd), "' in pipeline resource signature '", m_Desc.Name, "'."); - continue; - } + VERIFY(ResDesc.ResourceType == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, "Only constant buffers can be marked as INLINE_CONSTANTS."); + const ShaderResourceCacheD3D12::Resource& SrcRes = SrcRootTable.GetResource(SrcCacheOffset); const ShaderResourceCacheD3D12::Resource& DstRes = DstRootTable.GetResource(DstCacheOffset); - if (DstRes.pObject != SrcRes.pObject) - { - DEV_CHECK_ERR(DstRes.pObject == nullptr, "Static resource has already been initialized, and the new resource does not match previously assigned resource."); - DstResourceCache.CopyResource(d3d12Device, DstRootIndex, DstCacheOffset, SrcRes); - } - else + VERIFY(SrcRes.CPUDescriptorHandle.ptr != 0, "Inline constant resource must have valid CPU descriptor handle."); + VERIFY(DstRes.CPUDescriptorHandle.ptr != 0, "Inline constant resource must have valid CPU descriptor handle."); + + // For inline constants, array size is the number of 4-byte constant values + const Uint32 NumConstantValues = ResDesc.ArraySize; + VERIFY(SrcRes.BufferRangeSize == NumConstantValues * sizeof(Uint32), + "Source inline constant buffer range size (", SrcRes.BufferRangeSize, + ") does not match the expected size (", NumConstantValues * sizeof(Uint32), ")."); + VERIFY(DstRes.BufferRangeSize == NumConstantValues * sizeof(Uint32), + "Destination inline constant buffer range size (", DstRes.BufferRangeSize, + ") does not match the expected size (", NumConstantValues * sizeof(Uint32), ")."); + + // Copy the actual constant values. + // For inline constants, CPUDescriptorHandle.ptr stores the pointer to the constant values buffer. + memcpy(reinterpret_cast(DstRes.CPUDescriptorHandle.ptr), + reinterpret_cast(SrcRes.CPUDescriptorHandle.ptr), + NumConstantValues * sizeof(Uint32)); + } + else + { + for (Uint32 ArrInd = 0; ArrInd < ResDesc.ArraySize; ++ArrInd, ++SrcCacheOffset, ++DstCacheOffset) { - VERIFY_EXPR(DstRes.pObject == SrcRes.pObject); - VERIFY_EXPR(DstRes.Type == SrcRes.Type); - VERIFY_EXPR(DstRes.CPUDescriptorHandle.ptr == SrcRes.CPUDescriptorHandle.ptr); + const ShaderResourceCacheD3D12::Resource& SrcRes = SrcRootTable.GetResource(SrcCacheOffset); + if (!SrcRes.pObject) + { + if (DstCacheType == ResourceCacheContentType::SRB) + LOG_ERROR_MESSAGE("No resource is assigned to static shader variable '", GetShaderResourcePrintName(ResDesc, ArrInd), "' in pipeline resource signature '", m_Desc.Name, "'."); + continue; + } + + const ShaderResourceCacheD3D12::Resource& DstRes = DstRootTable.GetResource(DstCacheOffset); + if (DstRes.pObject != SrcRes.pObject) + { + DEV_CHECK_ERR(DstRes.pObject == nullptr, "Static resource has already been initialized, and the new resource does not match previously assigned resource."); + DstResourceCache.CopyResource(d3d12Device, DstRootIndex, DstCacheOffset, SrcRes); + } + else + { + VERIFY_EXPR(DstRes.pObject == SrcRes.pObject); + VERIFY_EXPR(DstRes.Type == SrcRes.Type); + VERIFY_EXPR(DstRes.CPUDescriptorHandle.ptr == SrcRes.CPUDescriptorHandle.ptr); + } } } } @@ -522,6 +576,44 @@ void PipelineResourceSignatureD3D12Impl::CommitRootViews(const CommitCacheResour } } +void PipelineResourceSignatureD3D12Impl::CommitRootConstants(const CommitCacheResourcesAttribs& CommitAttribs, + Uint64 ConstantsMask) const +{ + VERIFY_EXPR(CommitAttribs.pResourceCache != nullptr); + + while (ConstantsMask != 0) + { + // Root constants are stored as raw data in the CPU descriptor handle pointer of + // the single-resource root table. + + const Uint64 RootIndBit = ExtractLSB(ConstantsMask); + const Uint32 RootInd = PlatformMisc::GetLSB(RootIndBit); + const ShaderResourceCacheD3D12::RootTable& CacheTbl = CommitAttribs.pResourceCache->GetRootTable(RootInd); + const Uint32& BaseRootIndex = CommitAttribs.BaseRootIndex; + + VERIFY_EXPR(CacheTbl.GetSize() == 1); + const ShaderResourceCacheD3D12::Resource& Res = CacheTbl.GetResource(0); + VERIFY_EXPR(Res.Type == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER); + VERIFY(Res.IsNull(), "There should be no resource bound for root constants as they contain raw data."); + + const Uint32* pConstants = reinterpret_cast(Res.CPUDescriptorHandle.ptr); + VERIFY(pConstants != nullptr, "Resources used to store root constants must have valid pointer to the data."); + // Get the number of 4-byte constants from the buffer range size + const Uint32 NumConstants = static_cast(Res.BufferRangeSize / sizeof(Uint32)); + VERIFY_EXPR(NumConstants > 0); + + ID3D12GraphicsCommandList* const pd3d12CmdList = CommitAttribs.CmdCtx.GetCommandList(); + if (CommitAttribs.IsCompute) + { + pd3d12CmdList->SetComputeRoot32BitConstants(BaseRootIndex + RootInd, NumConstants, pConstants, 0); + } + else + { + pd3d12CmdList->SetGraphicsRoot32BitConstants(BaseRootIndex + RootInd, NumConstants, pConstants, 0); + } + } +} + void PipelineResourceSignatureD3D12Impl::CommitRootTables(const CommitCacheResourcesAttribs& CommitAttribs) const { VERIFY_EXPR(CommitAttribs.pResourceCache != nullptr); @@ -646,12 +738,16 @@ void PipelineResourceSignatureD3D12Impl::UpdateShaderResourceBindingMap(Resource if ((ResDesc.ShaderStages & ShaderStage) != 0) { + VERIFY((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) == 0 || ResDesc.ResourceType == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, + "Only constant buffers can be marked as INLINE_CONSTANTS. This error should've been caught by ValidatePipelineResourceSignatureDesc()."); + ResourceBinding::BindInfo BindInfo // { Attribs.Register, Attribs.Space + BaseRegisterSpace, - ResDesc.ArraySize, - ResDesc.ResourceType // + ResDesc.GetArraySize(), // For inline constants, ArraySize holds the number of 4-byte constants, + // while the resource occupies only one register. + ResDesc.ResourceType, }; bool IsUnique = ResourceMap.emplace(HashMapStringKey{ResDesc.Name}, BindInfo).second; VERIFY(IsUnique, "Shader resource '", ResDesc.Name, @@ -725,7 +821,7 @@ bool PipelineResourceSignatureD3D12Impl::DvpValidateCommittedResource(const Devi const PipelineResourceDesc& ResDesc = GetResourceDesc(ResIndex); const PipelineResourceAttribsD3D12& ResAttribs = GetResourceAttribs(ResIndex); VERIFY_EXPR(strcmp(ResDesc.Name, D3DAttribs.Name) == 0); - VERIFY_EXPR(D3DAttribs.BindCount <= ResDesc.ArraySize); + VERIFY_EXPR(D3DAttribs.BindCount <= ResDesc.GetArraySize()); if ((ResDesc.ResourceType == SHADER_RESOURCE_TYPE_SAMPLER) && ResAttribs.IsImmutableSamplerAssigned()) return true; @@ -740,6 +836,15 @@ bool PipelineResourceSignatureD3D12Impl::DvpValidateCommittedResource(const Devi for (Uint32 ArrIndex = 0; ArrIndex < D3DAttribs.BindCount; ++ArrIndex) { const ShaderResourceCacheD3D12::Resource& CachedRes = RootTable.GetResource(OffsetFromTableStart + ArrIndex); + if (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + { + VERIFY(ResDesc.ResourceType == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, "Only constant buffers can be marked as INLINE_CONSTANTS."); + VERIFY(CachedRes.IsNull(), "Inline constants should not have any resource bound to them."); + VERIFY(CachedRes.CPUDescriptorHandle.ptr != 0, "Inline constant resource must have valid CPU descriptor handle."); + // Inline constants are not actual resources + continue; + } + if (CachedRes.IsNull()) { LOG_ERROR_MESSAGE("No resource is bound to variable '", GetShaderResourcePrintName(D3DAttribs.Name, D3DAttribs.BindCount, ArrIndex), diff --git a/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp index 63f0bb8459..5bf117a939 100644 --- a/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp @@ -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"); @@ -380,7 +380,14 @@ PipelineResourceSignatureDescWrapper PipelineStateD3D12Impl::GetDefaultResourceS const SHADER_RESOURCE_TYPE ResType = Attribs.GetShaderResourceType(); const PIPELINE_RESOURCE_FLAGS ResFlags = Attribs.GetPipelineResourceFlags() | ShaderVariableFlagsToPipelineResourceFlags(VarDesc.Flags); - SignDesc.AddResource(VarDesc.ShaderStages, Attribs.Name, Attribs.BindCount, ResType, VarDesc.Type, ResFlags); + + Uint32 ArraySize = Attribs.BindCount; + if (ResFlags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + { + VERIFY(ResFlags == PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS, "INLINE_CONSTANTS flag cannot be combined with other flags."); + ArraySize = Attribs.GetInlineConstantCountOrThrow(pShader->GetDesc().Name); + } + SignDesc.AddResource(VarDesc.ShaderStages, Attribs.Name, ArraySize, ResType, VarDesc.Type, ResFlags); } else { diff --git a/Graphics/GraphicsEngineD3D12/src/RootParamsManager.cpp b/Graphics/GraphicsEngineD3D12/src/RootParamsManager.cpp index 81611b13cb..80d3cba945 100644 --- a/Graphics/GraphicsEngineD3D12/src/RootParamsManager.cpp +++ b/Graphics/GraphicsEngineD3D12/src/RootParamsManager.cpp @@ -144,13 +144,14 @@ size_t RootParameter::GetHash() const RootParamsManager::~RootParamsManager() { - static_assert(std::is_trivially_destructible::value, "Destructors for m_pRootTables and m_pRootViews are required"); + static_assert(std::is_trivially_destructible::value, "m_pRootTables, m_pRootViews and m_pRootConstants must be manually destroyed"); } bool RootParamsManager::operator==(const RootParamsManager& RootParams) const noexcept { if (m_NumRootTables != RootParams.m_NumRootTables || - m_NumRootViews != RootParams.m_NumRootViews) + m_NumRootViews != RootParams.m_NumRootViews || + m_NumRootConstants != RootParams.m_NumRootConstants) return false; for (Uint32 rv = 0; rv < m_NumRootViews; ++rv) @@ -169,6 +170,14 @@ bool RootParamsManager::operator==(const RootParamsManager& RootParams) const no return false; } + for (Uint32 rc = 0; rc < m_NumRootConstants; ++rc) + { + const RootParameter& RC0 = GetRootConstants(rc); + const RootParameter& RC1 = RootParams.GetRootConstants(rc); + if (RC0 != RC1) + return false; + } + return true; } @@ -230,6 +239,13 @@ void RootParamsManager::Validate() const VERIFY(RootView.TableOffsetInGroupAllocation == RootParameter::InvalidTableOffsetInGroupAllocation, "Root views must not be assigned to descriptor table allocations."); } + + for (Uint32 i = 0; i < GetNumRootConstants(); ++i) + { + const RootParameter& RootConst = GetRootConstants(i); + VERIFY(RootConst.TableOffsetInGroupAllocation == RootParameter::InvalidTableOffsetInGroupAllocation, + "Root constants must not be assigned to descriptor table allocations."); + } } #endif @@ -243,6 +259,18 @@ RootParamsBuilder::RootParamsBuilder() Map.fill(InvalidRootTableIndex); } +#ifdef DILIGENT_DEBUG +void RootParamsBuilder::DbgCheckRootIndexUniqueness(Uint32 RootIndex) const +{ + for (const RootTableData& RootTbl : m_RootTables) + VERIFY(RootTbl.RootIndex != RootIndex, "Index ", RootIndex, " is already used by another root table"); + for (const RootParameter& RootView : m_RootViews) + VERIFY(RootView.RootIndex != RootIndex, "Index ", RootIndex, " is already used by another root view"); + for (const RootParameter& RootConst : m_RootConstants) + VERIFY(RootConst.RootIndex != RootIndex, "Index ", RootIndex, " is already used by another root constant"); +} +#endif + RootParameter& RootParamsBuilder::AddRootView(D3D12_ROOT_PARAMETER_TYPE ParameterType, Uint32 RootIndex, UINT Register, @@ -255,11 +283,7 @@ RootParameter& RootParamsBuilder::AddRootView(D3D12_ROOT_PARAMETER_TYPE Paramete ParameterType == D3D12_ROOT_PARAMETER_TYPE_SRV || ParameterType == D3D12_ROOT_PARAMETER_TYPE_UAV), "Unexpected parameter type SBV, SRV or UAV is expected"); - - for (const RootTableData& RootTbl : m_RootTables) - VERIFY(RootTbl.RootIndex != RootIndex, "Index ", RootIndex, " is already used by another root table"); - for (const RootParameter& RootView : m_RootViews) - VERIFY(RootView.RootIndex != RootIndex, "Index ", RootIndex, " is already used by another root view"); + DbgCheckRootIndexUniqueness(RootIndex); #endif D3D12_ROOT_PARAMETER d3d12RootParam{ParameterType, {}, Visibility}; @@ -270,6 +294,26 @@ RootParameter& RootParamsBuilder::AddRootView(D3D12_ROOT_PARAMETER_TYPE Paramete return m_RootViews.back(); } +RootParameter& RootParamsBuilder::AddRootConstants(Uint32 RootIndex, + UINT Register, + UINT RegisterSpace, + UINT Num32BitValues, + D3D12_SHADER_VISIBILITY Visibility, + ROOT_PARAMETER_GROUP RootType) +{ +#ifdef DILIGENT_DEBUG + DbgCheckRootIndexUniqueness(RootIndex); +#endif + + D3D12_ROOT_PARAMETER d3d12RootParam{D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS, {}, Visibility}; + d3d12RootParam.Constants.ShaderRegister = Register; + d3d12RootParam.Constants.RegisterSpace = RegisterSpace; + d3d12RootParam.Constants.Num32BitValues = Num32BitValues; + m_RootConstants.emplace_back(RootIndex, RootType, d3d12RootParam); + + return m_RootConstants.back(); +} + RootParamsBuilder::RootTableData::RootTableData(Uint32 _RootIndex, D3D12_SHADER_VISIBILITY _Visibility, ROOT_PARAMETER_GROUP _Group, @@ -305,10 +349,7 @@ RootParamsBuilder::RootTableData& RootParamsBuilder::AddRootTable(Uint32 Uint32 NumRangesInNewTable) { #ifdef DILIGENT_DEBUG - for (const RootTableData& RootTbl : m_RootTables) - VERIFY(RootTbl.RootIndex != RootIndex, "Index ", RootIndex, " is already used by another root table"); - for (const RootParameter& RootView : m_RootViews) - VERIFY(RootView.RootIndex != RootIndex, "Index ", RootIndex, " is already used by another root view"); + DbgCheckRootIndexUniqueness(RootIndex); #endif m_RootTables.emplace_back(RootIndex, Visibility, Group, NumRangesInNewTable); @@ -330,8 +371,8 @@ void RootParamsBuilder::AllocateResourceSlot(SHADER_TYPE Shade const D3D12_SHADER_VISIBILITY ShaderVisibility = ShaderStagesToD3D12ShaderVisibility(ShaderStages); const ROOT_PARAMETER_GROUP ParameterGroup = VariableTypeToRootParameterGroup(VariableType); - // Get the next available root index past all allocated tables and root views - RootIndex = static_cast(m_RootTables.size() + m_RootViews.size()); + // Get the next available root index past all allocated tables, root views and root constants + RootIndex = static_cast(m_RootTables.size() + m_RootViews.size() + m_RootConstants.size()); if (RootParameterType == D3D12_ROOT_PARAMETER_TYPE_CBV || RootParameterType == D3D12_ROOT_PARAMETER_TYPE_SRV || @@ -345,6 +386,14 @@ void RootParamsBuilder::AllocateResourceSlot(SHADER_TYPE Shade // Add new root view to existing root parameters AddRootView(RootParameterType, RootIndex, Register, Space, ShaderVisibility, ParameterGroup); } + else if (RootParameterType == D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS) + { + OffsetFromTableStart = 0; + + // Add new 32-bit constants parameter to existing root parameters. + // ArraySize parameter specifies the number of 32-bit values in this case. + AddRootConstants(RootIndex, Register, Space, ArraySize, ShaderVisibility, ParameterGroup); + } else if (RootParameterType == D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE) { const bool IsSampler = (RangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER); @@ -406,15 +455,17 @@ void RootParamsBuilder::InitializeMgr(IMemoryAllocator& MemAllocator, RootParams { VERIFY(!ParamsMgr.m_pMemory, "Params manager has already been initialized!"); - Uint32& NumRootTables = ParamsMgr.m_NumRootTables; - Uint32& NumRootViews = ParamsMgr.m_NumRootViews; + Uint32& NumRootTables = ParamsMgr.m_NumRootTables; + Uint32& NumRootViews = ParamsMgr.m_NumRootViews; + Uint32& NumRootConstants = ParamsMgr.m_NumRootConstants; - NumRootTables = static_cast(m_RootTables.size()); - NumRootViews = static_cast(m_RootViews.size()); - if (NumRootTables == 0 && NumRootViews == 0) + NumRootTables = static_cast(m_RootTables.size()); + NumRootViews = static_cast(m_RootViews.size()); + NumRootConstants = static_cast(m_RootConstants.size()); + if (NumRootTables == 0 && NumRootViews == 0 && NumRootConstants == 0) return; - const size_t TotalRootParamsCount = m_RootTables.size() + m_RootViews.size(); + const size_t TotalRootParamsCount = m_RootTables.size() + m_RootViews.size() + m_RootConstants.size(); size_t TotalRangesCount = 0; for (RootTableData& Tbl : m_RootTables) @@ -437,7 +488,8 @@ void RootParamsBuilder::InitializeMgr(IMemoryAllocator& MemAllocator, RootParams // Note: this order is more efficient than views->tables->ranges RootParameter* const pRootTables = reinterpret_cast(ParamsMgr.m_pMemory.get()); RootParameter* const pRootViews = pRootTables + NumRootTables; - D3D12_DESCRIPTOR_RANGE* const pDescriptorRanges = reinterpret_cast(pRootViews + NumRootViews); + RootParameter* const pRootConstants = pRootViews + NumRootViews; + D3D12_DESCRIPTOR_RANGE* const pDescriptorRanges = reinterpret_cast(pRootConstants + NumRootConstants); // Copy descriptor tables D3D12_DESCRIPTOR_RANGE* pCurrDescrRangePtr = pDescriptorRanges; @@ -484,8 +536,20 @@ void RootParamsBuilder::InitializeMgr(IMemoryAllocator& MemAllocator, RootParams "Unexpected parameter type: SBV, SRV or UAV is expected"); new (pRootViews + rv) RootParameter{SrcView.RootIndex, SrcView.Group, d3d12RootParam}; } - ParamsMgr.m_pRootTables = NumRootTables != 0 ? pRootTables : nullptr; - ParamsMgr.m_pRootViews = NumRootViews != 0 ? pRootViews : nullptr; + + // Copy root constants + for (Uint32 rc = 0; rc < NumRootConstants; ++rc) + { + const RootParameter& SrcConst = m_RootConstants[rc]; + const D3D12_ROOT_PARAMETER& d3d12RootParam = SrcConst.d3d12RootParam; + VERIFY(d3d12RootParam.ParameterType == D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS, + "Unexpected parameter type: 32-bit constants is expected"); + new (pRootConstants + rc) RootParameter{SrcConst.RootIndex, SrcConst.Group, d3d12RootParam}; + } + + ParamsMgr.m_pRootTables = NumRootTables != 0 ? pRootTables : nullptr; + ParamsMgr.m_pRootViews = NumRootViews != 0 ? pRootViews : nullptr; + ParamsMgr.m_pRootConstants = NumRootConstants != 0 ? pRootConstants : nullptr; #ifdef DILIGENT_DEBUG ParamsMgr.Validate(); diff --git a/Graphics/GraphicsEngineD3D12/src/RootSignature.cpp b/Graphics/GraphicsEngineD3D12/src/RootSignature.cpp index 54c1d04f9e..7231b5f415 100644 --- a/Graphics/GraphicsEngineD3D12/src/RootSignature.cpp +++ b/Graphics/GraphicsEngineD3D12/src/RootSignature.cpp @@ -78,7 +78,7 @@ RootSignatureD3D12::RootSignatureD3D12(IReferenceCounters* const RootParamsManager& RootParams = pSignature->GetRootParams(); SignInfo.BaseRootIndex = TotalParams; - TotalParams += RootParams.GetNumRootTables() + RootParams.GetNumRootViews(); + TotalParams += RootParams.GetNumRootTables() + RootParams.GetNumRootViews() + RootParams.GetNumRootConstants(); for (Uint32 rt = 0; rt < RootParams.GetNumRootTables(); ++rt) { @@ -166,6 +166,20 @@ RootSignatureD3D12::RootSignatureD3D12(IReferenceCounters* d3d12Parameters[RootIndex].Descriptor.RegisterSpace += BaseRegisterSpace; } + for (Uint32 rc = 0; rc < RootParams.GetNumRootConstants(); ++rc) + { + const RootParameter& RootConsts = RootParams.GetRootConstants(rc); + const D3D12_ROOT_PARAMETER& d3d12SrcParam = RootConsts.d3d12RootParam; + const Uint32 RootIndex = SignInfo.BaseRootIndex + RootConsts.RootIndex; + VERIFY(d3d12SrcParam.ParameterType == D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS, "Root 32-bit constants is expected"); + + MaxSpaceUsed = std::max(MaxSpaceUsed, d3d12SrcParam.Constants.RegisterSpace); + + d3d12Parameters[RootIndex] = d3d12SrcParam; + // Offset register space value by the base register space of the current resource signature. + d3d12Parameters[RootIndex].Constants.RegisterSpace += BaseRegisterSpace; + } + for (Uint32 samp = 0, SampCount = pSignature->GetImmutableSamplerCount(); samp < SampCount; ++samp) { const ImmutableSamplerAttribsD3D12& SampAttr = pSignature->GetImmutableSamplerAttribs(samp); @@ -191,8 +205,8 @@ RootSignatureD3D12::RootSignatureD3D12(IReferenceCounters* SamDesc.MaxLOD, SampAttr.ShaderRegister + ArrInd, SampAttr.RegisterSpace + BaseRegisterSpace, - ShaderVisibility // - } // + ShaderVisibility, + } // ); } } diff --git a/Graphics/GraphicsEngineD3D12/src/ShaderResourceCacheD3D12.cpp b/Graphics/GraphicsEngineD3D12/src/ShaderResourceCacheD3D12.cpp index 5ae744f8cf..1b7ee3785a 100644 --- a/Graphics/GraphicsEngineD3D12/src/ShaderResourceCacheD3D12.cpp +++ b/Graphics/GraphicsEngineD3D12/src/ShaderResourceCacheD3D12.cpp @@ -45,6 +45,7 @@ ShaderResourceCacheD3D12::MemoryRequirements ShaderResourceCacheD3D12::GetMemory { const Uint32 NumRootTables = RootParams.GetNumRootTables(); const Uint32 NumRootViews = RootParams.GetNumRootViews(); + const Uint32 NumRootConsts = RootParams.GetNumRootConstants(); MemoryRequirements MemReqs; @@ -61,17 +62,26 @@ ShaderResourceCacheD3D12::MemoryRequirements ShaderResourceCacheD3D12::GetMemory } } } - // Root views' resources are stored in one-descriptor tables + // Root views and root constants resources are stored in one-descriptor tables MemReqs.TotalResources += NumRootViews; + MemReqs.TotalResources += NumRootConsts; static_assert(sizeof(RootTable) % sizeof(void*) == 0, "sizeof(RootTable) is not aligned by the sizeof(void*)"); static_assert(sizeof(Resource) % sizeof(void*) == 0, "sizeof(Resource) is not aligned by the sizeof(void*)"); - MemReqs.NumTables = NumRootTables + NumRootViews; + MemReqs.NumTables = NumRootTables + NumRootViews + NumRootConsts; + + MemReqs.TotalInlineConstantValues = 0; + for (Uint32 i = 0; i < NumRootConsts; ++i) + { + const RootParameter& RootConsts = RootParams.GetRootConstants(i); + MemReqs.TotalInlineConstantValues += RootConsts.d3d12RootParam.Constants.Num32BitValues; + } MemReqs.TotalSize = (MemReqs.NumTables * sizeof(RootTable) + MemReqs.TotalResources * sizeof(Resource) + - MemReqs.NumDescriptorAllocations * sizeof(DescriptorHeapAllocation)); + MemReqs.NumDescriptorAllocations * sizeof(DescriptorHeapAllocation) + + MemReqs.TotalInlineConstantValues * sizeof(Uint32)); return MemReqs; } @@ -82,21 +92,23 @@ ShaderResourceCacheD3D12::MemoryRequirements ShaderResourceCacheD3D12::GetMemory // m_pMemory | m_pResources, m_NumResources | // | | | // V | V -// | RootTable[0] | .... | RootTable[Nrt-1] | Res[0] | ... | Res[n-1] | .... | Res[0] | ... | Res[m-1] | DescriptorHeapAllocation[0] | ... +// | RootTable[0] | .... | RootTable[Nrt-1] | Res[0] | ... | Res[n-1] | .... | Res[0] | ... | Res[m-1] | Descriptor Heap Allocations | Inline cnstant values | // | A // | | // |________________________________________________| // m_pResources, m_NumResources // -size_t ShaderResourceCacheD3D12::AllocateMemory(IMemoryAllocator& MemAllocator) +size_t ShaderResourceCacheD3D12::AllocateMemory(IMemoryAllocator& MemAllocator, + Uint32 TotalInlineConstantValues) { VERIFY(!m_pMemory, "Memory has already been allocated"); const size_t MemorySize = (m_NumTables * sizeof(RootTable) + m_TotalResourceCount * sizeof(Resource) + - m_NumDescriptorAllocations * sizeof(DescriptorHeapAllocation)); + m_NumDescriptorAllocations * sizeof(DescriptorHeapAllocation) + + TotalInlineConstantValues * sizeof(Uint32)); if (MemorySize > 0) { @@ -105,12 +117,13 @@ size_t ShaderResourceCacheD3D12::AllocateMemory(IMemoryAllocator& MemAllocator) STDDeleter(MemAllocator) // }; - RootTable* const pTables = reinterpret_cast(m_pMemory.get()); - Resource* const pResources = reinterpret_cast(pTables + m_NumTables); - m_DescriptorAllocations = m_NumDescriptorAllocations > 0 ? reinterpret_cast(pResources + m_TotalResourceCount) : nullptr; + Resource* const pResources = GetResourceStorage(); + m_DescriptorAllocations = m_NumDescriptorAllocations > 0 ? GetDescriptorAllocationStorage() : nullptr; for (Uint32 res = 0; res < m_TotalResourceCount; ++res) + { new (pResources + res) Resource{}; + } for (Uint32 i = 0; i < m_NumDescriptorAllocations; ++i) new (m_DescriptorAllocations + i) DescriptorHeapAllocation{}; @@ -119,9 +132,10 @@ size_t ShaderResourceCacheD3D12::AllocateMemory(IMemoryAllocator& MemAllocator) return MemorySize; } -void ShaderResourceCacheD3D12::Initialize(IMemoryAllocator& MemAllocator, - Uint32 NumTables, - const Uint32 TableSizes[]) +void ShaderResourceCacheD3D12::Initialize(IMemoryAllocator& MemAllocator, + Uint32 NumTables, + const Uint32 TableSizes[], + const InlineConstantInfoVectorType& InlineConstants) { VERIFY(GetContentType() == ResourceCacheContentType::Signature, "This method should be called to initialize the cache to store resources of a pipeline resource signature"); @@ -137,7 +151,13 @@ void ShaderResourceCacheD3D12::Initialize(IMemoryAllocator& MemAllocator, m_DescriptorAllocations = 0; - AllocateMemory(MemAllocator); + Uint32 TotalInlineConstantValues = 0; + for (const InlineConstantParamInfo& InlineConstInfo : InlineConstants) + { + TotalInlineConstantValues += InlineConstInfo.NumValues; + } + + AllocateMemory(MemAllocator, TotalInlineConstantValues); Uint32 ResIdx = 0; for (Uint32 t = 0; t < m_NumTables; ++t) @@ -146,6 +166,22 @@ void ShaderResourceCacheD3D12::Initialize(IMemoryAllocator& MemAllocator, ResIdx += TableSizes[t]; } VERIFY_EXPR(ResIdx == m_TotalResourceCount); + + Uint32* pCurrInlineConstValueStorage = GetInlineConstantStorage(); + for (const InlineConstantParamInfo& InlineConstInfo : InlineConstants) + { + Resource& Res = GetRootTable(InlineConstInfo.RootIndex).GetResource(InlineConstInfo.OffsetFromTableStart); + Res.CPUDescriptorHandle.ptr = reinterpret_cast(pCurrInlineConstValueStorage); + // Note that normally resource type is set when a resource is bound to the cache. + // For inline constants, we set it here during tinitialization. + Res.Type = SHADER_RESOURCE_TYPE_CONSTANT_BUFFER; + // Set the buffer range size to match the number of constant values + Res.BufferRangeSize = InlineConstInfo.NumValues * sizeof(Uint32); + pCurrInlineConstValueStorage += InlineConstInfo.NumValues; + + m_RootConstantsMask |= (Uint64{1} << Uint64{InlineConstInfo.RootIndex}); + } + VERIFY_EXPR(pCurrInlineConstValueStorage == GetInlineConstantStorage() + TotalInlineConstantValues); } @@ -168,7 +204,7 @@ void ShaderResourceCacheD3D12::Initialize(IMemoryAllocator& MemAllocator, m_NumDescriptorAllocations = static_cast(MemReq.NumDescriptorAllocations); VERIFY_EXPR(m_NumDescriptorAllocations == MemReq.NumDescriptorAllocations); - const size_t MemSize = AllocateMemory(MemAllocator); + const size_t MemSize = AllocateMemory(MemAllocator, MemReq.TotalInlineConstantValues); VERIFY_EXPR(MemSize == MemReq.TotalSize); #ifdef DILIGENT_DEBUG @@ -177,7 +213,7 @@ void ShaderResourceCacheD3D12::Initialize(IMemoryAllocator& MemAllocator, Uint32 ResIdx = 0; - // Initialize root tables + // Initialize root tables. for (Uint32 i = 0; i < RootParams.GetNumRootTables(); ++i) { const RootParameter& RootTbl = RootParams.GetRootTable(i); @@ -220,6 +256,46 @@ void ShaderResourceCacheD3D12::Initialize(IMemoryAllocator& MemAllocator, RootTableInitFlags[RootView.RootIndex] = true; #endif } + + if (Uint32 NumRootConstants = RootParams.GetNumRootConstants()) + { + Uint32* pCurrInlineConstValueStorage = GetInlineConstantStorage(); + + // Initialize one-descriptor tables for root constants + for (Uint32 i = 0; i < NumRootConstants; ++i) + { + const RootParameter& RootConsts = RootParams.GetRootConstants(i); + VERIFY(!RootTableInitFlags[RootConsts.RootIndex], "Root table at index ", RootConsts.RootIndex, " has already been initialized."); + VERIFY_EXPR(RootConsts.TableOffsetInGroupAllocation == RootParameter::InvalidTableOffsetInGroupAllocation); + VERIFY_EXPR(RootConsts.d3d12RootParam.ParameterType == D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS); + + Resource& Res = GetResource(ResIdx); + Res.CPUDescriptorHandle.ptr = reinterpret_cast(pCurrInlineConstValueStorage); + // Note that normally resource type is set when a resource is bound to the cache. + // For inline constants, we set it here during tinitialization. + Res.Type = SHADER_RESOURCE_TYPE_CONSTANT_BUFFER; + // Set the buffer range size to match the number of constant values + Res.BufferRangeSize = RootConsts.d3d12RootParam.Constants.Num32BitValues * sizeof(Uint32); + + pCurrInlineConstValueStorage += RootConsts.d3d12RootParam.Constants.Num32BitValues; + + new (&GetRootTable(RootConsts.RootIndex)) RootTable{ + 1, + &Res, + false //IsRootView + }; + ++ResIdx; + + m_RootConstantsMask |= (Uint64{1} << Uint64{RootConsts.RootIndex}); + +#ifdef DILIGENT_DEBUG + RootTableInitFlags[RootConsts.RootIndex] = true; +#endif + } + + VERIFY_EXPR(pCurrInlineConstValueStorage == GetInlineConstantStorage() + MemReq.TotalInlineConstantValues); + } + VERIFY_EXPR(ResIdx == m_TotalResourceCount); #ifdef DILIGENT_DEBUG @@ -386,6 +462,23 @@ void ShaderResourceCacheD3D12::SetBufferDynamicOffset(Uint32 RootIndex, Res.BufferDynamicOffset = BufferDynamicOffset; } +void ShaderResourceCacheD3D12::SetInlineConstants(Uint32 RootIndex, + Uint32 OffsetFromTableStart, + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants) +{ + RootTable& Tbl = GetRootTable(RootIndex); + Resource& Res = Tbl.GetResource(OffsetFromTableStart); + VERIFY_EXPR(Res.Type == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER); + VERIFY(Res.IsNull(), "There should be no resource bound for root constants as they contain raw data."); + VERIFY(Res.CPUDescriptorHandle.ptr != 0, "Resources used to store root constants must have valid pointer to the data."); + VERIFY(FirstConstant + NumConstants <= Res.BufferRangeSize / sizeof(Uint32), + "Too many constants (", FirstConstant + NumConstants, ") for the allocated space (", Res.BufferRangeSize / sizeof(Uint32), ")"); + Uint32* pDstConstants = reinterpret_cast(Res.CPUDescriptorHandle.ptr); + memcpy(pDstConstants + FirstConstant, pConstants, NumConstants * sizeof(Uint32)); +} + const ShaderResourceCacheD3D12::Resource& ShaderResourceCacheD3D12::CopyResource(ID3D12Device* pd3d12Device, Uint32 RootIndex, Uint32 OffsetFromTableStart, @@ -485,6 +578,9 @@ void ShaderResourceCacheD3D12::DbgValidateDynamicBuffersMask() const void ShaderResourceCacheD3D12::Resource::TransitionResource(CommandContext& Ctx) { + if (IsNull()) + return; + static_assert(SHADER_RESOURCE_TYPE_LAST == 8, "Please update this function to handle the new resource type"); switch (Type) { @@ -569,6 +665,9 @@ void ShaderResourceCacheD3D12::Resource::TransitionResource(CommandContext& Ctx) #ifdef DILIGENT_DEVELOPMENT void ShaderResourceCacheD3D12::Resource::DvpVerifyResourceState() { + if (IsNull()) + return; + static_assert(SHADER_RESOURCE_TYPE_LAST == 8, "Please update this function to handle the new resource type"); switch (Type) { diff --git a/Graphics/GraphicsEngineD3D12/src/ShaderVariableManagerD3D12.cpp b/Graphics/GraphicsEngineD3D12/src/ShaderVariableManagerD3D12.cpp index 98be7eb388..ab636fc359 100644 --- a/Graphics/GraphicsEngineD3D12/src/ShaderVariableManagerD3D12.cpp +++ b/Graphics/GraphicsEngineD3D12/src/ShaderVariableManagerD3D12.cpp @@ -669,6 +669,26 @@ void ShaderVariableManagerD3D12::SetBufferDynamicOffset(Uint32 ResIndex, m_ResourceCache.SetBufferDynamicOffset(RootIndex, OffsetFromTableStart, BufferDynamicOffset); } +void ShaderVariableManagerD3D12::SetInlineConstants(Uint32 ResIndex, + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants) +{ + const ResourceAttribs& Attribs = m_pSignature->GetResourceAttribs(ResIndex); + const ResourceCacheContentType CacheType = m_ResourceCache.GetContentType(); + const Uint32 RootIndex = Attribs.RootIndex(CacheType); + const Uint32 OffsetFromTableStart = Attribs.OffsetFromTableStart(CacheType); + +#ifdef DILIGENT_DEVELOPMENT + { + const PipelineResourceDesc& ResDesc = m_pSignature->GetResourceDesc(ResIndex); + VerifyInlineConstants(ResDesc, pConstants, FirstConstant, NumConstants); + } +#endif + + m_ResourceCache.SetInlineConstants(RootIndex, OffsetFromTableStart, pConstants, FirstConstant, NumConstants); +} + IDeviceObject* ShaderVariableManagerD3D12::Get(Uint32 ArrayIndex, Uint32 ResIndex) const { diff --git a/Graphics/GraphicsEngineD3DBase/include/D3DShaderResourceLoader.hpp b/Graphics/GraphicsEngineD3DBase/include/D3DShaderResourceLoader.hpp index cc30988c17..a6b15609e7 100644 --- a/Graphics/GraphicsEngineD3DBase/include/D3DShaderResourceLoader.hpp +++ b/Graphics/GraphicsEngineD3DBase/include/D3DShaderResourceLoader.hpp @@ -97,7 +97,7 @@ void LoadShaderCodeVariableDesc(TD3DShaderReflectionType* pd3dReflecionType, Sha ShaderCodeVariableDesc MemberDesc; MemberDesc.Name = pd3dReflecionType->GetMemberTypeName(m); - auto idx = TypeDesc.AddMember(MemberDesc); + size_t idx = TypeDesc.AddMember(MemberDesc); VERIFY_EXPR(idx == m); auto* pd3dMemberType = pd3dReflecionType->GetMemberTypeByIndex(m); VERIFY_EXPR(pd3dMemberType != nullptr); @@ -122,7 +122,7 @@ void LoadD3DShaderConstantBufferReflection(TShaderReflection* pBuffReflection, S VarDesc.Name = d3dShaderVarDesc.Name; // The variable name. VarDesc.Offset = d3dShaderVarDesc.StartOffset; // Offset from the start of the parent structure to the beginning of the variable. - auto idx = BufferDesc.AddVariable(VarDesc); + size_t idx = BufferDesc.AddVariable(VarDesc); VERIFY_EXPR(idx == var); auto* pd3dReflecionType = pVaribable->GetType(); @@ -187,7 +187,7 @@ void LoadD3DShaderResources(TShaderReflection* pShaderReflection, pShaderReflection->GetResourceBindingDesc(Res, &BindingDesc); std::string Name; - const auto ArrayIndex = Parsing::GetArrayIndex(BindingDesc.Name, Name); + const int ArrayIndex = Parsing::GetArrayIndex(BindingDesc.Name, Name); if (BindingDesc.BindPoint == UINT32_MAX) { @@ -232,7 +232,7 @@ void LoadD3DShaderResources(TShaderReflection* pShaderReflection, VERIFY(BindCount == 1, "When array elements are enumerated individually, BindCount is expected to always be 1"); #ifdef DILIGENT_DEBUG - for (const auto& ExistingRes : Resources) + for (const D3DShaderResourceAttribs& ExistingRes : Resources) { VERIFY(Name.compare(ExistingRes.Name) != 0, "Resource with the same name has already been enumerated. All array elements are expected to be enumerated one after another"); } @@ -243,7 +243,7 @@ void LoadD3DShaderResources(TShaderReflection* pShaderReflection, pShaderReflection->GetResourceBindingDesc(ArrElem, &NextElemBindingDesc); std::string NextElemName; - const auto NextElemIndex = Parsing::GetArrayIndex(NextElemBindingDesc.Name, NextElemName); + const int NextElemIndex = Parsing::GetArrayIndex(NextElemBindingDesc.Name, NextElemName); // Make sure this case is handled correctly: // "g_tex2DDiffuse[.]" != "g_tex2DDiffuse2[.]" @@ -271,7 +271,7 @@ void LoadD3DShaderResources(TShaderReflection* pShaderReflection, switch (BindingDesc.Type) { - // clang-format off + // clang-format off case D3D_SIT_CBUFFER: ++RC.NumCBs; break; case D3D_SIT_TBUFFER: UNSUPPORTED( "TBuffers are not supported" ); break; case D3D_SIT_TEXTURE: ++(BindingDesc.Dimension == D3D_SRV_DIMENSION_BUFFER ? RC.NumBufSRVs : RC.NumTexSRVs); break; @@ -315,29 +315,29 @@ void LoadD3DShaderResources(TShaderReflection* pShaderReflection, for (size_t ResInd = 0; ResInd < Resources.size(); ++ResInd) { - const auto& Res = Resources[ResInd]; + const D3DShaderResourceAttribs& Res = Resources[ResInd]; switch (Res.GetInputType()) { case D3D_SIT_CBUFFER: { ShaderCodeBufferDescX BufferDesc; - if (LoadConstantBufferReflection) + if (auto* pBuffReflection = pShaderReflection->GetConstantBufferByName(Res.Name)) { - if (auto* pBuffReflection = pShaderReflection->GetConstantBufferByName(Res.Name)) - { - D3D_SHADER_BUFFER_DESC ShaderBuffDesc = {}; - pBuffReflection->GetDesc(&ShaderBuffDesc); - VERIFY_EXPR(SafeStrEqual(Res.Name, ShaderBuffDesc.Name)); - VERIFY_EXPR(ShaderBuffDesc.Type == D3D_CT_CBUFFER); + D3D_SHADER_BUFFER_DESC ShaderBuffDesc = {}; + pBuffReflection->GetDesc(&ShaderBuffDesc); + VERIFY_EXPR(SafeStrEqual(Res.Name, ShaderBuffDesc.Name)); + VERIFY_EXPR(ShaderBuffDesc.Type == D3D_CT_CBUFFER); - BufferDesc.Size = ShaderBuffDesc.Size; - LoadD3DShaderConstantBufferReflection(pBuffReflection, BufferDesc, ShaderBuffDesc.Variables); - } - else + BufferDesc.Size = ShaderBuffDesc.Size; + if (LoadConstantBufferReflection) { - UNEXPECTED("Failed to get constant buffer reflection information."); + LoadD3DShaderConstantBufferReflection(pBuffReflection, BufferDesc, ShaderBuffDesc.Variables); } } + else + { + UNEXPECTED("Failed to get constant buffer reflection information."); + } OnNewCB(Res, std::move(BufferDesc)); break; diff --git a/Graphics/GraphicsEngineD3DBase/include/ShaderResources.hpp b/Graphics/GraphicsEngineD3DBase/include/ShaderResources.hpp index 4fcd7db194..00185cb334 100644 --- a/Graphics/GraphicsEngineD3DBase/include/ShaderResources.hpp +++ b/Graphics/GraphicsEngineD3DBase/include/ShaderResources.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -91,11 +91,11 @@ struct D3DShaderResourceAttribs // 4 4 24 // bit | 0 1 2 3 | 4 5 6 7 | 8 9 10 ... 31 | // | | | | - // | InputType | SRV Dim | SamplerOrTexSRVIdBits | - static constexpr const Uint32 ShaderInputTypeBits = 4; - static constexpr const Uint32 SRVDimBits = 4; - static constexpr const Uint32 SamplerOrTexSRVIdBits = 24; - static_assert(ShaderInputTypeBits + SRVDimBits + SamplerOrTexSRVIdBits == 32, "Attributes are better be packed into 32 bits"); + // | InputType | SRV Dim | ExtraDataBits | + static constexpr const Uint32 ShaderInputTypeBits = 4; + static constexpr const Uint32 SRVDimBits = 4; + static constexpr const Uint32 ExtraDataBits = 24; + static_assert(ShaderInputTypeBits + SRVDimBits + ExtraDataBits == 32, "Attributes are better be packed into 32 bits"); static_assert(D3D_SIT_UAV_RWSTRUCTURED_WITH_COUNTER < (1 << ShaderInputTypeBits), "Not enough bits to represent D3D_SHADER_INPUT_TYPE"); static_assert(D3D_SRV_DIMENSION_BUFFEREX < (1 << SRVDimBits), "Not enough bits to represent D3D_SRV_DIMENSION"); @@ -105,17 +105,17 @@ struct D3DShaderResourceAttribs // There originally was a problem when the type of InputType was D3D_SHADER_INPUT_TYPE: // the value of D3D_SIT_UAV_RWBYTEADDRESS (8) was interpreted as -8 (as the underlying enum type // is signed) causing errors -/*20.0*/ const Uint32 InputType : ShaderInputTypeBits; // Max value: D3D_SIT_UAV_RWSTRUCTURED_WITH_COUNTER == 11 -/*20.4*/ const Uint32 SRVDimension : SRVDimBits; // Max value: D3D_SRV_DIMENSION_BUFFEREX == 11 -/*21.0*/ Uint32 SamplerOrTexSRVId : SamplerOrTexSRVIdBits; // Max value: 2^24-1 +/*20.0*/ const Uint32 InputType : ShaderInputTypeBits; // Max value: D3D_SIT_UAV_RWSTRUCTURED_WITH_COUNTER == 11 +/*20.4*/ const Uint32 SRVDimension : SRVDimBits; // Max value: D3D_SRV_DIMENSION_BUFFEREX == 11 +/*21.0*/ Uint32 ExtraData : ExtraDataBits; // Max value: 2^24-1 /*24 */ // End of structure // clang-format on public: - static constexpr const Uint32 InvalidSamplerId = (1U << SamplerOrTexSRVIdBits) - 1U; + static constexpr const Uint32 InvalidSamplerId = (1U << ExtraDataBits) - 1U; static constexpr const Uint32 MaxSamplerId = InvalidSamplerId - 1; - static constexpr const Uint32 InvalidTexSRVId = (1U << SamplerOrTexSRVIdBits) - 1U; + static constexpr const Uint32 InvalidTexSRVId = (1U << ExtraDataBits) - 1U; static constexpr const auto InvalidBindPoint = std::numeric_limits::max(); @@ -127,20 +127,20 @@ struct D3DShaderResourceAttribs D3D_SRV_DIMENSION _SRVDimension, Uint32 _SamplerId) noexcept : // clang-format off - Name {_Name}, - BindPoint {_BindPoint}, - BindCount {_BindCount}, - Space {_Space}, - InputType {static_cast (_InputType) }, - SRVDimension {static_cast(_SRVDimension)}, - SamplerOrTexSRVId {_SamplerId} + Name {_Name}, + BindPoint {_BindPoint}, + BindCount {_BindCount}, + Space {_Space}, + InputType {static_cast (_InputType) }, + SRVDimension{static_cast(_SRVDimension)}, + ExtraData {_SamplerId} // clang-format on { #ifdef DILIGENT_DEBUG // clang-format off - VERIFY(_InputType < (1 << ShaderInputTypeBits), "Shader input type is out of expected range"); - VERIFY(_SRVDimension < (1 << SRVDimBits), "SRV dimensions is out of expected range"); - VERIFY(_SamplerId < (1 << SamplerOrTexSRVIdBits), "SamplerOrTexSRVId is out of representable range"); + VERIFY(_InputType < (1 << ShaderInputTypeBits), "Shader input type is out of expected range"); + VERIFY(_SRVDimension < (1 << SRVDimBits), "SRV dimensions is out of expected range"); + VERIFY(_SamplerId < (1 << ExtraDataBits), "SamplerId is out of representable range"); // clang-format on if (_InputType == D3D_SIT_TEXTURE && _SRVDimension != D3D_SRV_DIMENSION_BUFFER) @@ -178,7 +178,7 @@ struct D3DShaderResourceAttribs rhs.Space, rhs.GetInputType(), rhs.GetSRVDimension(), - rhs.SamplerOrTexSRVId + rhs.ExtraData } // clang-format on { @@ -210,7 +210,7 @@ struct D3DShaderResourceAttribs bool IsCombinedWithSampler() const { - return GetInputType() == D3D_SIT_TEXTURE && SamplerOrTexSRVId != InvalidSamplerId; + return GetInputType() == D3D_SIT_TEXTURE && ExtraData != InvalidSamplerId; } bool IsCombinedWithTexSRV() const @@ -230,12 +230,12 @@ struct D3DShaderResourceAttribs Space == Attribs.Space && InputType == Attribs.InputType && SRVDimension == Attribs.SRVDimension && - SamplerOrTexSRVId == Attribs.SamplerOrTexSRVId; + ExtraData == Attribs.ExtraData; } size_t GetHash() const { - return ComputeHash(BindPoint, BindCount, Space, InputType, SRVDimension, SamplerOrTexSRVId); + return ComputeHash(BindPoint, BindCount, Space, InputType, SRVDimension, ExtraData); } HLSLShaderResourceDesc GetHLSLResourceDesc() const @@ -253,7 +253,32 @@ struct D3DShaderResourceAttribs Uint32 GetCombinedSamplerId() const { VERIFY(GetInputType() == D3D_SIT_TEXTURE && GetSRVDimension() != D3D_SRV_DIMENSION_BUFFER, "Invalid input type: D3D_SIT_TEXTURE is expected"); - return SamplerOrTexSRVId; + return ExtraData; + } + + Uint32 GetConstantBufferSize() const + { + VERIFY(GetInputType() == D3D_SIT_CBUFFER, "Invalid input type: D3D_SIT_CBUFFER is expected"); + return ExtraData; + } + + Uint32 GetInlineConstantCountOrThrow(const char* ShaderName) const + { + VERIFY_EXPR(GetShaderResourceType() == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER); + if (BindCount != 1) + { + LOG_ERROR_AND_THROW("Inline constants resource '", Name, "' in shader '", ShaderName, "' can not be an array."); + } + const Uint32 NumConstants = GetConstantBufferSize() / sizeof(Uint32); + + if (NumConstants > MAX_INLINE_CONSTANTS) + { + LOG_ERROR_AND_THROW("Inline constants resource '", Name, "' in shader '", ShaderName, "' has ", + NumConstants, " constants. The maximum supported number of inline constants is ", + MAX_INLINE_CONSTANTS, '.'); + } + + return NumConstants; } SHADER_RESOURCE_TYPE GetShaderResourceType() const; @@ -265,14 +290,20 @@ struct D3DShaderResourceAttribs void SetTexSRVId(Uint32 TexSRVId) { VERIFY(GetInputType() == D3D_SIT_SAMPLER, "Invalid input type: D3D_SIT_SAMPLER is expected"); - VERIFY(TexSRVId < (1 << SamplerOrTexSRVIdBits), "TexSRVId (", TexSRVId, ") is out of representable range"); - SamplerOrTexSRVId = TexSRVId; + VERIFY(TexSRVId < (1 << ExtraDataBits), "TexSRVId (", TexSRVId, ") is out of representable range"); + ExtraData = TexSRVId; } Uint32 GetCombinedTexSRVId() const { VERIFY(GetInputType() == D3D_SIT_SAMPLER, "Invalid input type: D3D_SIT_SAMPLER is expected"); - return SamplerOrTexSRVId; + return ExtraData; + } + + void SetConstantBufferSize(Uint32 Size) + { + VERIFY(GetInputType() == D3D_SIT_CBUFFER, "Invalid input type: D3D_SIT_CBUFFER is expected"); + ExtraData = Size; } }; static_assert(sizeof(D3DShaderResourceAttribs) == sizeof(void*) + sizeof(Uint32) * 4, "Unexpected sizeof(D3DShaderResourceAttribs)"); @@ -543,6 +574,7 @@ void ShaderResources::Initialize(TShaderReflection* pShaderReflection, { VERIFY_EXPR(CBAttribs.GetInputType() == D3D_SIT_CBUFFER); D3DShaderResourceAttribs* pNewCB = new (&GetCB(CurrCB++)) D3DShaderResourceAttribs{ResourceNamesPool, CBAttribs}; + pNewCB->SetConstantBufferSize(CBReflection.Size); NewResHandler.OnNewCB(*pNewCB); if (LoadConstantBufferReflection) CBReflections.emplace_back(std::move(CBReflection)); diff --git a/Graphics/GraphicsEngineOpenGL/include/ShaderResourceCacheGL.hpp b/Graphics/GraphicsEngineOpenGL/include/ShaderResourceCacheGL.hpp index e0e4190a7d..f7e83adcf0 100644 --- a/Graphics/GraphicsEngineOpenGL/include/ShaderResourceCacheGL.hpp +++ b/Graphics/GraphicsEngineOpenGL/include/ShaderResourceCacheGL.hpp @@ -345,6 +345,11 @@ class ShaderResourceCacheGL : public ShaderResourceCacheBase return m_DynamicUBOMask != 0 || m_DynamicSSBOMask != 0; } + bool HasInlineConstants() const + { + return false; + } + #ifdef DILIGENT_DEBUG void DbgVerifyDynamicBufferMasks() const; #endif diff --git a/Graphics/GraphicsEngineOpenGL/include/ShaderVariableManagerGL.hpp b/Graphics/GraphicsEngineOpenGL/include/ShaderVariableManagerGL.hpp index 7f89ece208..2d0a7be6b6 100644 --- a/Graphics/GraphicsEngineOpenGL/include/ShaderVariableManagerGL.hpp +++ b/Graphics/GraphicsEngineOpenGL/include/ShaderVariableManagerGL.hpp @@ -114,6 +114,11 @@ class ShaderVariableManagerGL : ShaderVariableManagerBaseCommitProgram(m_ContextState); - if (Uint32 BindSRBMask = m_BindInfo.GetCommitMask(Flags & DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT)) + if (Uint32 BindSRBMask = m_BindInfo.GetCommitMask(Flags & DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT, Flags & DRAW_FLAG_INLINE_CONSTANTS_INTACT)) { BindProgramResources(BindSRBMask); } diff --git a/Graphics/GraphicsEngineOpenGL/src/ShaderVariableManagerGL.cpp b/Graphics/GraphicsEngineOpenGL/src/ShaderVariableManagerGL.cpp index f4116258c6..00ea24e3f1 100644 --- a/Graphics/GraphicsEngineOpenGL/src/ShaderVariableManagerGL.cpp +++ b/Graphics/GraphicsEngineOpenGL/src/ShaderVariableManagerGL.cpp @@ -223,6 +223,10 @@ void ShaderVariableManagerGL::UniformBuffBindInfo::SetDynamicOffset(Uint32 Array m_ParentManager.m_ResourceCache.SetDynamicUBOffset(Attr.CacheOffset + ArrayIndex, Offset); } +void ShaderVariableManagerGL::UniformBuffBindInfo::SetConstants(const void* pConstants, Uint32 FirstConstant, Uint32 NumConstants) +{ + UNSUPPORTED("Not yet implemented"); +} void ShaderVariableManagerGL::TextureBindInfo::BindResource(const BindResourceInfo& BindInfo) { diff --git a/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.hpp b/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.hpp index d86ffd775d..5d526e23d2 100644 --- a/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.hpp +++ b/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.hpp @@ -555,6 +555,12 @@ class DeviceContextVkImpl final : public DeviceContextNextGenBase ppSignatures[], Uint32 SignatureCount) noexcept(false); + void Create(RenderDeviceVkImpl* pDeviceVk, + RefCntAutoPtr ppSignatures[], + Uint32 SignatureCount) noexcept(false); void Release(RenderDeviceVkImpl* pDeviceVkImpl, Uint64 CommandQueueMask); VkPipelineLayout GetVkPipelineLayout() const { return m_VkPipelineLayout; } @@ -59,6 +63,23 @@ class PipelineLayoutVk return m_FirstDescrSetIndex[Index]; } + // Returns true if this pipeline layout has push constants + bool HasPushConstants() const { return m_PushConstantSize > 0; } + + // Returns the size of push constants in bytes + Uint32 GetPushConstantSize() const { return m_PushConstantSize; } + + // Returns the shader stage flags for push constants + VkShaderStageFlags GetPushConstantStageFlags() const { return m_PushConstantStageFlags; } + + // Returns the signature index containing the push constant resource + // Returns INVALID_PUSH_CONSTANT_INDEX if no push constant is selected + Uint32 GetPushConstantSignatureIndex() const { return m_PushConstantSignatureIndex; } + + // Returns the resource index within the signature for push constant + // Returns INVALID_PUSH_CONSTANT_INDEX if no push constant is selected + Uint32 GetPushConstantResourceIndex() const { return m_PushConstantResourceIndex; } + private: VulkanUtilities::PipelineLayoutWrapper m_VkPipelineLayout; @@ -70,6 +91,18 @@ class PipelineLayoutVk // (Maximum is MAX_RESOURCE_SIGNATURES * 2) Uint8 m_DescrSetCount = 0; + // Push constant size in bytes + Uint32 m_PushConstantSize = 0; + + // Shader stages that use push constants + VkShaderStageFlags m_PushConstantStageFlags = 0; + + // Index of the signature containing the push constant resource + Uint32 m_PushConstantSignatureIndex = INVALID_PUSH_CONSTANT_INDEX; + + // Resource index within the signature for push constant + Uint32 m_PushConstantResourceIndex = INVALID_PUSH_CONSTANT_INDEX; + #ifdef DILIGENT_DEBUG Uint32 m_DbgMaxBindIndex = 0; #endif diff --git a/Graphics/GraphicsEngineVulkan/include/PipelineResourceSignatureVkImpl.hpp b/Graphics/GraphicsEngineVulkan/include/PipelineResourceSignatureVkImpl.hpp index d138b8e495..064ac9e502 100644 --- a/Graphics/GraphicsEngineVulkan/include/PipelineResourceSignatureVkImpl.hpp +++ b/Graphics/GraphicsEngineVulkan/include/PipelineResourceSignatureVkImpl.hpp @@ -50,6 +50,7 @@ namespace Diligent struct SPIRVShaderResourceAttribs; class DeviceContextVkImpl; +class BufferVkImpl; struct ImmutableSamplerAttribsVk { @@ -58,6 +59,27 @@ struct ImmutableSamplerAttribsVk }; ASSERT_SIZEOF(ImmutableSamplerAttribsVk, 8, "The struct is used in serialization and must be tightly packed"); +/// Inline constant buffer attributes for Vulkan backend. +/// All inline constants are treated uniformly at PRS level - they all get: +/// - DescriptorSet binding and cache allocation +/// - Shared emulated buffer (created in the Signature, shared by all SRBs) +/// +/// Push constant selection is deferred to PSO creation time. At PSO creation, +/// one inline constant may be selected to use vkCmdPushConstants based on: +/// 1. SPIR-V reflection (ResourceType::PushConstant in shader) +/// 2. First inline constant as fallback (converted via PatchShader) +struct InlineConstantBufferAttribsVk +{ + Uint32 ResIndex = 0; // Resource index in the signature (used for matching) + Uint32 DescrSet = 0; // Descriptor set index + Uint32 BindingIndex = 0; // Binding index within the descriptor set + Uint32 NumConstants = 0; // Number of 32-bit constants + + // Shared buffer created in the Signature (similar to D3D11) + // All SRBs reference this same buffer to reduce memory usage. + RefCntAutoPtr pBuffer; +}; + struct PipelineResourceSignatureInternalDataVk : PipelineResourceSignatureInternalData { Uint16 DynamicUniformBufferCount = 0; @@ -134,6 +156,23 @@ class PipelineResourceSignatureVkImpl final : public PipelineResourceSignatureBa void CommitDynamicResources(const ShaderResourceCacheVk& ResourceCache, VkDescriptorSet vkDynamicDescriptorSet) const; + // Updates inline constant buffers by mapping the internal buffers and copying data from the resource cache + // ResourceCache must be valid - each SRB has its own copy of inline constant data stored in the cache + // PushConstantResIndex: Resource index of the inline constant selected as push constant by PSO + // Pass ~0u if no push constant is selected from this signature + void UpdateInlineConstantBuffers(const ShaderResourceCacheVk& ResourceCache, + DeviceContextVkImpl& Ctx, + Uint32 PushConstantResIndex) const; + + // Returns the number of inline constant buffers + Uint32 GetNumInlineConstantBufferAttribs() const { return m_NumInlineConstantBufferAttribs; } + + // Returns the inline constant buffer attributes + const InlineConstantBufferAttribsVk* GetInlineConstantBufferAttribs() const { return m_InlineConstantBufferAttribs.get(); } + + // Returns push constant data from the resource cache for the specified resource index + const void* GetPushConstantData(const ShaderResourceCacheVk& ResourceCache, Uint32 ResIndex) const; + #ifdef DILIGENT_DEVELOPMENT /// Verifies committed resource using the SPIRV resource attributes from the PSO. bool DvpValidateCommittedResource(const DeviceContextVkImpl* pDeviceCtx, @@ -193,6 +232,11 @@ class PipelineResourceSignatureVkImpl final : public PipelineResourceSignatureBa // The total number storage buffers with dynamic offsets in both descriptor sets, // accounting for array size. Uint16 m_DynamicStorageBufferCount = 0; + + // Number of inline constant buffers + Uint32 m_NumInlineConstantBufferAttribs = 0; + // Inline constant buffer attributes + std::unique_ptr m_InlineConstantBufferAttribs; }; template <> Uint32 PipelineResourceSignatureVkImpl::GetDescriptorSetIndex() const; diff --git a/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.hpp b/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.hpp index 8923ac1fe9..f4100c7441 100644 --- a/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.hpp +++ b/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.hpp @@ -84,8 +84,9 @@ class PipelineStateVkImpl final : public PipelineStateBase // Shader stage type. All shaders in the stage must have the same type. SHADER_TYPE Type = SHADER_TYPE_UNKNOWN; - std::vector Shaders; - std::vector> SPIRVs; + std::vector Shaders; + std::vector> SPIRVs; + std::vector> ShaderResources; //This can be also updated due to push constant friend SHADER_TYPE GetShaderStageType(const ShaderStageInfo& Stage) { return Stage.Type; } }; @@ -133,6 +134,10 @@ class PipelineStateVkImpl final : public PipelineStateBase void InitializePipeline(const ComputePipelineStateCreateInfo& CreateInfo); void InitializePipeline(const RayTracingPipelineStateCreateInfo& CreateInfo); + void ValidateShaderPushConstants(const TShaderStages& ShaderStages) const noexcept(false); + + void PatchShaderConvertUniformBufferToPushConstant(TShaderStages& ShaderStages) const noexcept(false); + // TPipelineStateBase::Construct needs access to InitializePipeline friend TPipelineStateBase; diff --git a/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp b/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp index 0e3b376d8c..ce2b7f2237 100644 --- a/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp +++ b/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp @@ -37,7 +37,7 @@ // m_pMemory | | m_pResources, m_NumResources == m | // | m_DescriptorSetAllocation| | | // V | | V -// | DescriptorSet[0] | .... | DescriptorSet[Ns-1] | Res[0] | ... | Res[n-1] | .... | Res[0] | ... | Res[m-1] | +// | DescriptorSet[0] | .... | DescriptorSet[Ns-1] | Res[0] | ... | Res[n-1] | .... | Res[0] | ... | Res[m-1] | Inline constant values | // | | A \ // | | | \ // | |________________________________________________| \RefCntAutoPtr @@ -74,7 +74,8 @@ class ShaderResourceCacheVk : public ShaderResourceCacheBase public: explicit ShaderResourceCacheVk(ResourceCacheContentType ContentType) noexcept : m_TotalResources{0}, - m_ContentType{static_cast(ContentType)} + m_ContentType{static_cast(ContentType)}, + m_HasInlineConstants{0} { VERIFY_EXPR(GetContentType() == ContentType); } @@ -88,12 +89,12 @@ class ShaderResourceCacheVk : public ShaderResourceCacheBase ~ShaderResourceCacheVk(); - static size_t GetRequiredMemorySize(Uint32 NumSets, const Uint32* SetSizes); + static size_t GetRequiredMemorySize(Uint32 NumSets, const Uint32* SetSizes, Uint32 TotalInlineConstantBytes = 0); - void InitializeSets(IMemoryAllocator& MemAllocator, Uint32 NumSets, const Uint32* SetSizes); + void InitializeSets(IMemoryAllocator& MemAllocator, Uint32 NumSets, const Uint32* SetSizes, Uint32 TotalInlineConstantBytes = 0); void InitializeResources(Uint32 Set, Uint32 Offset, Uint32 ArraySize, DescriptorType Type, bool HasImmutableSampler); - // sizeof(Resource) == 32 (x64, msvc, Release) + // sizeof(Resource) == 40 (x64, msvc, Release) struct Resource { explicit Resource(DescriptorType _Type, bool _HasImmutableSampler) noexcept : @@ -120,6 +121,10 @@ class ShaderResourceCacheVk : public ShaderResourceCacheBase /*16 */ Uint64 BufferBaseOffset = 0; /*24 */ Uint64 BufferRangeSize = 0; + // For inline constants only - pointer to CPU-side staging buffer +/*32 */ void* pInlineConstantData = nullptr; +/*40 */ // End of structure + VkDescriptorBufferInfo GetUniformBufferDescriptorWriteInfo() const; VkDescriptorBufferInfo GetStorageBufferDescriptorWriteInfo() const; VkDescriptorImageInfo GetImageDescriptorWriteInfo () const; @@ -245,10 +250,48 @@ class ShaderResourceCacheVk : public ShaderResourceCacheBase Uint32 CacheOffset, Uint32 DynamicBufferOffset); + // Sets inline constant data in the resource cache + void SetInlineConstants(Uint32 DescrSetIndex, + Uint32 CacheOffset, + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants); + + // Gets the inline constant data pointer from the resource cache + const void* GetInlineConstantData(Uint32 DescrSetIndex, Uint32 CacheOffset) const; + + // Initialize inline constant buffer in the resource cache + void InitializeInlineConstantBuffer(Uint32 DescrSetIndex, + Uint32 CacheOffset, + Uint32 NumConstants, + Uint32 InlineConstantOffset); + + // Returns pointer to inline constant storage at the given byte offset + void* GetInlineConstantStorage(Uint32 ByteOffset = 0) + { + return reinterpret_cast(GetFirstResourcePtr() + m_TotalResources) + ByteOffset; + } + const void* GetInlineConstantStorage(Uint32 ByteOffset = 0) const + { + return reinterpret_cast(GetFirstResourcePtr() + m_TotalResources) + ByteOffset; + } + + // Explicitly marks that this cache contains inline constants. + // This is useful when inline constant memory is initialized externally + // (e.g., during SRB cache setup) before any data is written. + void MarkHasInlineConstants() + { + m_HasInlineConstants = 1; + } Uint32 GetNumDescriptorSets() const { return m_NumSets; } bool HasDynamicResources() const { return m_NumDynamicBuffers > 0; } + bool HasInlineConstants() const + { + return m_HasInlineConstants; + } + ResourceCacheContentType GetContentType() const { return static_cast(m_ContentType); } #ifdef DILIGENT_DEBUG @@ -287,11 +330,14 @@ class ShaderResourceCacheVk : public ShaderResourceCacheBase // Total actual number of dynamic buffers (that were created with USAGE_DYNAMIC) bound in the resource cache // regardless of the variable type. Note this variable is not equal to dynamic offsets count, which is constant. Uint16 m_NumDynamicBuffers = 0; - Uint32 m_TotalResources : 31; + Uint32 m_TotalResources : 30; // Indicates what types of resources are stored in the cache const Uint32 m_ContentType : 1; + // Indicates whether the cache contains inline constants + Uint32 m_HasInlineConstants : 1; + #ifdef DILIGENT_DEBUG // Debug array that stores flags indicating if resources in the cache have been initialized std::vector> m_DbgInitializedResources; diff --git a/Graphics/GraphicsEngineVulkan/include/ShaderVariableManagerVk.hpp b/Graphics/GraphicsEngineVulkan/include/ShaderVariableManagerVk.hpp index 5a91ded6d6..cad0cff662 100644 --- a/Graphics/GraphicsEngineVulkan/include/ShaderVariableManagerVk.hpp +++ b/Graphics/GraphicsEngineVulkan/include/ShaderVariableManagerVk.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 Diligent Graphics LLC + * Copyright 2019-2025 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -98,6 +98,11 @@ class ShaderVariableManagerVk : ShaderVariableManagerBase 0); + vkCmdPushConstants(m_VkCmdBuffer, layout, stageFlags, offset, size, pValues); + } + __forceinline void CopyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, uint32_t regionCount, diff --git a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp index 9ff4035128..2972bb787c 100644 --- a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp @@ -415,6 +415,81 @@ DeviceContextVkImpl::ResourceBindInfo& DeviceContextVkImpl::GetBindInfo(PIPELINE return m_BindInfo[Indices[Uint32{Type}]]; } +void DeviceContextVkImpl::UpdateInlineConstantBuffers(ResourceBindInfo& BindInfo) +{ + const PipelineLayoutVk& Layout = m_pPipelineState->GetPipelineLayout(); + const Uint32 PushConstSignIdx = Layout.GetPushConstantSignatureIndex(); + const Uint32 PushConstResIdx = Layout.GetPushConstantResourceIndex(); + const Uint32 SignCount = m_pPipelineState->GetResourceSignatureCount(); + + for (Uint32 i = 0; i < SignCount; ++i) + { + const PipelineResourceSignatureVkImpl* pSign = m_pPipelineState->GetResourceSignature(i); + if (pSign == nullptr || pSign->GetNumInlineConstantBufferAttribs() == 0) + continue; + + const ShaderResourceCacheVk* pResourceCache = BindInfo.ResourceCaches[i]; + if (pResourceCache == nullptr) + { + // Each signature with inline constants must have a bound SRB with a valid resource cache + DEV_CHECK_ERR(false, "Signature '", pSign->GetDesc().Name, "' has inline constants but no SRB is bound. " + "Did you call CommitShaderResources()?"); + continue; + } + + if (!pResourceCache->HasInlineConstants()) + continue; + + // Determine which resource (if any) in this signature should use push constant path + // If this signature contains the selected push constant, pass its resource index + // Otherwise pass INVALID_PUSH_CONSTANT_INDEX to use emulated buffer path for all + const Uint32 PushConstantResIndex = (i == PushConstSignIdx) ? PushConstResIdx : INVALID_PUSH_CONSTANT_INDEX; + + // Update inline constant buffers + pSign->UpdateInlineConstantBuffers(*pResourceCache, *this, PushConstantResIndex); + } +} + +void DeviceContextVkImpl::CommitPushConstants(ResourceBindInfo& BindInfo) +{ + VERIFY_EXPR(m_pPipelineState != nullptr); + const PipelineLayoutVk& Layout = m_pPipelineState->GetPipelineLayout(); + + if (!Layout.HasPushConstants()) + return; + + const Uint32 PushConstSignIdx = Layout.GetPushConstantSignatureIndex(); + const Uint32 PushConstResIdx = Layout.GetPushConstantResourceIndex(); + + VERIFY_EXPR(PushConstSignIdx != INVALID_PUSH_CONSTANT_INDEX); + VERIFY_EXPR(PushConstResIdx != INVALID_PUSH_CONSTANT_INDEX); + + const PipelineResourceSignatureVkImpl* pSign = m_pPipelineState->GetResourceSignature(PushConstSignIdx); + VERIFY_EXPR(pSign != nullptr); + + const ShaderResourceCacheVk* pResourceCache = BindInfo.ResourceCaches[PushConstSignIdx]; + if (pResourceCache == nullptr) + { + DEV_CHECK_ERR(false, "Signature '", pSign->GetDesc().Name, "' has push constants but no SRB is bound. " + "Did you call CommitShaderResources()?"); + return; + } + + // Get inline constant data directly from the resource cache + const void* pPushConstantData = pSign->GetPushConstantData(*pResourceCache, PushConstResIdx); + if (pPushConstantData == nullptr) + { + DEV_CHECK_ERR(false, "Push constant data is null in signature '", pSign->GetDesc().Name, "'"); + return; + } + + const Uint32 Size = Layout.GetPushConstantSize(); + const VkShaderStageFlags StageFlags = Layout.GetPushConstantStageFlags(); + const VkPipelineLayout vkLayout = Layout.GetVkPipelineLayout(); + + m_CommandBuffer.PushConstants(vkLayout, StageFlags, 0, Size, pPushConstantData); +} + void DeviceContextVkImpl::CommitDescriptorSets(ResourceBindInfo& BindInfo, Uint32 CommitSRBMask) { VERIFY(CommitSRBMask != 0, "This method should not be called when there is nothing to commit"); @@ -487,6 +562,8 @@ void DeviceContextVkImpl::DvpValidateCommittedShaderResources(ResourceBindInfo& if (BindInfo.ResourcesValidated) return; + // Use custom signature getter to skip signatures that have no resources at all + // Note: Signatures with only push constants still need SRB to store push constant data DvpVerifySRBCompatibility(BindInfo); const Uint32 SignCount = m_pPipelineState->GetResourceSignatureCount(); @@ -536,9 +613,30 @@ void DeviceContextVkImpl::CommitShaderResources(IShaderResourceBinding* pShaderR ShaderResourceBindingVkImpl* pResBindingVkImpl = ClassPtrCast(pShaderResourceBinding); ShaderResourceCacheVk& ResourceCache = pResBindingVkImpl->GetResourceCache(); + + const Uint32 SRBIndex = pResBindingVkImpl->GetBindingIndex(); + const PipelineResourceSignatureVkImpl* pSignature = pResBindingVkImpl->GetSignature(); + + // PRS has no resources and thus PIPELINE_TYPE_INVALID ? + PIPELINE_TYPE SRBPipelineType = pResBindingVkImpl->GetPipelineType(); + if (SRBPipelineType == PIPELINE_TYPE_INVALID) + return; + + ResourceBindInfo& BindInfo = GetBindInfo(SRBPipelineType); + ResourceBindInfo::DescriptorSetInfo& SetInfo = BindInfo.SetInfo[SRBIndex]; + + // Always bind the SRB, even if it has no descriptor sets (e.g., only push constants) + // The resource cache is needed to store push constant data + BindInfo.Set(SRBIndex, pResBindingVkImpl); + + // If there are no descriptor sets, we're done (SRB only contains push constants) + // Clear the stale flag since there are no descriptor sets to commit if (ResourceCache.GetNumDescriptorSets() == 0) { - // Ignore SRBs that contain no resources + // Clear the stale bit for this SRB since it has no descriptor sets to commit + // The SRB is still bound and ResourceCaches[SRBIndex] is set, so push constants can be accessed + const auto SRBBit = static_cast(1u << SRBIndex); + BindInfo.StaleSRBMask &= ~SRBBit; return; } @@ -557,12 +655,6 @@ void DeviceContextVkImpl::CommitShaderResources(IShaderResourceBinding* pShaderR } #endif - const Uint32 SRBIndex = pResBindingVkImpl->GetBindingIndex(); - const PipelineResourceSignatureVkImpl* pSignature = pResBindingVkImpl->GetSignature(); - ResourceBindInfo& BindInfo = GetBindInfo(pResBindingVkImpl->GetPipelineType()); - ResourceBindInfo::DescriptorSetInfo& SetInfo = BindInfo.SetInfo[SRBIndex]; - - BindInfo.Set(SRBIndex, pResBindingVkImpl); // We must not clear entire ResInfo as DescriptorSetBaseInd and DynamicOffsetCount // are set by SetPipelineState(). SetInfo.vkSets = {}; @@ -758,13 +850,28 @@ void DeviceContextVkImpl::PrepareForDraw(DRAW_FLAGS Flags) #endif ResourceBindInfo& BindInfo = GetBindInfo(PIPELINE_TYPE_GRAPHICS); + + // Update inline constant buffers (emulated path) before binding descriptor sets + const bool DynamicBuffersIntact = (Flags & DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT) != 0; + const bool InlineConstantsIntact = (Flags & DRAW_FLAG_INLINE_CONSTANTS_INTACT) != 0; + if (!InlineConstantsIntact) + { + UpdateInlineConstantBuffers(BindInfo); + } + // First time we must always bind descriptor sets with dynamic offsets as SRBs are stale. // If there are no dynamic buffers bound in the resource cache, for all subsequent // calls we do not need to bind the sets again. - if (Uint32 CommitMask = BindInfo.GetCommitMask(Flags & DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT)) + if (Uint32 CommitMask = BindInfo.GetCommitMask(DynamicBuffersIntact, InlineConstantsIntact)) { CommitDescriptorSets(BindInfo, CommitMask); } + + // Commit push constants directly from SRB cache. + // Push constants are always submitted before draw (similar to D3D11/D3D12) because + // inline constants are designed to change every draw call. + CommitPushConstants(BindInfo); + #ifdef DILIGENT_DEVELOPMENT // Must be called after CommitDescriptorSets as it needs SetInfo.BaseInd DvpValidateCommittedShaderResources(BindInfo); @@ -1059,11 +1166,18 @@ void DeviceContextVkImpl::PrepareForDispatchCompute() EndRenderScope(); ResourceBindInfo& BindInfo = GetBindInfo(PIPELINE_TYPE_COMPUTE); + + // Update inline constant buffers before binding descriptor sets + UpdateInlineConstantBuffers(BindInfo); + if (Uint32 CommitMask = BindInfo.GetCommitMask()) { CommitDescriptorSets(BindInfo, CommitMask); } + // Commit push constants directly from SRB cache + CommitPushConstants(BindInfo); + #ifdef DILIGENT_DEVELOPMENT // Must be called after CommitDescriptorSets as it needs SetInfo.BaseInd DvpValidateCommittedShaderResources(BindInfo); @@ -1075,11 +1189,18 @@ void DeviceContextVkImpl::PrepareForRayTracing() EnsureVkCmdBuffer(); ResourceBindInfo& BindInfo = GetBindInfo(PIPELINE_TYPE_RAY_TRACING); + + // Update inline constant buffers before binding descriptor sets + UpdateInlineConstantBuffers(BindInfo); + if (Uint32 CommitMask = BindInfo.GetCommitMask()) { CommitDescriptorSets(BindInfo, CommitMask); } + // Commit push constants directly from SRB cache + CommitPushConstants(BindInfo); + #ifdef DILIGENT_DEVELOPMENT // Must be called after CommitDescriptorSets as it needs SetInfo.BaseInd DvpValidateCommittedShaderResources(BindInfo); diff --git a/Graphics/GraphicsEngineVulkan/src/PipelineLayoutVk.cpp b/Graphics/GraphicsEngineVulkan/src/PipelineLayoutVk.cpp index 16d0b140b4..94942da268 100644 --- a/Graphics/GraphicsEngineVulkan/src/PipelineLayoutVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/PipelineLayoutVk.cpp @@ -59,7 +59,9 @@ void PipelineLayoutVk::Release(RenderDeviceVkImpl* pDeviceVk, Uint64 CommandQueu } } -void PipelineLayoutVk::Create(RenderDeviceVkImpl* pDeviceVk, RefCntAutoPtr ppSignatures[], Uint32 SignatureCount) noexcept(false) +void PipelineLayoutVk::Create(RenderDeviceVkImpl* pDeviceVk, + RefCntAutoPtr ppSignatures[], + Uint32 SignatureCount) noexcept(false) { VERIFY(m_DescrSetCount == 0 && !m_VkPipelineLayout, "This pipeline layout is already initialized"); @@ -69,6 +71,12 @@ void PipelineLayoutVk::Create(RenderDeviceVkImpl* pDeviceVk, RefCntAutoPtrGetDesc().BindingIndex}); #endif + + // Find the first inline constant resource (only if not already found) + if (pPushConstantResDesc == nullptr) + { + for (Uint32 r = 0; r < pSignature->GetTotalResourceCount(); ++r) + { + const PipelineResourceDesc& ResDesc = pSignature->GetResourceDesc(r); + if (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + { + pPushConstantResDesc = &ResDesc; + m_PushConstantSignatureIndex = BindInd; + m_PushConstantResourceIndex = r; + // For inline constants, ArraySize contains the number of 32-bit constants. + m_PushConstantSize = ResDesc.ArraySize * sizeof(Uint32); + for (SHADER_TYPE ShaderTypes = ResDesc.ShaderStages; ShaderTypes != SHADER_TYPE_UNKNOWN;) + { + const SHADER_TYPE ShaderType = ExtractLSB(ShaderTypes); + m_PushConstantStageFlags |= ShaderTypeToVkShaderStageFlagBit(ShaderType); + } + break; + } + } + } } VERIFY_EXPR(DescSetLayoutCount <= MAX_RESOURCE_SIGNATURES * 2); @@ -114,19 +145,45 @@ void PipelineLayoutVk::Create(RenderDeviceVkImpl* pDeviceVk, RefCntAutoPtr 0) + { + if (m_PushConstantSize > Limits.maxPushConstantsSize) + { + LOG_ERROR_AND_THROW("Push constant size (", m_PushConstantSize, + " bytes) exceeds device limit (", Limits.maxPushConstantsSize, " bytes)"); + } + } + VERIFY(m_DescrSetCount <= std::numeric_limits::max(), "Descriptor set count (", DescSetLayoutCount, ") exceeds the maximum representable value"); VkPipelineLayoutCreateInfo PipelineLayoutCI = {}; - PipelineLayoutCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - PipelineLayoutCI.pNext = nullptr; - PipelineLayoutCI.flags = 0; // reserved for future use - PipelineLayoutCI.setLayoutCount = DescSetLayoutCount; - PipelineLayoutCI.pSetLayouts = DescSetLayoutCount ? DescSetLayouts.data() : nullptr; - PipelineLayoutCI.pushConstantRangeCount = 0; - PipelineLayoutCI.pPushConstantRanges = nullptr; - m_VkPipelineLayout = pDeviceVk->GetLogicalDevice().CreatePipelineLayout(PipelineLayoutCI); + PipelineLayoutCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + PipelineLayoutCI.pNext = nullptr; + PipelineLayoutCI.flags = 0; // reserved for future use + PipelineLayoutCI.setLayoutCount = DescSetLayoutCount; + PipelineLayoutCI.pSetLayouts = DescSetLayoutCount ? DescSetLayouts.data() : nullptr; + + // Set up push constant range if present + VkPushConstantRange PushConstantRange = {}; + if (m_PushConstantSize > 0 && m_PushConstantStageFlags != 0) + { + PushConstantRange.stageFlags = m_PushConstantStageFlags; + PushConstantRange.offset = 0; + PushConstantRange.size = m_PushConstantSize; + + PipelineLayoutCI.pushConstantRangeCount = 1; + PipelineLayoutCI.pPushConstantRanges = &PushConstantRange; + } + else + { + PipelineLayoutCI.pushConstantRangeCount = 0; + PipelineLayoutCI.pPushConstantRanges = nullptr; + } + + m_VkPipelineLayout = pDeviceVk->GetLogicalDevice().CreatePipelineLayout(PipelineLayoutCI); m_DescrSetCount = static_cast(DescSetLayoutCount); } diff --git a/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp index d10dd562e4..372149f7c5 100644 --- a/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,11 +32,13 @@ #include "RenderDeviceVkImpl.hpp" #include "SamplerVkImpl.hpp" #include "TextureViewVkImpl.hpp" +#include "BufferVkImpl.hpp" #include "DeviceContextVkImpl.hpp" #include "VulkanTypeConversions.hpp" #include "DynamicLinearAllocator.hpp" #include "SPIRVShaderResources.hpp" +#include "GraphicsAccessories.hpp" namespace Diligent { @@ -168,30 +170,54 @@ PipelineResourceSignatureVkImpl::PipelineResourceSignatureVkImpl(IReferenceCount void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) { - // Initialize static resource cache first - if (GetNumStaticResStages() > 0) - { - Uint32 StaticResourceCount = 0; // The total number of static resources in all stages - // accounting for array sizes. - for (Uint32 i = 0; i < m_Desc.NumResources; ++i) - { - const PipelineResourceDesc& ResDesc = m_Desc.Resources[i]; - if (ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) - StaticResourceCount += ResDesc.ArraySize; - } - m_pStaticResCache->InitializeSets(GetRawAllocator(), 1, &StaticResourceCount); - } - CacheOffsetsType CacheGroupSizes = {}; // Required cache size for each cache group BindingCountType BindingCount = {}; // Binding count in each cache group + + // First pass: count resources and inline constants + Uint32 StaticResourceCount = 0; // The total number of static resources in all stages + Uint32 TotalStaticInlineConstantBytes = 0; // Total bytes for static inline constants for (Uint32 i = 0; i < m_Desc.NumResources; ++i) { const PipelineResourceDesc& ResDesc = m_Desc.Resources[i]; const CACHE_GROUP CacheGroup = GetResourceCacheGroup(ResDesc); + // All resources (including inline constants) use descriptor sets. + // Push constant selection is deferred to PSO creation time. BindingCount[CacheGroup] += 1; // Note that we may reserve space for separate immutable samplers, which will never be used, but this is OK. - CacheGroupSizes[CacheGroup] += ResDesc.ArraySize; + // For inline constants, GetArraySize() returns 1 (actual array size for cache), + // while ArraySize contains the number of 32-bit constants. + CacheGroupSizes[CacheGroup] += ResDesc.GetArraySize(); + + if (ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + { + StaticResourceCount += ResDesc.GetArraySize(); + } + + // Count inline constant buffers + if (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + { + VERIFY(ResDesc.ResourceType == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, + "Only constant buffers can have INLINE_CONSTANTS flag"); + ++m_NumInlineConstantBufferAttribs; + + if (ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + { + TotalStaticInlineConstantBytes += ResDesc.ArraySize * sizeof(Uint32); + } + } + } + + // Initialize static resource cache (now that we know the inline constant size) + if (GetNumStaticResStages() > 0 && StaticResourceCount > 0) + { + m_pStaticResCache->InitializeSets(GetRawAllocator(), 1, &StaticResourceCount, TotalStaticInlineConstantBytes); + } + + // Allocate inline constant buffer attributes array + if (m_NumInlineConstantBufferAttribs > 0) + { + m_InlineConstantBufferAttribs = std::make_unique(m_NumInlineConstantBufferAttribs); } // Descriptor set mapping (static/mutable (0) or dynamic (1) -> set index) @@ -245,6 +271,9 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) // Current offset in the static resource cache Uint32 StaticCacheOffset = 0; + // Current inline constant buffer index + Uint32 InlineConstantBufferIdx = 0; + std::array, DESCRIPTOR_SET_ID_NUM_SETS> vkSetLayoutBindings; DynamicLinearAllocator TempAllocator{GetRawAllocator(), 256}; @@ -292,6 +321,7 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) ResourceAttribs* const pAttribs = m_pResourceAttribs + i; if (!IsSerialized) { + // All resources use descriptor sets - push constant selection is deferred to PSO creation new (pAttribs) ResourceAttribs // { BindingIndices[CacheGroup], @@ -300,8 +330,8 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) DescrType, DSMapping[SetId], pVkImmutableSamplers != nullptr, - CacheGroupOffsets[CacheGroup], - ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC ? StaticCacheOffset : ~0u // + CacheGroupOffsets[CacheGroup], // SRBCacheOffset + ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC ? StaticCacheOffset : ~0u // StaticCacheOffset }; } else @@ -322,12 +352,18 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) "Static cache offset is invalid."); } + // For inline constants, ArraySize holds the number of 4-byte constants + const Uint32 DescriptorCount = ResDesc.GetArraySize(); + BindingIndices[CacheGroup] += 1; - CacheGroupOffsets[CacheGroup] += ResDesc.ArraySize; + + // All resources use descriptor sets - push constant selection is deferred to PSO creation + // For inline constants, GetArraySize() returns 1 (actual array size for cache) + CacheGroupOffsets[CacheGroup] += ResDesc.GetArraySize(); VkDescriptorSetLayoutBinding vkSetLayoutBinding{}; vkSetLayoutBinding.binding = pAttribs->BindingIndex; - vkSetLayoutBinding.descriptorCount = ResDesc.ArraySize; + vkSetLayoutBinding.descriptorCount = DescriptorCount; vkSetLayoutBinding.stageFlags = ShaderTypesToVkShaderStageFlags(ResDesc.ShaderStages); vkSetLayoutBinding.pImmutableSamplers = pVkImmutableSamplers; vkSetLayoutBinding.descriptorType = DescriptorTypeToVkDescriptorType(pAttribs->GetDescriptorType()); @@ -336,11 +372,48 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) if (ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) { VERIFY(pAttribs->DescrSet == 0, "Static resources must always be allocated in descriptor set 0"); - m_pStaticResCache->InitializeResources(pAttribs->DescrSet, StaticCacheOffset, ResDesc.ArraySize, + m_pStaticResCache->InitializeResources(pAttribs->DescrSet, StaticCacheOffset, DescriptorCount, pAttribs->GetDescriptorType(), pAttribs->IsImmutableSamplerAssigned()); - StaticCacheOffset += ResDesc.ArraySize; + StaticCacheOffset += DescriptorCount; + } + + // Handle inline constant buffers + // All inline constants get descriptor set bindings and emulated buffers. + // Push constant selection is deferred to PSO creation time. + if (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + { + VERIFY(ResDesc.ResourceType == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, + "Only constant buffers can have INLINE_CONSTANTS flag"); + + InlineConstantBufferAttribsVk& InlineCBAttribs = m_InlineConstantBufferAttribs[InlineConstantBufferIdx++]; + InlineCBAttribs.ResIndex = i; // Resource index for unique identification + InlineCBAttribs.DescrSet = pAttribs->DescrSet; + InlineCBAttribs.BindingIndex = pAttribs->BindingIndex; + InlineCBAttribs.NumConstants = ResDesc.ArraySize; // For inline constants, ArraySize is the number of 32-bit constants + + // Create a shared buffer in the Signature for all inline constants + // All SRBs will reference this same buffer (similar to D3D11 backend) + // Push constant selection is handled at PSO creation time + if (m_pDevice) + { + std::string Name = m_Desc.Name; + Name += " - "; + Name += ResDesc.Name; + BufferDesc CBDesc; + CBDesc.Name = Name.c_str(); + CBDesc.Size = ResDesc.ArraySize * sizeof(Uint32); + CBDesc.Usage = USAGE_DYNAMIC; + CBDesc.BindFlags = BIND_UNIFORM_BUFFER; + CBDesc.CPUAccessFlags = CPU_ACCESS_WRITE; + + RefCntAutoPtr pBuffer; + m_pDevice->CreateBuffer(CBDesc, nullptr, &pBuffer); + VERIFY_EXPR(pBuffer); + InlineCBAttribs.pBuffer = RefCntAutoPtr{pBuffer, IID_BufferVk}; + } } } + VERIFY_EXPR(InlineConstantBufferIdx == m_NumInlineConstantBufferAttribs); #ifdef DILIGENT_DEBUG if (m_pStaticResCache != nullptr) @@ -474,6 +547,28 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) } VERIFY_EXPR(NumSets == GetNumDescriptorSets()); } + + // Initialize inline constant buffers in the static resource cache + // Memory was already allocated in InitializeSets above + if (m_NumInlineConstantBufferAttribs > 0 && m_pStaticResCache != nullptr) + { + Uint32 InlineConstantOffset = 0; + for (Uint32 i = 0; i < m_NumInlineConstantBufferAttribs; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBufferAttribs[i]; + + const PipelineResourceDesc& ResDesc = GetResourceDesc(InlineCBAttr.ResIndex); + const ResourceAttribs& Attr = GetResourceAttribs(InlineCBAttr.ResIndex); + + if (ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + { + const Uint32 DataSize = InlineCBAttr.NumConstants * sizeof(Uint32); + const Uint32 CacheOffset = Attr.StaticCacheOffset; + m_pStaticResCache->InitializeInlineConstantBuffer(0, CacheOffset, InlineCBAttr.NumConstants, InlineConstantOffset); + InlineConstantOffset += DataSize; + } + } + } } PipelineResourceSignatureVkImpl::~PipelineResourceSignatureVkImpl() @@ -489,6 +584,18 @@ void PipelineResourceSignatureVkImpl::Destruct() GetDevice()->SafeReleaseDeviceObject(std::move(Layout), ~0ull); } + // Release shared inline constant buffers before base class Destruct + // Each InlineConstantBufferAttribsVk::pBuffer holds a RefCntAutoPtr to the shared buffer + if (m_InlineConstantBufferAttribs) + { + for (Uint32 i = 0; i < m_NumInlineConstantBufferAttribs; ++i) + { + m_InlineConstantBufferAttribs[i].pBuffer.Release(); + } + m_InlineConstantBufferAttribs.reset(); + } + m_NumInlineConstantBufferAttribs = 0; + TPipelineResourceSignatureBase::Destruct(); } @@ -500,8 +607,16 @@ void PipelineResourceSignatureVkImpl::InitSRBResourceCache(ShaderResourceCacheVk VERIFY_EXPR(m_DescriptorSetSizes[i] != ~0U); #endif + // Calculate total memory size for CPU staging data + Uint32 TotalInlineConstantBytes = 0; + for (Uint32 i = 0; i < m_NumInlineConstantBufferAttribs; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBufferAttribs[i]; + TotalInlineConstantBytes += InlineCBAttr.NumConstants * sizeof(Uint32); + } + IMemoryAllocator& CacheMemAllocator = m_SRBMemAllocator.GetResourceCacheDataAllocator(0); - ResourceCache.InitializeSets(CacheMemAllocator, NumSets, m_DescriptorSetSizes.data()); + ResourceCache.InitializeSets(CacheMemAllocator, NumSets, m_DescriptorSetSizes.data(), TotalInlineConstantBytes); const Uint32 TotalResources = GetTotalResourceCount(); const ResourceCacheContentType CacheType = ResourceCache.GetContentType(); @@ -509,10 +624,39 @@ void PipelineResourceSignatureVkImpl::InitSRBResourceCache(ShaderResourceCacheVk { const PipelineResourceDesc& ResDesc = GetResourceDesc(r); const ResourceAttribs& Attr = GetResourceAttribs(r); - ResourceCache.InitializeResources(Attr.DescrSet, Attr.CacheOffset(CacheType), ResDesc.ArraySize, + + // For inline constants, GetArraySize() returns 1 (actual array size), + // while ArraySize contains the number of 32-bit constants + ResourceCache.InitializeResources(Attr.DescrSet, Attr.CacheOffset(CacheType), ResDesc.GetArraySize(), Attr.GetDescriptorType(), Attr.IsImmutableSamplerAssigned()); } + // Initialize inline constant buffers - set up data pointers to the unified memory block + // All inline constants use the emulated buffer path at PRS level. + // Push constant selection is deferred to PSO creation time. + // All SRBs share the same GPU buffer (stored in InlineConstantBufferAttribsVk::pBuffer), + // but each has its own CPU staging data. + if (m_NumInlineConstantBufferAttribs > 0) + { + // Ensure the cache reports inline constants so DeviceContextVkImpl + // updates emulated buffers even if no data has been written yet. + ResourceCache.MarkHasInlineConstants(); + + Uint32 InlineConstantOffset = 0; + for (Uint32 i = 0; i < m_NumInlineConstantBufferAttribs; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBufferAttribs[i]; + const Uint32 DataSize = InlineCBAttr.NumConstants * sizeof(Uint32); + const ResourceAttribs& Attr = GetResourceAttribs(InlineCBAttr.ResIndex); + + // Initialize staging memory in the resource cache + // The GPU buffer is shared from InlineCBAttr.pBuffer (created in CreateSetLayouts) + ResourceCache.InitializeInlineConstantBuffer(Attr.DescrSet, Attr.CacheOffset(CacheType), + InlineCBAttr.NumConstants, InlineConstantOffset); + InlineConstantOffset += DataSize; + } + } + #ifdef DILIGENT_DEBUG ResourceCache.DbgVerifyResourceInitialization(); #endif @@ -528,6 +672,60 @@ void PipelineResourceSignatureVkImpl::InitSRBResourceCache(ShaderResourceCacheVk DescriptorSetAllocation SetAllocation = GetDevice()->AllocateDescriptorSet(~Uint64{0}, vkLayout, DescrSetName); ResourceCache.AssignDescriptorSetAllocation(GetDescriptorSetIndex(), std::move(SetAllocation)); } + + // Bind shared inline constant buffers to the resource cache + // This must be done after descriptor set allocation so that descriptor writes work correctly + // The buffers are created in CreateSetLayouts() and shared by all SRBs (similar to D3D11) + // Push constant selection is deferred to PSO creation - all inline constants get buffers bound here + for (Uint32 i = 0; i < m_NumInlineConstantBufferAttribs; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBufferAttribs[i]; + + // Get the shared buffer from the Signature (created in CreateSetLayouts) + BufferVkImpl* pBuffer = InlineCBAttr.pBuffer.RawPtr(); + if (!pBuffer) + continue; + + // Use ResIndex to directly access the resource attributes + const PipelineResourceDesc& ResDesc = GetResourceDesc(InlineCBAttr.ResIndex); + const ResourceAttribs& Attr = GetResourceAttribs(InlineCBAttr.ResIndex); + const Uint32 CacheOffset = Attr.CacheOffset(CacheType); + + // For static/mutable variables, bind to the allocated descriptor set + // For dynamic variables, the buffer will be bound during CommitDynamicResources + // Note: Dynamic descriptor sets are allocated per-draw call, so we can't write to them here + if (ResDesc.VarType != SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC) + { + // Bind the shared uniform buffer to the resource cache + ResourceCache.SetResource( + &GetDevice()->GetLogicalDevice(), + Attr.DescrSet, + CacheOffset, + { + Attr.BindingIndex, + 0, // ArrayIndex + RefCntAutoPtr{pBuffer}, + 0, // BufferBaseOffset + InlineCBAttr.NumConstants * sizeof(Uint32) // BufferRangeSize + }); + } + else + { + // For dynamic variables, we still need to set the buffer in the cache + // but we pass nullptr for LogicalDevice since the descriptor set is not allocated yet + ResourceCache.SetResource( + nullptr, // Don't write to descriptor set + Attr.DescrSet, + CacheOffset, + { + Attr.BindingIndex, + 0, // ArrayIndex + RefCntAutoPtr{pBuffer}, + 0, // BufferBaseOffset + InlineCBAttr.NumConstants * sizeof(Uint32) // BufferRangeSize + }); + } + } } void PipelineResourceSignatureVkImpl::CopyStaticResources(ShaderResourceCacheVk& DstResourceCache) const @@ -555,7 +753,26 @@ void PipelineResourceSignatureVkImpl::CopyStaticResources(ShaderResourceCacheVk& if (ResDesc.ResourceType == SHADER_RESOURCE_TYPE_SAMPLER && Attr.IsImmutableSamplerAssigned()) continue; // Skip immutable separate samplers - for (Uint32 ArrInd = 0; ArrInd < ResDesc.ArraySize; ++ArrInd) + // Handle inline constants separately - copy staging data + if (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + { + const Uint32 SrcCacheOffset = Attr.CacheOffset(SrcCacheType); + const ShaderResourceCacheVk::Resource& SrcCachedRes = SrcDescrSet.GetResource(SrcCacheOffset); + const Uint32 DstCacheOffset = Attr.CacheOffset(DstCacheType); + const ShaderResourceCacheVk::Resource& DstCachedRes = DstDescrSet.GetResource(DstCacheOffset); + + // Copy inline constant data from static cache to SRB cache + if (SrcCachedRes.pInlineConstantData != nullptr && DstCachedRes.pInlineConstantData != nullptr) + { + // ArraySize contains the number of 32-bit constants for inline constants + memcpy(DstCachedRes.pInlineConstantData, SrcCachedRes.pInlineConstantData, ResDesc.ArraySize * sizeof(Uint32)); + } + continue; + } + + // For regular resources (not inline constants), iterate through array elements + + for (Uint32 ArrInd = 0; ArrInd < ResDesc.GetArraySize(); ++ArrInd) { const Uint32 SrcCacheOffset = Attr.CacheOffset(SrcCacheType) + ArrInd; const ShaderResourceCacheVk::Resource& SrcCachedRes = SrcDescrSet.GetResource(SrcCacheOffset); @@ -659,7 +876,7 @@ void PipelineResourceSignatureVkImpl::CommitDynamicResources(const ShaderResourc #ifdef DILIGENT_DEBUG { const PipelineResourceDesc& Res = GetResourceDesc(ResIdx); - VERIFY_EXPR(ArraySize == GetResourceDesc(ResIdx).ArraySize); + VERIFY_EXPR(ArraySize == GetResourceDesc(ResIdx).GetArraySize()); VERIFY_EXPR(Res.VarType == SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC); } #endif @@ -866,7 +1083,16 @@ bool PipelineResourceSignatureVkImpl::DvpValidateCommittedResource(const DeviceC // is bound. It will be null if the type is incorrect. if (const BufferVkImpl* pBufferVk = Res.pObject.RawPtr()) { - pDeviceCtx->DvpVerifyDynamicAllocation(pBufferVk); + // Skip dynamic allocation verification for inline constant buffers. + // These are internal buffers managed by the signature and are updated + // via UpdateInlineConstantBuffers() before each draw/dispatch. + // The Dynamic Buffer ID may be reused after PSO recreation (e.g., from cache), + // which would cause false validation failures. + const bool IsInlineConstantBuffer = (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0; + if (!IsInlineConstantBuffer) + { + pDeviceCtx->DvpVerifyDynamicAllocation(pBufferVk); + } if ((pBufferVk->GetDesc().Size < SPIRVAttribs.BufferStaticSize) && (GetDevice()->GetValidationFlags() & VALIDATION_FLAG_CHECK_SHADER_BUFFER_SIZE) != 0) @@ -981,4 +1207,57 @@ PipelineResourceSignatureInternalDataVk PipelineResourceSignatureVkImpl::GetInte return InternalData; } +void PipelineResourceSignatureVkImpl::UpdateInlineConstantBuffers(const ShaderResourceCacheVk& ResourceCache, + DeviceContextVkImpl& Ctx, + Uint32 PushConstantResIndex) const +{ + // Determine the cache type based on the resource cache content + // SRB caches use SRBCacheOffset, static caches use StaticCacheOffset + const ResourceCacheContentType CacheType = ResourceCache.GetContentType(); + + for (Uint32 i = 0; i < m_NumInlineConstantBufferAttribs; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBufferAttribs[i]; + const Uint32 DataSize = InlineCBAttr.NumConstants * sizeof(Uint32); + const ResourceAttribs& Attr = GetResourceAttribs(InlineCBAttr.ResIndex); + const Uint32 CacheOffset = Attr.CacheOffset(CacheType); + const void* pInlineConstantData = ResourceCache.GetInlineConstantData(Attr.DescrSet, CacheOffset); + VERIFY_EXPR(pInlineConstantData != nullptr); + + // Skip push constants - they are handled directly by CommitPushConstants + if (InlineCBAttr.ResIndex == PushConstantResIndex) + continue; + + // For emulated inline constants, get the shared buffer from the Signature + // All SRBs share this buffer (similar to D3D11 backend) + BufferVkImpl* pBuffer = InlineCBAttr.pBuffer.RawPtr(); + + if (pBuffer) + { + // Map the shared buffer and copy the data + void* pMappedData = nullptr; + Ctx.MapBuffer(pBuffer, MAP_WRITE, MAP_FLAG_DISCARD, pMappedData); + memcpy(pMappedData, pInlineConstantData, DataSize); + Ctx.UnmapBuffer(pBuffer, MAP_WRITE); + } + } +} + +const void* PipelineResourceSignatureVkImpl::GetPushConstantData(const ShaderResourceCacheVk& ResourceCache, Uint32 ResIndex) const +{ + // Find the inline constant buffer with the specified resource index + for (Uint32 i = 0; i < m_NumInlineConstantBufferAttribs; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBufferAttribs[i]; + if (InlineCBAttr.ResIndex == ResIndex) + { + const ResourceCacheContentType CacheType = ResourceCache.GetContentType(); + const ResourceAttribs& Attr = GetResourceAttribs(InlineCBAttr.ResIndex); + const Uint32 CacheOffset = Attr.CacheOffset(CacheType); + return ResourceCache.GetInlineConstantData(Attr.DescrSet, CacheOffset); + } + } + return nullptr; +} + } // namespace Diligent diff --git a/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp index 26e9b64225..8fdfbff37f 100644 --- a/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp @@ -557,7 +557,8 @@ void VerifyResourceMerge(const char* PSOName, PipelineStateVkImpl::ShaderStageInfo::ShaderStageInfo(const ShaderVkImpl* pShader) : Type{pShader->GetDesc().ShaderType}, Shaders{pShader}, - SPIRVs{pShader->GetSPIRV()} + SPIRVs{pShader->GetSPIRV()}, + ShaderResources{pShader->IsCompiling() ? nullptr : pShader->GetShaderResources()} {} void PipelineStateVkImpl::ShaderStageInfo::Append(const ShaderVkImpl* pShader) @@ -580,6 +581,7 @@ void PipelineStateVkImpl::ShaderStageInfo::Append(const ShaderVkImpl* pShader) } Shaders.push_back(pShader); SPIRVs.push_back(pShader->GetSPIRV()); + ShaderResources.push_back(pShader->GetShaderResources()); } size_t PipelineStateVkImpl::ShaderStageInfo::Count() const @@ -633,7 +635,20 @@ PipelineResourceSignatureDescWrapper PipelineStateVkImpl::GetDefaultResourceSign const SHADER_RESOURCE_TYPE ResType = SPIRVShaderResourceAttribs::GetShaderResourceType(Attribs.Type); const PIPELINE_RESOURCE_FLAGS Flags = SPIRVShaderResourceAttribs::GetPipelineResourceFlags(Attribs.Type) | ShaderVariableFlagsToPipelineResourceFlags(VarDesc.Flags); - SignDesc.AddResource(VarDesc.ShaderStages, Attribs.Name, Attribs.ArraySize, ResType, VarDesc.Type, Flags); + + // For inline constants, ArraySize specifies the number of 32-bit constants, + // not the array dimension. We need to calculate it from the buffer size. + // the SPIRV ArraySize is 1 (buffer is not an array), but we need + // the number of 32-bit constants which is BufferStaticSize / sizeof(Uint32) + Uint32 ArraySize = Attribs.ArraySize; + if (Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + { + VERIFY(Flags == PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS, "INLINE_CONSTANTS flag cannot be combined with other flags."); + + ArraySize = Attribs.GetInlineConstantCountOrThrow(pShader->GetDesc().Name); + } + + SignDesc.AddResource(VarDesc.ShaderStages, Attribs.Name, ArraySize, ResType, VarDesc.Type, Flags); } else { @@ -670,18 +685,20 @@ void PipelineStateVkImpl::RemapOrVerifyShaderResources( // remap resource bindings. for (size_t s = 0; s < ShaderStages.size(); ++s) { - const std::vector& Shaders = ShaderStages[s].Shaders; - std::vector>& SPIRVs = ShaderStages[s].SPIRVs; - const SHADER_TYPE ShaderType = ShaderStages[s].Type; + const std::vector& Shaders = ShaderStages[s].Shaders; + std::vector>& SPIRVs = ShaderStages[s].SPIRVs; + const SHADER_TYPE ShaderType = ShaderStages[s].Type; + std::vector>& ShaderResources = ShaderStages[s].ShaderResources; VERIFY_EXPR(Shaders.size() == SPIRVs.size()); + VERIFY_EXPR(Shaders.size() == ShaderResources.size()); for (size_t i = 0; i < Shaders.size(); ++i) { - const ShaderVkImpl* pShader = Shaders[i]; - std::vector& SPIRV = SPIRVs[i]; + const ShaderVkImpl* pShader = Shaders[i]; + std::vector& SPIRV = SPIRVs[i]; + const std::shared_ptr& pShaderResources = ShaderResources[i]; - const auto& pShaderResources = pShader->GetShaderResources(); VERIFY_EXPR(pShaderResources); if (pDvpShaderResources) @@ -690,6 +707,10 @@ void PipelineStateVkImpl::RemapOrVerifyShaderResources( pShaderResources->ProcessResources( [&](const SPIRVShaderResourceAttribs& SPIRVAttribs, Uint32) // { + // Push constants don't use descriptor sets and don't need binding/set remapping. + // They are handled via vkCmdPushConstants. We still verify they exist in the signature. + const bool IsPushConstant = (SPIRVAttribs.Type == SPIRVShaderResourceAttribs::ResourceType::PushConstant); + const ResourceAttribution ResAttribution = GetResourceAttribution(SPIRVAttribs.Name, ShaderType, pSignatures, SignatureCount); if (!ResAttribution) { @@ -702,6 +723,22 @@ void PipelineStateVkImpl::RemapOrVerifyShaderResources( const SHADER_RESOURCE_TYPE ResType = SPIRVShaderResourceAttribs::GetShaderResourceType(SPIRVAttribs.Type); const PIPELINE_RESOURCE_FLAGS Flags = SPIRVShaderResourceAttribs::GetPipelineResourceFlags(SPIRVAttribs.Type); + // For push constants, skip descriptor set operations but validate the resource exists. + if (IsPushConstant) + { + if (ResAttribution.ResourceIndex != ResourceAttribution::InvalidResourceIndex) + { + const PipelineResourceDesc& ResDesc = ResAttribution.pSignature->GetResourceDesc(ResAttribution.ResourceIndex); + ValidatePipelineResourceCompatibility(ResDesc, ResType, Flags, SPIRVAttribs.ArraySize, + pShader->GetDesc().Name, SignDesc.Name); + } + if (pDvpResourceAttibutions) + pDvpResourceAttibutions->emplace_back(ResAttribution); + + // Skip descriptor set remapping for push constants + return; + } + Uint32 ResourceBinding = ~0u; Uint32 DescriptorSet = ~0u; if (ResAttribution.ResourceIndex != ResourceAttribution::InvalidResourceIndex) @@ -773,7 +810,7 @@ void PipelineStateVkImpl::RemapOrVerifyShaderResources( { OptimizationFlags |= SPIRV_OPTIMIZATION_FLAG_LEGALIZATION; } - std::vector StrippedSPIRV = OptimizeSPIRV(SPIRV, SPV_ENV_MAX, OptimizationFlags); + std::vector StrippedSPIRV = OptimizeSPIRV(SPIRV, OptimizationFlags); if (!StrippedSPIRV.empty()) SPIRV = std::move(StrippedSPIRV); else @@ -784,6 +821,57 @@ void PipelineStateVkImpl::RemapOrVerifyShaderResources( } } +void PipelineStateVkImpl::ValidateShaderPushConstants(const TShaderStages& ShaderStages) const noexcept(false) +{ + const Uint32 PushConstantSignIdx = m_PipelineLayout.GetPushConstantSignatureIndex(); + const Uint32 PushConstantResIdx = m_PipelineLayout.GetPushConstantResourceIndex(); + const Uint32 PushConstantSize = m_PipelineLayout.GetPushConstantSize(); + + const char* PushConstantName = nullptr; + if (PushConstantSignIdx != INVALID_PUSH_CONSTANT_INDEX && PushConstantResIdx != INVALID_PUSH_CONSTANT_INDEX) + { + const PipelineResourceSignatureVkImpl* pSignature = m_Signatures[PushConstantSignIdx]; + if (pSignature != nullptr) + PushConstantName = pSignature->GetResourceDesc(PushConstantResIdx).Name; + } + + // Validate shader-declared push constants against the selected inline constant (if any). + for (const ShaderStageInfo& Stage : ShaderStages) + { + for (const ShaderVkImpl* pShader : Stage.Shaders) + { + const auto& pShaderResources = pShader->GetShaderResources(); + if (!pShaderResources) + continue; + + for (Uint32 pc = 0; pc < pShaderResources->GetNumPushConstants(); ++pc) + { + const SPIRVShaderResourceAttribs& PCAttribs = pShaderResources->GetPushConstant(pc); + + if (PushConstantName == nullptr) + { + LOG_ERROR_AND_THROW("Shader '", pShader->GetDesc().Name, "' defines push constants block '", PCAttribs.Name, + "', but the pipeline resource signatures define no inline constant resource to promote to push constants."); + } + + if (strcmp(PushConstantName, PCAttribs.Name) != 0) + { + LOG_ERROR_AND_THROW("Shader '", pShader->GetDesc().Name, "' defines push constants block '", PCAttribs.Name, + "', but the pipeline resource signatures select inline constant resource '", PushConstantName, + "' as push constants (only the first inline constant is promoted in Vulkan)."); + } + + if (PCAttribs.BufferStaticSize != PushConstantSize) + { + LOG_ERROR_AND_THROW("Push constants block size mismatch for resource '", PushConstantName, "' in shader '", + pShader->GetDesc().Name, "': SPIR-V declares ", PCAttribs.BufferStaticSize, + " bytes, but the pipeline resource signature defines ", PushConstantSize, " bytes."); + } + } + } + } +} + void PipelineStateVkImpl::InitPipelineLayout(const PipelineStateCreateInfo& CreateInfo, TShaderStages& ShaderStages) noexcept(false) { const PSO_CREATE_INTERNAL_FLAGS InternalFlags = GetInternalCreateFlags(CreateInfo); @@ -798,8 +886,16 @@ void PipelineStateVkImpl::InitPipelineLayout(const PipelineStateCreateInfo& Crea DvpValidateResourceLimits(); #endif + // Create the pipeline layout - this also extracts push constant info from signatures m_PipelineLayout.Create(GetDevice(), m_Signatures, m_SignatureCount); + // Validate shader-declared push constants against the selected inline constant (if any) + ValidateShaderPushConstants(ShaderStages); + + // If we promoted an inline constant as push constant (not an existing SPIR-V push constant), + // convert the uniform buffer to push constant in SPIRV bytecode. + PatchShaderConvertUniformBufferToPushConstant(ShaderStages); + const bool RemapResources = (CreateInfo.Flags & PSO_CREATE_FLAG_DONT_REMAP_SHADER_RESOURCES) == 0; const bool VerifyBindings = !RemapResources && ((InternalFlags & PSO_CREATE_INTERNAL_FLAG_NO_SHADER_REFLECTION) == 0); if (RemapResources || VerifyBindings) @@ -826,6 +922,105 @@ void PipelineStateVkImpl::InitPipelineLayout(const PipelineStateCreateInfo& Crea } } +void PipelineStateVkImpl::PatchShaderConvertUniformBufferToPushConstant(TShaderStages& ShaderStages) const noexcept(false) +{ + const Uint32 PushConstantSignIdx = m_PipelineLayout.GetPushConstantSignatureIndex(); + const Uint32 PushConstantResIdx = m_PipelineLayout.GetPushConstantResourceIndex(); + + // If no push constant was selected, no patching needed + if (PushConstantSignIdx == INVALID_PUSH_CONSTANT_INDEX || + PushConstantResIdx == INVALID_PUSH_CONSTANT_INDEX) + return; + + // Get the name of the selected push constant resource + const PipelineResourceSignatureVkImpl* pSignature = m_Signatures[PushConstantSignIdx]; + if (pSignature == nullptr) + return; + + const PipelineResourceDesc& ResDesc = pSignature->GetResourceDesc(PushConstantResIdx); + const std::string PushConstantName = ResDesc.Name; + + // For each shader stage, check if the uniform buffer needs to be patched + for (ShaderStageInfo& Stage : ShaderStages) + { + for (size_t i = 0; i < Stage.Shaders.size(); ++i) + { + ShaderVkImpl* pShader = const_cast(Stage.Shaders[i]); + + // First check if the shader already has this as push constant + bool AlreadyPushConstant = false; + + // Check if this shader has a uniform buffer with the push constant name + bool ShouldPatchUniformBuffer = false; + { + const SPIRVShaderResources* pShaderRes = pShader->GetShaderResources().get(); + + if (pShaderRes == nullptr) + continue; + + for (Uint32 pc = 0; pc < pShaderRes->GetNumPushConstants(); ++pc) + { + const SPIRVShaderResourceAttribs& PCAttribs = pShaderRes->GetPushConstant(pc); + if (PCAttribs.Name == PushConstantName) + { + AlreadyPushConstant = true; + break; + } + } + + if (!AlreadyPushConstant) + { + for (Uint32 ub = 0; ub < pShaderRes->GetNumUBs(); ++ub) + { + const SPIRVShaderResourceAttribs& UBAttribs = pShaderRes->GetUB(ub); + if (UBAttribs.Name == PushConstantName) + { + ShouldPatchUniformBuffer = true; + break; + } + } + } + } + + // If already push constant, no conversion needed + if (AlreadyPushConstant) + continue; + + if (ShouldPatchUniformBuffer) + { +#if !DILIGENT_NO_HLSL + const std::vector& SPIRV = Stage.SPIRVs[i]; + + std::vector PatchedSPIRV = ConvertUBOToPushConstants( + SPIRV, + PushConstantName); + + if (!PatchedSPIRV.empty()) + { + Stage.SPIRVs[i] = PatchedSPIRV; + + SPIRVShaderResources::CreateInfo ResCI; + ResCI.ShaderType = pShader->GetDesc().ShaderType; + ResCI.Name = pShader->GetDesc().Name; + ResCI.CombinedSamplerSuffix = pShader->GetDesc().UseCombinedTextureSamplers ? pShader->GetDesc().CombinedSamplerSuffix : nullptr; + ResCI.LoadShaderStageInputs = pShader->GetDesc().ShaderType == SHADER_TYPE_VERTEX; + ResCI.LoadUniformBufferReflection = true; //LoadConstantBufferReflection; + + Stage.ShaderResources[i] = SPIRVShaderResources::Create(GetRawAllocator(), PatchedSPIRV, ResCI); + } + else + { + LOG_ERROR_MESSAGE("Failed to convert uniform buffer '", PushConstantName, + "' to push constant in shader '", pShader->GetDesc().Name, "'"); + } +#else + LOG_ERROR_AND_THROW("Cannot patch shader, SPIRV-Tools is not available when DILIGENT_NO_HLSL defined."); +#endif + } + } + } +} + template PipelineStateVkImpl::TShaderStages PipelineStateVkImpl::InitInternalObjects( const PSOCreateInfoType& CreateInfo, @@ -946,14 +1141,22 @@ void PipelineStateVkImpl::DvpVerifySRBResources(const DeviceContextVkImpl* pCtx, pResources->ProcessResources( [&](const SPIRVShaderResourceAttribs& ResAttribs, Uint32) // { + // Push constants are validated but don't use descriptor sets + const bool IsPushConstant = (ResAttribs.Type == SPIRVShaderResourceAttribs::ResourceType::PushConstant); + if (!res_info->IsImmutableSampler()) // There are also immutable samplers in the list { VERIFY_EXPR(res_info->pSignature != nullptr); VERIFY_EXPR(res_info->pSignature->GetDesc().BindingIndex == res_info->SignatureIndex); - const ShaderResourceCacheVk* pResourceCache = ResourceCaches[res_info->SignatureIndex]; - DEV_CHECK_ERR(pResourceCache != nullptr, "Resource cache at index ", res_info->SignatureIndex, " is null."); - res_info->pSignature->DvpValidateCommittedResource(pCtx, ResAttribs, res_info->ResourceIndex, *pResourceCache, - pResources->GetShaderName(), m_Desc.Name); + + // Skip descriptor set validation for push constants (they don't use descriptor sets) + if (!IsPushConstant) + { + const ShaderResourceCacheVk* pResourceCache = ResourceCaches[res_info->SignatureIndex]; + DEV_CHECK_ERR(pResourceCache != nullptr, "Resource cache at index ", res_info->SignatureIndex, " is null."); + res_info->pSignature->DvpValidateCommittedResource(pCtx, ResAttribs, res_info->ResourceIndex, *pResourceCache, + pResources->GetShaderName(), m_Desc.Name); + } } ++res_info; } // @@ -986,7 +1189,7 @@ void PipelineStateVkImpl::DvpValidateResourceLimits() const const PipelineResourceSignatureVkImpl::ResourceAttribs& ResAttr = pSignature->GetResourceAttribs(r); const Uint32 DescIndex = static_cast(ResAttr.DescrType); - DescriptorCount[DescIndex] += ResAttr.ArraySize; + DescriptorCount[DescIndex] += ResDesc.GetArraySize(); for (SHADER_TYPE ShaderStages = ResDesc.ShaderStages; ShaderStages != 0;) { diff --git a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp index e186c946c3..19135a8ce6 100644 --- a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp @@ -41,16 +41,16 @@ namespace Diligent { -size_t ShaderResourceCacheVk::GetRequiredMemorySize(Uint32 NumSets, const Uint32* SetSizes) +size_t ShaderResourceCacheVk::GetRequiredMemorySize(Uint32 NumSets, const Uint32* SetSizes, Uint32 TotalInlineConstantBytes) { Uint32 TotalResources = 0; for (Uint32 t = 0; t < NumSets; ++t) TotalResources += SetSizes[t]; - size_t MemorySize = NumSets * sizeof(DescriptorSet) + TotalResources * sizeof(Resource); + size_t MemorySize = NumSets * sizeof(DescriptorSet) + TotalResources * sizeof(Resource) + TotalInlineConstantBytes; return MemorySize; } -void ShaderResourceCacheVk::InitializeSets(IMemoryAllocator& MemAllocator, Uint32 NumSets, const Uint32* SetSizes) +void ShaderResourceCacheVk::InitializeSets(IMemoryAllocator& MemAllocator, Uint32 NumSets, const Uint32* SetSizes, Uint32 TotalInlineConstantBytes) { VERIFY(!m_pMemory, "Memory has already been allocated"); @@ -59,7 +59,7 @@ void ShaderResourceCacheVk::InitializeSets(IMemoryAllocator& MemAllocator, Uint3 // m_pMemory // | // V - // || DescriptorSet[0] | .... | DescriptorSet[Ns-1] | Res[0] | ... | Res[n-1] | .... | Res[0] | ... | Res[m-1] || + // || DescriptorSet[0] | .... | DescriptorSet[Ns-1] | Res[0] | ... | Res[n-1] | .... | Res[0] | ... | Res[m-1] | Inline constant values || // // // Ns = m_NumSets @@ -74,8 +74,8 @@ void ShaderResourceCacheVk::InitializeSets(IMemoryAllocator& MemAllocator, Uint3 m_TotalResources += SetSizes[t]; } - const size_t MemorySize = NumSets * sizeof(DescriptorSet) + m_TotalResources * sizeof(Resource); - VERIFY_EXPR(MemorySize == GetRequiredMemorySize(NumSets, SetSizes)); + const size_t MemorySize = NumSets * sizeof(DescriptorSet) + m_TotalResources * sizeof(Resource) + TotalInlineConstantBytes; + VERIFY_EXPR(MemorySize == GetRequiredMemorySize(NumSets, SetSizes, TotalInlineConstantBytes)); #ifdef DILIGENT_DEBUG m_DbgInitializedResources.resize(m_NumSets); #endif @@ -85,6 +85,7 @@ void ShaderResourceCacheVk::InitializeSets(IMemoryAllocator& MemAllocator, Uint3 ALLOCATE_RAW(MemAllocator, "Memory for shader resource cache data", MemorySize), STDDeleter(MemAllocator) // }; + memset(m_pMemory.get(), 0, MemorySize); DescriptorSet* pSets = reinterpret_cast(m_pMemory.get()); Resource* pCurrResPtr = reinterpret_cast(pSets + m_NumSets); @@ -96,7 +97,7 @@ void ShaderResourceCacheVk::InitializeSets(IMemoryAllocator& MemAllocator, Uint3 m_DbgInitializedResources[t].resize(SetSizes[t]); #endif } - VERIFY_EXPR((char*)pCurrResPtr == (char*)m_pMemory.get() + MemorySize); + VERIFY_EXPR(reinterpret_cast(pCurrResPtr) + TotalInlineConstantBytes == reinterpret_cast(m_pMemory.get()) + MemorySize); } } @@ -950,4 +951,54 @@ Uint32 ShaderResourceCacheVk::GetDynamicBufferOffsets(DeviceContextVkImpl* pCt return OffsetInd - StartInd; } + +void ShaderResourceCacheVk::SetInlineConstants(Uint32 DescrSetIndex, + Uint32 CacheOffset, + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants) +{ + VERIFY(pConstants != nullptr, "Source constant data pointer is null"); + VERIFY(NumConstants > 0, "Number of constants must be greater than zero"); + + DescriptorSet& DescrSet = GetDescriptorSet(DescrSetIndex); + Resource& DstRes = DescrSet.GetResource(CacheOffset); + + VERIFY(DstRes.pInlineConstantData != nullptr, "Inline constant data pointer is null. " + "Make sure InitializeInlineConstantBuffer was called for this resource."); + + // Copy to CPU-side staging buffer + Uint32* pDstConstants = reinterpret_cast(DstRes.pInlineConstantData); + memcpy(pDstConstants + FirstConstant, pConstants, NumConstants * sizeof(Uint32)); +} + +const void* ShaderResourceCacheVk::GetInlineConstantData(Uint32 DescrSetIndex, Uint32 CacheOffset) const +{ + const DescriptorSet& DescrSet = GetDescriptorSet(DescrSetIndex); + + VERIFY(CacheOffset < DescrSet.GetSize(), "CacheOffset out of bounds"); + + const Resource& Res = DescrSet.GetResource(CacheOffset); + if (Res.pInlineConstantData != nullptr) + { + return Res.pInlineConstantData; + } + + return nullptr; +} + +void ShaderResourceCacheVk::InitializeInlineConstantBuffer(Uint32 DescrSetIndex, + Uint32 CacheOffset, + Uint32 NumConstants, + Uint32 InlineConstantOffset) +{ + DescriptorSet& DescrSet = GetDescriptorSet(DescrSetIndex); + Resource& DstRes = DescrSet.GetResource(CacheOffset); + + DstRes.pInlineConstantData = GetInlineConstantStorage(InlineConstantOffset); + + // Mark that this cache has inline constants + m_HasInlineConstants = 1; +} + } // namespace Diligent diff --git a/Graphics/GraphicsEngineVulkan/src/ShaderVariableManagerVk.cpp b/Graphics/GraphicsEngineVulkan/src/ShaderVariableManagerVk.cpp index 851f40a73e..4506f2b610 100644 --- a/Graphics/GraphicsEngineVulkan/src/ShaderVariableManagerVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/ShaderVariableManagerVk.cpp @@ -286,7 +286,8 @@ BindResourceHelper::BindResourceHelper(const PipelineResourceSignatureVkImpl& Si m_DstRes {m_CachedSet.GetResource(m_DstResCacheOffset)} // clang-format on { - VERIFY(ArrayIndex < m_ResDesc.ArraySize, "Array index is out of range, but it should've been corrected by ShaderVariableBase::SetArray()"); + // For inline constants, GetArraySize() returns 1 (actual array size), while ArraySize is the number of constants + VERIFY(ArrayIndex < m_ResDesc.GetArraySize(), "Array index is out of range, but it should've been corrected by ShaderVariableBase::SetArray()"); VERIFY(m_DstRes.Type == m_Attribs.GetDescriptorType(), "Inconsistent types"); #ifdef DILIGENT_DEBUG @@ -650,13 +651,35 @@ void ShaderVariableManagerVk::SetBufferDynamicOffset(Uint32 ResIndex, m_ResourceCache.SetDynamicBufferOffset(Attribs.DescrSet, DstResCacheOffset, BufferDynamicOffset); } +void ShaderVariableManagerVk::SetInlineConstants(Uint32 ResIndex, + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants) +{ + const PipelineResourceAttribsVk& Attribs = m_pSignature->GetResourceAttribs(ResIndex); + const PipelineResourceDesc& ResDesc = m_pSignature->GetResourceDesc(ResIndex); + +#ifdef DILIGENT_DEVELOPMENT + VerifyInlineConstants(ResDesc, pConstants, FirstConstant, NumConstants); +#endif + + // All inline constants use the same path at PRS level - store data in the resource cache. + // Push constant selection is done at PSO creation time, not here. + // The data will be used either for push constants (vkCmdPushConstants) or emulated buffers + // depending on the PSO's selection during UpdateInlineConstantBuffers(). + const ResourceCacheContentType CacheType = m_ResourceCache.GetContentType(); + const Uint32 CacheOffset = Attribs.CacheOffset(CacheType); + m_ResourceCache.SetInlineConstants(Attribs.DescrSet, CacheOffset, pConstants, FirstConstant, NumConstants); +} + IDeviceObject* ShaderVariableManagerVk::Get(Uint32 ArrayIndex, Uint32 ResIndex) const { const PipelineResourceDesc& ResDesc = GetResourceDesc(ResIndex); const PipelineResourceAttribsVk& Attribs = GetResourceAttribs(ResIndex); const Uint32 CacheOffset = Attribs.CacheOffset(m_ResourceCache.GetContentType()); - VERIFY_EXPR(ArrayIndex < ResDesc.ArraySize); + // For inline constants, GetArraySize() returns 1 (actual array size) + VERIFY_EXPR(ArrayIndex < ResDesc.GetArraySize()); if (Attribs.DescrSet < m_ResourceCache.GetNumDescriptorSets()) { diff --git a/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp index 0de01359d1..3ba50c7874 100644 --- a/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -78,7 +78,7 @@ std::vector CompileShaderDXC(const ShaderCreateInfo& ShaderCI, { // SPIR-V bytecode generated from HLSL must be legalized to // turn it into a valid vulkan SPIR-V shader. - std::vector LegalizedSPIRV = OptimizeSPIRV(SPIRV, SPV_ENV_MAX, SPIRV_OPTIMIZATION_FLAG_LEGALIZATION); + std::vector LegalizedSPIRV = OptimizeSPIRV(SPIRV, SPIRV_OPTIMIZATION_FLAG_LEGALIZATION); if (!LegalizedSPIRV.empty()) SPIRV = std::move(LegalizedSPIRV); else @@ -235,28 +235,19 @@ void ShaderVkImpl::Initialize(const ShaderCreateInfo& ShaderCI, { if ((ShaderCI.CompileFlags & SHADER_COMPILE_FLAG_SKIP_REFLECTION) == 0) { - IMemoryAllocator& Allocator = GetRawAllocator(); - - std::unique_ptr> pRawMem{ - ALLOCATE(Allocator, "Memory for SPIRVShaderResources", SPIRVShaderResources, 1), - STDDeleterRawMem(Allocator), - }; - const bool LoadShaderInputs = m_Desc.ShaderType == SHADER_TYPE_VERTEX; - new (pRawMem.get()) SPIRVShaderResources // May throw - { - Allocator, - m_SPIRV, - m_Desc, - m_Desc.UseCombinedTextureSamplers ? m_Desc.CombinedSamplerSuffix : nullptr, - LoadShaderInputs, - ShaderCI.LoadConstantBufferReflection, - m_EntryPoint // - }; + SPIRVShaderResources::CreateInfo ResCI; + ResCI.ShaderType = m_Desc.ShaderType; + ResCI.Name = m_Desc.Name; + ResCI.CombinedSamplerSuffix = m_Desc.UseCombinedTextureSamplers ? m_Desc.CombinedSamplerSuffix : nullptr; + ResCI.LoadShaderStageInputs = m_Desc.ShaderType == SHADER_TYPE_VERTEX; + ResCI.LoadUniformBufferReflection = ShaderCI.LoadConstantBufferReflection; + + m_pShaderResources = SPIRVShaderResources::Create(GetRawAllocator(), m_SPIRV, ResCI, &m_EntryPoint); + VERIFY_EXPR(ShaderCI.ByteCode != nullptr || m_EntryPoint == ShaderCI.EntryPoint || (m_EntryPoint == "main" && (ShaderCI.CompileFlags & SHADER_COMPILE_FLAG_HLSL_TO_SPIRV_VIA_GLSL) != 0)); - m_pShaderResources.reset(static_cast(pRawMem.release()), STDDeleterRawMem(Allocator)); - if (LoadShaderInputs && m_pShaderResources->IsHLSLSource()) + if (ResCI.LoadShaderStageInputs && m_pShaderResources->IsHLSLSource()) { m_pShaderResources->MapHLSLVertexShaderInputs(m_SPIRV); } diff --git a/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp b/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp index 4c8067eb59..206fe5926b 100644 --- a/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp @@ -176,6 +176,11 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase Uint32 GetNumBindGroups() const { return m_NumBindGroups; } bool HasDynamicResources() const { return m_NumDynamicBuffers > 0; } + bool HasInlineConstants() const + { + return false; + } + ResourceCacheContentType GetContentType() const { return static_cast(m_ContentType); } WGPUBindGroup UpdateBindGroup(WGPUDevice wgpuDevice, Uint32 GroupIndex, WGPUBindGroupLayout wgpuGroupLayout); diff --git a/Graphics/GraphicsEngineWebGPU/include/ShaderVariableManagerWebGPU.hpp b/Graphics/GraphicsEngineWebGPU/include/ShaderVariableManagerWebGPU.hpp index 1e428fff45..f711c5a8b4 100644 --- a/Graphics/GraphicsEngineWebGPU/include/ShaderVariableManagerWebGPU.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/ShaderVariableManagerWebGPU.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 Diligent Graphics LLC + * Copyright 2023-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. @@ -65,6 +65,11 @@ class ShaderVariableManagerWebGPU : ShaderVariableManagerBase CompileShaderToSPIRV(const ShaderCreateInfo& S { SPIRV = GLSLangUtils::HLSLtoSPIRV(ShaderCI, GLSLangUtils::SpirvVersion::Vk100, WebGPUDefine, WebGPUShaderCI.ppCompilerOutput); - std::string EntryPoint; + SPIRVShaderResources::CreateInfo ResCI; + ResCI.ShaderType = ShaderCI.Desc.ShaderType; + ResCI.Name = ShaderCI.Desc.Name; + ResCI.CombinedSamplerSuffix = ShaderCI.Desc.UseCombinedTextureSamplers ? ShaderCI.Desc.CombinedSamplerSuffix : nullptr; + ResCI.LoadShaderStageInputs = ShaderCI.Desc.ShaderType == SHADER_TYPE_VERTEX; + ResCI.LoadUniformBufferReflection = false; SPIRVShaderResources Resources{ GetRawAllocator(), SPIRV, - ShaderCI.Desc, - ShaderCI.Desc.UseCombinedTextureSamplers ? ShaderCI.Desc.CombinedSamplerSuffix : nullptr, - ShaderCI.Desc.ShaderType == SHADER_TYPE_VERTEX, // LoadShaderStageInputs - false, // LoadUniformBufferReflection - EntryPoint, + ResCI, }; if (ShaderCI.Desc.ShaderType == SHADER_TYPE_VERTEX) diff --git a/Graphics/ShaderTools/CMakeLists.txt b/Graphics/ShaderTools/CMakeLists.txt index 749d5dc633..7c927af1c7 100644 --- a/Graphics/ShaderTools/CMakeLists.txt +++ b/Graphics/ShaderTools/CMakeLists.txt @@ -117,6 +117,7 @@ if(ENABLE_SPIRV) if (${USE_SPIRV_TOOLS}) list(APPEND SOURCE src/SPIRVTools.cpp) + list(APPEND SOURCE src/ConvertUBOToPushConstant.cpp) list(APPEND INCLUDE include/SPIRVTools.hpp) endif() @@ -180,6 +181,17 @@ if(ENABLE_SPIRV) PRIVATE SPIRV-Tools-opt ) + # Add SPIRV-Tools internal headers path for custom pass implementation + # We need both the source directory (for internal headers like pass.h) + # and the binary directory (for generated headers like NonSemanticShaderDebugInfo100.h) + get_target_property(SPIRV_TOOLS_SOURCE_DIR SPIRV-Tools-opt SOURCE_DIR) + get_target_property(SPIRV_TOOLS_BINARY_DIR SPIRV-Tools-opt BINARY_DIR) + get_filename_component(SPIRV_TOOLS_ROOT_DIR "${SPIRV_TOOLS_SOURCE_DIR}/../.." ABSOLUTE) + get_filename_component(SPIRV_TOOLS_ROOT_BINARY_DIR "${SPIRV_TOOLS_BINARY_DIR}/../.." ABSOLUTE) + target_include_directories(Diligent-ShaderTools PRIVATE + ${SPIRV_TOOLS_ROOT_DIR} + ${SPIRV_TOOLS_ROOT_BINARY_DIR} + ) target_compile_definitions(Diligent-ShaderTools PRIVATE USE_SPIRV_TOOLS=1) endif() diff --git a/Graphics/ShaderTools/include/SPIRVShaderResources.hpp b/Graphics/ShaderTools/include/SPIRVShaderResources.hpp index 891cd8a5e2..a29c271ecc 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-2026 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,38 @@ 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); + + if (NumConstants > MAX_INLINE_CONSTANTS) + { + LOG_ERROR_AND_THROW("Inline constants resource '", Name, "' in shader '", ShaderName, "' has ", + NumConstants, " constants. The maximum supported number of inline constants is ", + MAX_INLINE_CONSTANTS, '.'); + } + + 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*)"); @@ -147,13 +196,24 @@ static_assert(sizeof(SPIRVShaderStageInputAttribs) % sizeof(void*) == 0, "Size o class SPIRVShaderResources { public: + struct CreateInfo + { + SHADER_TYPE ShaderType = SHADER_TYPE_UNKNOWN; + const char* Name = nullptr; + const char* CombinedSamplerSuffix = nullptr; + bool LoadShaderStageInputs = false; + bool LoadUniformBufferReflection = false; + }; SPIRVShaderResources(IMemoryAllocator& Allocator, std::vector spirv_binary, - const ShaderDesc& shaderDesc, - const char* CombinedSamplerSuffix, - bool LoadShaderStageInputs, - bool LoadUniformBufferReflection, - std::string& EntryPoint) noexcept(false); + const CreateInfo& CI, + std::string* pEntryPoint = nullptr) noexcept(false); + + static std::shared_ptr Create( + IMemoryAllocator& Allocator, + std::vector spirv_binary, + const CreateInfo& CI, + std::string* pEntryPoint = nullptr) noexcept(false); // clang-format off SPIRVShaderResources (const SPIRVShaderResources&) = delete; @@ -166,28 +226,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 +279,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 +306,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 +373,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 @@ -325,9 +396,10 @@ class SPIRVShaderResources // clang-format off - const char* GetCombinedSamplerSuffix() const { return m_CombinedSamplerSuffix; } - const char* GetShaderName() const { return m_ShaderName; } - bool IsUsingCombinedSamplers() const { return m_CombinedSamplerSuffix != nullptr; } + const char* GetCombinedSamplerSuffix() const { return m_CombinedSamplerSuffix; } + const char* GetShaderName() const { return m_ShaderName; } + bool IsUsingCombinedSamplers() const { return m_CombinedSamplerSuffix != nullptr; } + bool HasUniformBufferReflection() const { return m_UBReflectionBuffer != nullptr; } // clang-format on @@ -359,16 +431,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 +467,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/include/SPIRVTools.hpp b/Graphics/ShaderTools/include/SPIRVTools.hpp index 98bd2565b1..e79aeb2f25 100644 --- a/Graphics/ShaderTools/include/SPIRVTools.hpp +++ b/Graphics/ShaderTools/include/SPIRVTools.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 Diligent Graphics LLC + * Copyright 2019-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. @@ -27,11 +27,10 @@ #pragma once #include +#include #include "FlagEnum.h" -#include "spirv-tools/libspirv.h" - namespace Diligent { @@ -46,7 +45,20 @@ DEFINE_FLAG_ENUM_OPERATORS(SPIRV_OPTIMIZATION_FLAGS); std::vector OptimizeSPIRV(const std::vector& SrcSPIRV, - spv_target_env TargetEnv, + int TargetEnv, // spv_target_env SPIRV_OPTIMIZATION_FLAGS Passes); +std::vector OptimizeSPIRV(const std::vector& SrcSPIRV, + SPIRV_OPTIMIZATION_FLAGS Passes); + +/// Converts a uniform buffer variable to a push constant in SPIR-V bytecode. +/// This function modifies the storage class of the specified variable from Uniform to PushConstant, +/// and removes Binding and DescriptorSet decorations. +/// +/// \param [in] SPIRV - Source SPIR-V bytecode +/// \param [in] BlockName - Name of the uniform buffer block to convert +/// \return Modified SPIR-V bytecode, or empty vector on failure +std::vector ConvertUBOToPushConstants(const std::vector& SPIRV, + const std::string& BlockName); + } // namespace Diligent diff --git a/Graphics/ShaderTools/src/ConvertUBOToPushConstant.cpp b/Graphics/ShaderTools/src/ConvertUBOToPushConstant.cpp new file mode 100644 index 0000000000..1bf2561a73 --- /dev/null +++ b/Graphics/ShaderTools/src/ConvertUBOToPushConstant.cpp @@ -0,0 +1,573 @@ +/* + * Copyright 2019-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. + */ + +#include "SPIRVTools.hpp" +#include "DebugUtilities.hpp" + +// Temporarily disable warning C4127: conditional expression is constant +// This warning is triggered by SPIRV-Tools headers in ThirdParty +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4127) +#endif + +#include "spirv-tools/optimizer.hpp" + +// SPIRV-Tools internal headers for custom pass implementation +#include "source/opt/pass.h" +#include "source/opt/ir_context.h" +#include "source/opt/type_manager.h" +#include "source/opt/decoration_manager.h" + +// Restore warning settings +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#include +#include + +namespace Diligent +{ + +namespace SPIRVToolsInternal +{ + +//Forward declarations + +void SpvOptimizerMessageConsumer( + spv_message_level_t level, + const char* /* source */, + const spv_position_t& /* position */, + const char* message); + +spv_target_env SpvTargetEnvFromSPIRV(const std::vector& SPIRV); + +} // namespace SPIRVToolsInternal + +namespace +{ + +// A pass that converts a uniform buffer variable to a push constant. +// This pass: +// 1. Finds the variable with the specified block name +// 2. Changes its storage class from Uniform to PushConstant +// 3. Updates all pointer types that reference this variable +// 4. Removes Binding and DescriptorSet decorations +class ConvertUBOToPushConstantPass : public spvtools::opt::Pass +{ +public: + explicit ConvertUBOToPushConstantPass(const std::string& block_name) : + m_BlockName{block_name} + {} + + static spvtools::Optimizer::PassToken Create(std::string BlockName) + { + return spvtools::Optimizer::PassToken{ + std::make_unique(BlockName)}; + } + + const char* name() const override { return "convert-ubo-to-push-constant"; } + + Status Process() override + { + bool modified = false; + + // Collect all IDs that match the block name by searching OpName instructions. + // Multiple OpName instructions may have the same name, so we need to check all of them + // to find the one that refers to a UniformBuffer (Uniform storage class + Block decoration). + std::vector candidate_ids; + for (auto& debug_inst : context()->module()->debugs2()) + { + if (debug_inst.opcode() == spv::Op::OpName && + debug_inst.GetOperand(1).AsString() == m_BlockName) + { + candidate_ids.push_back(debug_inst.GetOperand(0).AsId()); + } + } + + if (candidate_ids.empty()) + { + LOG_ERROR_MESSAGE("Failed to convert UBO block '", m_BlockName, "': no OpName found."); + return Status::Failure; + } + + // Try each candidate ID to find a UniformBuffer + spvtools::opt::Instruction* target_var = nullptr; + for (uint32_t named_id : candidate_ids) + { + spvtools::opt::Instruction* named_inst = get_def_use_mgr()->GetDef(named_id); + if (named_inst == nullptr) + { + continue; + } + + if (named_inst->opcode() == spv::Op::OpVariable) + { + // The name refers directly to a variable - check if it's a UniformBuffer + spvtools::opt::Instruction* var_inst = named_inst; + + // Get the pointer type of the variable + spvtools::opt::Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(var_inst->type_id()); + if (ptr_type_inst == nullptr || ptr_type_inst->opcode() != spv::Op::OpTypePointer) + { + continue; + } + + // Check if the storage class is Uniform + spv::StorageClass storage_class = + static_cast(ptr_type_inst->GetSingleWordInOperand(0)); + if (storage_class != spv::StorageClass::Uniform) + { + continue; + } + + // Get the pointee type ID and verify it has Block decoration + uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1); + if (IsUBOBlockType(pointee_type_id)) + { + // Found a UniformBuffer! + target_var = var_inst; + break; + } + } + else if (named_inst->opcode() == spv::Op::OpTypeStruct) + { + // The name refers to a struct type, we need to find the variable + // that uses a pointer to this struct type with Uniform storage class + uint32_t struct_type_id = named_id; + + // Search for a variable that points to this struct type with Uniform storage class + for (auto& inst : context()->types_values()) + { + if (inst.opcode() != spv::Op::OpVariable) + { + continue; + } + + // Get the pointer type of this variable + spvtools::opt::Instruction* ptr_type = get_def_use_mgr()->GetDef(inst.type_id()); + if (ptr_type == nullptr || ptr_type->opcode() != spv::Op::OpTypePointer) + { + continue; + } + + // Check storage class is Uniform + spv::StorageClass sc = static_cast( + ptr_type->GetSingleWordInOperand(0)); + if (sc != spv::StorageClass::Uniform) + { + continue; + } + + // Check if the pointee type is our struct type + uint32_t pointee_type_id = ptr_type->GetSingleWordInOperand(1); + if (pointee_type_id == struct_type_id) + { + // Verify it has Block decoration + if (IsUBOBlockType(pointee_type_id)) + { + // Found a UniformBuffer! + target_var = &inst; + break; + } + } + } + + if (target_var != nullptr) + { + break; + } + } + } + + if (target_var == nullptr) + { + LOG_ERROR_MESSAGE("Failed to convert UBO block '", m_BlockName, "': no matching UniformBuffer found."); + return Status::Failure; + } + + uint32_t target_var_id = target_var->result_id(); + + // Get the pointer type of the variable (we already verified it above, but get it again for consistency) + spvtools::opt::Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(target_var->type_id()); + if (ptr_type_inst == nullptr || ptr_type_inst->opcode() != spv::Op::OpTypePointer) + { + LOG_ERROR_MESSAGE("Failed to convert UBO block '", m_BlockName, "': target variable has unexpected type."); + return Status::Failure; + } + + // Get the pointee type ID + uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1); + + // Create or find a pointer type with PushConstant storage class + uint32_t new_ptr_type_id = + context()->get_type_mgr()->FindPointerToType(pointee_type_id, spv::StorageClass::PushConstant); + + if (new_ptr_type_id == 0) + { + // Failed to create new pointer type + LOG_ERROR_MESSAGE("Failed to convert UBO block '", m_BlockName, "': could not create PushConstant pointer type."); + return Status::Failure; + } + + // IMPORTANT: FindPointerToType() may create a new type instruction at the end of + // the types_values section, or it may return an existing type. In either case, + // SPIR-V requires all IDs to be defined before use (SSA form). + // + // We must ensure the pointer type instruction appears BEFORE the OpVariable + // that will reference it. However, we must NOT move an existing type that is + // already correctly positioned, as that could break other instructions that + // depend on it being defined before their use. + spvtools::opt::Instruction* new_ptr_type_inst = get_def_use_mgr()->GetDef(new_ptr_type_id); + EnsureTypeBeforeUseInTypesValues(new_ptr_type_inst, target_var); + + // Update the variable's type to the new pointer type + target_var->SetResultType(new_ptr_type_id); + + // Also update the storage class operand of OpVariable itself + // OpVariable has the storage class as the first operand (index 0) + target_var->SetInOperand(0, {static_cast(spv::StorageClass::PushConstant)}); + + context()->UpdateDefUse(target_var); + modified = true; + + // IMPORTANT: We changed pointer types + storage class; TypeManager and other analyses may be stale. + // Invalidate analyses that can cache type information and def-use. + context()->InvalidateAnalyses(spvtools::opt::IRContext::kAnalysisTypes | + spvtools::opt::IRContext::kAnalysisDefUse | + spvtools::opt::IRContext::kAnalysisDecorations); + + // Propagate storage class change to all users of this variable + std::vector users; + get_def_use_mgr()->ForEachUser(target_var, [&users](spvtools::opt::Instruction* user) { + users.push_back(user); + }); + + std::unordered_set visited; + for (spvtools::opt::Instruction* user : users) + { + modified |= PropagateStorageClass(*user, visited); + } + + // Remove Binding and DescriptorSet decorations from the variable + auto* deco_mgr = context()->get_decoration_mgr(); + deco_mgr->RemoveDecorationsFrom(target_var_id, [](const spvtools::opt::Instruction& inst) { + if (inst.opcode() != spv::Op::OpDecorate) + { + return false; + } + spv::Decoration decoration = + static_cast(inst.GetSingleWordInOperand(1)); + return decoration == spv::Decoration::Binding || + decoration == spv::Decoration::DescriptorSet; + }); + + // Invalidate decoration analysis since we modified decorations + context()->InvalidateAnalyses(spvtools::opt::IRContext::kAnalysisDecorations); + + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; + } + + spvtools::opt::IRContext::Analysis GetPreservedAnalyses() override + { + // This pass modifies types and decorations + return spvtools::opt::IRContext::kAnalysisNone; + } + +private: + // Returns true if instruction 'a' appears before instruction 'b' in module->types_values(). + // This is used to check if a type definition comes before its use in the SPIR-V module. + bool ComesBeforeInTypesValues(const spvtools::opt::Instruction* a, + const spvtools::opt::Instruction* b) const + { + if (a == nullptr || b == nullptr) + return false; + if (a == b) + return true; // Same instruction, no reordering needed + + bool seen_a = false; + for (auto& inst : context()->module()->types_values()) + { + if (&inst == a) + { + seen_a = true; + } + else if (&inst == b) + { + return seen_a; + } + } + // If either is not in types_values(), be conservative and return false. + return false; + } + + // Ensures 'type_inst' is placed before 'use_inst' in the types_values section. + // Only moves the instruction when type_inst currently appears after use_inst. + // This is necessary because SPIR-V requires all IDs to be defined before use (SSA form). + // + // IMPORTANT: This function only moves type_inst forward (earlier in the list), never backward. + // If type_inst is already before use_inst, no action is taken to avoid breaking existing uses. + // + // If use_inst is not in types_values (e.g., it's in a function body), no action is taken + // because the entire types_values section appears before any function in the module. + void EnsureTypeBeforeUseInTypesValues(spvtools::opt::Instruction* type_inst, + spvtools::opt::Instruction* use_inst) + { + if (type_inst == nullptr || use_inst == nullptr) + return; + + // Check if use_inst is in types_values section. + // If it's not (e.g., it's in a function body like OpAccessChain), + // we don't need to move type_inst because the entire types_values section + // appears before any function in the module. + bool use_in_types_values = false; + for (auto& inst : context()->module()->types_values()) + { + if (&inst == use_inst) + { + use_in_types_values = true; + break; + } + } + + if (!use_in_types_values) + return; + + // If type_inst already comes before use_inst, do nothing. + // This is critical: moving an existing type that's already correctly positioned + // could break other instructions that depend on it. + if (ComesBeforeInTypesValues(type_inst, use_inst)) + return; + + // type_inst appears after use_inst (or not found), so we need to move it. + // Insert it immediately before the use_inst to satisfy the SSA requirement. + type_inst->RemoveFromList(); + type_inst->InsertBefore(use_inst); + } + + // Recursively updates the storage class of pointer types used by instructions + // that reference the target variable. + bool PropagateStorageClass(spvtools::opt::Instruction& inst, std::unordered_set& visited) + { + if (!IsPointerResultType(inst)) + { + return false; + } + + // Use a "visited" set keyed by result_id for ANY pointer-producing instruction. + // This avoids infinite recursion in pointer SSA loops. + if (inst.result_id() != 0) + { + if (!visited.insert(inst.result_id()).second) + return false; + } + + // Already has the correct storage class + if (IsPointerToStorageClass(inst, spv::StorageClass::PushConstant)) + { + std::vector users; + get_def_use_mgr()->ForEachUser(&inst, [&users](spvtools::opt::Instruction* user) { + users.push_back(user); + }); + + bool modified = false; + for (spvtools::opt::Instruction* user : users) + { + if (PropagateStorageClass(*user, visited)) + modified = true; + } + + return modified; + } + + // Handle instructions that produce pointer results + // This switch covers the common pointer-producing opcodes. + // Reference: SPIRV-Tools fix_storage_class.cpp + switch (inst.opcode()) + { + case spv::Op::OpAccessChain: + case spv::Op::OpPtrAccessChain: + case spv::Op::OpInBoundsAccessChain: + case spv::Op::OpInBoundsPtrAccessChain: + case spv::Op::OpCopyObject: + case spv::Op::OpPhi: + case spv::Op::OpSelect: + case spv::Op::OpBitcast: + case spv::Op::OpUndef: + case spv::Op::OpConstantNull: + ChangeResultStorageClass(inst); + { + std::vector users; + get_def_use_mgr()->ForEachUser(&inst, [&users](spvtools::opt::Instruction* user) { + users.push_back(user); + }); + for (spvtools::opt::Instruction* user : users) + { + PropagateStorageClass(*user, visited); + } + } + return true; + + case spv::Op::OpFunctionCall: + // We cannot be sure of the actual connection between the storage class + // of the parameter and the storage class of the result, so we should not + // do anything. If the result type needs to be fixed, the function call + // should be inlined first. + return false; + + case spv::Op::OpLoad: + case spv::Op::OpStore: + case spv::Op::OpCopyMemory: + case spv::Op::OpCopyMemorySized: + case spv::Op::OpImageTexelPointer: + case spv::Op::OpVariable: + // These don't produce pointer results that need updating, + // or the result type is independent of the operand's storage class. + return false; + + default: + // Unexpected pointer-producing instruction. This may indicate + // a new SPIR-V extension or pattern not yet handled. + // Stop propagation on this path and log a warning. + LOG_WARNING_MESSAGE("ConvertUBOToPushConstantPass: unhandled pointer-producing opcode ", + static_cast(inst.opcode()), + " (", spvOpcodeString(inst.opcode()), + "). Storage-class propagation stopped for this chain. " + "If this causes issues, please report it to the developers."); + return false; + } + } + + // Changes the result type of an instruction to use the new storage class. + void ChangeResultStorageClass(spvtools::opt::Instruction& inst) + { + spvtools::opt::Instruction* result_type_inst = get_def_use_mgr()->GetDef(inst.type_id()); + if (result_type_inst == nullptr || result_type_inst->opcode() != spv::Op::OpTypePointer) + return; + + uint32_t pointee_type_id = result_type_inst->GetSingleWordInOperand(1); + uint32_t new_result_type_id = + context()->get_type_mgr()->FindPointerToType(pointee_type_id, spv::StorageClass::PushConstant); + + if (new_result_type_id == 0) + return; + + // Ensure the pointer type is properly positioned in the types section. + // FindPointerToType may return an existing type or create a new one at the end. + // If inst is in types_values (e.g., OpConstantNull), we need to ensure the type + // is defined before inst. If inst is in a function body (e.g., OpAccessChain), + // no reordering is needed since types_values always precedes functions. + spvtools::opt::Instruction* new_type_inst = get_def_use_mgr()->GetDef(new_result_type_id); + EnsureTypeBeforeUseInTypesValues(new_type_inst, &inst); + + inst.SetResultType(new_result_type_id); + context()->UpdateDefUse(&inst); + } + + // Checks if the instruction result type is a pointer. + bool IsPointerResultType(const spvtools::opt::Instruction& inst) const + { + if (inst.type_id() == 0) + { + return false; + } + + spvtools::opt::Instruction* type_def = get_def_use_mgr()->GetDef(inst.type_id()); + return type_def != nullptr && type_def->opcode() == spv::Op::OpTypePointer; + } + + // Checks if the instruction result type is a pointer to the specified storage class. + bool IsPointerToStorageClass(const spvtools::opt::Instruction& inst, spv::StorageClass storage_class) const + { + if (inst.type_id() == 0) + { + return false; + } + + spvtools::opt::Instruction* type_def = get_def_use_mgr()->GetDef(inst.type_id()); + if (type_def == nullptr || type_def->opcode() != spv::Op::OpTypePointer) + { + return false; + } + + spv::StorageClass pointer_storage_class = + static_cast(type_def->GetSingleWordInOperand(0)); + return pointer_storage_class == storage_class; + } + + bool HasDecoration(uint32_t id, spv::Decoration deco) const + { + bool found = false; + get_decoration_mgr()->ForEachDecoration( + id, static_cast(deco), + [&found](const spvtools::opt::Instruction&) { + found = true; + }); + return found; + } + + // Checks if a type has the Block decoration (but not the BufferBlock), + // which identifies it as a UBO struct type. + bool IsUBOBlockType(uint32_t type_id) const + { + return HasDecoration(type_id, spv::Decoration::Block) && + !HasDecoration(type_id, spv::Decoration::BufferBlock); + } + + std::string m_BlockName; +}; + +} // namespace + +std::vector ConvertUBOToPushConstants( + const std::vector& SPIRV, + const std::string& BlockName) +{ + spv_target_env TargetEnv = SPIRVToolsInternal::SpvTargetEnvFromSPIRV(SPIRV); + + spvtools::Optimizer optimizer{TargetEnv}; + + optimizer.SetMessageConsumer(SPIRVToolsInternal::SpvOptimizerMessageConsumer); + + // Inline all function calls to eliminate OpFunctionCall instructions + optimizer.RegisterPass(spvtools::CreateInlineExhaustivePass()); + + // Inlining tends to leave junk behind. You can clean it up + optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); + optimizer.RegisterPass(spvtools::CreateEliminateDeadFunctionsPass()); + + // Register the pass to convert UBO to push constant using custom out-of-tree pass + optimizer.RegisterPass(ConvertUBOToPushConstantPass::Create(BlockName)); + + spvtools::OptimizerOptions options; +#ifdef DILIGENT_DEVELOPMENT + // Run validator in debug build + options.set_run_validator(true); +#else + // Do not run validator in release build + options.set_run_validator(false); +#endif + + std::vector result; + if (!optimizer.Run(SPIRV.data(), SPIRV.size(), &result, options)) + { + return {}; + } + return result; +} + +} // namespace Diligent diff --git a/Graphics/ShaderTools/src/GLSLangUtils.cpp b/Graphics/ShaderTools/src/GLSLangUtils.cpp index df53587760..80257e970f 100644 --- a/Graphics/ShaderTools/src/GLSLangUtils.cpp +++ b/Graphics/ShaderTools/src/GLSLangUtils.cpp @@ -49,6 +49,7 @@ #include "ShaderToolsCommon.hpp" #ifdef USE_SPIRV_TOOLS # include "SPIRVTools.hpp" +# include "spirv-tools/libspirv.h" #endif // clang-format off diff --git a/Graphics/ShaderTools/src/SPIRVShaderResources.cpp b/Graphics/ShaderTools/src/SPIRVShaderResources.cpp index dc65e596bb..7fb9163408 100644 --- a/Graphics/ShaderTools/src/SPIRVShaderResources.cpp +++ b/Graphics/ShaderTools/src/SPIRVShaderResources.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -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) @@ -378,12 +471,9 @@ ShaderCodeBufferDescX LoadUBReflection(const diligent_spirv_cross::Compiler& Com SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, std::vector spirv_binary, - const ShaderDesc& shaderDesc, - const char* CombinedSamplerSuffix, - bool LoadShaderStageInputs, - bool LoadUniformBufferReflection, - std::string& EntryPoint) noexcept(false) : - m_ShaderType{shaderDesc.ShaderType} + const CreateInfo& CI, + std::string* pEntryPoint) noexcept(false) : + m_ShaderType{CI.ShaderType} { // https://github.com/KhronosGroup/SPIRV-Cross/wiki/Reflection-API-user-guide diligent_spirv_cross::Parser parser{std::move(spirv_binary)}; @@ -393,7 +483,10 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, m_IsHLSLSource = ParsedIRSource.hlsl; diligent_spirv_cross::Compiler Compiler{std::move(parser.get_parsed_ir())}; - spv::ExecutionModel ExecutionModel = ShaderTypeToSpvExecutionModel(shaderDesc.ShaderType); + std::string EntryPointLocal; + std::string& EntryPoint = pEntryPoint != nullptr ? *pEntryPoint : EntryPointLocal; + + spv::ExecutionModel ExecutionModel = ShaderTypeToSpvExecutionModel(m_ShaderType); auto EntryPoints = Compiler.get_entry_points_and_stages(); for (const diligent_spirv_cross::EntryPoint& CurrEntryPoint : EntryPoints) { @@ -401,7 +494,7 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, { if (!EntryPoint.empty()) { - LOG_WARNING_MESSAGE("More than one entry point of type ", GetShaderTypeLiteralName(shaderDesc.ShaderType), " found in SPIRV binary for shader '", shaderDesc.Name, "'. The first one ('", EntryPoint, "') will be used."); + LOG_WARNING_MESSAGE("More than one entry point of type ", GetShaderTypeLiteralName(m_ShaderType), " found in SPIRV binary for shader '", CI.Name, "'. The first one ('", EntryPoint, "') will be used."); } else { @@ -411,7 +504,7 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, } if (EntryPoint.empty()) { - LOG_ERROR_AND_THROW("Unable to find entry point of type ", GetShaderTypeLiteralName(shaderDesc.ShaderType), " in SPIRV binary for shader '", shaderDesc.Name, "'"); + LOG_ERROR_AND_THROW("Unable to find entry point of type ", GetShaderTypeLiteralName(m_ShaderType), " in SPIRV binary for shader '", CI.Name, "'"); } Compiler.set_entry_point(EntryPoint, ExecutionModel); @@ -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 '", CI.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, @@ -439,16 +543,17 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, ResourceNamesPoolSize += res.name.length() + 1; } - if (CombinedSamplerSuffix != nullptr) + if (CI.CombinedSamplerSuffix != nullptr) { - ResourceNamesPoolSize += strlen(CombinedSamplerSuffix) + 1; + ResourceNamesPoolSize += strlen(CI.CombinedSamplerSuffix) + 1; } - VERIFY_EXPR(shaderDesc.Name != nullptr); - ResourceNamesPoolSize += strlen(shaderDesc.Name) + 1; + VERIFY_EXPR(CI.Name != nullptr); + ResourceNamesPoolSize += strlen(CI.Name) + 1; Uint32 NumShaderStageInputs = 0; + bool LoadShaderStageInputs = CI.LoadShaderStageInputs; if (!m_IsHLSLSource || resources.stage_inputs.empty()) LoadShaderStageInputs = false; if (LoadShaderStageInputs) @@ -483,7 +588,7 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, LoadShaderStageInputs = false; if (m_IsHLSLSource) { - LOG_WARNING_MESSAGE("SPIRV byte code of shader '", shaderDesc.Name, + LOG_WARNING_MESSAGE("SPIRV byte code of shader '", CI.Name, "' does not use SPV_GOOGLE_hlsl_functionality1 extension. " "As a result, it is not possible to get semantics of shader inputs and map them to proper locations. " "The shader will still work correctly if all attributes are declared in ascending order without any gaps. " @@ -493,16 +598,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; @@ -527,7 +633,7 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, static_cast(Size) // }; - if (LoadUniformBufferReflection) + if (CI.LoadUniformBufferReflection) { UBReflections.emplace_back(LoadUBReflection(Compiler, UB, m_IsHLSLSource)); } @@ -687,14 +793,33 @@ 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); - if (CombinedSamplerSuffix != nullptr) + // 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 (CI.CombinedSamplerSuffix != nullptr) { - m_CombinedSamplerSuffix = ResourceNamesPool.CopyString(CombinedSamplerSuffix); + m_CombinedSamplerSuffix = ResourceNamesPool.CopyString(CI.CombinedSamplerSuffix); } - m_ShaderName = ResourceNamesPool.CopyString(shaderDesc.Name); + m_ShaderName = ResourceNamesPool.CopyString(CI.Name); if (LoadShaderStageInputs) { @@ -716,7 +841,7 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, VERIFY(ResourceNamesPool.GetRemainingSize() == 0, "Names pool must be empty"); - if (shaderDesc.ShaderType == SHADER_TYPE_COMPUTE) + if (m_ShaderType == SHADER_TYPE_COMPUTE) { for (uint32_t i = 0; i < m_ComputeGroupSize.size(); ++i) m_ComputeGroupSize[i] = Compiler.get_execution_mode_argument(spv::ExecutionModeLocalSize, i); @@ -724,7 +849,7 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, if (!UBReflections.empty()) { - VERIFY_EXPR(LoadUniformBufferReflection); + VERIFY_EXPR(CI.LoadUniformBufferReflection); VERIFY_EXPR(UBReflections.size() == GetNumUBs()); m_UBReflectionBuffer = ShaderCodeBufferDescX::PackArray(UBReflections.cbegin(), UBReflections.cend(), GetRawAllocator()); } @@ -757,8 +882,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 +897,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 +953,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 +1078,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 +1106,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()); @@ -973,4 +1120,30 @@ std::string SPIRVShaderResources::DumpResources() const return ss.str(); } + +std::shared_ptr SPIRVShaderResources::Create( + IMemoryAllocator& Allocator, + std::vector spirv_binary, + const CreateInfo& CI, + std::string* pEntryPoint) noexcept(false) +{ + std::unique_ptr> pRawMem{ + ALLOCATE(Allocator, "Memory for SPIRVShaderResources", SPIRVShaderResources, 1), + STDDeleterRawMem(Allocator), + }; + + new (pRawMem.get()) SPIRVShaderResources // May throw + { + Allocator, + std::move(spirv_binary), + CI, + pEntryPoint, + }; + + return { + static_cast(pRawMem.release()), + STDDeleterRawMem(Allocator), + }; +} + } // namespace Diligent diff --git a/Graphics/ShaderTools/src/SPIRVTools.cpp b/Graphics/ShaderTools/src/SPIRVTools.cpp index 75793da993..0681426298 100644 --- a/Graphics/ShaderTools/src/SPIRVTools.cpp +++ b/Graphics/ShaderTools/src/SPIRVTools.cpp @@ -32,7 +32,7 @@ namespace Diligent { -namespace +namespace SPIRVToolsInternal { void SpvOptimizerMessageConsumer( @@ -103,19 +103,20 @@ spv_target_env SpvTargetEnvFromSPIRV(const std::vector& SPIRV) case SPV_SPIRV_VERSION_WORD(1, 6): return SPV_ENV_VULKAN_1_3; default: return SPV_ENV_VULKAN_1_3; } +#undef SPV_SPIRV_VERSION_WORD } -} // namespace +} // namespace SPIRVToolsInternal -std::vector OptimizeSPIRV(const std::vector& SrcSPIRV, spv_target_env TargetEnv, SPIRV_OPTIMIZATION_FLAGS Passes) +std::vector OptimizeSPIRV(const std::vector& SrcSPIRV, int TargetEnv, SPIRV_OPTIMIZATION_FLAGS Passes) { VERIFY_EXPR(Passes != SPIRV_OPTIMIZATION_FLAG_NONE); if (TargetEnv == SPV_ENV_MAX) - TargetEnv = SpvTargetEnvFromSPIRV(SrcSPIRV); + TargetEnv = SPIRVToolsInternal::SpvTargetEnvFromSPIRV(SrcSPIRV); - spvtools::Optimizer SpirvOptimizer(TargetEnv); - SpirvOptimizer.SetMessageConsumer(SpvOptimizerMessageConsumer); + spvtools::Optimizer SpirvOptimizer(static_cast(TargetEnv)); + SpirvOptimizer.SetMessageConsumer(SPIRVToolsInternal::SpvOptimizerMessageConsumer); spvtools::OptimizerOptions Options; #ifndef DILIGENT_DEVELOPMENT @@ -153,4 +154,10 @@ std::vector OptimizeSPIRV(const std::vector& SrcSPIRV, spv_t return OptimizedSPIRV; } +std::vector OptimizeSPIRV(const std::vector& SrcSPIRV, + SPIRV_OPTIMIZATION_FLAGS Passes) +{ + return OptimizeSPIRV(SrcSPIRV, SPV_ENV_MAX, Passes); +} + } // namespace Diligent diff --git a/Tests/DiligentCoreAPITest/CMakeLists.txt b/Tests/DiligentCoreAPITest/CMakeLists.txt index b2b1ccdd5f..ef5c7fe764 100644 --- a/Tests/DiligentCoreAPITest/CMakeLists.txt +++ b/Tests/DiligentCoreAPITest/CMakeLists.txt @@ -56,6 +56,12 @@ if(VULKAN_SUPPORTED) list(APPEND SOURCE ${VK_SOURCE}) endif() +if(NOT VULKAN_SUPPORTED OR NOT ${DILIGENT_USE_SPIRV_TOOLCHAIN} OR ${DILIGENT_NO_GLSLANG}) + list(REMOVE_ITEM SOURCE + ${CMAKE_CURRENT_SOURCE_DIR}/src/Vulkan/ConvertUBOToPushConstantsTestVk.cpp + ) +endif() + if(METAL_SUPPORTED) file(GLOB MTL_SOURCE LIST_DIRECTORIES false src/Metal/*) file(GLOB MTL_INCLUDE LIST_DIRECTORIES false include/Metal/*) diff --git a/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp b/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp new file mode 100644 index 0000000000..656f74bd43 --- /dev/null +++ b/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp @@ -0,0 +1,772 @@ +/* + * 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 "GPUTestingEnvironment.hpp" + +#include "gtest/gtest.h" + +#include "RenderStateCache.hpp" +#include "GraphicsTypesX.hpp" +#include "FastRand.hpp" + + +namespace Diligent +{ +namespace Testing +{ +void RenderDrawCommandReference(ISwapChain* pSwapChain, const float* pClearColor = nullptr); +} +} // namespace Diligent + + +using namespace Diligent; +using namespace Diligent::Testing; + +namespace +{ + +namespace HLSL +{ + +const std::string InlineConstantsTest_VS{ + R"( +cbuffer cbInlinePositions +{ + float4 g_Positions[6]; +} + +cbuffer cbInlineColors +{ + float4 g_Colors[3]; +} + +struct PSInput +{ + float4 Pos : SV_POSITION; + float3 Color : COLOR; +}; + +void main(uint VertexId : SV_VertexId, + out PSInput PSIn) +{ + PSIn.Pos = g_Positions[VertexId]; + PSIn.Color = g_Colors[VertexId % 3].rgb; +} +)"}; + +const std::string InlineConstantsTest_PS{ + R"( +struct PSInput +{ + float4 Pos : SV_POSITION; + float3 Color : COLOR; +}; + +cbuffer cbInlineColors +{ + float4 g_Colors[3]; +} + +float4 main(in PSInput PSIn) : SV_Target +{ + float3 Color = PSIn.Color.rgb; + Color.r *= g_Colors[0].a; + Color.g *= g_Colors[1].a; + Color.b *= g_Colors[2].a; + return float4(Color, 1.0); +} +)"}; + +} // namespace HLSL + +float4 g_Positions[] = { + float4{-1.0f, -0.5f, 0.f, 1.f}, + float4{-0.5f, +0.5f, 0.f, 1.f}, + float4{0.0f, -0.5f, 0.f, 1.f}, + + float4{+0.0f, -0.5f, 0.f, 1.f}, + float4{+0.5f, +0.5f, 0.f, 1.f}, + float4{+1.0f, -0.5f, 0.f, 1.f}, +}; + +float4 g_Colors[] = { + float4{1.f, 0.f, 0.f, 1.f}, + float4{0.f, 1.f, 0.f, 1.f}, + float4{0.f, 0.f, 1.f, 1.f}, +}; + +constexpr Uint32 kNumPosConstants = sizeof(g_Positions) / 4; +constexpr Uint32 kNumColConstants = sizeof(g_Colors) / 4; + +class InlineConstants : public ::testing::Test +{ +protected: + static void SetUpTestSuite() + { + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + IRenderDevice* pDevice = pEnv->GetDevice(); + + if (!pDevice->GetDeviceInfo().IsD3DDevice() && !pDevice->GetDeviceInfo().IsVulkanDevice()) + { + GTEST_SKIP(); + } + + ShaderCreateInfo ShaderCI; + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL; + ShaderCI.ShaderCompiler = pEnv->GetDefaultCompiler(ShaderCI.SourceLanguage); + + { + ShaderCI.Desc = {"Inline constants test", SHADER_TYPE_VERTEX, true}; + ShaderCI.EntryPoint = "main"; + ShaderCI.Source = HLSL::InlineConstantsTest_VS.c_str(); + pDevice->CreateShader(ShaderCI, &sm_Res.pVS); + ASSERT_NE(sm_Res.pVS, nullptr); + } + + { + ShaderCI.Desc = {"Inline constants test", SHADER_TYPE_PIXEL, true}; + ShaderCI.EntryPoint = "main"; + ShaderCI.Source = HLSL::InlineConstantsTest_PS.c_str(); + pDevice->CreateShader(ShaderCI, &sm_Res.pPS); + ASSERT_NE(sm_Res.pPS, nullptr); + } + } + + static void TearDownTestSuite() + { + sm_Res = {}; + + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + pEnv->Reset(); + } + + static void TestSignatures(Uint32 NumSignatures); + + static void Present() + { + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + ISwapChain* pSwapChain = pEnv->GetSwapChain(); + IDeviceContext* pContext = pEnv->GetDeviceContext(); + + pSwapChain->Present(); + + pContext->Flush(); + pContext->InvalidateState(); + } + + static void VerifyPSOFromCache(IPipelineState* pPSO, + IShaderResourceBinding* pSRB); + + struct Resources + { + RefCntAutoPtr pVS; + RefCntAutoPtr pPS; + }; + static Resources sm_Res; + + static FastRandFloat sm_Rnd; +}; + +InlineConstants::Resources InlineConstants::sm_Res; +FastRandFloat InlineConstants::sm_Rnd{0, 0.f, 1.f}; + + +TEST_F(InlineConstants, ResourceLayout) +{ + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + IRenderDevice* pDevice = pEnv->GetDevice(); + IDeviceContext* pContext = pEnv->GetDeviceContext(); + ISwapChain* pSwapChain = pEnv->GetSwapChain(); + + for (Uint32 pos_type = 0; pos_type < SHADER_RESOURCE_VARIABLE_TYPE_NUM_TYPES; ++pos_type) + { + for (Uint32 col_type = 0; col_type < SHADER_RESOURCE_VARIABLE_TYPE_NUM_TYPES; ++col_type) + { + const float ClearColor[] = {sm_Rnd(), sm_Rnd(), sm_Rnd(), sm_Rnd()}; + RenderDrawCommandReference(pSwapChain, ClearColor); + + SHADER_RESOURCE_VARIABLE_TYPE PosType = static_cast(pos_type); + SHADER_RESOURCE_VARIABLE_TYPE ColType = static_cast(col_type); + + GraphicsPipelineStateCreateInfoX PsoCI{"Inline constants test"}; + + PipelineResourceLayoutDescX ResLayoutDesc; + ResLayoutDesc + .AddVariable(SHADER_TYPE_VERTEX, "cbInlinePositions", PosType, SHADER_VARIABLE_FLAG_INLINE_CONSTANTS) + .AddVariable(SHADER_TYPE_VS_PS, "cbInlineColors", ColType, SHADER_VARIABLE_FLAG_INLINE_CONSTANTS); + + PsoCI + .AddRenderTarget(pSwapChain->GetDesc().ColorBufferFormat) + .SetPrimitiveTopology(PRIMITIVE_TOPOLOGY_TRIANGLE_LIST) + .AddShader(sm_Res.pVS) + .AddShader(sm_Res.pPS) + .SetResourceLayout(ResLayoutDesc); + PsoCI.GraphicsPipeline.DepthStencilDesc.DepthEnable = False; + + RefCntAutoPtr pPSO; + pDevice->CreateGraphicsPipelineState(PsoCI, &pPSO); + ASSERT_TRUE(pPSO); + + if (PosType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + { + IShaderResourceVariable* pVar = pPSO->GetStaticVariableByName(SHADER_TYPE_VERTEX, "cbInlinePositions"); + ASSERT_TRUE(pVar); + pVar->SetInlineConstants(g_Positions, 0, kNumPosConstants); + } + + if (ColType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + { + IShaderResourceVariable* pVar = pPSO->GetStaticVariableByName(SHADER_TYPE_VERTEX, "cbInlineColors"); + ASSERT_TRUE(pVar); + pVar->SetInlineConstants(g_Colors, 0, kNumColConstants); + } + + RefCntAutoPtr pSRB; + pPSO->CreateShaderResourceBinding(&pSRB, true); + ASSERT_TRUE(pSRB); + + IShaderResourceVariable* pPosVar = nullptr; + if (PosType != SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + { + pPosVar = pSRB->GetVariableByName(SHADER_TYPE_VERTEX, "cbInlinePositions"); + ASSERT_TRUE(pPosVar); + } + + IShaderResourceVariable* pColVarVS = nullptr; + IShaderResourceVariable* pColVarPS = nullptr; + if (ColType != SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + { + pColVarVS = pSRB->GetVariableByName(SHADER_TYPE_VERTEX, "cbInlineColors"); + ASSERT_TRUE(pColVarVS); + pColVarPS = pSRB->GetVariableByName(SHADER_TYPE_PIXEL, "cbInlineColors"); + ASSERT_TRUE(pColVarPS); + } + + ITextureView* pRTVs[] = {pSwapChain->GetCurrentBackBufferRTV()}; + pContext->SetRenderTargets(1, pRTVs, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + pContext->ClearRenderTarget(pRTVs[0], ClearColor, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + + if (pColVarVS != nullptr) + { + // Set first half of color constants before committing SRB + pColVarVS->SetInlineConstants(g_Colors, 0, kNumColConstants / 2); + } + + pContext->CommitShaderResources(pSRB, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + if (pColVarPS != nullptr) + { + // Set second half of color constants after committing SRB + pColVarPS->SetInlineConstants(g_Colors[0].Data() + kNumColConstants / 2, kNumColConstants / 2, kNumColConstants / 2); + } + + pContext->SetPipelineState(pPSO); + + if (pPosVar == nullptr) + { + // Draw both triangles as positions are static + pContext->Draw({6, DRAW_FLAG_VERIFY_ALL}); + } + else + { + // Draw first triangle + pPosVar->SetInlineConstants(g_Positions, 0, kNumPosConstants / 2); + pContext->Draw({3, DRAW_FLAG_VERIFY_ALL}); + + // Draw second triangle + pPosVar->SetInlineConstants(g_Positions[0].Data() + kNumPosConstants / 2, 0, kNumPosConstants / 2); + pContext->Draw({3, DRAW_FLAG_VERIFY_ALL}); + } + + Present(); + + std::cout << TestingEnvironment::GetCurrentTestStatusString() << ' ' + << " Pos " << GetShaderVariableTypeLiteralName(PosType) << ',' + << " Col " << GetShaderVariableTypeLiteralName(ColType) << std::endl; + } + } +} + + +void InlineConstants::TestSignatures(Uint32 NumSignatures) +{ + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + IRenderDevice* pDevice = pEnv->GetDevice(); + IDeviceContext* pContext = pEnv->GetDeviceContext(); + ISwapChain* pSwapChain = pEnv->GetSwapChain(); + + RefCntAutoPtr pConstBuffer = pEnv->CreateBuffer({"InlineConstants - dummy const buffer", 256, BIND_UNIFORM_BUFFER}); + ASSERT_TRUE(pConstBuffer); + RefCntAutoPtr pTexture = pEnv->CreateTexture("InlineConstants - dummy texture", TEX_FORMAT_RGBA8_UNORM, BIND_SHADER_RESOURCE, 64, 64); + ASSERT_TRUE(pTexture); + ITextureView* pTexSRV = pTexture->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE); + ASSERT_TRUE(pTexSRV); + + for (Uint32 pos_type = 0; pos_type < SHADER_RESOURCE_VARIABLE_TYPE_NUM_TYPES; ++pos_type) + { + for (Uint32 col_type = 0; col_type < SHADER_RESOURCE_VARIABLE_TYPE_NUM_TYPES; ++col_type) + { + const float ClearColor[] = {sm_Rnd(), sm_Rnd(), sm_Rnd(), sm_Rnd()}; + RenderDrawCommandReference(pSwapChain, ClearColor); + + SHADER_RESOURCE_VARIABLE_TYPE PosType = static_cast(pos_type); + SHADER_RESOURCE_VARIABLE_TYPE ColType = static_cast(col_type); + + RefCntAutoPtr pPosSign; + RefCntAutoPtr pColSign; + + PipelineResourceSignatureDescX SignDesc{"Inline constants test"}; + SignDesc + .AddResource(SHADER_TYPE_VERTEX, "cb0_stat", 1u, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_STATIC, PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS) + .AddResource(SHADER_TYPE_VERTEX, "cb0_mut", 1u, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE) + .AddResource(SHADER_TYPE_VERTEX, "cb0_dyn", 1u, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC) + .AddResource(SHADER_TYPE_VERTEX, "tex0_stat", SHADER_RESOURCE_TYPE_TEXTURE_SRV, SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + .AddResource(SHADER_TYPE_VERTEX, "tex0_mut", SHADER_RESOURCE_TYPE_TEXTURE_SRV, SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE) + .AddResource(SHADER_TYPE_VERTEX, "tex0_dyn", SHADER_RESOURCE_TYPE_TEXTURE_SRV, SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC) + + .AddResource(SHADER_TYPE_VERTEX, "cbInlinePositions", kNumPosConstants, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, PosType, PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS); + + if (NumSignatures == 2) + { + pDevice->CreatePipelineResourceSignature(SignDesc, &pPosSign); + ASSERT_TRUE(pPosSign); + + SignDesc.Clear(); + SignDesc.Name = "Inline constants test 2"; + SignDesc.BindingIndex = 1; + } + + SignDesc.AddResource(SHADER_TYPE_VERTEX, "cb1_stat", 1u, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_STATIC, PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS) + .AddResource(SHADER_TYPE_VERTEX, "cb1_mut", 1u, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE) + .AddResource(SHADER_TYPE_VERTEX, "cb1_dyn", 1u, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC) + .AddResource(SHADER_TYPE_VERTEX, "tex1_stat", SHADER_RESOURCE_TYPE_TEXTURE_SRV, SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + .AddResource(SHADER_TYPE_VERTEX, "tex1_mut", SHADER_RESOURCE_TYPE_TEXTURE_SRV, SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE) + .AddResource(SHADER_TYPE_VERTEX, "tex1_dyn", SHADER_RESOURCE_TYPE_TEXTURE_SRV, SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC) + + .AddResource(SHADER_TYPE_VS_PS, "cbInlineColors", kNumColConstants, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, ColType, PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + + .AddResource(SHADER_TYPE_VERTEX, "cb2_stat", 1u, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_STATIC, PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS) + .AddResource(SHADER_TYPE_VERTEX, "cb2_mut", 1u, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE) + .AddResource(SHADER_TYPE_VERTEX, "cb2_dyn", 1u, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC) + .AddResource(SHADER_TYPE_VERTEX, "tex2_stat", SHADER_RESOURCE_TYPE_TEXTURE_SRV, SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + .AddResource(SHADER_TYPE_VERTEX, "tex2_mut", SHADER_RESOURCE_TYPE_TEXTURE_SRV, SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE) + .AddResource(SHADER_TYPE_VERTEX, "tex2_dyn", SHADER_RESOURCE_TYPE_TEXTURE_SRV, SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC); + + if (NumSignatures == 1) + { + pDevice->CreatePipelineResourceSignature(SignDesc, &pPosSign); + ASSERT_TRUE(pPosSign); + pColSign = pPosSign; + } + else if (NumSignatures == 2) + { + pDevice->CreatePipelineResourceSignature(SignDesc, &pColSign); + ASSERT_TRUE(pColSign); + } + else + { + GTEST_FAIL() << "Invalid number of signatures: " << NumSignatures; + } + + pPosSign->GetStaticVariableByName(SHADER_TYPE_VERTEX, "cb0_stat")->Set(pConstBuffer); + pPosSign->GetStaticVariableByName(SHADER_TYPE_VERTEX, "tex0_stat")->Set(pTexSRV); + pColSign->GetStaticVariableByName(SHADER_TYPE_VERTEX, "cb1_stat")->Set(pConstBuffer); + pColSign->GetStaticVariableByName(SHADER_TYPE_VERTEX, "tex1_stat")->Set(pTexSRV); + pColSign->GetStaticVariableByName(SHADER_TYPE_VERTEX, "cb2_stat")->Set(pConstBuffer); + pColSign->GetStaticVariableByName(SHADER_TYPE_VERTEX, "tex2_stat")->Set(pTexSRV); + + GraphicsPipelineStateCreateInfoX PsoCI{"Inline constants test"}; + PsoCI + .AddRenderTarget(pSwapChain->GetDesc().ColorBufferFormat) + .SetPrimitiveTopology(PRIMITIVE_TOPOLOGY_TRIANGLE_LIST) + .AddShader(sm_Res.pVS) + .AddShader(sm_Res.pPS) + .AddSignature(pPosSign); + if (NumSignatures == 2) + { + PsoCI.AddSignature(pColSign); + } + PsoCI.GraphicsPipeline.DepthStencilDesc.DepthEnable = False; + + RefCntAutoPtr pPSO; + pDevice->CreateGraphicsPipelineState(PsoCI, &pPSO); + ASSERT_TRUE(pPSO); + + if (PosType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + { + IShaderResourceVariable* pVar = pPosSign->GetStaticVariableByName(SHADER_TYPE_VERTEX, "cbInlinePositions"); + ASSERT_TRUE(pVar); + pVar->SetInlineConstants(g_Positions, 0, kNumPosConstants); + } + + if (ColType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + { + IShaderResourceVariable* pVar = pColSign->GetStaticVariableByName(SHADER_TYPE_VERTEX, "cbInlineColors"); + ASSERT_TRUE(pVar); + pVar->SetInlineConstants(g_Colors, 0, kNumColConstants); + } + + RefCntAutoPtr pPosSRB; + pPosSign->CreateShaderResourceBinding(&pPosSRB, true); + ASSERT_TRUE(pPosSRB); + + RefCntAutoPtr pColSRB; + if (NumSignatures == 1) + { + pColSRB = pPosSRB; + } + else if (NumSignatures == 2) + { + pColSign->CreateShaderResourceBinding(&pColSRB, true); + ASSERT_TRUE(pColSRB); + } + + pPosSRB->GetVariableByName(SHADER_TYPE_VERTEX, "cb0_mut")->Set(pConstBuffer); + pPosSRB->GetVariableByName(SHADER_TYPE_VERTEX, "tex0_mut")->Set(pTexSRV); + pColSRB->GetVariableByName(SHADER_TYPE_VERTEX, "cb1_mut")->Set(pConstBuffer); + pColSRB->GetVariableByName(SHADER_TYPE_VERTEX, "tex1_mut")->Set(pTexSRV); + pColSRB->GetVariableByName(SHADER_TYPE_VERTEX, "cb2_mut")->Set(pConstBuffer); + pColSRB->GetVariableByName(SHADER_TYPE_VERTEX, "tex2_mut")->Set(pTexSRV); + pPosSRB->GetVariableByName(SHADER_TYPE_VERTEX, "cb0_dyn")->Set(pConstBuffer); + pPosSRB->GetVariableByName(SHADER_TYPE_VERTEX, "tex0_dyn")->Set(pTexSRV); + pColSRB->GetVariableByName(SHADER_TYPE_VERTEX, "cb1_dyn")->Set(pConstBuffer); + pColSRB->GetVariableByName(SHADER_TYPE_VERTEX, "tex1_dyn")->Set(pTexSRV); + pColSRB->GetVariableByName(SHADER_TYPE_VERTEX, "cb2_dyn")->Set(pConstBuffer); + pColSRB->GetVariableByName(SHADER_TYPE_VERTEX, "tex2_dyn")->Set(pTexSRV); + + IShaderResourceVariable* pPosVar = nullptr; + if (PosType != SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + { + pPosVar = pPosSRB->GetVariableByName(SHADER_TYPE_VERTEX, "cbInlinePositions"); + ASSERT_TRUE(pPosVar); + } + + IShaderResourceVariable* pColVarVS = nullptr; + IShaderResourceVariable* pColVarPS = nullptr; + if (ColType != SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + { + pColVarVS = pColSRB->GetVariableByName(SHADER_TYPE_VERTEX, "cbInlineColors"); + ASSERT_TRUE(pColVarVS); + pColVarPS = pColSRB->GetVariableByName(SHADER_TYPE_PIXEL, "cbInlineColors"); + ASSERT_TRUE(pColVarPS); + } + + ITextureView* pRTVs[] = {pSwapChain->GetCurrentBackBufferRTV()}; + pContext->SetRenderTargets(1, pRTVs, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + pContext->ClearRenderTarget(pRTVs[0], ClearColor, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + + if (pColVarVS != nullptr) + { + // Set first half of color constants before committing SRB + pColVarVS->SetInlineConstants(g_Colors, 0, kNumColConstants / 2); + } + + pContext->CommitShaderResources(pPosSRB, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + if (NumSignatures == 2) + { + pContext->TransitionShaderResources(pColSRB); + pContext->CommitShaderResources(pColSRB, RESOURCE_STATE_TRANSITION_MODE_VERIFY); + } + + if (pColVarPS != nullptr) + { + // Set second half of color constants after committing SRB + pColVarPS->SetInlineConstants(g_Colors[0].Data() + kNumColConstants / 2, kNumColConstants / 2, kNumColConstants / 2); + } + + pContext->SetPipelineState(pPSO); + + if (pPosVar == nullptr) + { + // Draw both triangles as positions are static + pContext->Draw({6, DRAW_FLAG_VERIFY_ALL}); + } + else + { + // Draw first triangle + pPosVar->SetInlineConstants(g_Positions, 0, kNumPosConstants / 2); + pContext->Draw({3, DRAW_FLAG_VERIFY_ALL}); + + // Draw second triangle + pPosVar->SetInlineConstants(g_Positions[0].Data() + kNumPosConstants / 2, 0, kNumPosConstants / 2); + pContext->Draw({3, DRAW_FLAG_VERIFY_ALL}); + } + + Present(); + + std::cout << TestingEnvironment::GetCurrentTestStatusString() << ' ' + << " Pos " << GetShaderVariableTypeLiteralName(PosType) << ',' + << " Col " << GetShaderVariableTypeLiteralName(ColType) << std::endl; + } + } +} + +TEST_F(InlineConstants, ResourceSignature) +{ + TestSignatures(1); +} + +TEST_F(InlineConstants, TwoResourceSignatures) +{ + TestSignatures(2); +} + +constexpr Uint32 kCacheContentVersion = 7; + +RefCntAutoPtr CreateCache(IRenderDevice* pDevice, + bool HotReload, + bool OptimizeGLShaders, + IDataBlob* pCacheData = nullptr, + IShaderSourceInputStreamFactory* pShaderReloadFactory = nullptr) +{ + RenderStateCacheCreateInfo CacheCI{ + pDevice, + GPUTestingEnvironment::GetInstance()->GetArchiverFactory(), + RENDER_STATE_CACHE_LOG_LEVEL_VERBOSE, + RENDER_STATE_CACHE_FILE_HASH_MODE_BY_CONTENT, + HotReload, + }; + + RefCntAutoPtr pCache; + CreateRenderStateCache(CacheCI, &pCache); + + if (pCacheData != nullptr) + pCache->Load(pCacheData, kCacheContentVersion); + + return pCache; +} + + +void CreateShadersFromCache(IRenderStateCache* pCache, bool PresentInCache, IShader** ppVS, IShader** ppPS) +{ + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + IRenderDevice* pDevice = pEnv->GetDevice(); + + ShaderCreateInfo ShaderCI; + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL; + ShaderCI.ShaderCompiler = pEnv->GetDefaultCompiler(ShaderCI.SourceLanguage); + + { + ShaderCI.Desc = {"Inline constants test", SHADER_TYPE_VERTEX, true}; + ShaderCI.EntryPoint = "main"; + ShaderCI.Source = HLSL::InlineConstantsTest_VS.c_str(); + if (pCache != nullptr) + { + EXPECT_EQ(pCache->CreateShader(ShaderCI, ppVS), PresentInCache); + } + else + { + pDevice->CreateShader(ShaderCI, ppVS); + EXPECT_EQ(PresentInCache, false); + } + } + + { + ShaderCI.Desc = {"Inline constants test", SHADER_TYPE_PIXEL, true}; + ShaderCI.EntryPoint = "main"; + ShaderCI.Source = HLSL::InlineConstantsTest_PS.c_str(); + if (pCache != nullptr) + { + EXPECT_EQ(pCache->CreateShader(ShaderCI, ppPS), PresentInCache); + } + else + { + pDevice->CreateShader(ShaderCI, ppPS); + EXPECT_EQ(PresentInCache, false); + } + } +} + +void CreatePSOFromCache(IRenderStateCache* pCache, bool PresentInCache, IShader* pVS, IShader* pPS, IPipelineState** ppPSO) +{ + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + ISwapChain* pSwapChain = pEnv->GetSwapChain(); + + GraphicsPipelineStateCreateInfo PsoCI; + PsoCI.PSODesc.Name = "Render State Cache Test"; + + PsoCI.pVS = pVS; + PsoCI.pPS = pPS; + + PsoCI.GraphicsPipeline.NumRenderTargets = 1; + PsoCI.GraphicsPipeline.RTVFormats[0] = pSwapChain->GetDesc().ColorBufferFormat; + + PsoCI.GraphicsPipeline.DepthStencilDesc.DepthEnable = False; + + static ShaderResourceVariableDesc Vars[] = + { + {SHADER_TYPE_VERTEX, "cbInlinePositions", SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE, SHADER_VARIABLE_FLAG_INLINE_CONSTANTS}, + {SHADER_TYPE_VS_PS, "cbInlineColors", SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE, SHADER_VARIABLE_FLAG_INLINE_CONSTANTS}, + }; + PsoCI.PSODesc.ResourceLayout.Variables = Vars; + PsoCI.PSODesc.ResourceLayout.NumVariables = _countof(Vars); + + if (pCache != nullptr) + { + bool PSOFound = pCache->CreateGraphicsPipelineState(PsoCI, ppPSO); + EXPECT_EQ(PSOFound, PresentInCache); + } + else + { + EXPECT_FALSE(PresentInCache); + pEnv->GetDevice()->CreateGraphicsPipelineState(PsoCI, ppPSO); + ASSERT_NE(*ppPSO, nullptr); + } + + if (*ppPSO != nullptr && (*ppPSO)->GetStatus() == PIPELINE_STATE_STATUS_READY) + { + const PipelineStateDesc& Desc = (*ppPSO)->GetDesc(); + EXPECT_EQ(PsoCI.PSODesc, Desc); + } +} + +void InlineConstants::VerifyPSOFromCache(IPipelineState* pPSO, + IShaderResourceBinding* pSRB) +{ + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + IDeviceContext* pContext = pEnv->GetDeviceContext(); + ISwapChain* pSwapChain = pEnv->GetSwapChain(); + + const float ClearColor[] = {sm_Rnd(), sm_Rnd(), sm_Rnd(), sm_Rnd()}; + RenderDrawCommandReference(pSwapChain, ClearColor); + + ITextureView* pRTVs[] = {pSwapChain->GetCurrentBackBufferRTV()}; + pContext->SetRenderTargets(1, pRTVs, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + pContext->ClearRenderTarget(pRTVs[0], ClearColor, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + RefCntAutoPtr _pSRB; + if (pSRB == nullptr) + { + pPSO->CreateShaderResourceBinding(&_pSRB, true); + pSRB = _pSRB; + } + + pContext->SetPipelineState(pPSO); + pContext->CommitShaderResources(pSRB, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + IShaderResourceVariable* pColVar = pSRB->GetVariableByName(SHADER_TYPE_VERTEX, "cbInlineColors"); + ASSERT_TRUE(pColVar); + pColVar->SetInlineConstants(g_Colors, 0, kNumColConstants); + + IShaderResourceVariable* pPosVar = pSRB->GetVariableByName(SHADER_TYPE_VERTEX, "cbInlinePositions"); + ASSERT_TRUE(pPosVar); + pPosVar->SetInlineConstants(g_Positions, 0, kNumPosConstants / 2); + pContext->Draw({3, DRAW_FLAG_VERIFY_ALL}); + + pPosVar->SetInlineConstants(g_Positions[0].Data() + kNumPosConstants / 2, 0, kNumPosConstants / 2); + pContext->Draw({3, DRAW_FLAG_VERIFY_ALL}); + + Present(); +} + +TEST_F(InlineConstants, RenderStateCache) +{ + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + IRenderDevice* pDevice = pEnv->GetDevice(); + + GPUTestingEnvironment::ScopedReset AutoReset; + + RefCntAutoPtr pShaderSourceFactory; + pDevice->GetEngineFactory()->CreateDefaultShaderSourceStreamFactory("shaders/RenderStateCache", &pShaderSourceFactory); + ASSERT_TRUE(pShaderSourceFactory); + + RefCntAutoPtr pUncachedVS, pUncachedPS; + CreateShadersFromCache(nullptr, false, &pUncachedVS, &pUncachedPS); + ASSERT_NE(pUncachedVS, nullptr); + ASSERT_NE(pUncachedPS, nullptr); + + RefCntAutoPtr pRefPSO; + CreatePSOFromCache(nullptr, false, pUncachedVS, pUncachedPS, &pRefPSO); + ASSERT_NE(pRefPSO, nullptr); + + RefCntAutoPtr pRefSRB; + pRefPSO->CreateShaderResourceBinding(&pRefSRB); + + RefCntAutoPtr pData; + for (Uint32 pass = 0; pass < 3; ++pass) + { + // 0: empty cache + // 1: loaded cache + // 2: reloaded cache (loaded -> stored -> loaded) + + RefCntAutoPtr pCache = CreateCache(pDevice, false, false, pData); + ASSERT_TRUE(pCache); + + RefCntAutoPtr pVS1, pPS1; + CreateShadersFromCache(pCache, pData != nullptr, &pVS1, &pPS1); + ASSERT_NE(pVS1, nullptr); + ASSERT_NE(pPS1, nullptr); + + RefCntAutoPtr pPSO; + CreatePSOFromCache(pCache, pData != nullptr, pVS1, pPS1, &pPSO); + ASSERT_NE(pPSO, nullptr); + ASSERT_EQ(pPSO->GetStatus(), PIPELINE_STATE_STATUS_READY); + EXPECT_TRUE(pRefPSO->IsCompatibleWith(pPSO)); + EXPECT_TRUE(pPSO->IsCompatibleWith(pRefPSO)); + + VerifyPSOFromCache(pPSO, nullptr); + VerifyPSOFromCache(pPSO, pRefSRB); + + { + RefCntAutoPtr pPSO2; + CreatePSOFromCache(pCache, true, pVS1, pPS1, &pPSO2); + EXPECT_EQ(pPSO, pPSO2); + } + + { + RefCntAutoPtr pPSO2; + + bool PresentInCache = pData != nullptr; +#if !DILIGENT_DEBUG + if (pDevice->GetDeviceInfo().IsD3DDevice()) + { + // For some reason, hash computation consistency depends on D3DCOMPILE_DEBUG flag and differs between debug and release builds + PresentInCache = true; + } +#endif + if (pDevice->GetDeviceInfo().IsVulkanDevice()) + { + // For some reason, pUncachedVS and pVS1 got same hash computation result on Vulkan. + PresentInCache = true; + } + CreatePSOFromCache(pCache, PresentInCache, pUncachedVS, pUncachedPS, &pPSO2); + ASSERT_NE(pPSO2, nullptr); + ASSERT_EQ(pPSO2->GetStatus(), PIPELINE_STATE_STATUS_READY); + EXPECT_TRUE(pRefPSO->IsCompatibleWith(pPSO2)); + EXPECT_TRUE(pPSO2->IsCompatibleWith(pRefPSO)); + VerifyPSOFromCache(pPSO2, nullptr); + VerifyPSOFromCache(pPSO2, pRefSRB); + } + + pData.Release(); + pCache->WriteToBlob(pass == 0 ? kCacheContentVersion : ~0u, &pData); + } +} + +} // namespace diff --git a/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PRSCreationFailureTest.cpp b/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PRSCreationFailureTest.cpp index 9a652354b2..00948ffdf5 100644 --- a/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PRSCreationFailureTest.cpp +++ b/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PRSCreationFailureTest.cpp @@ -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"); @@ -40,8 +40,8 @@ namespace static void TestCreatePRSFailure(PipelineResourceSignatureDesc CI, const char* ExpectedErrorSubstring) { - auto* const pEnv = GPUTestingEnvironment::GetInstance(); - auto* const pDevice = pEnv->GetDevice(); + GPUTestingEnvironment* const pEnv = GPUTestingEnvironment::GetInstance(); + IRenderDevice* const pDevice = pEnv->GetDevice(); RefCntAutoPtr pSignature; pEnv->SetErrorAllowance(2, "Errors below are expected: testing PRS creation failure\n"); @@ -166,7 +166,7 @@ TEST(PRSCreationFailureTest, InvalidResourceFlag) {SHADER_TYPE_VERTEX, "g_Buffer", 1, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_STATIC, PIPELINE_RESOURCE_FLAG_COMBINED_SAMPLER | PIPELINE_RESOURCE_FLAG_FORMATTED_BUFFER}}; PRSDesc.Resources = Resources; PRSDesc.NumResources = _countof(Resources); - TestCreatePRSFailure(PRSDesc, "Incorrect Desc.Resources[1].Flags (COMBINED_SAMPLER|FORMATTED_BUFFER). Only the following flags are valid for a constant buffer: NO_DYNAMIC_BUFFERS, RUNTIME_ARRAY"); + TestCreatePRSFailure(PRSDesc, "Incorrect Desc.Resources[1].Flags (COMBINED_SAMPLER|FORMATTED_BUFFER). Only the following flags are valid for a constant buffer: NO_DYNAMIC_BUFFERS, INLINE_CONSTANTS, RUNTIME_ARRAY"); } TEST(PRSCreationFailureTest, InvalidTexSRVFlag) @@ -255,7 +255,7 @@ TEST(PRSCreationFailureTest, InvalidAccelStructFlag) TEST(PRSCreationFailureTest, InvalidCombinedSamplerFlag) { - const auto& DeviceInfo = GPUTestingEnvironment::GetInstance()->GetDevice()->GetDeviceInfo(); + const RenderDeviceInfo& DeviceInfo = GPUTestingEnvironment::GetInstance()->GetDevice()->GetDeviceInfo(); if (!(DeviceInfo.IsD3DDevice() || DeviceInfo.IsMetalDevice())) { GTEST_SKIP() << "Direct3D11, Direct3D12 and Metal only"; @@ -271,6 +271,30 @@ TEST(PRSCreationFailureTest, InvalidCombinedSamplerFlag) TestCreatePRSFailure(PRSDesc, "Desc.Resources[0].Flags contain COMBINED_SAMPLER flag, but Desc.UseCombinedTextureSamplers is false"); } +TEST(PRSCreationFailureTest, InvalidInlineConstantsFlag) +{ + PipelineResourceSignatureDesc PRSDesc; + PRSDesc.Name = "Invalid inline constants Flag"; + PRSDesc.UseCombinedTextureSamplers = false; + PipelineResourceDesc Resources[]{ + {SHADER_TYPE_PIXEL, "g_InlineConstants", 1, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_STATIC, PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS | PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS}}; + PRSDesc.Resources = Resources; + PRSDesc.NumResources = _countof(Resources); + TestCreatePRSFailure(PRSDesc, "INLINE_CONSTANTS flag cannot be combined with other flags"); +} + +TEST(PRSCreationFailureTest, InvalidInlineConstantCount) +{ + PipelineResourceSignatureDesc PRSDesc; + PRSDesc.Name = "Invalid inline constant count"; + PRSDesc.UseCombinedTextureSamplers = false; + PipelineResourceDesc Resources[]{ + {SHADER_TYPE_PIXEL, "g_InlineConstants", 65, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_STATIC, PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS}}; + PRSDesc.Resources = Resources; + PRSDesc.NumResources = _countof(Resources); + TestCreatePRSFailure(PRSDesc, "Desc.Resources[0].ArraySize (65) exceeds the maximum allowed value (64) for inline constants."); +} + TEST(PRSCreationFailureTest, InvalidAssignedSamplerResourceType) { PipelineResourceSignatureDesc PRSDesc; diff --git a/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp b/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp index c47105e0e5..79d614daf1 100644 --- a/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp +++ b/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp @@ -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"); @@ -887,6 +887,20 @@ TEST_F(PSOCreationFailureTest, OverlappingImmutableSamplerStages) TestCreatePSOFailure(PsoCI, "'g_Texture_sampler' is defined in overlapping shader stages (SHADER_TYPE_VERTEX, SHADER_TYPE_GEOMETRY and SHADER_TYPE_VERTEX, SHADER_TYPE_PIXEL)"); } +TEST_F(PSOCreationFailureTest, InvalidInlineConstantsFlag) +{ + auto PsoCI{GetGraphicsPSOCreateInfo("PSO Create Failure - Invalid Inline Constants Flag")}; + + ShaderResourceVariableDesc Variables[] // + { + ShaderResourceVariableDesc{SHADER_TYPE_VERTEX | SHADER_TYPE_PIXEL, "g_Texture", SHADER_RESOURCE_VARIABLE_TYPE_STATIC}, + ShaderResourceVariableDesc{SHADER_TYPE_VERTEX | SHADER_TYPE_PIXEL, "g_InlineConstants", SHADER_RESOURCE_VARIABLE_TYPE_STATIC, SHADER_VARIABLE_FLAG_INLINE_CONSTANTS | SHADER_VARIABLE_FLAG_NO_DYNAMIC_BUFFERS} // + }; + PsoCI.PSODesc.ResourceLayout.Variables = Variables; + PsoCI.PSODesc.ResourceLayout.NumVariables = _countof(Variables); + TestCreatePSOFailure(PsoCI, "ResourceLayout.Variables[1].Flags: INLINE_CONSTANTS flag cannot be combined with other flags."); +} + TEST_F(PSOCreationFailureTest, RenderPassWithNonZeroNumRenderTargets) { auto PsoCI{GetGraphicsPSOCreateInfo("PSO Create Failure - Render Pass With non-zero NumRenderTargets", true)}; @@ -1167,7 +1181,7 @@ TEST_F(PSOCreationFailureTest, NullProcHitShader) TestCreatePSOFailure(PsoCI, "pProceduralHitShaders[0].pIntersectionShader must not be null"); } -TEST_F(PSOCreationFailureTest, InvalidShaderinGeneralGroup) +TEST_F(PSOCreationFailureTest, InvalidShaderInGeneralGroup) { if (!HasRayTracing()) GTEST_SKIP(); @@ -1180,7 +1194,7 @@ TEST_F(PSOCreationFailureTest, InvalidShaderinGeneralGroup) TestCreatePSOFailure(PsoCI, "SHADER_TYPE_RAY_CLOSEST_HIT is not a valid type for ray tracing general shader"); } -TEST_F(PSOCreationFailureTest, InvalidShaderinTriangleHitGroup1) +TEST_F(PSOCreationFailureTest, InvalidShaderInTriangleHitGroup1) { if (!HasRayTracing()) GTEST_SKIP(); @@ -1197,7 +1211,7 @@ TEST_F(PSOCreationFailureTest, InvalidShaderinTriangleHitGroup1) TestCreatePSOFailure(PsoCI, "SHADER_TYPE_RAY_MISS is not a valid type for ray tracing triangle closest hit"); } -TEST_F(PSOCreationFailureTest, InvalidShaderinTriangleHitGroup2) +TEST_F(PSOCreationFailureTest, InvalidShaderInTriangleHitGroup2) { if (!HasRayTracing()) GTEST_SKIP(); @@ -1214,7 +1228,7 @@ TEST_F(PSOCreationFailureTest, InvalidShaderinTriangleHitGroup2) TestCreatePSOFailure(PsoCI, "SHADER_TYPE_RAY_INTERSECTION is not a valid type for ray tracing triangle any hit"); } -TEST_F(PSOCreationFailureTest, InvalidShaderinProceduralHitGroup1) +TEST_F(PSOCreationFailureTest, InvalidShaderInProceduralHitGroup1) { if (!HasRayTracing()) GTEST_SKIP(); @@ -1232,7 +1246,7 @@ TEST_F(PSOCreationFailureTest, InvalidShaderinProceduralHitGroup1) TestCreatePSOFailure(PsoCI, "SHADER_TYPE_CALLABLE is not a valid type for ray tracing procedural intersection"); } -TEST_F(PSOCreationFailureTest, InvalidShaderinProceduralHitGroup2) +TEST_F(PSOCreationFailureTest, InvalidShaderInProceduralHitGroup2) { if (!HasRayTracing()) GTEST_SKIP(); @@ -1250,7 +1264,7 @@ TEST_F(PSOCreationFailureTest, InvalidShaderinProceduralHitGroup2) TestCreatePSOFailure(PsoCI, "SHADER_TYPE_RAY_GEN is not a valid type for ray tracing procedural closest hit"); } -TEST_F(PSOCreationFailureTest, InvalidShaderinProceduralHitGroup3) +TEST_F(PSOCreationFailureTest, InvalidShaderInProceduralHitGroup3) { if (!HasRayTracing()) GTEST_SKIP(); diff --git a/Tests/DiligentCoreAPITest/src/Vulkan/ConvertUBOToPushConstantsTestVk.cpp b/Tests/DiligentCoreAPITest/src/Vulkan/ConvertUBOToPushConstantsTestVk.cpp new file mode 100644 index 0000000000..88b390d6a6 --- /dev/null +++ b/Tests/DiligentCoreAPITest/src/Vulkan/ConvertUBOToPushConstantsTestVk.cpp @@ -0,0 +1,873 @@ +/* + * 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 "Vulkan/TestingEnvironmentVk.hpp" +#include "Vulkan/TestingSwapChainVk.hpp" + +#include "DeviceContextVk.h" +#include "RenderDeviceVk.h" +#include "TextureVk.h" +#include "TextureViewVk.h" + +#include "GLSLangUtils.hpp" +#include "DXCompiler.hpp" +#include "SPIRVTools.hpp" +#include "FastRand.hpp" + +#include "volk.h" + +#include "gtest/gtest.h" + +namespace Diligent +{ + +VkFormat TexFormatToVkFormat(TEXTURE_FORMAT TexFmt); + +namespace Testing +{ + +// Forward declaration of reference renderer +void RenderDrawCommandReferenceVk(ISwapChain* pSwapChain, const float* pClearColor); + +namespace +{ + +class VkConvertUBOToPushConstantsTest : public ::testing::Test +{ +public: + static IDXCompiler* GetDXCompiler() + { + return DXCompiler.get(); + } + +protected: + static void SetUpTestSuite() + { + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + if (pEnv->GetDevice()->GetDeviceInfo().Type != RENDER_DEVICE_TYPE_VULKAN) + { + // Skip all tests in this suite + GTEST_SKIP() << "This test is only for Vulkan device"; + } + + GLSLangUtils::InitializeGlslang(); + + DXCompiler = CreateDXCompiler(DXCompilerTarget::Vulkan, 0, nullptr); + } + + static void TearDownTestSuite() + { + if (IsSkipped()) + { + return; + } + + GLSLangUtils::FinalizeGlslang(); + + DXCompiler.reset(); + } + + void RunConvertUBOToPushConstantsTest(SHADER_COMPILER Compiler, SHADER_SOURCE_LANGUAGE SourceLanguage, const std::string& BlockName); + +private: + static std::unique_ptr DXCompiler; + static FastRandFloat Rnd; +}; + +std::unique_ptr VkConvertUBOToPushConstantsTest::DXCompiler; +FastRandFloat VkConvertUBOToPushConstantsTest::Rnd{0, 0.f, 1.f}; + +// GLSL Vertex Shader - procedural two triangles, reads colors from UBO and outputs for interpolation +const std::string GLSL_ProceduralTriangleVS = R"( +#version 450 core + +// Vertex colors stored in UBO - will be patched to push constants +// Matching the same UBO structure as fragment shader +struct Level3Data +{ + vec4 Factor; +}; + +struct Level2Data +{ + Level3Data Inner; +}; + +struct Level1Data +{ + Level2Data Nested; +}; + +struct ColorData +{ + vec4 Colors[6]; +}; + +layout(set = 0, binding = 0) uniform CB1 +{ + Level1Data Data; + ColorData VertexColors; +} cb; + +layout(location = 0) out vec3 out_Color; + +// Helper function to access colors from the UBO +vec3 GetVertexColor(ColorData colors, int index) +{ + return colors.Colors[index].rgb; +} + +void main() +{ + vec4 Pos[6]; + Pos[0] = vec4(-1.0, -0.5, 0.0, 1.0); + Pos[1] = vec4(-0.5, +0.5, 0.0, 1.0); + Pos[2] = vec4( 0.0, -0.5, 0.0, 1.0); + + Pos[3] = vec4(+0.0, -0.5, 0.0, 1.0); + Pos[4] = vec4(+0.5, +0.5, 0.0, 1.0); + Pos[5] = vec4(+1.0, -0.5, 0.0, 1.0); + + gl_Position = Pos[gl_VertexIndex]; + // Output color from UBO via helper function - will be interpolated across triangle + out_Color = GetVertexColor(cb.VertexColors, gl_VertexIndex); +} +)"; + +// GLSL Fragment Shader with UBO - will be patched to push constants +// Tests: +// 1. Nested structs for multiple access chains and storage class propagation +// 2. Passing struct to function (tests function inlining before conversion) +const std::string GLSL_FragmentShaderWithUBO = R"( +#version 450 core + +// Deeply nested structs to test multiple access chains and storage class propagation +struct Level3Data +{ + vec4 Factor; +}; + +struct Level2Data +{ + Level3Data Inner; +}; + +struct Level1Data +{ + Level2Data Nested; +}; + +struct ColorData +{ + vec4 Colors[6]; +}; + +// UBO named "CB1" with instance name "cb" - allows testing both name matching paths +layout(set = 0, binding = 0) uniform CB1 +{ + Level1Data Data; + ColorData VertexColors; +} cb; + +layout(location = 0) in vec3 in_Color; +layout(location = 0) out vec4 out_Color; + +// Helper function that takes UBO struct as parameter +// This tests OpFunctionCall handling - requires function inlining before conversion +vec4 GetNestedFactor(Level1Data data) +{ + return data.Nested.Inner.Factor; +} + +void main() +{ + // Access deeply nested member through function call + // This generates OpFunctionCall which requires inlining before storage class propagation + vec4 factor = GetNestedFactor(cb.Data); + + // Use interpolated color from vertex shader + out_Color = vec4(in_Color, 1.0) * factor; +} +)"; + +// Push constant data structure matching the UBO layout +// Must match the layout of CB1 in the shader: +// - Level1Data Data (containing nested Factor vec4) +// - ColorData VertexColors (containing Colors[6] vec3 array) +// +// IMPORTANT: Do NOT use float Factor[4] - in std140, arrays have each element +// aligned to 16 bytes, so float[4] would be 64 bytes instead of 16! +// Use a struct to represent vec4 as 4 contiguous floats. +struct PushConstantData +{ + float4 Factor; // vec4 Factor in Level1Data.Nested.Inner (16 bytes) + float4 Colors[6]; // vec4 Colors[6] (16 bytes * 6) +}; + +// HLSL Vertex Shader - procedural two triangles, reads colors from CB and outputs for interpolation +const std::string HLSL_ProceduralTriangleVS = R"( +// Matching the same CB structure as fragment shader +struct Level3Data +{ + float4 Factor; +}; + +struct Level2Data +{ + Level3Data Inner; +}; + +struct Level1Data +{ + Level2Data Nested; +}; + +struct ColorData +{ + float4 Colors[6]; +}; + +cbuffer CB1 : register(b0) +{ + Level1Data Data; + ColorData VertexColors; +}; + +struct PSInput +{ + float4 Pos : SV_POSITION; + float3 Color : COLOR0; +}; + +// Helper function to access colors from the CB +// This tests OpFunctionCall handling in vertex shader +float3 GetVertexColor(ColorData colors, uint index) +{ + return colors.Colors[index].rgb; +} + +PSInput main(uint VertexId : SV_VertexID) +{ + float4 Pos[6]; + Pos[0] = float4(-1.0, -0.5, 0.0, 1.0); + Pos[1] = float4(-0.5, +0.5, 0.0, 1.0); + Pos[2] = float4( 0.0, -0.5, 0.0, 1.0); + + Pos[3] = float4(+0.0, -0.5, 0.0, 1.0); + Pos[4] = float4(+0.5, +0.5, 0.0, 1.0); + Pos[5] = float4(+1.0, -0.5, 0.0, 1.0); + + PSInput Out; + Out.Pos = Pos[VertexId]; + // Output color from CB via helper function - will be interpolated across triangle + Out.Color = GetVertexColor(VertexColors, VertexId); + return Out; +} +)"; + +// HLSL Fragment Shader with constant buffer - will be patched to push constants +// Tests: +// 1. Deeply nested structs for multiple access chains +// 2. Passing struct to function (tests function inlining before conversion) +const std::string HLSL_FragmentShaderWithCB = R"( +// Deeply nested structs to test multiple access chains +struct Level3Data +{ + float4 Factor; +}; + +struct Level2Data +{ + Level3Data Inner; +}; + +struct Level1Data +{ + Level2Data Nested; +}; + +struct ColorData +{ + float4 Colors[6]; +}; + +// Constant buffer named "CB1" +cbuffer CB1 : register(b0) +{ + Level1Data Data; + ColorData VertexColors; +}; + +struct PSInput +{ + float4 Pos : SV_POSITION; + float3 Color : COLOR0; +}; + +// Helper function that takes CB struct as parameter +// This tests OpFunctionCall handling - requires function inlining before conversion +float4 GetNestedFactor(Level1Data data) +{ + return data.Nested.Inner.Factor; +} + +float4 main(PSInput In) : SV_Target +{ + // Access deeply nested member through function call + // This generates OpFunctionCall which requires inlining before storage class propagation + float4 factor = GetNestedFactor(Data); + + // Use interpolated color from vertex shader + return float4(In.Color, 1.0) * factor; +} +)"; + +// Helper to create VkShaderModule from SPIR-V bytecode +VkShaderModule CreateVkShaderModuleFromSPIRV(VkDevice vkDevice, const std::vector& SPIRV) +{ + VkShaderModuleCreateInfo ShaderModuleCI{}; + ShaderModuleCI.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + ShaderModuleCI.pNext = nullptr; + ShaderModuleCI.flags = 0; + ShaderModuleCI.codeSize = SPIRV.size() * sizeof(uint32_t); + ShaderModuleCI.pCode = SPIRV.data(); + + VkShaderModule vkShaderModule = VK_NULL_HANDLE; + VkResult res = vkCreateShaderModule(vkDevice, &ShaderModuleCI, nullptr, &vkShaderModule); + VERIFY_EXPR(res == VK_SUCCESS); + (void)res; + + return vkShaderModule; +} + +std::vector LoadSPIRVFromHLSL(const std::string& ShaderSource, + SHADER_TYPE ShaderType, + SHADER_COMPILER Compiler = SHADER_COMPILER_DEFAULT) +{ + ShaderCreateInfo ShaderCI; + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL; + ShaderCI.Source = ShaderSource.data(); + ShaderCI.SourceLength = ShaderSource.size(); + ShaderCI.Desc = {"SPIRV test shader", ShaderType}; + ShaderCI.EntryPoint = "main"; + + std::vector SPIRV; + + if (Compiler == SHADER_COMPILER_DXC) + { + IDXCompiler* DXCompiler = VkConvertUBOToPushConstantsTest::GetDXCompiler(); + if (!DXCompiler || !DXCompiler->IsLoaded()) + { + UNEXPECTED("Test should be skipped if DXCompiler is not available"); + return {}; + } + + RefCntAutoPtr pCompilerOutput; + DXCompiler->Compile(ShaderCI, ShaderVersion{6, 0}, nullptr, nullptr, &SPIRV, &pCompilerOutput); + + if (pCompilerOutput && pCompilerOutput->GetSize() > 0) + { + const char* CompilerOutput = static_cast(pCompilerOutput->GetConstDataPtr()); + if (*CompilerOutput != 0) + LOG_INFO_MESSAGE("DXC compiler output:\n", CompilerOutput); + } + } + else + { + SPIRV = GLSLangUtils::HLSLtoSPIRV(ShaderCI, GLSLangUtils::SpirvVersion::Vk100, nullptr, nullptr); + } + + return SPIRV; +} + +std::vector LoadSPIRVFromGLSL(const std::string& ShaderSource, SHADER_TYPE ShaderType = SHADER_TYPE_PIXEL) +{ + GLSLangUtils::GLSLtoSPIRVAttribs Attribs; + Attribs.ShaderType = ShaderType; + Attribs.ShaderSource = ShaderSource.data(); + Attribs.SourceCodeLen = static_cast(ShaderSource.size()); + Attribs.Version = GLSLangUtils::SpirvVersion::Vk100; + Attribs.AssignBindings = true; + + return GLSLangUtils::GLSLtoSPIRV(Attribs); +} + +void CompileSPIRV(const std::string& ShaderSource, + const std::string& ShaderIdentifier, + SHADER_COMPILER Compiler, + SHADER_TYPE ShaderType, + SHADER_SOURCE_LANGUAGE SourceLanguage, + std::vector& SPIRV) +{ + SPIRV = (SourceLanguage == SHADER_SOURCE_LANGUAGE_GLSL) ? + LoadSPIRVFromGLSL(ShaderSource, ShaderType) : + LoadSPIRVFromHLSL(ShaderSource, ShaderType, Compiler); + ASSERT_FALSE(SPIRV.empty()) << "Failed to compile shader " << ShaderIdentifier; +} + +// Renderer that uses patched push constants shader +class PatchedPushConstantsRenderer +{ +public: + PatchedPushConstantsRenderer(TestingSwapChainVk* pSwapChain, + const std::vector& VS_SPIRV, + const std::vector& FS_SPIRV, + uint32_t PushConstantSize, + VkShaderStageFlags PushConstantStages = VK_SHADER_STAGE_FRAGMENT_BIT) : + m_pSwapChain{pSwapChain}, + m_vkDevice{TestingEnvironmentVk::GetInstance()->GetVkDevice()} + + { + const SwapChainDesc& SCDesc = pSwapChain->GetDesc(); + + CreateRenderPass(); + + // Create shader modules from SPIR-V + m_vkVSModule = CreateVkShaderModuleFromSPIRV(m_vkDevice, VS_SPIRV); + VERIFY_EXPR(m_vkVSModule != VK_NULL_HANDLE); + + m_vkFSModule = CreateVkShaderModuleFromSPIRV(m_vkDevice, FS_SPIRV); + VERIFY_EXPR(m_vkFSModule != VK_NULL_HANDLE); + + // Pipeline layout with push constants (no descriptor sets) + VkPushConstantRange PushConstantRange{}; + PushConstantRange.stageFlags = PushConstantStages; + PushConstantRange.offset = 0; + PushConstantRange.size = PushConstantSize; + + VkPipelineLayoutCreateInfo PipelineLayoutCI{}; + PipelineLayoutCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + PipelineLayoutCI.setLayoutCount = 0; + PipelineLayoutCI.pSetLayouts = nullptr; + PipelineLayoutCI.pushConstantRangeCount = 1; + PipelineLayoutCI.pPushConstantRanges = &PushConstantRange; + + VkResult res = vkCreatePipelineLayout(m_vkDevice, &PipelineLayoutCI, nullptr, &m_vkLayout); + VERIFY_EXPR(res == VK_SUCCESS); + (void)res; + + // Create graphics pipeline + VkGraphicsPipelineCreateInfo PipelineCI{}; + PipelineCI.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + + VkPipelineShaderStageCreateInfo ShaderStages[2]{}; + ShaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + ShaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + ShaderStages[0].module = m_vkVSModule; + ShaderStages[0].pName = "main"; + + ShaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + ShaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + ShaderStages[1].module = m_vkFSModule; + ShaderStages[1].pName = "main"; + + PipelineCI.pStages = ShaderStages; + PipelineCI.stageCount = _countof(ShaderStages); + PipelineCI.layout = m_vkLayout; + + VkPipelineVertexInputStateCreateInfo VertexInputStateCI{}; + VertexInputStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + PipelineCI.pVertexInputState = &VertexInputStateCI; + + VkPipelineInputAssemblyStateCreateInfo InputAssemblyCI{}; + InputAssemblyCI.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + InputAssemblyCI.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + InputAssemblyCI.primitiveRestartEnable = VK_FALSE; + PipelineCI.pInputAssemblyState = &InputAssemblyCI; + + VkPipelineTessellationStateCreateInfo TessStateCI{}; + TessStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO; + PipelineCI.pTessellationState = &TessStateCI; + + VkPipelineViewportStateCreateInfo ViewPortStateCI{}; + ViewPortStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + ViewPortStateCI.viewportCount = 1; + + VkViewport Viewport{}; + Viewport.y = static_cast(SCDesc.Height); + Viewport.width = static_cast(SCDesc.Width); + Viewport.height = -static_cast(SCDesc.Height); + Viewport.maxDepth = 1; + ViewPortStateCI.pViewports = &Viewport; + + ViewPortStateCI.scissorCount = 1; + VkRect2D ScissorRect{}; + ScissorRect.extent.width = SCDesc.Width; + ScissorRect.extent.height = SCDesc.Height; + ViewPortStateCI.pScissors = &ScissorRect; + PipelineCI.pViewportState = &ViewPortStateCI; + + VkPipelineRasterizationStateCreateInfo RasterizerStateCI{}; + RasterizerStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + RasterizerStateCI.polygonMode = VK_POLYGON_MODE_FILL; + RasterizerStateCI.cullMode = VK_CULL_MODE_NONE; + RasterizerStateCI.lineWidth = 1; + PipelineCI.pRasterizationState = &RasterizerStateCI; + + // Multisample state + VkPipelineMultisampleStateCreateInfo MSStateCI{}; + + MSStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + MSStateCI.pNext = nullptr; + MSStateCI.flags = 0; // reserved for future use + // If subpass uses color and/or depth/stencil attachments, then the rasterizationSamples member of + // pMultisampleState must be the same as the sample count for those subpass attachments + MSStateCI.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + MSStateCI.sampleShadingEnable = VK_FALSE; + MSStateCI.minSampleShading = 0; // a minimum fraction of sample shading if sampleShadingEnable is set to VK_TRUE. + uint32_t SampleMask[] = {0xFFFFFFFF, 0}; // Vulkan spec allows up to 64 samples + MSStateCI.pSampleMask = SampleMask; // an array of static coverage information that is ANDed with + // the coverage information generated during rasterization + MSStateCI.alphaToCoverageEnable = VK_FALSE; // whether a temporary coverage value is generated based on + // the alpha component of the fragment's first color output + MSStateCI.alphaToOneEnable = VK_FALSE; // whether the alpha component of the fragment's first color output is replaced with one + PipelineCI.pMultisampleState = &MSStateCI; + + VkPipelineDepthStencilStateCreateInfo DepthStencilStateCI{}; + DepthStencilStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + PipelineCI.pDepthStencilState = &DepthStencilStateCI; + + VkPipelineColorBlendStateCreateInfo BlendStateCI{}; + VkPipelineColorBlendAttachmentState Attachment{}; + Attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + BlendStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + BlendStateCI.pAttachments = &Attachment; + BlendStateCI.attachmentCount = 1; + PipelineCI.pColorBlendState = &BlendStateCI; + + VkPipelineDynamicStateCreateInfo DynamicStateCI{}; + DynamicStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + PipelineCI.pDynamicState = &DynamicStateCI; + + PipelineCI.renderPass = m_vkRenderPass; + PipelineCI.subpass = 0; + PipelineCI.basePipelineHandle = VK_NULL_HANDLE; + PipelineCI.basePipelineIndex = 0; + + res = vkCreateGraphicsPipelines(m_vkDevice, VK_NULL_HANDLE, 1, &PipelineCI, nullptr, &m_vkPipeline); + VERIFY_EXPR(res == VK_SUCCESS); + VERIFY_EXPR(m_vkPipeline != VK_NULL_HANDLE); + + m_PushConstantStages = PushConstantStages; + + CreateFramebuffer(); + } + + void CreateRenderPass() + { + VkFormat ColorFormat = TexFormatToVkFormat(m_pSwapChain->GetCurrentBackBufferRTV()->GetDesc().Format); + VkFormat DepthFormat = TexFormatToVkFormat(m_pSwapChain->GetDepthBufferDSV()->GetDesc().Format); + + std::array Attachments{}; + std::array AttachmentReferences{}; + + VkSubpassDescription Subpass; + + VkRenderPassCreateInfo RenderPassCI = + TestingEnvironmentVk::GetRenderPassCreateInfo(1, &ColorFormat, DepthFormat, 1, + VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_LOAD_OP_CLEAR, + Attachments, AttachmentReferences, Subpass); + VkResult res = vkCreateRenderPass(m_vkDevice, &RenderPassCI, nullptr, &m_vkRenderPass); + VERIFY_EXPR(res >= 0); + (void)res; + } + + void CreateFramebuffer() + { + // Use Diligent Engine managed images (different from TestingSwapChainVk's internal images). + // The test compares rendering to Diligent Engine images against TestingSwapChainVk's internal images. + RefCntAutoPtr pRTV{m_pSwapChain->GetCurrentBackBufferRTV(), IID_TextureViewVk}; + VERIFY_EXPR(pRTV); + RefCntAutoPtr pDSV{m_pSwapChain->GetDepthBufferDSV(), IID_TextureViewVk}; + VERIFY_EXPR(pDSV); + + VkFramebufferCreateInfo FramebufferCI{}; + FramebufferCI.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + FramebufferCI.pNext = nullptr; + FramebufferCI.flags = 0; // reserved for future use + FramebufferCI.renderPass = m_vkRenderPass; + FramebufferCI.attachmentCount = 2; + VkImageView Attachments[2] = {pDSV->GetVulkanImageView(), pRTV->GetVulkanImageView()}; + FramebufferCI.pAttachments = Attachments; + FramebufferCI.width = m_pSwapChain->GetDesc().Width; + FramebufferCI.height = m_pSwapChain->GetDesc().Height; + FramebufferCI.layers = 1; + + VkResult res = vkCreateFramebuffer(m_vkDevice, &FramebufferCI, nullptr, &m_vkFramebuffer); + VERIFY_EXPR(res >= 0); + (void)res; + } + + void BeginRenderPass(VkCommandBuffer vkCmdBuffer, const float* ClearColor) + { + RefCntAutoPtr pBackBuffer{m_pSwapChain->GetCurrentBackBufferRTV()->GetTexture(), IID_TextureVk}; + VERIFY_EXPR(pBackBuffer); + RefCntAutoPtr pDepthBuffer{m_pSwapChain->GetDepthBufferDSV()->GetTexture(), IID_TextureVk}; + VERIFY_EXPR(pDepthBuffer); + + // Manually transition Diligent Engine managed images to the required layouts. + // We cannot use TestingSwapChainVk::TransitionRenderTarget/TransitionDepthBuffer + // because they operate on TestingSwapChainVk's internal images, not the Diligent Engine images. + VkImageMemoryBarrier ImageBarriers[2]{}; + + // Render target barrier: UNDEFINED -> COLOR_ATTACHMENT_OPTIMAL + ImageBarriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + ImageBarriers[0].srcAccessMask = 0; + ImageBarriers[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + ImageBarriers[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + ImageBarriers[0].newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + ImageBarriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + ImageBarriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + ImageBarriers[0].image = pBackBuffer->GetVkImage(); + ImageBarriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + ImageBarriers[0].subresourceRange.baseMipLevel = 0; + ImageBarriers[0].subresourceRange.levelCount = 1; + ImageBarriers[0].subresourceRange.baseArrayLayer = 0; + ImageBarriers[0].subresourceRange.layerCount = 1; + + // Depth buffer barrier: UNDEFINED -> DEPTH_STENCIL_ATTACHMENT_OPTIMAL + ImageBarriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + ImageBarriers[1].srcAccessMask = 0; + ImageBarriers[1].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + ImageBarriers[1].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + ImageBarriers[1].newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + ImageBarriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + ImageBarriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + ImageBarriers[1].image = pDepthBuffer->GetVkImage(); + ImageBarriers[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + ImageBarriers[1].subresourceRange.baseMipLevel = 0; + ImageBarriers[1].subresourceRange.levelCount = 1; + ImageBarriers[1].subresourceRange.baseArrayLayer = 0; + ImageBarriers[1].subresourceRange.layerCount = 1; + + vkCmdPipelineBarrier(vkCmdBuffer, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + 0, + 0, nullptr, + 0, nullptr, + 2, ImageBarriers); + + VkRenderPassBeginInfo BeginInfo{}; + BeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + BeginInfo.renderPass = m_vkRenderPass; + BeginInfo.framebuffer = m_vkFramebuffer; + BeginInfo.renderArea.extent = VkExtent2D{m_pSwapChain->GetDesc().Width, m_pSwapChain->GetDesc().Height}; + + VkClearValue ClearValues[2]{}; + ClearValues[0].depthStencil.depth = 1; + ClearValues[1].color.float32[0] = ClearColor[0]; + ClearValues[1].color.float32[1] = ClearColor[1]; + ClearValues[1].color.float32[2] = ClearColor[2]; + ClearValues[1].color.float32[3] = ClearColor[3]; + + BeginInfo.clearValueCount = 2; + BeginInfo.pClearValues = ClearValues; + + vkCmdBeginRenderPass(vkCmdBuffer, &BeginInfo, VK_SUBPASS_CONTENTS_INLINE); + } + + void Draw(VkCommandBuffer vkCmdBuffer, const void* pPushConstantData, uint32_t PushConstantSize) + { + vkCmdBindPipeline(vkCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_vkPipeline); + vkCmdPushConstants(vkCmdBuffer, m_vkLayout, m_PushConstantStages, 0, PushConstantSize, pPushConstantData); + vkCmdDraw(vkCmdBuffer, 6, 1, 0, 0); + } + + void EndRenderPass(VkCommandBuffer vkCmdBuffer) + { + vkCmdEndRenderPass(vkCmdBuffer); + } + + ~PatchedPushConstantsRenderer() + { + vkDestroyPipeline(m_vkDevice, m_vkPipeline, nullptr); + vkDestroyPipelineLayout(m_vkDevice, m_vkLayout, nullptr); + vkDestroyShaderModule(m_vkDevice, m_vkVSModule, nullptr); + vkDestroyShaderModule(m_vkDevice, m_vkFSModule, nullptr); + vkDestroyRenderPass(m_vkDevice, m_vkRenderPass, nullptr); + vkDestroyFramebuffer(m_vkDevice, m_vkFramebuffer, nullptr); + } + +private: + TestingSwapChainVk* m_pSwapChain = nullptr; + VkDevice m_vkDevice = VK_NULL_HANDLE; + VkShaderModule m_vkVSModule = VK_NULL_HANDLE; + VkShaderModule m_vkFSModule = VK_NULL_HANDLE; + VkPipeline m_vkPipeline = VK_NULL_HANDLE; + VkPipelineLayout m_vkLayout = VK_NULL_HANDLE; + VkRenderPass m_vkRenderPass = VK_NULL_HANDLE; + VkFramebuffer m_vkFramebuffer = VK_NULL_HANDLE; + VkShaderStageFlags m_PushConstantStages = 0; +}; + +// Test helper that runs the full test flow +void VkConvertUBOToPushConstantsTest::RunConvertUBOToPushConstantsTest(SHADER_COMPILER Compiler, SHADER_SOURCE_LANGUAGE SourceLanguage, const std::string& BlockName) +{ + TestingEnvironmentVk* pEnv = TestingEnvironmentVk::GetInstance(); + + if (Compiler == SHADER_COMPILER_DXC) + { + if (!DXCompiler || !DXCompiler->IsLoaded()) + { + GTEST_SKIP() << "DXCompiler is not available"; + } + } + + IDeviceContext* pContext = pEnv->GetDeviceContext(); + ISwapChain* pSwapChain = pEnv->GetSwapChain(); + + RefCntAutoPtr pTestingSwapChain{pSwapChain, IID_TestingSwapChain}; + + TestingSwapChainVk* pTestingSwapChainVk = ClassPtrCast(pSwapChain); + + // Step 1: Render reference using existing ReferenceTriangleRenderer + pContext->Flush(); + pContext->InvalidateState(); + + const float ClearColor[] = {Rnd(), Rnd(), Rnd(), Rnd()}; + RenderDrawCommandReferenceVk(pSwapChain, ClearColor); + + // Take snapshot of reference image + pTestingSwapChain->TakeSnapshot(); + + // Step 2: Compile shaders to SPIR-V + std::vector VS_SPIRV, FS_SPIRV; + + if (SourceLanguage == SHADER_SOURCE_LANGUAGE_HLSL) + { + CompileSPIRV(HLSL_ProceduralTriangleVS, "HLSL_ProceduralTriangleVS", Compiler, SHADER_TYPE_VERTEX, SHADER_SOURCE_LANGUAGE_HLSL, VS_SPIRV); + CompileSPIRV(HLSL_FragmentShaderWithCB, "HLSL_FragmentShaderWithCB", Compiler, SHADER_TYPE_PIXEL, SHADER_SOURCE_LANGUAGE_HLSL, FS_SPIRV); + } + else + { + CompileSPIRV(GLSL_ProceduralTriangleVS, "GLSL_ProceduralTriangleVS", Compiler, SHADER_TYPE_VERTEX, SHADER_SOURCE_LANGUAGE_GLSL, VS_SPIRV); + CompileSPIRV(GLSL_FragmentShaderWithUBO, "GLSL_FragmentShaderWithUBO", Compiler, SHADER_TYPE_PIXEL, SHADER_SOURCE_LANGUAGE_GLSL, FS_SPIRV); + } + + ASSERT_FALSE(VS_SPIRV.empty()) << "Failed to compile vertex shader"; + ASSERT_FALSE(FS_SPIRV.empty()) << "Failed to compile fragment shader"; + + // Step 3: Patch both vertex and fragment shaders to use push constants + // Both shaders reference the same UBO (CB1), so both need to be patched + std::vector VS_SPIRV_Patched = ConvertUBOToPushConstants(VS_SPIRV, BlockName); + std::vector FS_SPIRV_Patched = ConvertUBOToPushConstants(FS_SPIRV, BlockName); + ASSERT_FALSE(VS_SPIRV_Patched.empty()) << "Failed to patch VS UBO to push constants with BlockName: " << BlockName; + ASSERT_FALSE(FS_SPIRV_Patched.empty()) << "Failed to patch FS UBO to push constants with BlockName: " << BlockName; + + if (SourceLanguage == SHADER_SOURCE_LANGUAGE_HLSL) + { + // SPIR-V bytecode generated from HLSL must be legalized to + // turn it into a valid vulkan SPIR-V shader. + SPIRV_OPTIMIZATION_FLAGS OptimizationFlags = SPIRV_OPTIMIZATION_FLAG_LEGALIZATION | SPIRV_OPTIMIZATION_FLAG_STRIP_REFLECTION; + VS_SPIRV_Patched = OptimizeSPIRV(VS_SPIRV_Patched, OptimizationFlags); + FS_SPIRV_Patched = OptimizeSPIRV(FS_SPIRV_Patched, OptimizationFlags); + } + + // Step 4: Render with push constants + { + PatchedPushConstantsRenderer Renderer{ + pTestingSwapChainVk, + VS_SPIRV_Patched, + FS_SPIRV_Patched, + sizeof(PushConstantData), + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT}; + + VkCommandBuffer vkCmdBuffer = pEnv->AllocateCommandBuffer(); + + Renderer.BeginRenderPass(vkCmdBuffer, ClearColor); + + // Set push constant data + // Factor = (1,1,1,1) to make output identical to reference + // Colors match the reference triangle colors (RGB for each vertex) + PushConstantData PushData{}; + PushData.Factor = {1, 1, 1, 1}; + + // Vertex colors matching the reference (std140 layout: vec3 padded to 16 bytes) + // Triangle 1 + PushData.Colors[0] = {1, 0, 0, 0}; + PushData.Colors[1] = {0, 1, 0, 0}; + PushData.Colors[2] = {0, 0, 1, 0}; + // Triangle 2 + PushData.Colors[3] = {1, 0, 0, 0}; + PushData.Colors[4] = {0, 1, 0, 0}; + PushData.Colors[5] = {0, 0, 1, 0}; + + Renderer.Draw(vkCmdBuffer, &PushData, sizeof(PushData)); + + Renderer.EndRenderPass(vkCmdBuffer); + + vkEndCommandBuffer(vkCmdBuffer); + + pEnv->SubmitCommandBuffer(vkCmdBuffer, true); + } + + // Sync Diligent Engine's internal layout tracking with the actual image layouts. + // After our native Vulkan rendering, the images are in COLOR_ATTACHMENT_OPTIMAL + // and DEPTH_STENCIL_ATTACHMENT_OPTIMAL layouts, but Diligent Engine doesn't know this. + // We need to update the tracked layouts so that CompareWithSnapshot() can correctly + // transition the images for the copy operation. + if (RefCntAutoPtr pRenderTargetVk{pTestingSwapChainVk->GetCurrentBackBufferRTV()->GetTexture(), IID_TextureVk}) + pRenderTargetVk->SetLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + if (RefCntAutoPtr pDepthBufferVk{pTestingSwapChainVk->GetDepthBufferDSV()->GetTexture(), IID_TextureVk}) + pDepthBufferVk->SetLayout(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + // Step 5: Comparison native draw image with ref snapshot + pTestingSwapChainVk->Present(); +} + +} // namespace + +// Test patching UBO using struct type name "CB1" +TEST_F(VkConvertUBOToPushConstantsTest, PatchByStructTypeName_GLSLang_GLSL) +{ + RunConvertUBOToPushConstantsTest(SHADER_COMPILER_GLSLANG, SHADER_SOURCE_LANGUAGE_GLSL, "CB1"); +} + +// Test patching UBO using variable instance name "cb" +TEST_F(VkConvertUBOToPushConstantsTest, PatchByVariableName_GLSLang_GLSL) +{ + RunConvertUBOToPushConstantsTest(SHADER_COMPILER_GLSLANG, SHADER_SOURCE_LANGUAGE_GLSL, "cb"); +} + +// Test patching CB using cbuffer block name "CB1" with DXC compiler +// Note: In HLSL, cbuffer name and struct name may be the same or different. +// DXC typically generates both OpName for the struct type and the variable. + +TEST_F(VkConvertUBOToPushConstantsTest, PatchByStructTypeName_GLSLang_HLSL) +{ + RunConvertUBOToPushConstantsTest(SHADER_COMPILER_GLSLANG, SHADER_SOURCE_LANGUAGE_HLSL, "CB1"); +} + +TEST_F(VkConvertUBOToPushConstantsTest, PatchByStructTypeName_DXC_HLSL) +{ + RunConvertUBOToPushConstantsTest(SHADER_COMPILER_DXC, SHADER_SOURCE_LANGUAGE_HLSL, "CB1"); +} + +} // namespace Testing + +} // namespace Diligent diff --git a/Tests/DiligentCoreTest/CMakeLists.txt b/Tests/DiligentCoreTest/CMakeLists.txt index 3b29ae0533..1cde824899 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") @@ -58,6 +64,10 @@ if(WEBGPU_SUPPORTED) target_link_libraries(DiligentCoreTest PRIVATE libtint) endif() +if (PLATFORM_WIN32) + copy_shader_compiler_dlls(DiligentCoreTest DXCOMPILER_FOR_SPIRV YES) +endif() + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE} ${SHADERS}}) set_target_properties(DiligentCoreTest 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..d93a8c9959 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/MixedResources.psh @@ -0,0 +1,63 @@ +// 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 struct for push constants +struct PushConstants_t +{ + float2 Offset; + float Time; + uint FrameCount; +}; + +//note that cbuffer PushConstants is not allowed in DXC, but ConstantBuffer is allowed +[[vk::push_constant]] ConstantBuffer 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 += PushConstants.Offset; + color.rgb *= sin(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..6ee483fc88 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/PushConstants.psh @@ -0,0 +1,28 @@ +// Test struct for push constants +struct PushConstants_t +{ + float4x4 g_MVPMatrix; + float4 g_Color; + float2 g_Offset; + float g_Scale; + uint g_Padding; +}; + +//note that cbuffer PushConstants is not allowed in DXC, but ConstantBuffer is allowed +[[vk::push_constant]] ConstantBuffer PushConstants; + +float GetScale(ConstantBuffer PC) +{ + return PC.g_Scale; +} + +float4 main() : SV_Target +{ + // Use push constant data through the structure instance + float4 result = PushConstants.g_Color * GetScale(PushConstants); + + // Apply offset to result (simplified example) + result.xy += 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..46122fe43b --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/UniformBuffers.psh @@ -0,0 +1,54 @@ +// Test for uniform buffers (constant buffers) + +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; +}; + +// Test for nested constant buffers + +struct InnerConstant_t +{ + float4 g_Color; +}; + +struct InnerConstant2_t +{ + float4 g_MaterialParams; +}; + +struct MergedConstant_t +{ + InnerConstant_t InnerConstant; + InnerConstant2_t InnerConstant2; +}; + +cbuffer CB3 +{ + MergedConstant_t g_MergedConstant; +}; + +cbuffer CB4 +{ + InnerConstant_t g_InnerConstant; + InnerConstant2_t g_InnerConstant2; +}; + +float4 main() : SV_Target +{ + return g_Color * + g_MaterialParams * + g_MergedConstant.InnerConstant.g_Color * + g_MergedConstant.InnerConstant2.g_MaterialParams * + g_InnerConstant.g_Color * + g_InnerConstant2.g_MaterialParams; +} \ No newline at end of file diff --git a/Tests/DiligentCoreTest/src/GraphicsAccessories/GraphicsAccessoriesTest.cpp b/Tests/DiligentCoreTest/src/GraphicsAccessories/GraphicsAccessoriesTest.cpp index 1cf0238d7b..5b4c9f342f 100644 --- a/Tests/DiligentCoreTest/src/GraphicsAccessories/GraphicsAccessoriesTest.cpp +++ b/Tests/DiligentCoreTest/src/GraphicsAccessories/GraphicsAccessoriesTest.cpp @@ -705,7 +705,7 @@ TEST(GraphicsAccessories_GraphicsAccessories, PipelineTypeFromShaderStages) TEST(GraphicsAccessories_GraphicsAccessories, GetPipelineResourceFlagsString) { - static_assert(PIPELINE_RESOURCE_FLAG_LAST == (1u << 4), "Please add a test for the new flag here"); + static_assert(PIPELINE_RESOURCE_FLAG_LAST == (1u << 5), "Please add a test for the new flag here"); EXPECT_STREQ(GetPipelineResourceFlagsString(PIPELINE_RESOURCE_FLAG_NONE, true).c_str(), "PIPELINE_RESOURCE_FLAG_NONE"); EXPECT_STREQ(GetPipelineResourceFlagsString(PIPELINE_RESOURCE_FLAG_NONE).c_str(), "UNKNOWN"); @@ -714,11 +714,13 @@ TEST(GraphicsAccessories_GraphicsAccessories, GetPipelineResourceFlagsString) EXPECT_STREQ(GetPipelineResourceFlagsString(PIPELINE_RESOURCE_FLAG_COMBINED_SAMPLER, true).c_str(), "PIPELINE_RESOURCE_FLAG_COMBINED_SAMPLER"); EXPECT_STREQ(GetPipelineResourceFlagsString(PIPELINE_RESOURCE_FLAG_FORMATTED_BUFFER, true).c_str(), "PIPELINE_RESOURCE_FLAG_FORMATTED_BUFFER"); EXPECT_STREQ(GetPipelineResourceFlagsString(PIPELINE_RESOURCE_FLAG_GENERAL_INPUT_ATTACHMENT, true).c_str(), "PIPELINE_RESOURCE_FLAG_GENERAL_INPUT_ATTACHMENT"); + EXPECT_STREQ(GetPipelineResourceFlagsString(PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS, true).c_str(), "PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS"); EXPECT_STREQ(GetPipelineResourceFlagsString(PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS).c_str(), "NO_DYNAMIC_BUFFERS"); EXPECT_STREQ(GetPipelineResourceFlagsString(PIPELINE_RESOURCE_FLAG_COMBINED_SAMPLER).c_str(), "COMBINED_SAMPLER"); EXPECT_STREQ(GetPipelineResourceFlagsString(PIPELINE_RESOURCE_FLAG_FORMATTED_BUFFER).c_str(), "FORMATTED_BUFFER"); EXPECT_STREQ(GetPipelineResourceFlagsString(PIPELINE_RESOURCE_FLAG_GENERAL_INPUT_ATTACHMENT).c_str(), "GENERAL_INPUT_ATTACHMENT"); + EXPECT_STREQ(GetPipelineResourceFlagsString(PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS).c_str(), "INLINE_CONSTANTS"); EXPECT_STREQ(GetPipelineResourceFlagsString(PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS | PIPELINE_RESOURCE_FLAG_COMBINED_SAMPLER, true).c_str(), "PIPELINE_RESOURCE_FLAG_NO_DYNAMIC_BUFFERS|PIPELINE_RESOURCE_FLAG_COMBINED_SAMPLER"); diff --git a/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp b/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp new file mode 100644 index 0000000000..93da75f2a4 --- /dev/null +++ b/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp @@ -0,0 +1,579 @@ +/* + * Copyright 2025-2026 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 "DXCompiler.hpp" +#include "DefaultShaderSourceStreamFactory.h" +#include "RefCntAutoPtr.hpp" +#include "EngineMemory.h" +#include "BasicFileSystem.hpp" +#include "SPIRVTools.hpp" + +#include +#include +#include + +#include "TestingEnvironment.hpp" + +#include "gtest/gtest.h" + +using namespace Diligent; +using namespace Diligent::Testing; + +namespace +{ + +class SPIRVShaderResourcesTest : public ::testing::Test +{ +public: + static std::unique_ptr DXCompiler; + +protected: + static void SetUpTestSuite() + { + GLSLangUtils::InitializeGlslang(); + + DXCompiler = CreateDXCompiler(DXCompilerTarget::Vulkan, 0, nullptr); + } + + static void TearDownTestSuite() + { + GLSLangUtils::FinalizeGlslang(); + + DXCompiler.reset(); + } +}; + +std::unique_ptr SPIRVShaderResourcesTest::DXCompiler; + +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_COMPILER Compiler = SHADER_COMPILER_DEFAULT) +{ + RefCntAutoPtr pShaderSourceStreamFactory; + CreateDefaultShaderSourceStreamFactory("shaders/SPIRV", &pShaderSourceStreamFactory); + if (!pShaderSourceStreamFactory) + return {}; + + ShaderCreateInfo ShaderCI; + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL; + ShaderCI.FilePath = FilePath; + ShaderCI.Desc = {"SPIRV test shader", ShaderType}; + ShaderCI.EntryPoint = "main"; + + ShaderCI.pShaderSourceStreamFactory = pShaderSourceStreamFactory; + + std::vector SPIRV; + + if (Compiler == SHADER_COMPILER_DXC) + { + if (!SPIRVShaderResourcesTest::DXCompiler || !SPIRVShaderResourcesTest::DXCompiler->IsLoaded()) + { + UNEXPECTED("Test should be skipped if DXCompiler is not available"); + return {}; + } + + RefCntAutoPtr pCompilerOutput; + SPIRVShaderResourcesTest::DXCompiler->Compile(ShaderCI, ShaderVersion{6, 0}, nullptr, nullptr, &SPIRV, &pCompilerOutput); + + if (pCompilerOutput && pCompilerOutput->GetSize() > 0) + { + const char* CompilerOutput = static_cast(pCompilerOutput->GetConstDataPtr()); + if (*CompilerOutput != 0) + LOG_INFO_MESSAGE("DXC compiler output:\n", CompilerOutput); + } + } + else + { + SPIRV = GLSLangUtils::HLSLtoSPIRV(ShaderCI, GLSLangUtils::SpirvVersion::Vk100, nullptr, nullptr); + } + + return SPIRV; +} + +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 CompileSPIRV(const char* FilePath, + SHADER_COMPILER Compiler, + SHADER_TYPE ShaderType, + SHADER_SOURCE_LANGUAGE SourceLanguage, + std::vector& SPIRV) +{ + if (Compiler == SHADER_COMPILER_DXC) + { + VERIFY(SourceLanguage == SHADER_SOURCE_LANGUAGE_HLSL, "DXC only supports HLSL"); + if (!SPIRVShaderResourcesTest::DXCompiler || !SPIRVShaderResourcesTest::DXCompiler->IsLoaded()) + { + GTEST_SKIP() << "DXC compiler is not available"; + } + } + + SPIRV = (SourceLanguage == SHADER_SOURCE_LANGUAGE_GLSL) ? + LoadSPIRVFromGLSL(FilePath, ShaderType) : + LoadSPIRVFromHLSL(FilePath, ShaderType, Compiler); + ASSERT_FALSE(SPIRV.empty()) << "Failed to compile shader: " << FilePath; +} + +void TestSPIRVResources(const char* FilePath, + const std::vector& RefResources, + SHADER_COMPILER Compiler, + SHADER_TYPE ShaderType = SHADER_TYPE_PIXEL, + SHADER_SOURCE_LANGUAGE SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL, + const std::function& SPIRV)>& PatchSPIRVCallback = nullptr) +{ + std::vector SPIRV; + ASSERT_NO_FATAL_FAILURE(CompileSPIRV(FilePath, Compiler, ShaderType, SourceLanguage, SPIRV)); + + if (::testing::Test::IsSkipped()) + return; + + if (PatchSPIRVCallback) + { + PatchSPIRVCallback(SPIRV); + ASSERT_FALSE(SPIRV.empty()) << "Failed to patch shader: " << FilePath; + } + + SPIRVShaderResources::CreateInfo ResCI; + ResCI.ShaderType = ShaderType; + ResCI.Name = "SPIRVResources test"; + SPIRVShaderResources Resources{ + GetRawAllocator(), + SPIRV, + ResCI, + }; + + 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; + +void TestUniformBuffers(SHADER_COMPILER Compiler) +{ + TestSPIRVResources("UniformBuffers.psh", + { + SPIRVShaderResourceRefAttribs{"CB1", 1, SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0, 48, 0}, + SPIRVShaderResourceRefAttribs{"CB2", 1, SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0, 16, 0}, + SPIRVShaderResourceRefAttribs{"CB3", 1, SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0, 32, 0}, + SPIRVShaderResourceRefAttribs{"CB4", 1, SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0, 32, 0}, + }, + Compiler); +} + +TEST_F(SPIRVShaderResourcesTest, UniformBuffers_GLSLang) +{ + TestUniformBuffers(SHADER_COMPILER_GLSLANG); +} + +TEST_F(SPIRVShaderResourcesTest, UniformBuffers_DXC) +{ + TestUniformBuffers(SHADER_COMPILER_DXC); +} + +void TestConvertUBOToPushConstant(SHADER_COMPILER Compiler) +{ + const std::vector BaseRefAttribs = { + SPIRVShaderResourceRefAttribs{"CB1", 1, SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0, 48, 0}, + SPIRVShaderResourceRefAttribs{"CB2", 1, SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0, 16, 0}, + SPIRVShaderResourceRefAttribs{"CB3", 1, SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0, 32, 0}, + SPIRVShaderResourceRefAttribs{"CB4", 1, SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0, 32, 0}, + }; + + //Try to patch uniform buffer block to push constants one for each + for (size_t i = 0; i < BaseRefAttribs.size(); ++i) + { + const char* PatchedAttribName = BaseRefAttribs[i].Name; + + std::vector PatchedRefAttribs; + PatchedRefAttribs.reserve(BaseRefAttribs.size()); + for (size_t j = 0; j < BaseRefAttribs.size(); ++j) + { + const SPIRVShaderResourceRefAttribs& RefAttrib = BaseRefAttribs[j]; + // Build a new attrib with Type changed to PushConstant, other members remain the same + PatchedRefAttribs.push_back({RefAttrib.Name, + RefAttrib.ArraySize, + i == j ? SPIRVResourceType::PushConstant : RefAttrib.Type, + RefAttrib.ResourceDim, + RefAttrib.IsMS, + RefAttrib.BufferStaticSize, + RefAttrib.BufferStride}); + } + + TestSPIRVResources("UniformBuffers.psh", + PatchedRefAttribs, + Compiler, + SHADER_TYPE_PIXEL, + SHADER_SOURCE_LANGUAGE_HLSL, + [PatchedAttribName](std::vector& SPIRV) { + SPIRV = ConvertUBOToPushConstants(SPIRV, PatchedAttribName); + }); + } +} + +TEST_F(SPIRVShaderResourcesTest, ConvertUBOToPushConstant_GLSLang) +{ + TestConvertUBOToPushConstant(SHADER_COMPILER_GLSLANG); +} + +TEST_F(SPIRVShaderResourcesTest, ConvertUBOToPushConstant_DXC) +{ + TestConvertUBOToPushConstant(SHADER_COMPILER_DXC); +} + +void TestConvertUBOToPushConstant_InvalidBlockName(SHADER_COMPILER Compiler) +{ + //"CB5" is not available in given HLSL thus cannot be patched with ConvertUBOToPushConstants. + std::string PatchedAttribName = "CB5"; + + std::vector SPIRV; + ASSERT_NO_FATAL_FAILURE(CompileSPIRV("UniformBuffers.psh", Compiler, SHADER_TYPE_PIXEL, SHADER_SOURCE_LANGUAGE_HLSL, SPIRV)); + if (::testing::Test::IsSkipped()) + return; + + TestingEnvironment* pEnv = TestingEnvironment::GetInstance(); + pEnv->SetErrorAllowance(1, "No worries, errors are expected: testing invalid input\n"); + pEnv->PushExpectedErrorSubstring("Failed to convert UBO block 'CB5': no OpName found."); + + SPIRV = ConvertUBOToPushConstants(SPIRV, PatchedAttribName); + EXPECT_TRUE(SPIRV.empty()); +} + +TEST_F(SPIRVShaderResourcesTest, ConvertUBOToPushConstant_InvalidBlockName_GLSLang) +{ + TestConvertUBOToPushConstant_InvalidBlockName(SHADER_COMPILER_GLSLANG); +} + +TEST_F(SPIRVShaderResourcesTest, ConvertUBOToPushConstant_InvalidBlockName_DXC) +{ + TestConvertUBOToPushConstant_InvalidBlockName(SHADER_COMPILER_DXC); +} + +void TestConvertUBOToPushConstant_InvalidResourceType(SHADER_COMPILER Compiler) +{ + //"g_ROBuffer" is a ROStorageBuffer and cannot be patched with ConvertUBOToPushConstants. + std::string PatchedAttribName = "g_ROBuffer"; + + std::vector SPIRV; + ASSERT_NO_FATAL_FAILURE(CompileSPIRV("StorageBuffers.psh", Compiler, SHADER_TYPE_PIXEL, SHADER_SOURCE_LANGUAGE_HLSL, SPIRV)); + if (::testing::Test::IsSkipped()) + return; + + TestingEnvironment* pEnv = TestingEnvironment::GetInstance(); + pEnv->SetErrorAllowance(1, "No worries, errors are expected: testing invalid input\n"); + pEnv->PushExpectedErrorSubstring("Failed to convert UBO block 'g_ROBuffer': no matching UniformBuffer found."); + + SPIRV = ConvertUBOToPushConstants(SPIRV, PatchedAttribName); + EXPECT_TRUE(SPIRV.empty()); +} + +TEST_F(SPIRVShaderResourcesTest, ConvertUBOToPushConstant_InvalidResourceType_GLSLang) +{ + TestConvertUBOToPushConstant_InvalidResourceType(SHADER_COMPILER_GLSLANG); +} + +TEST_F(SPIRVShaderResourcesTest, ConvertUBOToPushConstant_InvalidResourceType_DXC) +{ + TestConvertUBOToPushConstant_InvalidResourceType(SHADER_COMPILER_DXC); +} + +void TestStorageBuffers(SHADER_COMPILER Compiler) +{ + 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}, + }, + Compiler); +} + +TEST_F(SPIRVShaderResourcesTest, StorageBuffers_GLSLang) +{ + TestStorageBuffers(SHADER_COMPILER_GLSLANG); +} + +TEST_F(SPIRVShaderResourcesTest, StorageBuffers_DXC) +{ + TestStorageBuffers(SHADER_COMPILER_DXC); +} + +void TestTexelBuffers(SHADER_COMPILER Compiler) +{ + 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}, + }, + Compiler); +} + +TEST_F(SPIRVShaderResourcesTest, TexelBuffers_GLSLang) +{ + TestTexelBuffers(SHADER_COMPILER_GLSLANG); +} + +TEST_F(SPIRVShaderResourcesTest, TexelBuffers_DXC) +{ + TestTexelBuffers(SHADER_COMPILER_DXC); +} + +void TestTextures(SHADER_COMPILER Compiler) +{ + 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}, + }, + Compiler); +} + +TEST_F(SPIRVShaderResourcesTest, Textures_GLSLang) +{ + TestTextures(SHADER_COMPILER_GLSLANG); +} + +TEST_F(SPIRVShaderResourcesTest, Textures_DXC) +{ + TestTextures(SHADER_COMPILER_DXC); +} + +void TestStorageImages(SHADER_COMPILER Compiler) +{ + 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}, + }, + Compiler); +} + +TEST_F(SPIRVShaderResourcesTest, StorageImages_GLSLang) +{ + TestStorageImages(SHADER_COMPILER_GLSLANG); +} + +TEST_F(SPIRVShaderResourcesTest, StorageImages_DXC) +{ + TestStorageImages(SHADER_COMPILER_DXC); +} + +TEST_F(SPIRVShaderResourcesTest, AtomicCounters_GLSLang) +{ + // 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_COMPILER_GLSLANG, + SHADER_TYPE_PIXEL, + SHADER_SOURCE_LANGUAGE_GLSL); +} + +void TestInputAttachments(SHADER_COMPILER Compiler) +{ + TestSPIRVResources("InputAttachments.psh", + { + SPIRVShaderResourceRefAttribs{"g_InputAttachment", 1, SPIRVResourceType::InputAttachment, RESOURCE_DIM_UNDEFINED, 0, 0, 0}, + }, + Compiler); +} + +TEST_F(SPIRVShaderResourcesTest, InputAttachments_GLSLang) +{ + TestInputAttachments(SHADER_COMPILER_GLSLANG); +} + +TEST_F(SPIRVShaderResourcesTest, InputAttachments_DXC) +{ + TestInputAttachments(SHADER_COMPILER_DXC); +} + +TEST_F(SPIRVShaderResourcesTest, AccelerationStructures_GLSLang) +{ + // 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_COMPILER_GLSLANG, + SHADER_TYPE_RAY_GEN, + SHADER_SOURCE_LANGUAGE_GLSL); +} + +void TestPushConstants(SHADER_COMPILER Compiler) +{ + // 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}, + }, + Compiler); +} + +TEST_F(SPIRVShaderResourcesTest, PushConstants_GLSLang) +{ + TestPushConstants(SHADER_COMPILER_GLSLANG); +} + +TEST_F(SPIRVShaderResourcesTest, PushConstants_DXC) +{ + TestPushConstants(SHADER_COMPILER_DXC); +} + +void TestMixedResources(SHADER_COMPILER Compiler) +{ + 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}, + }, + Compiler); +} + +TEST_F(SPIRVShaderResourcesTest, MixedResources_GLSLang) +{ + TestMixedResources(SHADER_COMPILER_GLSLANG); +} + +TEST_F(SPIRVShaderResourcesTest, MixedResources_DXC) +{ + TestMixedResources(SHADER_COMPILER_DXC); +} + +} // namespace