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/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..350a5a0b9c 100644 --- a/Graphics/GraphicsEngine/src/PipelineResourceSignatureBase.cpp +++ b/Graphics/GraphicsEngine/src/PipelineResourceSignatureBase.cpp @@ -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 > 64) + { + LOG_PRS_ERROR_AND_THROW("Desc.Resources[", i, "].ArraySize (", Res.ArraySize, + ") exceeds the maximum allowed value (64) 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 e16fca6a80..a05bbe9b40 100644 --- a/Graphics/GraphicsEngineD3D11/include/DeviceContextD3D11Impl.hpp +++ b/Graphics/GraphicsEngineD3D11/include/DeviceContextD3D11Impl.hpp @@ -359,7 +359,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 a800eee65a..e297101f42 100644 --- a/Graphics/GraphicsEngineD3D11/include/ShaderVariableManagerD3D11.hpp +++ b/Graphics/GraphicsEngineD3D11/include/ShaderVariableManagerD3D11.hpp @@ -130,6 +130,11 @@ class ShaderVariableManagerD3D11 : ShaderVariableManagerBase @@ -141,6 +146,8 @@ class ShaderVariableManagerD3D11 : ShaderVariableManagerBase diff --git a/Graphics/GraphicsEngineD3D11/src/DeviceContextD3D11Impl.cpp b/Graphics/GraphicsEngineD3D11/src/DeviceContextD3D11Impl.cpp index fccc955464..319cd82d31 100755 --- a/Graphics/GraphicsEngineD3D11/src/DeviceContextD3D11Impl.cpp +++ b/Graphics/GraphicsEngineD3D11/src/DeviceContextD3D11Impl.cpp @@ -426,7 +426,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); @@ -452,7 +454,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); @@ -460,17 +463,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."); } } @@ -672,9 +715,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 8155c04617..bd42290a5f 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 44e5e31fd7..21aeccf209 100644 --- a/Graphics/GraphicsEngineD3D12/include/ShaderVariableManagerD3D12.hpp +++ b/Graphics/GraphicsEngineD3D12/include/ShaderVariableManagerD3D12.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2024 Diligent Graphics LLC + * Copyright 2019-2025 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -104,6 +104,11 @@ class ShaderVariableManagerD3D12 : public ShaderVariableManagerBase(-1); diff --git a/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp index 6f49c65d14..ce56c4b42e 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..0daab12f72 100644 --- a/Graphics/GraphicsEngineD3DBase/include/ShaderResources.hpp +++ b/Graphics/GraphicsEngineD3DBase/include/ShaderResources.hpp @@ -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,33 @@ 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); + + constexpr Uint32 kMaxInlineConstants = 64; + if (NumConstants > kMaxInlineConstants) + { + LOG_ERROR_AND_THROW("Inline constants resource '", Name, "' in shader '", ShaderName, "' has ", + NumConstants, " constants. The maximum supported number of inline constants is ", + kMaxInlineConstants, '.'); + } + + return NumConstants; } SHADER_RESOURCE_TYPE GetShaderResourceType() const; @@ -265,14 +291,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 +575,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..6c2866d1e9 100644 --- a/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.hpp +++ b/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.hpp @@ -60,6 +60,9 @@ #include "HashUtils.hpp" #include "ManagedVulkanObject.hpp" +// Push constants data storage (max size is typically 128-256 bytes, use 256 for safety) +#define DILIGENT_MAX_PUSH_CONSTANTS_SIZE 256 + namespace Diligent { @@ -321,6 +324,9 @@ class DeviceContextVkImpl final : public DeviceContextNextGenBase m_PushConstantsData = {}; + Uint32 m_PushConstantsDataSize = 0; // Actual size of valid push constants data + // Graphics/mesh, compute, ray tracing static constexpr Uint32 NUM_PIPELINE_BIND_POINTS = 3; @@ -555,6 +568,12 @@ class DeviceContextVkImpl final : public DeviceContextNextGenBase ppSignatures[], Uint32 SignatureCount) noexcept(false); + void Create(RenderDeviceVkImpl* pDeviceVk, + RefCntAutoPtr ppSignatures[], + Uint32 SignatureCount, + const PushConstantInfoVk& PushConstant = {}) noexcept(false); void Release(RenderDeviceVkImpl* pDeviceVkImpl, Uint64 CommandQueueMask); VkPipelineLayout GetVkPipelineLayout() const { return m_VkPipelineLayout; } @@ -59,6 +73,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 +101,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/PipelineResourceAttribsVk.hpp b/Graphics/GraphicsEngineVulkan/include/PipelineResourceAttribsVk.hpp index 5a76ab680a..8b5fa67db9 100644 --- a/Graphics/GraphicsEngineVulkan/include/PipelineResourceAttribsVk.hpp +++ b/Graphics/GraphicsEngineVulkan/include/PipelineResourceAttribsVk.hpp @@ -63,17 +63,20 @@ enum class DescriptorType : Uint8 // sizeof(PipelineResourceAttribsVk) == 16, x64 +// Note: Push constant selection is deferred to PSO creation time. +// At PRS level, all inline constants are treated uniformly with descriptor sets. struct PipelineResourceAttribsVk { private: static constexpr Uint32 _BindingIndexBits = 16; static constexpr Uint32 _SamplerIndBits = 16; - static constexpr Uint32 _ArraySizeBits = 25; + static constexpr Uint32 _ArraySizeBits = 24; static constexpr Uint32 _DescrTypeBits = 5; static constexpr Uint32 _DescrSetBits = 1; static constexpr Uint32 _SamplerAssignedBits = 1; + static constexpr Uint32 _ReservedBits = 1; // Reserved for future use - static_assert((_BindingIndexBits + _ArraySizeBits + _SamplerIndBits + _DescrTypeBits + _DescrSetBits + _SamplerAssignedBits) % 32 == 0, "Bits are not optimally packed"); + static_assert((_BindingIndexBits + _ArraySizeBits + _SamplerIndBits + _DescrTypeBits + _DescrSetBits + _SamplerAssignedBits + _ReservedBits) % 32 == 0, "Bits are not optimally packed"); // clang-format off static_assert((1u << _DescrTypeBits) > static_cast(DescriptorType::Count), "Not enough bits to store DescriptorType values"); @@ -88,10 +91,11 @@ struct PipelineResourceAttribsVk // clang-format off const Uint32 BindingIndex : _BindingIndexBits; // Binding in the descriptor set const Uint32 SamplerInd : _SamplerIndBits; // Index of the assigned sampler in m_Desc.Resources and m_pPipelineResourceAttribsVk - const Uint32 ArraySize : _ArraySizeBits; // Array size + const Uint32 ArraySize : _ArraySizeBits; // Array size (for inline constants, this is the number of 32-bit constants) const Uint32 DescrType : _DescrTypeBits; // Descriptor type (DescriptorType) const Uint32 DescrSet : _DescrSetBits; // Descriptor set (0 or 1) const Uint32 ImtblSamplerAssigned : _SamplerAssignedBits; // Immutable sampler flag + const Uint32 Reserved : _ReservedBits; // Reserved for future use const Uint32 SRBCacheOffset; // Offset in the SRB resource cache const Uint32 StaticCacheOffset; // Offset in the static resource cache @@ -112,6 +116,7 @@ struct PipelineResourceAttribsVk DescrType {static_cast(_DescrType)}, DescrSet {_DescrSet }, ImtblSamplerAssigned {_ImtblSamplerAssigned ? 1u : 0u}, + Reserved {0 }, SRBCacheOffset {_SRBCacheOffset }, StaticCacheOffset {_StaticCacheOffset } // clang-format on diff --git a/Graphics/GraphicsEngineVulkan/include/PipelineResourceSignatureVkImpl.hpp b/Graphics/GraphicsEngineVulkan/include/PipelineResourceSignatureVkImpl.hpp index d138b8e495..63c8eba987 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,20 @@ 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(); } + #ifdef DILIGENT_DEVELOPMENT /// Verifies committed resource using the SPIRV resource attributes from the PSO. bool DvpValidateCommittedResource(const DeviceContextVkImpl* pDeviceCtx, @@ -193,6 +229,15 @@ 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; + + // Note: Static inline constant data is stored in m_pStaticResCache. + // Push constant selection is deferred to PSO creation time - the PRS treats + // all inline constants uniformly with emulated buffers. }; template <> Uint32 PipelineResourceSignatureVkImpl::GetDescriptorSetIndex() const; diff --git a/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.hpp b/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.hpp index 8923ac1fe9..8691afc051 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,12 @@ class PipelineStateVkImpl final : public PipelineStateBase void InitializePipeline(const ComputePipelineStateCreateInfo& CreateInfo); void InitializePipeline(const RayTracingPipelineStateCreateInfo& CreateInfo); + bool InitPushConstantInfoFromSignatures(PushConstantInfoVk& PushConstant, + TShaderStages& ShaderStages) const noexcept(false); + + void PatchShaderConvertUniformBufferToPushConstant(const PushConstantInfoVk& PushConstant, + 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..941108805d 100644 --- a/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp +++ b/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp @@ -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); } @@ -93,7 +94,7 @@ class ShaderResourceCacheVk : public ShaderResourceCacheBase void InitializeSets(IMemoryAllocator& MemAllocator, Uint32 NumSets, const Uint32* SetSizes); 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,46 @@ 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, + void* pInlineConstantData); + + // Sets the inline constant memory block (takes ownership, will be freed in destructor) + void SetInlineConstantMemory(IMemoryAllocator& Allocator, void* pMemory) + { + m_pInlineConstantMemory = decltype(m_pInlineConstantMemory){ + pMemory, + STDDeleter(Allocator)}; + } + + // 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 @@ -282,16 +323,29 @@ class ShaderResourceCacheVk : public ShaderResourceCacheBase std::unique_ptr> m_pMemory; + // Memory for inline constant data (allocated separately, freed in destructor) + // All inline constants use this memory for CPU-side staging. + // Push constant selection is done at PSO level, not cache level. + std::unique_ptr> m_pInlineConstantMemory; + + // Note: Inline constant buffers (for emulated inline constants) are stored in + // InlineConstantBufferAttribsVk::pBuffer in the PipelineResourceSignature, similar to D3D11. + // All SRBs share the same buffer to reduce memory usage. + // Push constant selection is deferred to PSO creation time. + Uint16 m_NumSets = 0; // 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 const std::shared_ptr& GetShaderResources() const { - DEV_CHECK_ERR(!IsCompiling(), "Shader resources are not available until the shader is compiled. Use GetStatus() to check the shader status."); + static const std::shared_ptr NullShaderResource; + // NOTE: while shader is compiled asynchronously, is not available + if (IsCompiling()) + return NullShaderResource; + return m_pShaderResources; } @@ -110,6 +114,8 @@ class ShaderVkImpl final : public ShaderBase Size = m_SPIRV.size() * sizeof(m_SPIRV[0]); } + std::shared_ptr CreateSPIRVShaderResources(const std::vector& SPIRV) noexcept(false); + private: void Initialize(const ShaderCreateInfo& ShaderCI, const CreateInfo& VkShaderCI) noexcept(false); @@ -119,6 +125,8 @@ class ShaderVkImpl final : public ShaderBase std::string m_EntryPoint; std::vector m_SPIRV; + + bool m_LoadConstantBufferReflection = false; }; } // namespace Diligent diff --git a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/CommandBuffer.hpp b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/CommandBuffer.hpp index 6732457fa9..ed738a3036 100644 --- a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/CommandBuffer.hpp +++ b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/CommandBuffer.hpp @@ -504,6 +504,18 @@ class CommandBuffer vkCmdBindDescriptorSets(m_VkCmdBuffer, pipelineBindPoint, layout, firstSet, descriptorSetCount, pDescriptorSets, dynamicOffsetCount, pDynamicOffsets); } + __forceinline void PushConstants(VkPipelineLayout layout, + VkShaderStageFlags stageFlags, + uint32_t offset, + uint32_t size, + const void* pValues) + { + VERIFY_EXPR(m_VkCmdBuffer != VK_NULL_HANDLE); + VERIFY_EXPR(pValues != nullptr); + VERIFY_EXPR(size > 0); + vkCmdPushConstants(m_VkCmdBuffer, layout, stageFlags, offset, size, pValues); + } + __forceinline void CopyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, uint32_t regionCount, diff --git a/Graphics/GraphicsEngineVulkan/interface/DeviceContextVk.h b/Graphics/GraphicsEngineVulkan/interface/DeviceContextVk.h index 06c39dd92c..99c5eccdb0 100644 --- a/Graphics/GraphicsEngineVulkan/interface/DeviceContextVk.h +++ b/Graphics/GraphicsEngineVulkan/interface/DeviceContextVk.h @@ -84,6 +84,25 @@ DILIGENT_BEGIN_INTERFACE(IDeviceContextVk, IDeviceContext) /// calling IDeviceContext::InvalidateState() and then manually restore all required states via /// appropriate Diligent API calls. VIRTUAL VkCommandBuffer METHOD(GetVkCommandBuffer)(THIS) PURE; + + /// Sets push constant data for the currently bound pipeline. + + /// \param [in] pData - Pointer to the push constant data. + /// \param [in] Offset - Offset in bytes from the start of the push constant block. + /// \param [in] Size - Size of the data to set in bytes. + /// + /// \remarks This method sets push constant data that will be used in subsequent draw or dispatch calls. + /// The pipeline state must be set before calling this method. + /// Push constants are a Vulkan feature that allows small amounts of data to be passed to + /// shaders efficiently without using descriptor sets. In shaders, push constants are declared + /// using the layout(push_constant) qualifier in GLSL or [[vk::push_constant]] attribute in HLSL. + /// + /// \note The total size of push constants is limited by the device (typically 128-256 bytes). + /// Check VkPhysicalDeviceLimits::maxPushConstantsSize for the exact limit. + VIRTUAL void METHOD(SetPushConstants)(THIS_ + const void* pData, + Uint32 Offset, + Uint32 Size) PURE; }; DILIGENT_END_INTERFACE @@ -95,6 +114,7 @@ DILIGENT_END_INTERFACE # define IDeviceContextVk_TransitionImageLayout(This, ...) CALL_IFACE_METHOD(DeviceContextVk, TransitionImageLayout, This, __VA_ARGS__) # define IDeviceContextVk_BufferMemoryBarrier(This, ...) CALL_IFACE_METHOD(DeviceContextVk, BufferMemoryBarrier, This, __VA_ARGS__) +# define IDeviceContextVk_SetPushConstants(This, ...) CALL_IFACE_METHOD(DeviceContextVk, SetPushConstants, This, __VA_ARGS__) // clang-format on diff --git a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp index df035cdb4b..df317b9328 100644 --- a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp @@ -390,6 +390,16 @@ void DeviceContextVkImpl::SetPipelineState(IPipelineState* pPipelineState) // Reserve space to store all dynamic buffer offsets m_DynamicBufferOffsets.resize(TotalDynamicOffsetCount); + + // Mark push constants as dirty when PSO changes, since the new PSO might have + // a different pipeline layout. If the new PSO has push constants, they need to + // be re-committed even if the data hasn't changed. + if (Layout.HasPushConstants()) + { + m_State.PushConstantsDirty = true; + // Ensure we have initialized push constants data for the new PSO + // (user should call SetPushConstants before draw/dispatch) + } } DeviceContextVkImpl::ResourceBindInfo& DeviceContextVkImpl::GetBindInfo(PIPELINE_TYPE Type) @@ -415,6 +425,90 @@ 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::SetPushConstants(const void* pData, Uint32 Offset, Uint32 Size) +{ + DEV_CHECK_ERR(pData != nullptr || Size == 0, "pData must not be null when Size is non-zero"); + DEV_CHECK_ERR(Offset + Size <= DILIGENT_MAX_PUSH_CONSTANTS_SIZE, + "Push constant data range [", Offset, ", ", Offset + Size, ") exceeds the maximum supported size (", DILIGENT_MAX_PUSH_CONSTANTS_SIZE, ")"); + + // Note: This function may be called from UpdateInlineConstantBuffers during draw preparation, + // at which point the pipeline state is guaranteed to be bound. When called from user code, + // we validate that the pipeline has push constants. + if (m_pPipelineState != nullptr) + { + const PipelineLayoutVk& Layout = m_pPipelineState->GetPipelineLayout(); + if (Layout.HasPushConstants()) + { + DEV_CHECK_ERR(Offset + Size <= Layout.GetPushConstantSize(), + "Push constant data range [", Offset, ", ", Offset + Size, ") exceeds the push constant block size (", Layout.GetPushConstantSize(), ")"); + } + } + else + { + DEV_ERROR("A valid PipelineState must be bound before calling SetPushConstants!"); + } + + memcpy(m_PushConstantsData.data() + Offset, pData, Size); + m_PushConstantsDataSize = std::max(m_PushConstantsDataSize, Offset + Size); + m_State.PushConstantsDirty = true; +} + +void DeviceContextVkImpl::CommitPushConstants() +{ + if (!m_State.PushConstantsDirty) + return; + + VERIFY_EXPR(m_pPipelineState != nullptr); + const PipelineLayoutVk& Layout = m_pPipelineState->GetPipelineLayout(); + + if (!Layout.HasPushConstants()) + return; + + const Uint32 Size = Layout.GetPushConstantSize(); + const VkShaderStageFlags StageFlags = Layout.GetPushConstantStageFlags(); + const VkPipelineLayout vkLayout = Layout.GetVkPipelineLayout(); + + VERIFY_EXPR(Size <= m_PushConstantsData.size()); + + m_CommandBuffer.PushConstants(vkLayout, StageFlags, 0, Size, m_PushConstantsData.data()); + m_State.PushConstantsDirty = false; +} + void DeviceContextVkImpl::CommitDescriptorSets(ResourceBindInfo& BindInfo, Uint32 CommitSRBMask) { VERIFY(CommitSRBMask != 0, "This method should not be called when there is nothing to commit"); @@ -487,6 +581,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 +632,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 +674,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 +869,26 @@ void DeviceContextVkImpl::PrepareForDraw(DRAW_FLAGS Flags) #endif ResourceBindInfo& BindInfo = GetBindInfo(PIPELINE_TYPE_GRAPHICS); + + // Update inline constant buffers 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 if dirty + CommitPushConstants(); + #ifdef DILIGENT_DEVELOPMENT // Must be called after CommitDescriptorSets as it needs SetInfo.BaseInd DvpValidateCommittedShaderResources(BindInfo); @@ -1059,11 +1183,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 if dirty + CommitPushConstants(); + #ifdef DILIGENT_DEVELOPMENT // Must be called after CommitDescriptorSets as it needs SetInfo.BaseInd DvpValidateCommittedShaderResources(BindInfo); @@ -1075,11 +1206,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 if dirty + CommitPushConstants(); + #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..4d057357d9 100644 --- a/Graphics/GraphicsEngineVulkan/src/PipelineLayoutVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/PipelineLayoutVk.cpp @@ -59,7 +59,10 @@ 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, + const PushConstantInfoVk& PushConstant) noexcept(false) { VERIFY(m_DescrSetCount == 0 && !m_VkPipelineLayout, "This pipeline layout is already initialized"); @@ -114,19 +117,50 @@ void PipelineLayoutVk::Create(RenderDeviceVkImpl* pDeviceVk, RefCntAutoPtr 0) + { + if (PushConstant.Size > Limits.maxPushConstantsSize) + { + LOG_ERROR_AND_THROW("Push constant size (", PushConstant.Size, + " 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 (PushConstant.Size > 0 && PushConstant.StageFlags != 0) + { + PushConstantRange.stageFlags = PushConstant.StageFlags; + PushConstantRange.offset = 0; + PushConstantRange.size = PushConstant.Size; + + PipelineLayoutCI.pushConstantRangeCount = 1; + PipelineLayoutCI.pPushConstantRanges = &PushConstantRange; + + m_PushConstantSize = PushConstant.Size; + m_PushConstantStageFlags = PushConstant.StageFlags; + m_PushConstantSignatureIndex = PushConstant.SignatureIndex; + m_PushConstantResourceIndex = PushConstant.ResourceIndex; + } + 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..f5f0fbd843 100644 --- a/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp @@ -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 { @@ -177,9 +179,16 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) { const PipelineResourceDesc& ResDesc = m_Desc.Resources[i]; if (ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) - StaticResourceCount += ResDesc.ArraySize; + { + // For inline constants, GetArraySize() returns 1 (actual array size), + // while ArraySize contains the number of 32-bit constants + StaticResourceCount += ResDesc.GetArraySize(); + } + } + if (StaticResourceCount > 0) + { + m_pStaticResCache->InitializeSets(GetRawAllocator(), 1, &StaticResourceCount); } - m_pStaticResCache->InitializeSets(GetRawAllocator(), 1, &StaticResourceCount); } CacheOffsetsType CacheGroupSizes = {}; // Required cache size for each cache group @@ -189,9 +198,27 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) 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(); + + // 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; + } + } + + // 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 +272,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}; @@ -263,6 +293,7 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) // If all resources are dynamic, then the signature contains only one descriptor set layout with index 0, // so remap SetId to the actual descriptor set index. + // All resources (including inline constants) use descriptor sets - push constant selection is deferred to PSO creation. VERIFY_EXPR(DSMapping[SetId] < MAX_DESCRIPTOR_SETS); // The sampler may not be yet initialized, but this is OK as all resources are initialized @@ -292,6 +323,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 +332,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,25 +354,66 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) "Static cache offset is invalid."); } - 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.binding = pAttribs->BindingIndex; + // For inline constants, descriptor count is 1 (single uniform buffer) + vkSetLayoutBinding.descriptorCount = ResDesc.GetArraySize(); vkSetLayoutBinding.stageFlags = ShaderTypesToVkShaderStageFlags(ResDesc.ShaderStages); vkSetLayoutBinding.pImmutableSamplers = pVkImmutableSamplers; vkSetLayoutBinding.descriptorType = DescriptorTypeToVkDescriptorType(pAttribs->GetDescriptorType()); vkSetLayoutBindings[SetId].push_back(vkSetLayoutBinding); + BindingIndices[CacheGroup] += 1; 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, + // For inline constants, GetArraySize() returns 1 (actual array size) + m_pStaticResCache->InitializeResources(pAttribs->DescrSet, StaticCacheOffset, ResDesc.GetArraySize(), pAttribs->GetDescriptorType(), pAttribs->IsImmutableSamplerAssigned()); - StaticCacheOffset += ResDesc.ArraySize; + StaticCacheOffset += ResDesc.GetArraySize(); + } + + // 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) @@ -354,7 +427,7 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) VERIFY_EXPR(m_DynamicUniformBufferCount == CacheGroupSizes[CACHE_GROUP_DYN_UB_STAT_VAR] + CacheGroupSizes[CACHE_GROUP_DYN_UB_DYN_VAR]); VERIFY_EXPR(m_DynamicStorageBufferCount == CacheGroupSizes[CACHE_GROUP_DYN_SB_STAT_VAR] + CacheGroupSizes[CACHE_GROUP_DYN_SB_DYN_VAR]); - VERIFY_EXPR(m_pStaticResCache == nullptr || const_cast(m_pStaticResCache)->GetDescriptorSet(0).GetSize() == StaticCacheOffset); + VERIFY_EXPR(m_pStaticResCache == nullptr || GetNumDescriptorSets() == 0 || const_cast(m_pStaticResCache)->GetDescriptorSet(0).GetSize() == StaticCacheOffset); VERIFY_EXPR(CacheGroupOffsets[CACHE_GROUP_DYN_UB_STAT_VAR] == CacheGroupSizes[CACHE_GROUP_DYN_UB_STAT_VAR]); VERIFY_EXPR(CacheGroupOffsets[CACHE_GROUP_DYN_SB_STAT_VAR] == CacheGroupSizes[CACHE_GROUP_DYN_UB_STAT_VAR] + CacheGroupSizes[CACHE_GROUP_DYN_SB_STAT_VAR]); VERIFY_EXPR(CacheGroupOffsets[CACHE_GROUP_OTHER_STAT_VAR] == CacheGroupSizes[CACHE_GROUP_DYN_UB_STAT_VAR] + CacheGroupSizes[CACHE_GROUP_DYN_SB_STAT_VAR] + CacheGroupSizes[CACHE_GROUP_OTHER_STAT_VAR]); @@ -474,6 +547,54 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) } VERIFY_EXPR(NumSets == GetNumDescriptorSets()); } + + // Initialize inline constant buffers in the static resource cache + // This must be done after the resources are initialized above + if (m_NumInlineConstantBufferAttribs > 0 && m_pStaticResCache != nullptr) + { + // Calculate total memory size needed for all static inline constants + Uint32 TotalStaticInlineConstantSize = 0; + for (Uint32 i = 0; i < m_NumInlineConstantBufferAttribs; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBufferAttribs[i]; + + const PipelineResourceDesc& ResDesc = GetResourceDesc(InlineCBAttr.ResIndex); + if (ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + { + TotalStaticInlineConstantSize += InlineCBAttr.NumConstants * sizeof(Uint32); + } + } + + // Allocate memory for all static inline constants in the static resource cache + // All inline constants use the same path - push constant selection is deferred to PSO creation + if (TotalStaticInlineConstantSize > 0) + { + IMemoryAllocator& Allocator = GetRawAllocator(); + void* pInlineConstMemory = Allocator.Allocate(TotalStaticInlineConstantSize, "Static inline constant data", __FILE__, __LINE__); + memset(pInlineConstMemory, 0, TotalStaticInlineConstantSize); + + // Pass ownership of the memory to the static resource cache for proper cleanup + m_pStaticResCache->SetInlineConstantMemory(Allocator, pInlineConstMemory); + + // Assign memory to each static inline constant + Uint8* pCurrentDataPtr = static_cast(pInlineConstMemory); + 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, pCurrentDataPtr); + pCurrentDataPtr += DataSize; + } + } + } + } } PipelineResourceSignatureVkImpl::~PipelineResourceSignatureVkImpl() @@ -489,6 +610,21 @@ 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; + + // Note: Static inline constant data is now managed by m_pStaticResCache + // and will be freed when the cache is destroyed + TPipelineResourceSignatureBase::Destruct(); } @@ -509,10 +645,60 @@ 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 - allocate staging memory and set up data pointers + // 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(); + + // Calculate total memory size for CPU staging data + Uint32 TotalInlineConstantSize = 0; + for (Uint32 i = 0; i < m_NumInlineConstantBufferAttribs; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBufferAttribs[i]; + TotalInlineConstantSize += InlineCBAttr.NumConstants * sizeof(Uint32); + } + + // Allocate memory for all inline constants (CPU staging data) + // Each SRB has its own copy of the staging data, but shares the GPU buffer + void* pInlineConstantMemory = nullptr; + if (TotalInlineConstantSize > 0) + { + pInlineConstantMemory = CacheMemAllocator.Allocate(TotalInlineConstantSize, "Inline constant data", __FILE__, __LINE__); + memset(pInlineConstantMemory, 0, TotalInlineConstantSize); + + // Pass ownership of the memory to the resource cache for proper cleanup + ResourceCache.SetInlineConstantMemory(CacheMemAllocator, pInlineConstantMemory); + } + + // Assign staging memory to each inline constant buffer + Uint8* pCurrentDataPtr = static_cast(pInlineConstantMemory); + 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, pCurrentDataPtr); + pCurrentDataPtr += DataSize; + } + } + #ifdef DILIGENT_DEBUG ResourceCache.DbgVerifyResourceInitialization(); #endif @@ -528,6 +714,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 @@ -535,6 +775,11 @@ void PipelineResourceSignatureVkImpl::CopyStaticResources(ShaderResourceCacheVk& if (!HasDescriptorSet(DESCRIPTOR_SET_ID_STATIC_MUTABLE) || m_pStaticResCache == nullptr) return; + if (m_pStaticResCache->GetNumDescriptorSets() == 0) + return; + + const auto ResIdxRange = GetResourceIndexRange(SHADER_RESOURCE_VARIABLE_TYPE_STATIC); + // SrcResourceCache contains only static resources. // In case of SRB, DstResourceCache contains static, mutable and dynamic resources. // In case of Signature, DstResourceCache contains only static resources. @@ -542,7 +787,6 @@ void PipelineResourceSignatureVkImpl::CopyStaticResources(ShaderResourceCacheVk& const Uint32 StaticSetIdx = GetDescriptorSetIndex(); const ShaderResourceCacheVk::DescriptorSet& SrcDescrSet = SrcResourceCache.GetDescriptorSet(StaticSetIdx); const ShaderResourceCacheVk::DescriptorSet& DstDescrSet = const_cast(DstResourceCache).GetDescriptorSet(StaticSetIdx); - const auto ResIdxRange = GetResourceIndexRange(SHADER_RESOURCE_VARIABLE_TYPE_STATIC); const ResourceCacheContentType SrcCacheType = SrcResourceCache.GetContentType(); const ResourceCacheContentType DstCacheType = DstResourceCache.GetContentType(); @@ -555,7 +799,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 + const Uint32 ActualArraySize = ResDesc.GetArraySize(); + for (Uint32 ArrInd = 0; ArrInd < ActualArraySize; ++ArrInd) { const Uint32 SrcCacheOffset = Attr.CacheOffset(SrcCacheType) + ArrInd; const ShaderResourceCacheVk::Resource& SrcCachedRes = SrcDescrSet.GetResource(SrcCacheOffset); @@ -652,15 +915,16 @@ void PipelineResourceSignatureVkImpl::CommitDynamicResources(const ShaderResourc for (Uint32 ResIdx = DynResIdxRange.first, ArrElem = 0; ResIdx < DynResIdxRange.second;) { const PipelineResourceAttribsType& Attr = GetResourceAttribs(ResIdx); + const PipelineResourceDesc& ResDesc = GetResourceDesc(ResIdx); const Uint32 CacheOffset = Attr.CacheOffset(CacheType); - const Uint32 ArraySize = Attr.ArraySize; - const DescriptorType DescrType = Attr.GetDescriptorType(); + // For inline constants, GetArraySize() returns 1 (actual array size for cache), + // while ArraySize contains the number of 32-bit constants + const Uint32 ArraySize = ResDesc.GetArraySize(); + const DescriptorType DescrType = Attr.GetDescriptorType(); #ifdef DILIGENT_DEBUG { - const PipelineResourceDesc& Res = GetResourceDesc(ResIdx); - VERIFY_EXPR(ArraySize == GetResourceDesc(ResIdx).ArraySize); - VERIFY_EXPR(Res.VarType == SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC); + VERIFY_EXPR(ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC); } #endif @@ -866,7 +1130,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 +1254,46 @@ 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); + + // Check if this inline constant is selected as push constant by the PSO + if (InlineCBAttr.ResIndex == PushConstantResIndex) + { + // This inline constant is selected as push constant at PSO level + // Copy data to the device context's push constants buffer + // This will be submitted via vkCmdPushConstants before draw/dispatch + Ctx.SetPushConstants(pInlineConstantData, 0, DataSize); + 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); + } + } +} + } // namespace Diligent diff --git a/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp index 26e9b64225..298f57c789 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->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,21 @@ 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(); + //const auto& pShaderResources = pShader->GetShaderResources(); VERIFY_EXPR(pShaderResources); if (pDvpShaderResources) @@ -690,6 +708,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 +724,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) @@ -784,6 +822,97 @@ void PipelineStateVkImpl::RemapOrVerifyShaderResources( } } +bool PipelineStateVkImpl::InitPushConstantInfoFromSignatures(PushConstantInfoVk& PushConstant, + TShaderStages& ShaderStages) const noexcept(false) +{ + // Step 1: Check if any shader already has a push constant declared in SPIR-V + // This happens when the shader source uses layout(push_constant) explicitly + for (const ShaderStageInfo& Stage : ShaderStages) + { + for (const ShaderVkImpl* pShader : Stage.Shaders) + { + const auto& pShaderResources = pShader->GetShaderResources(); + if (!pShaderResources) + continue; + + // Check for push constants in shader resources + for (Uint32 pc = 0; pc < pShaderResources->GetNumPushConstants(); ++pc) + { + const SPIRVShaderResourceAttribs& PCAttribs = pShaderResources->GetPushConstant(pc); + + // Find matching resource in signatures + for (Uint32 s = 0; s < m_SignatureCount; ++s) + { + const PipelineResourceSignatureVkImpl* pSignature = m_Signatures[s]; + if (pSignature == nullptr) + continue; + + for (Uint32 r = 0; r < pSignature->GetTotalResourceCount(); ++r) + { + const PipelineResourceDesc& ResDesc = pSignature->GetResourceDesc(r); + if (strcmp(ResDesc.Name, PCAttribs.Name) == 0) + { + // Found matching resource - this is the push constant + const Uint32 PCSize = PCAttribs.BufferStaticSize; + + PushConstant.Size = PCSize; + PushConstant.SignatureIndex = s; + PushConstant.ResourceIndex = r; + + // Add shader stages + for (SHADER_TYPE ShaderTypes = ResDesc.ShaderStages; ShaderTypes != SHADER_TYPE_UNKNOWN;) + { + const SHADER_TYPE ShaderType = ExtractLSB(ShaderTypes); + PushConstant.StageFlags |= ShaderTypeToVkShaderStageFlagBit(ShaderType); + } + + // Found push constant from SPIR-V + return true; + } + } + } + } + } + } + + // Step 2: No explicit push constant in SPIR-V, select first inline constant from signatures + for (Uint32 s = 0; s < m_SignatureCount; ++s) + { + const PipelineResourceSignatureVkImpl* pSignature = m_Signatures[s]; + if (pSignature == nullptr) + continue; + + for (Uint32 r = 0; r < pSignature->GetTotalResourceCount(); ++r) + { + const PipelineResourceDesc& ResDesc = pSignature->GetResourceDesc(r); + + // Check if this resource is marked as inline constant + if ((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0) + { + // For inline constants, ArraySize contains the number of 32-bit constants + const Uint32 PCSize = ResDesc.ArraySize * sizeof(Uint32); + + PushConstant.Size = PCSize; + PushConstant.SignatureIndex = s; + PushConstant.ResourceIndex = r; + + // Add shader stages + for (SHADER_TYPE ShaderTypes = ResDesc.ShaderStages; ShaderTypes != SHADER_TYPE_UNKNOWN;) + { + const SHADER_TYPE ShaderType = ExtractLSB(ShaderTypes); + PushConstant.StageFlags |= ShaderTypeToVkShaderStageFlagBit(ShaderType); + } + + // Found first inline constant + return true; + } + } + } + + // No inline constants found - PushConstant remains empty + return false; +} + void PipelineStateVkImpl::InitPipelineLayout(const PipelineStateCreateInfo& CreateInfo, TShaderStages& ShaderStages) noexcept(false) { const PSO_CREATE_INTERNAL_FLAGS InternalFlags = GetInternalCreateFlags(CreateInfo); @@ -798,7 +927,16 @@ void PipelineStateVkImpl::InitPipelineLayout(const PipelineStateCreateInfo& Crea DvpValidateResourceLimits(); #endif - m_PipelineLayout.Create(GetDevice(), m_Signatures, m_SignatureCount); + // Vulkan allows only one push constant block per pipeline, but it can be accessed from multiple shader stages. + // First check if SPIR-V already has a push constant, otherwise select the first inline constant and promote it to push constant. + PushConstantInfoVk PushConstant; + InitPushConstantInfoFromSignatures(PushConstant, ShaderStages); + + m_PipelineLayout.Create(GetDevice(), m_Signatures, m_SignatureCount, PushConstant); + + // 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(PushConstant, 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); @@ -826,6 +964,95 @@ void PipelineStateVkImpl::InitPipelineLayout(const PipelineStateCreateInfo& Crea } } +void PipelineStateVkImpl::PatchShaderConvertUniformBufferToPushConstant(const PushConstantInfoVk& PushConstantInfo, + TShaderStages& ShaderStages) const noexcept(false) +{ + // If no push constant was selected, no patching needed + if (PushConstantInfo.SignatureIndex == INVALID_PUSH_CONSTANT_INDEX || + PushConstantInfo.ResourceIndex == INVALID_PUSH_CONSTANT_INDEX) + return; + + // Get the name of the selected push constant resource + const PipelineResourceSignatureVkImpl* pSignature = m_Signatures[PushConstantInfo.SignatureIndex]; + if (pSignature == nullptr) + return; + + const PipelineResourceDesc& ResDesc = pSignature->GetResourceDesc(PushConstantInfo.ResourceIndex); + 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 = PatchSPIRVConvertUniformBufferToPushConstant( + SPIRV, + PushConstantName); + + if (!PatchedSPIRV.empty()) + { + Stage.SPIRVs[i] = PatchedSPIRV; + Stage.ShaderResources[i] = pShader->CreateSPIRVShaderResources(PatchedSPIRV); + } + 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 +1173,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; } // @@ -982,11 +1217,14 @@ void PipelineStateVkImpl::DvpValidateResourceLimits() const for (Uint32 r = 0; r < pSignature->GetTotalResourceCount(); ++r) { - const PipelineResourceDesc& ResDesc = pSignature->GetResourceDesc(r); - const PipelineResourceSignatureVkImpl::ResourceAttribs& ResAttr = pSignature->GetResourceAttribs(r); - const Uint32 DescIndex = static_cast(ResAttr.DescrType); + const PipelineResourceDesc& ResDesc = pSignature->GetResourceDesc(r); + const PipelineResourceSignatureVkImpl::ResourceAttribs& ResAttr = pSignature->GetResourceAttribs(r); + + // All resources (including inline constants) use descriptor sets at PRS level. + // Push constant selection is deferred to PSO creation time. + 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..94a4bd529c 100644 --- a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp @@ -950,4 +950,56 @@ 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, + void* pInlineConstantData) +{ + VERIFY(pInlineConstantData != nullptr, "Inline constant data pointer is null"); + + DescriptorSet& DescrSet = GetDescriptorSet(DescrSetIndex); + Resource& DstRes = DescrSet.GetResource(CacheOffset); + + DstRes.pInlineConstantData = pInlineConstantData; + + // 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..475990616a 100644 --- a/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp @@ -229,37 +229,21 @@ void ShaderVkImpl::Initialize(const ShaderCreateInfo& ShaderCI, // We cannot create shader module here because resource bindings are assigned when // pipeline state is created - + m_LoadConstantBufferReflection = ShaderCI.LoadConstantBufferReflection; // Load shader resources if (!m_SPIRV.empty()) { 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 // - }; - 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)); + m_pShaderResources = CreateSPIRVShaderResources(m_SPIRV); - if (LoadShaderInputs && m_pShaderResources->IsHLSLSource()) + if (m_Desc.ShaderType == SHADER_TYPE_VERTEX && m_pShaderResources->IsHLSLSource()) { m_pShaderResources->MapHLSLVertexShaderInputs(m_SPIRV); } + + VERIFY_EXPR(ShaderCI.ByteCode != nullptr || m_EntryPoint == ShaderCI.EntryPoint || + (m_EntryPoint == "main" && (ShaderCI.CompileFlags & SHADER_COMPILE_FLAG_HLSL_TO_SPIRV_VIA_GLSL) != 0)); } else { @@ -270,6 +254,31 @@ void ShaderVkImpl::Initialize(const ShaderCreateInfo& ShaderCI, } } +std::shared_ptr ShaderVkImpl::CreateSPIRVShaderResources(const std::vector& SPIRV) noexcept(false) +{ + IMemoryAllocator& Allocator = GetRawAllocator(); + + std::unique_ptr> pRawMem{ + ALLOCATE(Allocator, "Memory for SPIRVShaderResources", SPIRVShaderResources, 1), + STDDeleterRawMem(Allocator), + }; + new (pRawMem.get()) SPIRVShaderResources // May throw + { + Allocator, + SPIRV, + m_Desc, + m_Desc.UseCombinedTextureSamplers ? m_Desc.CombinedSamplerSuffix : nullptr, + m_Desc.ShaderType == SHADER_TYPE_VERTEX ? true : false, + m_LoadConstantBufferReflection, + m_EntryPoint // + }; + + std::shared_ptr pShaderResources; + + pShaderResources.reset(static_cast(pRawMem.release()), STDDeleterRawMem(Allocator)); + + return pShaderResources; +} ShaderVkImpl::ShaderVkImpl(IReferenceCounters* pRefCounters, RenderDeviceVkImpl* pRenderDeviceVk, 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 #include @@ -79,6 +79,7 @@ struct SPIRVShaderResourceAttribs SeparateSampler, InputAttachment, AccelerationStructure, + PushConstant, // Vulkan push constant buffer - special inline constant that uses vkCmdPushConstants NumResourceTypes }; @@ -111,6 +112,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 +131,39 @@ struct SPIRVShaderResourceAttribs { return IsMS != 0; } + + Uint32 GetConstantBufferSize() const + { + VERIFY((Type == PushConstant || Type == UniformBuffer), "Invalid resource type: PushConstant or UniformBuffer expected"); + return BufferStaticSize; + } + + Uint32 GetInlineConstantCountOrThrow(const char* ShaderName) const + { + VERIFY_EXPR(Type == PushConstant || Type == UniformBuffer); + + if (ArraySize != 1) + { + LOG_ERROR_AND_THROW("Inline constants resource '", Name, "' in shader '", ShaderName, "' can not be an array."); + } + const Uint32 NumConstants = GetConstantBufferSize() / sizeof(Uint32); + + constexpr Uint32 kMaxInlineConstants = 64; + if (NumConstants > kMaxInlineConstants) + { + LOG_ERROR_AND_THROW("Inline constants resource '", Name, "' in shader '", ShaderName, "' has ", + NumConstants, " constants. The maximum supported number of inline constants is ", + kMaxInlineConstants, '.'); + } + + if (NumConstants == 0) + { + LOG_ERROR_AND_THROW("Resource '", Name, "' in shader '", ShaderName, + "' is marked as inline constants, but has zero buffer size. "); + } + + return NumConstants; + } }; static_assert(sizeof(SPIRVShaderResourceAttribs) % sizeof(void*) == 0, "Size of SPIRVShaderResourceAttribs struct must be multiple of sizeof(void*)"); @@ -166,28 +205,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 +258,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 +285,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 +352,13 @@ class SPIRVShaderResources HandleAccelStruct(AccelStruct, n); } - static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 12, "Please handle the new resource type here, if needed"); + for (Uint32 n = 0; n < GetNumPushConstants(); ++n) + { + const SPIRVShaderResourceAttribs& PushConstant = GetPushConstant(n); + HandlePushConstant(PushConstant, n); + } + + static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please handle the new resource type here, if needed"); } template @@ -359,16 +409,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 +445,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..323e408119 100644 --- a/Graphics/ShaderTools/include/SPIRVTools.hpp +++ b/Graphics/ShaderTools/include/SPIRVTools.hpp @@ -27,6 +27,7 @@ #pragma once #include +#include #include "FlagEnum.h" @@ -49,4 +50,14 @@ std::vector OptimizeSPIRV(const std::vector& SrcSPIRV, spv_target_env TargetEnv, 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 PatchSPIRVConvertUniformBufferToPushConstant(const std::vector& SPIRV, + const std::string& BlockName); + } // namespace Diligent diff --git a/Graphics/ShaderTools/src/SPIRVShaderResources.cpp b/Graphics/ShaderTools/src/SPIRVShaderResources.cpp index dc65e596bb..92c15dd470 100644 --- a/Graphics/ShaderTools/src/SPIRVShaderResources.cpp +++ b/Graphics/ShaderTools/src/SPIRVShaderResources.cpp @@ -126,10 +126,29 @@ 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 represents the number of 32-bit constants (BufferStaticSize / 4) + // This is consistent with how inline constants work in the pipeline resource signature + ArraySize {static_cast(_BufferStaticSize / sizeof(Uint32))}, + 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 +190,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 +202,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,6 +212,11 @@ PIPELINE_RESOURCE_FLAGS SPIRVShaderResourceAttribs::GetPipelineResourceFlags(Res case SPIRVShaderResourceAttribs::ResourceType::SampledImage: return PIPELINE_RESOURCE_FLAG_COMBINED_SAMPLER; + case SPIRVShaderResourceAttribs::ResourceType::PushConstant: + // Push constants are special inline constants that use vkCmdPushConstants + // Mark them with both INLINE_CONSTANTS and VULKAN_PUSH_CONSTANT flags + return PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS; + default: return PIPELINE_RESOURCE_FLAG_NONE; } @@ -262,6 +290,33 @@ const std::string& GetUBOrSBName(diligent_spirv_cross::Compiler& C return ((IRSource.hlsl || IRSource.lang == spv::SourceLanguageSlang) && !instance_name.empty()) ? instance_name : UB.name; } +// Get name for push constant buffer +// Push constants may have their variable name empty (e.g., when declared as [[vk::push_constant]] cbuffer in HLSL) +// In that case, we try to get the name from the base type (the struct type) +const std::string& GetPushConstantName(diligent_spirv_cross::Compiler& Compiler, + const diligent_spirv_cross::Resource& PC, + const diligent_spirv_cross::ParsedIR::Source& IRSource) +{ + // First, try the standard method used for UBs/SBs + const std::string& Name = GetUBOrSBName(Compiler, PC, IRSource); + if (!Name.empty()) + return Name; + + // If the name is empty, try to get the name from the base type + // This handles cases like: + // [[vk::push_constant]] + // cbuffer Constants { ... }; + // where the variable has no name but the struct type does + const std::string& base_type_name = Compiler.get_name(PC.base_type_id); + if (!base_type_name.empty()) + return base_type_name; + + // As a last resort, try to get the fallback name + // Note: get_fallback_name returns by value, so we can't return a reference to it + // Return the empty name and let the caller handle it + return Name; +} + static SHADER_CODE_BASIC_TYPE SpirvBaseTypeToShaderCodeBasicType(diligent_spirv_cross::SPIRType::BaseType SpvBaseType) { switch (SpvBaseType) @@ -423,7 +478,18 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, ResourceNamesPoolSize += GetUBOrSBName(Compiler, ub, ParsedIRSource).length() + 1; for (const diligent_spirv_cross::Resource& sb : resources.storage_buffers) ResourceNamesPoolSize += GetUBOrSBName(Compiler, sb, ParsedIRSource).length() + 1; - static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 12, "Please account for the new resource type below"); + static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please account for the new resource type below"); + + // Process push constant buffers - Vulkan spec allows only one push_constant buffer per pipeline + if (resources.push_constant_buffers.size() > 1) + { + LOG_ERROR_AND_THROW("Shader '", shaderDesc.Name, "' contains ", resources.push_constant_buffers.size(), + " push constant buffers, but Vulkan spec allows only one push_constant buffer per pipeline."); + } + // For push constants, use GetPushConstantName which also tries to get the name from the base type + for (const diligent_spirv_cross::Resource& pc : resources.push_constant_buffers) + ResourceNamesPoolSize += GetPushConstantName(Compiler, pc, ParsedIRSource).length() + 1; + for (auto* pResType : { &resources.storage_images, @@ -493,16 +559,17 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, } ResourceCounters ResCounters; - ResCounters.NumUBs = static_cast(resources.uniform_buffers.size()); - ResCounters.NumSBs = static_cast(resources.storage_buffers.size()); - ResCounters.NumImgs = static_cast(resources.storage_images.size()); - ResCounters.NumSmpldImgs = static_cast(resources.sampled_images.size()); - ResCounters.NumACs = static_cast(resources.atomic_counters.size()); - ResCounters.NumSepSmplrs = static_cast(resources.separate_samplers.size()); - ResCounters.NumSepImgs = static_cast(resources.separate_images.size()); - ResCounters.NumInptAtts = static_cast(resources.subpass_inputs.size()); - ResCounters.NumAccelStructs = static_cast(resources.acceleration_structures.size()); - static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 12, "Please set the new resource type counter here"); + ResCounters.NumUBs = static_cast(resources.uniform_buffers.size()); + ResCounters.NumSBs = static_cast(resources.storage_buffers.size()); + ResCounters.NumImgs = static_cast(resources.storage_images.size()); + ResCounters.NumSmpldImgs = static_cast(resources.sampled_images.size()); + ResCounters.NumACs = static_cast(resources.atomic_counters.size()); + ResCounters.NumSepSmplrs = static_cast(resources.separate_samplers.size()); + ResCounters.NumSepImgs = static_cast(resources.separate_images.size()); + ResCounters.NumInptAtts = static_cast(resources.subpass_inputs.size()); + ResCounters.NumAccelStructs = static_cast(resources.acceleration_structures.size()); + ResCounters.NumPushConstants = static_cast(resources.push_constant_buffers.size()); + static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please set the new resource type counter here"); // Resource names pool is only needed to facilitate string allocation. StringPool ResourceNamesPool; @@ -687,7 +754,26 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, VERIFY_EXPR(CurrAccelStruct == GetNumAccelStructs()); } - static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 12, "Please initialize SPIRVShaderResourceAttribs for the new resource type here"); + { + Uint32 CurrPushConstant = 0; + for (const diligent_spirv_cross::Resource& PushConst : resources.push_constant_buffers) + { + const std::string& pcName = GetPushConstantName(Compiler, PushConst, ParsedIRSource); + const diligent_spirv_cross::SPIRType& Type = Compiler.get_type(PushConst.type_id); + const size_t Size = Compiler.get_declared_struct_size(Type); + + // Push constants use a special constructor without binding/descriptor set decorations + new (&GetPushConstant(CurrPushConstant++)) SPIRVShaderResourceAttribs // + { + ResourceNamesPool.CopyString(pcName), + SPIRVShaderResourceAttribs::ResourceType::PushConstant, + static_cast(Size) // + }; + } + VERIFY_EXPR(CurrPushConstant == GetNumPushConstants()); + } + + static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please initialize SPIRVShaderResourceAttribs for the new resource type here"); if (CombinedSamplerSuffix != nullptr) { @@ -757,8 +843,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 +858,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 +914,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 @@ -966,6 +1057,13 @@ std::string SPIRVShaderResources::DumpResources() const ss << std::endl << std::setw(3) << ResNum << " Accel Struct "; DumpResource(AccelStruct); + }, + [&](const SPIRVShaderResourceAttribs& PushConst, Uint32) // + { + VERIFY(PushConst.Type == SPIRVShaderResourceAttribs::ResourceType::PushConstant, "Unexpected resource type"); + ss << std::endl + << std::setw(3) << ResNum << " Push Constant "; + DumpResource(PushConst); } // ); VERIFY_EXPR(ResNum == GetTotalResources()); diff --git a/Graphics/ShaderTools/src/SPIRVTools.cpp b/Graphics/ShaderTools/src/SPIRVTools.cpp index 75793da993..a269b4281c 100644 --- a/Graphics/ShaderTools/src/SPIRVTools.cpp +++ b/Graphics/ShaderTools/src/SPIRVTools.cpp @@ -27,8 +27,26 @@ #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 + namespace Diligent { @@ -83,6 +101,8 @@ void SpvOptimizerMessageConsumer( LOG_DEBUG_MESSAGE(MsgSeverity, "Spirv optimizer ", LevelText, ": ", message); } +#define SPV_SPIRV_VERSION_WORD(MAJOR, MINOR) ((uint32_t(uint8_t(MAJOR)) << 16) | (uint32_t(uint8_t(MINOR)) << 8)) + spv_target_env SpvTargetEnvFromSPIRV(const std::vector& SPIRV) { if (SPIRV.size() < 2) @@ -91,7 +111,6 @@ spv_target_env SpvTargetEnvFromSPIRV(const std::vector& SPIRV) return SPV_ENV_VULKAN_1_0; } -#define SPV_SPIRV_VERSION_WORD(MAJOR, MINOR) ((uint32_t(uint8_t(MAJOR)) << 16) | (uint32_t(uint8_t(MINOR)) << 8)) switch (SPIRV[1]) { case SPV_SPIRV_VERSION_WORD(1, 0): return SPV_ENV_VULKAN_1_0; @@ -105,6 +124,341 @@ spv_target_env SpvTargetEnvFromSPIRV(const std::vector& SPIRV) } } +#undef SPV_SPIRV_VERSION_WORD + +// 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} + {} + + const char* name() const override { return "convert-ubo-to-push-constant"; } + + Status Process() override + { + bool modified = false; + + // Find the ID that matches the block name by searching OpName instructions + // This could be either a variable ID or a type ID (struct type) + uint32_t named_id = 0; + for (auto& debug_inst : context()->module()->debugs2()) + { + if (debug_inst.opcode() == spv::Op::OpName && + debug_inst.GetOperand(1).AsString() == m_BlockName) + { + named_id = debug_inst.GetOperand(0).AsId(); + break; + } + } + + if (named_id == 0) + { + // Block name not found + return Status::SuccessWithoutChange; + } + + // Check if the named_id is a variable or a type + spvtools::opt::Instruction* target_var = nullptr; + spvtools::opt::Instruction* named_inst = get_def_use_mgr()->GetDef(named_id); + + if (named_inst == nullptr) + { + return Status::SuccessWithoutChange; + } + + if (named_inst->opcode() == spv::Op::OpVariable) + { + // The name refers directly to a variable + target_var = named_inst; + } + 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) + { + target_var = &inst; + break; + } + } + } + + if (target_var == nullptr) + { + // Variable not found + return Status::SuccessWithoutChange; + } + + uint32_t target_var_id = target_var->result_id(); + + // Get the pointer type of the variable + 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) + { + return Status::SuccessWithoutChange; + } + + // Check if the storage class is Uniform + spv::StorageClass storage_class = + static_cast(ptr_type_inst->GetSingleWordInOperand(0)); + if (storage_class != spv::StorageClass::Uniform) + { + // Not a uniform buffer, nothing to do + return Status::SuccessWithoutChange; + } + + // 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 + spvtools::opt::analysis::TypeManager* type_mgr = context()->get_type_mgr(); + uint32_t new_ptr_type_id = + type_mgr->FindPointerToType(pointee_type_id, spv::StorageClass::PushConstant); + + if (new_ptr_type_id == 0) + { + // Failed to create new pointer type + return Status::Failure; + } + + // Ensure the new pointer type is defined before the variable + // FindPointerToType may have created it at the end, we need to move it + spvtools::opt::Instruction* new_ptr_type_inst = get_def_use_mgr()->GetDef(new_ptr_type_id); + if (new_ptr_type_inst != nullptr) + { + // Find the pointee type instruction to insert after it + spvtools::opt::Instruction* pointee_type_inst = get_def_use_mgr()->GetDef(pointee_type_id); + + // Check if new_ptr_type_inst is after target_var in the types_values list + bool needs_move = false; + for (auto& inst : context()->types_values()) + { + if (&inst == target_var) + { + // Found target_var first, so new_ptr_type_inst is after it + needs_move = true; + break; + } + if (&inst == new_ptr_type_inst) + { + // Found new_ptr_type_inst first, it's in the right position + needs_move = false; + break; + } + } + + if (needs_move && pointee_type_inst != nullptr) + { + // Move the new pointer type to right after the pointee type + // InsertAfter will automatically remove it from its current position + new_ptr_type_inst->InsertAfter(pointee_type_inst); + } + } + + // 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; + + // Propagate storage class change to all users of this variable + std::set seen; + std::vector users; + get_def_use_mgr()->ForEachUser(target_var, [&users](spvtools::opt::Instruction* user) { + users.push_back(user); + }); + + for (spvtools::opt::Instruction* user : users) + { + modified |= PropagateStorageClass(user, &seen); + } + + // 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; + }); + + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; + } + + spvtools::opt::IRContext::Analysis GetPreservedAnalyses() override + { + // This pass modifies types and decorations + return spvtools::opt::IRContext::kAnalysisNone; + } + +private: + // Recursively updates the storage class of pointer types used by instructions + // that reference the target variable. + bool PropagateStorageClass(spvtools::opt::Instruction* inst, std::set* seen) + { + if (!IsPointerResultType(inst)) + { + return false; + } + + // Already has the correct storage class + if (IsPointerToStorageClass(inst, spv::StorageClass::PushConstant)) + { + if (inst->opcode() == spv::Op::OpPhi) + { + if (!seen->insert(inst->result_id()).second) + { + return false; + } + } + + bool modified = false; + std::vector users; + get_def_use_mgr()->ForEachUser(inst, [&users](spvtools::opt::Instruction* user) { + users.push_back(user); + }); + for (spvtools::opt::Instruction* user : users) + { + modified |= PropagateStorageClass(user, seen); + } + + if (inst->opcode() == spv::Op::OpPhi) + { + seen->erase(inst->result_id()); + } + return modified; + } + + // Handle instructions that produce pointer results + 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: + 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, seen); + } + } + return true; + + case spv::Op::OpLoad: + case spv::Op::OpStore: + case spv::Op::OpCopyMemory: + case spv::Op::OpCopyMemorySized: + // These don't produce pointer results that need updating + return false; + + default: + return false; + } + } + + // Changes the result type of an instruction to use the new storage class. + void ChangeResultStorageClass(spvtools::opt::Instruction* inst) + { + spvtools::opt::analysis::TypeManager* type_mgr = context()->get_type_mgr(); + spvtools::opt::Instruction* result_type_inst = get_def_use_mgr()->GetDef(inst->type_id()); + + if (result_type_inst->opcode() != spv::Op::OpTypePointer) + { + return; + } + + uint32_t pointee_type_id = result_type_inst->GetSingleWordInOperand(1); + uint32_t new_result_type_id = + type_mgr->FindPointerToType(pointee_type_id, spv::StorageClass::PushConstant); + + inst->SetResultType(new_result_type_id); + context()->UpdateDefUse(inst); + } + + // Checks if the instruction result type is a pointer. + bool IsPointerResultType(spvtools::opt::Instruction* inst) + { + 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(spvtools::opt::Instruction* inst, spv::StorageClass storage_class) + { + 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; + } + + std::string m_BlockName; +}; + } // namespace std::vector OptimizeSPIRV(const std::vector& SrcSPIRV, spv_target_env TargetEnv, SPIRV_OPTIMIZATION_FLAGS Passes) @@ -153,4 +507,35 @@ std::vector OptimizeSPIRV(const std::vector& SrcSPIRV, spv_t return OptimizedSPIRV; } +std::vector PatchSPIRVConvertUniformBufferToPushConstant( + const std::vector& SPIRV, + const std::string& BlockName) +{ + spv_target_env TargetEnv = SpvTargetEnvFromSPIRV(SPIRV); + + spvtools::Optimizer optimizer(TargetEnv); + + optimizer.SetMessageConsumer(SpvOptimizerMessageConsumer); + + // Register the pass to convert UBO to push constant using custom out-of-tree pass + optimizer.RegisterPass(spvtools::Optimizer::PassToken( + std::make_unique(BlockName))); + + spvtools::OptimizerOptions options; +#ifndef DILIGENT_DEVELOPMENT + // Do not run validator in release build + options.set_run_validator(false); +#else + // Run validator in debug build + options.set_run_validator(true); +#endif + + std::vector result; + if (!optimizer.Run(SPIRV.data(), SPIRV.size(), &result, options)) + { + return {}; + } + return result; +} + } // namespace Diligent diff --git a/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp b/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp new file mode 100644 index 0000000000..86ab7b6276 --- /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 \ No newline at end of file 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/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/doc/SetInlineConstants.md b/doc/SetInlineConstants.md new file mode 100644 index 0000000000..d0149344de --- /dev/null +++ b/doc/SetInlineConstants.md @@ -0,0 +1,844 @@ +# SetInlineConstants Implementation Guide + +## Overview + +Inline constants (also known as push constants in Vulkan or root constants in Direct3D12) provide an efficient way to pass small, frequently-updated data directly from the CPU to shaders without creating buffers or modifying descriptor sets. + +This document describes the implementation of `SetInlineConstants` API in DiligentCore for D3D12 and Vulkan backends. + +## API Usage + +### Defining Inline Constants in Pipeline Resource Signature + +```cpp +PipelineResourceDesc Resources[] = { + { + .Name = "Constants", + .ShaderStages = SHADER_TYPE_VERTEX, + .ArraySize = 16, // Number of 32-bit values (not bytes!) + .ResourceType = SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, + .VarType = SHADER_RESOURCE_VARIABLE_TYPE_STATIC, + .Flags = PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS + } +}; + +PipelineResourceSignatureDesc PRSDesc; +PRSDesc.Resources = Resources; +PRSDesc.NumResources = _countof(Resources); +pDevice->CreatePipelineResourceSignature(PRSDesc, &pPRS); +``` + +### Setting Inline Constants at Runtime + +```cpp +// For static variables, use the signature +pPRS->GetStaticVariableByName(SHADER_TYPE_VERTEX, "Constants") + ->SetInlineConstants(pData, FirstConstant, NumConstants); + +// For mutable/dynamic variables, use the SRB +pSRB->GetVariableByName(SHADER_TYPE_VERTEX, "Constants") + ->SetInlineConstants(pData, FirstConstant, NumConstants); +``` + +### Parameters + +| Parameter | Description | +|-----------------|-------------------------------------------------------| +| `pData` | Pointer to the constant data (array of 32-bit values) | +| `FirstConstant` | Index of the first 32-bit constant to update | +| `NumConstants` | Number of 32-bit constants to update | + +## Limitations + +- **Maximum size**: 64 constants (256 bytes) per inline constant resource +- **Resource type**: Only `SHADER_RESOURCE_TYPE_CONSTANT_BUFFER` supports inline constants +- **Flag exclusivity**: `PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS` cannot be combined with other flags + +--- + +## D3D12 Backend Implementation + +### Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ User Application │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ pVariable->SetInlineConstants(pData, FirstConstant, NumConstants) │ +└──────────────────────────────────┬──────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ ShaderVariableManagerD3D12 │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ SetInlineConstants(): │ │ +│ │ 1. Get ResourceAttribs (RootIndex, OffsetFromTableStart) │ │ +│ │ 2. Validate parameters (DEV build only) │ │ +│ │ 3. Call m_ResourceCache.SetInlineConstants(...) │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────┬──────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ ShaderResourceCacheD3D12 │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ SetInlineConstants(): │ │ +│ │ 1. Get RootTable by RootIndex │ │ +│ │ 2. Get Resource (CPUDescriptorHandle.ptr points to storage) │ │ +│ │ 3. memcpy(pDstConstants + FirstConstant, pConstants, NumConstants) │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ Memory Layout: │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ RootTables | Resources | DescriptorAllocations | InlineConstStorage │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ ▲ │ +│ Resource.CPUDescriptorHandle.ptr ─┘ │ +└──────────────────────────────────┬──────────────────────────────────────────┘ + │ + ▼ (During CommitShaderResources) +┌─────────────────────────────────────────────────────────────────────────────┐ +│ PipelineResourceSignatureD3D12Impl │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ CommitRootConstants(): │ │ +│ │ while (ConstantsMask != 0) { │ │ +│ │ RootInd = ExtractLSB(ConstantsMask); │ │ +│ │ pConstants = (Uint32*)Res.CPUDescriptorHandle.ptr; │ │ +│ │ NumConstants = Res.BufferRangeSize; │ │ +│ │ pd3d12CmdList->SetGraphicsRoot32BitConstants( │ │ +│ │ BaseRootIndex + RootInd, NumConstants, pConstants, 0); │ │ +│ │ } │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### Step 1: Resource Signature Creation + +When creating a `PipelineResourceSignature` with `PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS`: + +**File**: `PipelineResourceSignatureD3D12Impl.cpp` + +```cpp +case SHADER_RESOURCE_TYPE_CONSTANT_BUFFER: + if ((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0) + { + // Use D3D12 root constants instead of descriptor table or root CBV + d3d12RootParamType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + } + // ... +``` + +The `RootParamsBuilder` allocates a dedicated root parameter: + +**File**: `RootParamsManager.cpp` + +```cpp +void RootParamsBuilder::AllocateResourceSlot(...) +{ + if (RootParameterType == D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS) + { + OffsetFromTableStart = 0; + // ArraySize = number of 32-bit values + AddRootConstants(RootIndex, Register, Space, ArraySize, ShaderVisibility, ParameterGroup); + } +} +``` + +### Step 2: Resource Cache Initialization + +The `ShaderResourceCacheD3D12` allocates memory for inline constants storage: + +**File**: `ShaderResourceCacheD3D12.cpp` + +```cpp +void ShaderResourceCacheD3D12::Initialize(...) +{ + // Calculate total inline constant values + Uint32 TotalInlineConstantValues = 0; + for (const RootParameter& RootConsts : RootParams.GetRootConstants()) + { + TotalInlineConstantValues += RootConsts.d3d12RootParam.Constants.Num32BitValues; + } + + // Allocate memory including space for inline constants + AllocateMemory(MemAllocator, TotalInlineConstantValues); + + // Initialize inline constant resources + Uint32* pCurrInlineConstValueStorage = GetInlineConstantStorage(); + for (Uint32 i = 0; i < NumRootConstants; ++i) + { + Resource& Res = GetResource(ResIdx); + // Store pointer to inline constants storage in CPUDescriptorHandle.ptr + Res.CPUDescriptorHandle.ptr = reinterpret_cast(pCurrInlineConstValueStorage); + Res.Type = SHADER_RESOURCE_TYPE_CONSTANT_BUFFER; + Res.BufferRangeSize = RootConsts.d3d12RootParam.Constants.Num32BitValues; + + pCurrInlineConstValueStorage += RootConsts.d3d12RootParam.Constants.Num32BitValues; + m_RootConstantsMask |= (Uint64{1} << Uint64{RootConsts.RootIndex}); + } +} +``` + +### Step 3: SetInlineConstants Implementation + +**File**: `ShaderVariableManagerD3D12.cpp` + +```cpp +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); + +#ifdef DILIGENT_DEVELOPMENT + const PipelineResourceDesc& ResDesc = m_pSignature->GetResourceDesc(ResIndex); + VerifyInlineConstants(ResDesc, pConstants, FirstConstant, NumConstants); +#endif + + m_ResourceCache.SetInlineConstants(RootIndex, pConstants, FirstConstant, NumConstants); +} +``` + +**File**: `ShaderResourceCacheD3D12.cpp` + +```cpp +void ShaderResourceCacheD3D12::SetInlineConstants(Uint32 RootIndex, + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants) +{ + RootTable& Tbl = GetRootTable(RootIndex); + Resource& Res = Tbl.GetResource(0); + + VERIFY_EXPR(Res.Type == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER); + VERIFY(Res.CPUDescriptorHandle.ptr != 0, "Invalid pointer to inline constants storage"); + + Uint32* pDstConstants = reinterpret_cast(Res.CPUDescriptorHandle.ptr); + memcpy(pDstConstants + FirstConstant, pConstants, NumConstants * sizeof(Uint32)); +} +``` + +### Step 4: Committing to GPU + +During `CommitShaderResources`, inline constants are submitted to the D3D12 command list: + +**File**: `PipelineResourceSignatureD3D12Impl.cpp` + +```cpp +void PipelineResourceSignatureD3D12Impl::CommitRootConstants( + const CommitCacheResourcesAttribs& CommitAttribs, + Uint64 ConstantsMask) const +{ + while (ConstantsMask != 0) + { + const Uint64 RootIndBit = ExtractLSB(ConstantsMask); + const Uint32 RootInd = PlatformMisc::GetLSB(RootIndBit); + + const ShaderResourceCacheD3D12::RootTable& CacheTbl = + CommitAttribs.pResourceCache->GetRootTable(RootInd); + const ShaderResourceCacheD3D12::Resource& Res = CacheTbl.GetResource(0); + + const Uint32* pConstants = reinterpret_cast(Res.CPUDescriptorHandle.ptr); + const Uint32 NumConstants = static_cast(Res.BufferRangeSize); + + ID3D12GraphicsCommandList* pd3d12CmdList = CommitAttribs.CmdCtx.GetCommandList(); + if (CommitAttribs.IsCompute) + { + pd3d12CmdList->SetComputeRoot32BitConstants( + CommitAttribs.BaseRootIndex + RootInd, NumConstants, pConstants, 0); + } + else + { + pd3d12CmdList->SetGraphicsRoot32BitConstants( + CommitAttribs.BaseRootIndex + RootInd, NumConstants, pConstants, 0); + } + } +} +``` + +### Multiple Inline Constants Support + +D3D12 backend fully supports multiple inline constant buffers. Each inline constant resource: + +- Gets its own `D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS` root parameter +- Has a unique `RootIndex` +- Has separate memory storage in the resource cache +- Is committed independently via `SetGraphicsRoot32BitConstants` + +**Example**: + +```cpp +PipelineResourceDesc Resources[] = { + {"TransformCB", SHADER_TYPE_VERTEX, 16, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, + SHADER_RESOURCE_VARIABLE_TYPE_STATIC, PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS}, + {"MaterialCB", SHADER_TYPE_PIXEL, 8, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, + SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC, PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS}, +}; +``` + +This creates two separate root constant parameters in the D3D12 root signature. + +--- + +## D3D11 Backend Implementation + +### Architecture Overview + +In D3D11, inline constants are emulated using **dynamic constant buffers** (`USAGE_DYNAMIC` with `D3D11_MAP_WRITE_DISCARD`). Unlike D3D12's direct root constants, D3D11 requires creating actual buffer objects and updating them via `Map`/`Unmap`. + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ User Application │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ pVariable->SetInlineConstants(pData, FirstConstant, NumConstants) │ +└──────────────────────────────────┬──────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ ShaderVariableManagerD3D11 │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ ConstBuffBindInfo::SetConstants(): │ │ +│ │ 1. Validate parameters (DEV build only) │ │ +│ │ 2. Call m_ResourceCache.SetInlineConstants(...) │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────┬──────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ ShaderResourceCacheD3D11 │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ SetInlineConstants(): │ │ +│ │ 1. Get CachedCB by BindPoints │ │ +│ │ 2. memcpy to pInlineConstantData (CPU-side staging buffer) │ │ +│ │ 3. Mark as dirty (implicit - data changed) │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ CachedCB Structure: │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ pBuff (ID3D11Buffer*) │ │ +│ │ pInlineConstantData (void*) ──> CPU staging buffer │ │ +│ │ RangeSize, BaseOffset, DynamicOffset │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────┬──────────────────────────────────────────┘ + │ + ▼ (During Draw/Dispatch) +┌─────────────────────────────────────────────────────────────────────────────┐ +│ PipelineResourceSignatureD3D11Impl │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ UpdateInlineConstantBuffers(): │ │ +│ │ for each inline constant buffer { │ │ +│ │ pd3d11Ctx->Map(pd3d11CB, 0, D3D11_MAP_WRITE_DISCARD, 0, &Mapped);│ │ +│ │ memcpy(Mapped.pData, InlineCB.pInlineConstantData, Size); │ │ +│ │ pd3d11Ctx->Unmap(pd3d11CB, 0); │ │ +│ │ } │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### Key Differences from D3D12 + +| Aspect | D3D12 | D3D11 | +|-------------------------|--------------------------------|-----------------------------------| +| Implementation | Root constants (direct) | Dynamic constant buffers (emulated)| +| Buffer creation | Not needed | `USAGE_DYNAMIC` buffer required | +| Update mechanism | `SetGraphicsRoot32BitConstants`| `Map` + `memcpy` + `Unmap` | +| Performance | Very fast (no buffer) | Slower (buffer map overhead) | +| Memory | Inline in command buffer | Separate buffer object | + +### Step 1: Resource Signature Creation + +When creating a `PipelineResourceSignature` with `PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS`: + +**File**: `PipelineResourceSignatureD3D11Impl.cpp` + +```cpp +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 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; + + // Create a USAGE_DYNAMIC constant buffer + BufferDesc CBDesc; + CBDesc.Name = m_Desc.Name + " - " + ResDesc.Name; + CBDesc.Size = ResDesc.ArraySize * sizeof(Uint32); + CBDesc.Usage = USAGE_DYNAMIC; + CBDesc.BindFlags = BIND_UNIFORM_BUFFER; + CBDesc.CPUAccessFlags = CPU_ACCESS_WRITE; + + m_pDevice->CreateBuffer(CBDesc, nullptr, &InlineCBAttribs.pBuffer); +} +``` + +**Key Points**: +- Each inline constant resource creates a `USAGE_DYNAMIC` buffer +- Buffer is shared across all SRBs created from this signature +- CPU-side staging buffer (`pInlineConstantData`) is allocated separately + +### Step 2: Resource Cache Initialization + +**File**: `ShaderResourceCacheD3D11.cpp` + +```cpp +void ShaderResourceCacheD3D11::Initialize( + const D3D11ShaderResourceCounters& ResCount, + IMemoryAllocator& MemAllocator, + const std::array* pDynamicCBSlotsMask, + const InlineConstantBufferAttribsD3D11* pInlineCBs, + Uint32 NumInlineCBs) +{ + // Calculate total memory including inline constant data storage + Uint32 TotalInlineConstantDataSize = 0; + for (Uint32 i = 0; i < NumInlineCBs; ++i) + { + TotalInlineConstantDataSize += pInlineCBs[i].NumConstants * sizeof(Uint32); + } + + // Allocate memory: Resources + d3d11 resources + inline constant data + size_t TotalSize = /* resource arrays */ + TotalInlineConstantDataSize; + m_pResourceData = ALLOCATE_RAW(MemAllocator, "Memory for cache data", TotalSize); + + // Initialize inline constant buffers + Uint8* pInlineConstDataStorage = /* end of resource arrays */; + for (Uint32 i = 0; i < NumInlineCBs; ++i) + { + const InlineConstantBufferAttribsD3D11& InlineCBAttr = pInlineCBs[i]; + + InitInlineConstantBuffer( + InlineCBAttr.BindPoints, + InlineCBAttr.pBuffer, + InlineCBAttr.NumConstants, + pInlineConstDataStorage // CPU-side staging buffer + ); + + pInlineConstDataStorage += InlineCBAttr.NumConstants * sizeof(Uint32); + } +} +``` + +**Memory Layout**: +``` +┌──────────────────────────────────────────────────────────────────────────┐ +│ CBV Resources | SRV Resources | Sampler Resources | UAV Resources | │ +│ | | | | │ +│ d3d11 CBVs | d3d11 SRVs | d3d11 Samplers | d3d11 UAVs | │ +│ │ +│ Inline Constant Data Storage (CPU-side staging buffers) │ +│ ┌──────────────┬──────────────┬──────────────┐ │ +│ │ InlineCB[0] │ InlineCB[1] │ InlineCB[2] │ ... │ +│ └──────────────┴──────────────┴──────────────┘ │ +└──────────────────────────────────────────────────────────────────────────┘ +``` + +### Step 3: SetInlineConstants Implementation + +**File**: `ShaderVariableManagerD3D11.cpp` + +```cpp +void ShaderVariableManagerD3D11::ConstBuffBindInfo::SetConstants( + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants) +{ + const PipelineResourceAttribsD3D11& Attr = GetAttribs(); + const PipelineResourceDesc& Desc = GetDesc(); + +#ifdef DILIGENT_DEVELOPMENT + VerifyInlineConstants(Desc, pConstants, FirstConstant, NumConstants); +#endif + + m_ParentManager.m_ResourceCache.SetInlineConstants( + Attr.BindPoints, pConstants, FirstConstant, NumConstants); +} +``` + +**File**: `ShaderResourceCacheD3D11.hpp` (inline implementation) + +```cpp +__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]; + + const auto ResArrays0 = GetResourceArrays(ShaderInd0); + ResArrays0.first[Binding0].SetInlineConstants(pConstants, FirstConstant, NumConstants); +} +``` + +**File**: `ShaderResourceCacheD3D11.hpp` (CachedCB member) + +```cpp +void CachedCB::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 for the allocated space"); + VERIFY(pInlineConstantData != nullptr, "Inline constant data pointer is null"); + + // Copy to CPU-side staging buffer + memcpy(reinterpret_cast(pInlineConstantData) + FirstConstant * sizeof(Uint32), + pSrcConstants, + NumConstants * sizeof(Uint32)); +} +``` + +### Step 4: Updating GPU Buffer + +During draw/dispatch commands, inline constant buffers are updated: + +**File**: `DeviceContextD3D11Impl.cpp` + +```cpp +void DeviceContextD3D11Impl::CommitShaderResources(...) +{ + // For each bound SRB + for (Uint32 SignIdx = 0; SignIdx < m_NumActiveSignatures; ++SignIdx) + { + if ((m_BindInfo.InlineConstantsSRBMask & (1u << SignIdx)) != 0) + { + // Always update inline constant buffers if the SRB is stale + // or inline constants are not intact + if (SRBStale || !InlineConstantsIntact) + { + PipelineResourceSignatureD3D11Impl* pSign = + m_pPipelineState->GetResourceSignature(SignIdx); + + pSign->UpdateInlineConstantBuffers(*pResourceCache, m_pd3d11DeviceContext); + } + } + } +} +``` + +**File**: `PipelineResourceSignatureD3D11Impl.cpp` + +```cpp +void PipelineResourceSignatureD3D11Impl::UpdateInlineConstantBuffers( + const ShaderResourceCacheD3D11& ResourceCache, + ID3D11DeviceContext* pd3d11Ctx) const +{ + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) + { + const InlineConstantBufferAttribsD3D11& InlineCBAttr = m_InlineConstantBuffers[i]; + + // Get the cached constant buffer + 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"); + + // Map the D3D11 buffer + D3D11_MAPPED_SUBRESOURCE MappedData{}; + if (SUCCEEDED(pd3d11Ctx->Map(pd3d11CB, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedData))) + { + // Copy from CPU staging buffer to GPU buffer + memcpy(MappedData.pData, + InlineCB.pInlineConstantData, + InlineCBAttr.NumConstants * sizeof(Uint32)); + + pd3d11Ctx->Unmap(pd3d11CB, 0); + } + } +} +``` + +### Multiple Inline Constants Support + +D3D11 backend fully supports multiple inline constant buffers, similar to D3D12: + +- Each inline constant resource creates its own `USAGE_DYNAMIC` buffer +- Each has separate CPU-side staging buffer (`pInlineConstantData`) +- All inline constant buffers are updated together during `CommitShaderResources` + +**Example**: + +```cpp +PipelineResourceDesc Resources[] = { + {"TransformCB", SHADER_TYPE_VERTEX, 16, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, + SHADER_RESOURCE_VARIABLE_TYPE_STATIC, PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS}, + {"MaterialCB", SHADER_TYPE_PIXEL, 8, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, + SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC, PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS}, +}; +``` + +This creates two separate dynamic constant buffers. + +### Performance Considerations + +**D3D11 Inline Constants Performance**: + +1. **Overhead**: Higher than D3D12 due to `Map`/`Unmap` operations +2. **Update frequency**: Best for per-draw updates (not per-constant updates) +3. **Buffer sharing**: All SRBs share the same buffer, reducing memory but requiring updates +4. **Optimization flags**: + - Use `DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT` if constants unchanged + - This skips the `Map`/`Unmap` overhead + +--- + +## Vulkan Backend Implementation + +### Architecture Overview + +The Vulkan backend now supports **two** inline constant paths: + +- `PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS` + `PIPELINE_RESOURCE_FLAG_VULKAN_PUSH_CONSTANT` → true push constants backed by `vkCmdPushConstants`. +- `PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS` without the push-constant flag → emulated via internal `USAGE_DYNAMIC` uniform buffers (same behavior as D3D11). + +This split lets us mix SPIR-V `layout(push_constant)` blocks with regular inline constant buffers in a single signature while still supporting multiple inline constant resources per pipeline. + +### Implementation Architecture + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ ShaderVariableManagerVk │ +│ SetInlineConstants(): │ +│ if resource has PIPELINE_RESOURCE_FLAG_VULKAN_PUSH_CONSTANT │ +│ ├─ STATIC var -> copy into InlineCBAttr.pPushConstantData │ +│ └─ SRB cache -> write to ShaderResourceCacheVk push constant slot │ +│ else │ +│ └─ ShaderResourceCacheVk::SetInlineConstants(...) (CPU staging) │ +└────────────────────────────────────────────────────────────────────────────┘ + │ │ + │ ┌────────────────────┘ + ▼ (Bind SRB) ▼ (Bind SRB with emulation) +┌────────────────────────────────────────────────────────────────────────────┐ +│ DeviceContextVkImpl::UpdateInlineConstantBuffers │ +│ for each InlineConstantBufferAttribsVk │ +│ if IsPushConstant │ +│ -> ctx.SetPushConstants(pData, 0, Size) │ +│ else │ +│ -> Map internal uniform buffer, memcpy, Unmap │ +└────────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌────────────────────────────────────────────────────────────────────────────┐ +│ DeviceContextVkImpl::CommitPushConstants │ +│ vkCmdPushConstants(vkPipelineLayout, StageFlags, 0, Size, pCpuData) │ +└────────────────────────────────────────────────────────────────────────────┘ +``` + +### Step 1: Resource Signature Creation + +**SPIR-V reflection → default signature** +`PipelineStateVkImpl::GetDefaultResourceSignatureDesc()` converts SPIR-V resources into `PipelineResourceDesc`. When an attribute reports inline constants, the code derives the **number of 32-bit values** from `BufferStaticSize` instead of the logical array size, and SPIR-V `push_constant` resources propagate `PIPELINE_RESOURCE_FLAG_VULKAN_PUSH_CONSTANT` (see `Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp:592-666`). + +**Push constant metadata for the pipeline layout** +`PipelineStateVkImpl::InitPushConstantInfo()` walks every shader and records the max push constant size and the union of stage flags. `PipelineLayoutVk::Create()` stores that information so that `vkCmdPushConstants` can be issued later (`Graphics/GraphicsEngineVulkan/src/PipelineLayoutVk.cpp:62-164`). + +**InlineConstantBufferAttribsVk initialization** +Inside `PipelineResourceSignatureVkImpl::CreateSetLayouts()` every inline constant resource gets an `InlineConstantBufferAttribsVk` entry (`Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp:200-441`): + +- `IsPushConstant` mirrors `PIPELINE_RESOURCE_FLAG_VULKAN_PUSH_CONSTANT`. +- Push constants skip descriptor-set bookkeeping altogether; emulated inline constants allocate a `USAGE_DYNAMIC` uniform buffer that is shared by all SRBs originating from the signature. +- Static push constants allocate CPU-side storage (`pPushConstantData`) inside `m_pStaticInlineConstantData`. + +### Step 2: Resource Cache Initialization + +`PipelineResourceSignatureVkImpl::InitSRBResourceCache()` now sets up both flavors (`Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp:653-739`): + +- A single block of CPU memory is allocated for all inline constants and the pointer is owned by `ShaderResourceCacheVk::m_pInlineConstantMemory`. +- Push-constant resources register their per-SRB staging pointers via `ShaderResourceCacheVk::InitializePushConstantDataPtrs()` / `SetPushConstantDataPtr()` so that each SRB maintains its own copy. +- Emulated inline constants call `InitializeInlineConstantBuffer()` to hook the CPU staging memory into the descriptor-set resource slots and bind the internal dynamic buffer through `SetResource()`. + +Static inline constants follow the same pattern during `CreateSetLayouts()` (lines `576-626`), so signatures keep zeroed data for static variables and copy it into SRBs later. + +### Step 3: ShaderVariableManagerVk::SetInlineConstants + +`ShaderVariableManagerVk::SetInlineConstants()` branches on the new flag (`Graphics/GraphicsEngineVulkan/src/ShaderVariableManagerVk.cpp:654-725`): + +``` +if (IsPushConstant) +{ + if (CacheType == ResourceCacheContentType::Signature) + { + // Static push constants live inside InlineCBAttr.pPushConstantData. + } + else + { + // SRB cache owns per-instance push constant storage. + void* pDst = m_ResourceCache.GetPushConstantDataPtr(PushConstantBufferIdx); + } + memcpy(...); // copy 32-bit values + return; +} + +const Uint32 CacheOffset = Attribs.CacheOffset(CacheType); +m_ResourceCache.SetInlineConstants(Attribs.DescrSet, CacheOffset, pConstants, + FirstConstant, NumConstants); +``` + +Emulated inline constants therefore keep using `ShaderResourceCacheVk::SetInlineConstants`, while push constants never touch descriptor sets. + +### Step 4: Updating GPU State + +`PipelineResourceSignatureVkImpl::UpdateInlineConstantBuffers()` now understands both data paths (`Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp:1309-1345`): + +``` +if (InlineCBAttr.IsPushConstant) +{ + const void* pData = ResourceCache.GetPushConstantDataPtr(PushConstantBufferIdx++); + Ctx.SetPushConstants(pData, 0, DataSize); +} +else if (InlineCBAttr.pBuffer) +{ + void* pMappedData = nullptr; + Ctx.MapBuffer(InlineCBAttr.pBuffer, MAP_WRITE, MAP_FLAG_DISCARD, pMappedData); + memcpy(pMappedData, pInlineConstantData, DataSize); + Ctx.UnmapBuffer(InlineCBAttr.pBuffer, MAP_WRITE); +} +``` + +- `DeviceContextVkImpl::SetPushConstants()` copies the data into an internal `std::array` buffer and marks it dirty (`Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp:454-480`). +- After descriptor sets are committed, `CommitPushConstants()` checks whether the current pipeline layout exposes push constants and issues `vkCmdPushConstants` with the stored stage flags (`Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp:482-501`). Draw, dispatch, and trace entry points call `CommitPushConstants()` right after `CommitDescriptorSets`. +- SRBs that only contain push constants no longer trigger descriptor-set binding (`CommitDescriptorSets()` filters them out at lines `503-521`). + +### Step 5: Example Usage + +``` +PipelineResourceDesc Resources[] = { + // True push constant block declared with layout(push_constant) + {"PerFramePC", SHADER_TYPE_VERTEX | SHADER_TYPE_PIXEL, 32, + SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_STATIC, + PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS | PIPELINE_RESOURCE_FLAG_VULKAN_PUSH_CONSTANT}, + + // Emulated inline constant buffer bound through a uniform buffer + {"PerDrawInlineCB", SHADER_TYPE_VERTEX, 16, + SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC, + PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS}, +}; +``` + +`PerFramePC` is copied directly with `vkCmdPushConstants`, while `PerDrawInlineCB` maps the internal dynamic uniform buffer each time `UpdateInlineConstantBuffers()` runs. + +### Memory Management + +1. **Static inline constants / push constants** + - Single allocation in `CreateSetLayouts()` stored inside `m_pStaticInlineConstantData`. + - Freed by `PipelineResourceSignatureVkImpl::Destruct()`. + - Push constants also stash the pointer inside `InlineConstantBufferAttribsVk::pPushConstantData`. + +2. **SRB inline constants** + - Single block per SRB, allocated in `InitSRBResourceCache()` and owned by `ShaderResourceCacheVk::m_pInlineConstantMemory`. + - Push constants additionally keep an array of pointers (`m_pPushConstantDataPtrs`) managed by `InitializePushConstantDataPtrs()` / `SetPushConstantDataPtr()`. + +### Performance Considerations + +- Push constants avoid the `Map`/`Unmap` cost entirely—`DeviceContextVkImpl` simply memcpy's into a small CPU buffer before issuing `vkCmdPushConstants`. +- Emulated inline constants still incur a map/discard write, so they are best suited for larger constant blocks or when more than one inline constant buffer is needed. +- SRBs sharing the internal uniform buffers keeps memory usage small even when every SRB uses emulation. + +--- + + +--- + +## Static vs Mutable/Dynamic Variables + +### Important Note on Static Variables + +When using **static** inline constants, there's a critical distinction between Signature cache and SRB cache: + +```cpp +// This updates the SIGNATURE's cache (for initialization) +pPRS->GetStaticVariableByName(SHADER_TYPE_VERTEX, "Constants") + ->SetInlineConstants(pData, 0, 16); + +// SRB creation copies from Signature cache to SRB cache +pPRS->CreateShaderResourceBinding(&pSRB, true); + +// For per-frame updates, use the SRB's variable +pSRB->GetVariableByName(SHADER_TYPE_VERTEX, "Constants") + ->SetInlineConstants(pNewData, 0, 16); +``` + +**Key Points**: +- Static variable bindings are copied from Signature to SRB during `CreateShaderResourceBinding(pSRB, true)` +- Updates to Signature's static variables after SRB creation do **not** propagate to existing SRBs +- For per-frame updates, always use the SRB's variable, not the Signature's + +### Recommended Variable Types for Inline Constants + +| Update Frequency | Recommended Variable Type | +|------------------|---------------------------| +| Once at init | `STATIC` | +| Once per SRB | `MUTABLE` | +| Per-frame/draw | `DYNAMIC` or `MUTABLE` | + +--- + +## Performance Considerations + +### D3D12 + +- Root constants are stored directly in the root signature, no indirection +- Each `SetGraphicsRoot32BitConstants` call updates constants immediately +- Optimal for small, frequently-updated data (< 64 constants / 256 bytes) +- Performance: Fastest + +### D3D11 + +- Emulated using `USAGE_DYNAMIC` constant buffers +- Requires `Map` + `memcpy` + `Unmap` for each update +- Buffer shared across all SRBs (reduces memory, increases update frequency) +- Use `DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT` to skip updates when unchanged +- Performance: same as Constant Buffers, Moderate overhead due to Map/Unmap + +### Vulkan + +- Resources flagged with `PIPELINE_RESOURCE_FLAG_VULKAN_PUSH_CONSTANT` use `vkCmdPushConstants` (no descriptor binding, no mapping). +- All other inline constants are emulated through internal `USAGE_DYNAMIC` uniform buffers that are shared by every SRB. +- Map/Unmap is only required for the emulated path; push constants merely copy into a small CPU buffer right before `vkCmdPushConstants`. +- Performance: push-constant path is comparable to D3D12 root constants, emulated path matches the D3D11 behavior. + +### Performance Comparison + +| Backend | Implementation | Update Cost | +|---------|-------------------------------------|----------------| +| D3D12 | Root constants | Very Low | +| D3D11 | Dynamic CB + Map | Moderate | +| Vulkan | Push constants / Dynamic UB + Map | Low / Moderate | + +### General Guidelines + +1. **Size**: Keep inline constants small (typically <= 128 bytes) +2. **Frequency**: Use for per-draw or per-dispatch data +3. **Fallback**: For larger data, use dynamic uniform buffers +4. **Multiple resources**: + - D3D12: Full support for multiple inline constants + - D3D11: Full support for multiple inline constants (emulated with separate dynamic buffer) + - Vulkan: One push constant block per pipeline + unlimited emulated inline buffers via dynamic uniform buffers + +--- + +## References + +- [D3D12 Root Signatures](https://docs.microsoft.com/en-us/windows/win32/direct3d12/root-signatures) +- [Vulkan Push Constants](https://docs.vulkan.org/guide/latest/push_constants.html) +- [GLSL Vulkan Extension (GL_KHR_vulkan_glsl)](https://github.com/KhronosGroup/GLSL/blob/master/extensions/khr/GL_KHR_vulkan_glsl.txt) +