From 0324728dcfc08a971aea1988f8cbde03e0f3e23f Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 6 Dec 2025 22:20:32 +0800 Subject: [PATCH 1/5] Add vulkan inline constants support, emulated with uniform buffers. (PushConstants not impl yet) --- .../include/DeviceContextVkImpl.hpp | 3 + .../PipelineResourceSignatureVkImpl.hpp | 31 + .../include/ShaderResourceCacheVk.hpp | 42 +- .../src/DeviceContextVkImpl.cpp | 34 + .../src/PipelineResourceSignatureVkImpl.cpp | 305 ++++- .../src/PipelineStateVkImpl.cpp | 23 +- .../src/ShaderResourceCacheVk.cpp | 58 + .../src/ShaderVariableManagerVk.cpp | 17 +- doc/SetInlineConstants.md | 1037 +++++++++++++++++ 9 files changed, 1530 insertions(+), 20 deletions(-) create mode 100644 doc/SetInlineConstants.md diff --git a/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.hpp b/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.hpp index d86ffd775d..5fb0ed0797 100644 --- a/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.hpp +++ b/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.hpp @@ -555,6 +555,9 @@ class DeviceContextVkImpl final : public DeviceContextNextGenBase pBuffer; // Internal dynamic uniform buffer +}; + struct PipelineResourceSignatureInternalDataVk : PipelineResourceSignatureInternalData { Uint16 DynamicUniformBufferCount = 0; @@ -134,6 +146,16 @@ 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 + void UpdateInlineConstantBuffers(const ShaderResourceCacheVk& ResourceCache, + DeviceContextVkImpl& Ctx) const; + + // Returns the number of inline constant buffers + Uint32 GetNumInlineConstantBuffers() const { return m_NumInlineConstantBuffers; } + + // Returns the inline constant buffer attributes + const InlineConstantBufferAttribsVk* GetInlineConstantBuffers() const { return m_InlineConstantBuffers.get(); } + #ifdef DILIGENT_DEVELOPMENT /// Verifies committed resource using the SPIRV resource attributes from the PSO. bool DvpValidateCommittedResource(const DeviceContextVkImpl* pDeviceCtx, @@ -193,6 +215,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_NumInlineConstantBuffers = 0; + // Inline constant buffer attributes + std::unique_ptr m_InlineConstantBuffers; + + // Pointer to CPU-side staging buffer for static inline constants + // This memory is allocated in CreateSetLayouts and must be freed in Destruct + void* m_pStaticInlineConstantData = nullptr; }; template <> Uint32 PipelineResourceSignatureVkImpl::GetDescriptorSetIndex() const; diff --git a/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp b/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp index 26bf336e4d..ae548888fb 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,13 +250,36 @@ 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 BindingIndex) 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)}; + } Uint32 GetNumDescriptorSets() const { return m_NumSets; } bool HasDynamicResources() const { return m_NumDynamicBuffers > 0; } bool HasInlineConstants() const { - return false; + return m_HasInlineConstants; } ResourceCacheContentType GetContentType() const { return static_cast(m_ContentType); } @@ -287,16 +315,22 @@ class ShaderResourceCacheVk : public ShaderResourceCacheBase std::unique_ptr> m_pMemory; + // Memory for inline constant data (allocated separately, freed in destructor) + std::unique_ptr> m_pInlineConstantMemory; + 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/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp index 140acab84c..f22ee03040 100644 --- a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp @@ -415,6 +415,24 @@ DeviceContextVkImpl::ResourceBindInfo& DeviceContextVkImpl::GetBindInfo(PIPELINE return m_BindInfo[Indices[Uint32{Type}]]; } +void DeviceContextVkImpl::UpdateInlineConstantBuffers(ResourceBindInfo& BindInfo) +{ + const Uint32 SignCount = m_pPipelineState->GetResourceSignatureCount(); + for (Uint32 i = 0; i < SignCount; ++i) + { + const PipelineResourceSignatureVkImpl* pSign = m_pPipelineState->GetResourceSignature(i); + if (pSign == nullptr || pSign->GetNumInlineConstantBuffers() == 0) + continue; + + const ShaderResourceCacheVk* pResourceCache = BindInfo.ResourceCaches[i]; + if (pResourceCache == nullptr || !pResourceCache->HasInlineConstants()) + continue; + + // Update inline constant buffers + pSign->UpdateInlineConstantBuffers(*pResourceCache, *this); + } +} + void DeviceContextVkImpl::CommitDescriptorSets(ResourceBindInfo& BindInfo, Uint32 CommitSRBMask) { VERIFY(CommitSRBMask != 0, "This method should not be called when there is nothing to commit"); @@ -758,6 +776,14 @@ void DeviceContextVkImpl::PrepareForDraw(DRAW_FLAGS Flags) #endif ResourceBindInfo& BindInfo = GetBindInfo(PIPELINE_TYPE_GRAPHICS); + + // Update inline constant buffers before binding descriptor sets + 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. @@ -1059,6 +1085,10 @@ 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); @@ -1075,6 +1105,10 @@ 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); diff --git a/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp index d10dd562e4..1edbcb484d 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,7 +179,11 @@ 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(); + } } m_pStaticResCache->InitializeSets(GetRawAllocator(), 1, &StaticResourceCount); } @@ -191,7 +197,23 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) 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_NumInlineConstantBuffers; + } + } + + // Allocate inline constant buffer attributes array + if (m_NumInlineConstantBuffers > 0) + { + m_InlineConstantBuffers = std::make_unique(m_NumInlineConstantBuffers); } // Descriptor set mapping (static/mutable (0) or dynamic (1) -> set index) @@ -245,6 +267,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}; @@ -323,11 +348,13 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) } BindingIndices[CacheGroup] += 1; - CacheGroupOffsets[CacheGroup] += ResDesc.ArraySize; + // 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; + // 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()); @@ -336,11 +363,45 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) if (ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) { VERIFY(pAttribs->DescrSet == 0, "Static resources must always be allocated in descriptor set 0"); - m_pStaticResCache->InitializeResources(pAttribs->DescrSet, StaticCacheOffset, ResDesc.ArraySize, + // 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 - create internal dynamic uniform 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"); + + InlineConstantBufferAttribsVk& InlineCBAttribs = m_InlineConstantBuffers[InlineConstantBufferIdx++]; + 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 USAGE_DYNAMIC uniform buffer for emulating inline constants + // All SRBs created from this signature will share the same inline constant buffer. + 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_NumInlineConstantBuffers); #ifdef DILIGENT_DEBUG if (m_pStaticResCache != nullptr) @@ -474,6 +535,68 @@ 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_pStaticResCache != nullptr && m_NumInlineConstantBuffers > 0) + { + const Uint32 TotalResources = GetTotalResourceCount(); + constexpr ResourceCacheContentType StaticCacheType = ResourceCacheContentType::Signature; + + // First pass: calculate total memory size needed for all static inline constants + Uint32 TotalStaticInlineConstantSize = 0; + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBuffers[i]; + + for (Uint32 r = 0; r < TotalResources; ++r) + { + const PipelineResourceDesc& ResDesc = GetResourceDesc(r); + const ResourceAttribs& Attr = GetResourceAttribs(r); + + if ((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) && + ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC && + Attr.DescrSet == InlineCBAttr.DescrSet && + Attr.BindingIndex == InlineCBAttr.BindingIndex) + { + TotalStaticInlineConstantSize += InlineCBAttr.NumConstants * sizeof(Uint32); + break; + } + } + } + + // Allocate single memory block for all static inline constants + if (TotalStaticInlineConstantSize > 0) + { + m_pStaticInlineConstantData = GetRawAllocator().Allocate(TotalStaticInlineConstantSize, "Static inline constant data", __FILE__, __LINE__); + memset(m_pStaticInlineConstantData, 0, TotalStaticInlineConstantSize); + + // Second pass: assign memory to each static inline constant + Uint8* pCurrentDataPtr = static_cast(m_pStaticInlineConstantData); + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBuffers[i]; + + for (Uint32 r = 0; r < TotalResources; ++r) + { + const PipelineResourceDesc& ResDesc = GetResourceDesc(r); + const ResourceAttribs& Attr = GetResourceAttribs(r); + + if ((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) && + ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC && + Attr.DescrSet == InlineCBAttr.DescrSet && + Attr.BindingIndex == InlineCBAttr.BindingIndex) + { + const Uint32 DataSize = InlineCBAttr.NumConstants * sizeof(Uint32); + const Uint32 CacheOffset = Attr.StaticCacheOffset; + m_pStaticResCache->InitializeInlineConstantBuffer(0, CacheOffset, InlineCBAttr.NumConstants, pCurrentDataPtr); + pCurrentDataPtr += DataSize; + break; + } + } + } + } + } } PipelineResourceSignatureVkImpl::~PipelineResourceSignatureVkImpl() @@ -489,6 +612,13 @@ void PipelineResourceSignatureVkImpl::Destruct() GetDevice()->SafeReleaseDeviceObject(std::move(Layout), ~0ull); } + // Free static inline constant data allocated in CreateSetLayouts + if (m_pStaticInlineConstantData != nullptr) + { + GetRawAllocator().Free(m_pStaticInlineConstantData); + m_pStaticInlineConstantData = nullptr; + } + TPipelineResourceSignatureBase::Destruct(); } @@ -509,10 +639,55 @@ 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 + if (m_NumInlineConstantBuffers > 0) + { + // Calculate total memory size needed for all inline constants + Uint32 TotalInlineConstantSize = 0; + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) + { + TotalInlineConstantSize += m_InlineConstantBuffers[i].NumConstants * sizeof(Uint32); + } + + // Allocate single memory block for all inline constants + void* 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 memory to each inline constant buffer + Uint8* pCurrentDataPtr = static_cast(pInlineConstantMemory); + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBuffers[i]; + const Uint32 DataSize = InlineCBAttr.NumConstants * sizeof(Uint32); + + // Find the resource and set its inline constant data pointer + for (Uint32 r = 0; r < TotalResources; ++r) + { + const PipelineResourceDesc& ResDesc = GetResourceDesc(r); + const ResourceAttribs& Attr = GetResourceAttribs(r); + + if ((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) && + Attr.DescrSet == InlineCBAttr.DescrSet && + Attr.BindingIndex == InlineCBAttr.BindingIndex) + { + ResourceCache.InitializeInlineConstantBuffer(Attr.DescrSet, Attr.CacheOffset(CacheType), + InlineCBAttr.NumConstants, pCurrentDataPtr); + break; + } + } + pCurrentDataPtr += DataSize; + } + } + #ifdef DILIGENT_DEBUG ResourceCache.DbgVerifyResourceInitialization(); #endif @@ -528,6 +703,66 @@ void PipelineResourceSignatureVkImpl::InitSRBResourceCache(ShaderResourceCacheVk DescriptorSetAllocation SetAllocation = GetDevice()->AllocateDescriptorSet(~Uint64{0}, vkLayout, DescrSetName); ResourceCache.AssignDescriptorSetAllocation(GetDescriptorSetIndex(), std::move(SetAllocation)); } + + // Bind internal inline constant buffers to the resource cache + // This must be done after descriptor set allocation so that descriptor writes work correctly + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBuffers[i]; + + if (!InlineCBAttr.pBuffer) + continue; + + // Find the resource and bind the internal buffer + for (Uint32 r = 0; r < TotalResources; ++r) + { + const PipelineResourceDesc& ResDesc = GetResourceDesc(r); + const ResourceAttribs& Attr = GetResourceAttribs(r); + + if ((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) && + Attr.DescrSet == InlineCBAttr.DescrSet && + Attr.BindingIndex == InlineCBAttr.BindingIndex) + { + 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 internal uniform buffer to the resource cache + ResourceCache.SetResource( + &GetDevice()->GetLogicalDevice(), + Attr.DescrSet, + CacheOffset, + { + Attr.BindingIndex, + 0, // ArrayIndex + RefCntAutoPtr{InlineCBAttr.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{InlineCBAttr.pBuffer}, + 0, // BufferBaseOffset + InlineCBAttr.NumConstants * sizeof(Uint32) // BufferRangeSize + }); + } + break; + } + } + } } void PipelineResourceSignatureVkImpl::CopyStaticResources(ShaderResourceCacheVk& DstResourceCache) const @@ -555,7 +790,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 + 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 +906,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; + // 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 @@ -981,4 +1236,30 @@ PipelineResourceSignatureInternalDataVk PipelineResourceSignatureVkImpl::GetInte return InternalData; } +void PipelineResourceSignatureVkImpl::UpdateInlineConstantBuffers(const ShaderResourceCacheVk& ResourceCache, + DeviceContextVkImpl& Ctx) const +{ + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBuffers[i]; + + if (!InlineCBAttr.pBuffer) + continue; + + // Get the inline constant data from the resource cache + const void* pInlineConstantData = ResourceCache.GetInlineConstantData(InlineCBAttr.DescrSet, InlineCBAttr.BindingIndex); + if (pInlineConstantData == nullptr) + continue; + + // Map the buffer and copy the data + void* pMappedData = nullptr; + Ctx.MapBuffer(InlineCBAttr.pBuffer, MAP_WRITE, MAP_FLAG_DISCARD, pMappedData); + if (pMappedData != nullptr) + { + memcpy(pMappedData, pInlineConstantData, InlineCBAttr.NumConstants * sizeof(Uint32)); + Ctx.UnmapBuffer(InlineCBAttr.pBuffer, MAP_WRITE); + } + } +} + } // namespace Diligent diff --git a/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp index 26e9b64225..3c74aa9fc8 100644 --- a/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp @@ -633,7 +633,28 @@ 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. + Uint32 ArraySize = Attribs.ArraySize; + if ((Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0) + { + // For inline constants specified via ShaderResourceVariableDesc, + // the SPIRV ArraySize is 1 (buffer is not an array), but we need + // the number of 32-bit constants which is BufferStaticSize / sizeof(Uint32) + if (Attribs.BufferStaticSize > 0) + { + ArraySize = Attribs.BufferStaticSize / sizeof(Uint32); + } + else + { + LOG_ERROR_AND_THROW("Resource '", Attribs.Name, "' in shader '", pShader->GetDesc().Name, + "' is marked as inline constants, but has zero buffer size. " + "Make sure the buffer type is defined in the shader."); + } + } + + SignDesc.AddResource(VarDesc.ShaderStages, Attribs.Name, ArraySize, ResType, VarDesc.Type, Flags); } else { diff --git a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp index e186c946c3..8d634624a7 100644 --- a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp @@ -950,4 +950,62 @@ 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)); + + UpdateRevision(); +} + +const void* ShaderResourceCacheVk::GetInlineConstantData(Uint32 DescrSetIndex, Uint32 BindingIndex) const +{ + const DescriptorSet& DescrSet = GetDescriptorSet(DescrSetIndex); + + // Find the resource with the matching binding index + for (Uint32 i = 0; i < DescrSet.GetSize(); ++i) + { + const Resource& Res = DescrSet.GetResource(i); + if (Res.pInlineConstantData != nullptr) + { + // For now, we assume binding index matches the resource index for inline constants + // This may need adjustment based on actual binding layout + 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 f3d870cb08..27aac2377c 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 @@ -655,7 +656,16 @@ void ShaderVariableManagerVk::SetInlineConstants(Uint32 ResIndex, Uint32 FirstConstant, Uint32 NumConstants) { - UNSUPPORTED("Not yet implemented"); + const PipelineResourceAttribsVk& Attribs = m_pSignature->GetResourceAttribs(ResIndex); + const ResourceCacheContentType CacheType = m_ResourceCache.GetContentType(); + const Uint32 CacheOffset = Attribs.CacheOffset(CacheType); + +#ifdef DILIGENT_DEVELOPMENT + const PipelineResourceDesc& ResDesc = m_pSignature->GetResourceDesc(ResIndex); + VerifyInlineConstants(ResDesc, pConstants, FirstConstant, NumConstants); +#endif + + m_ResourceCache.SetInlineConstants(Attribs.DescrSet, CacheOffset, pConstants, FirstConstant, NumConstants); } IDeviceObject* ShaderVariableManagerVk::Get(Uint32 ArrayIndex, Uint32 ResIndex) const @@ -664,7 +674,8 @@ IDeviceObject* ShaderVariableManagerVk::Get(Uint32 ArrayIndex, Uint32 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/doc/SetInlineConstants.md b/doc/SetInlineConstants.md new file mode 100644 index 0000000000..249158c150 --- /dev/null +++ b/doc/SetInlineConstants.md @@ -0,0 +1,1037 @@ +# 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 implements inline constants using **dynamic uniform buffers** for emulation, similar to the D3D11 backend. While Vulkan natively supports push constants, the current implementation uses uniform buffer emulation to support multiple inline constant resources and broader compatibility. + +**Design Decision**: +- True Vulkan push constants are reserved for SPIRV blocks explicitly marked with `layout(push_constant)` (future implementation) +- All inline constant resources currently use dynamic uniform buffer emulation +- This approach allows multiple inline constant resources (Vulkan only supports one push constant block per pipeline) + +### Implementation Architecture + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ User Application │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ pVariable->SetInlineConstants(pData, FirstConstant, NumConstants) │ +└──────────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ ShaderVariableManagerVk │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ SetInlineConstants(): │ │ +│ │ 1. Get resource offset in merged push constant block │ │ +│ │ 2. Copy data to push constant storage │ │ +│ │ 3. Mark push constants as dirty │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────┬──────────────────────────────────────────┘ + 1 │ + ▼ (During CommitShaderResources) +┌─────────────────────────────────────────────────────────────────────────────┐ +│ DeviceContextVkImpl │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ CommitPushConstants(): │ │ +│ │ vkCmdPushConstants(cmdBuffer, pipelineLayout, │ │ +│ │ stageFlags, offset, size, pData); │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### Step 1: Resource Signature Creation + +When creating a `PipelineResourceSignature` with `PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS`: + +**File**: `PipelineResourceSignatureVkImpl.cpp` - `CreateSetLayouts()` + +```cpp +if (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) +{ + VERIFY(ResDesc.ResourceType == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, + "Only constant buffers can have INLINE_CONSTANTS flag"); + + // Create internal dynamic uniform buffer for emulation + InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBuffers[InlineCBIdx++]; + InlineCBAttr.DescrSet = DescrSetId; + InlineCBAttr.BindingIndex = vkBinding; + InlineCBAttr.NumConstants = ResDesc.ArraySize; // Number of 32-bit values + + // Create a USAGE_DYNAMIC uniform buffer + BufferDesc BuffDesc; + BuffDesc.Name = std::string(m_Desc.Name) + " - " + ResDesc.Name; + BuffDesc.Size = ResDesc.ArraySize * sizeof(Uint32); + BuffDesc.Usage = USAGE_DYNAMIC; + BuffDesc.BindFlags = BIND_UNIFORM_BUFFER; + BuffDesc.CPUAccessFlags = CPU_ACCESS_WRITE; + + RefCntAutoPtr pBuffer; + GetDevice()->CreateBuffer(BuffDesc, nullptr, &pBuffer); + InlineCBAttr.pBuffer = RefCntAutoPtr(pBuffer, IID_BufferVk); +} +``` + +**Key Points**: +- Each inline constant resource creates a `USAGE_DYNAMIC` uniform buffer +- Buffer is stored in `InlineConstantBufferAttribsVk` structure +- Buffer is shared across all SRBs created from this signature + +### Step 2: Resource Cache Initialization + +#### For Static Inline Constants + +**File**: `PipelineResourceSignatureVkImpl.cpp` - `CreateSetLayouts()` + +Static inline constants require CPU-side staging buffers that are allocated once during signature creation and freed in the destructor. + +```cpp +// Calculate total memory size for all static inline constants +Uint32 TotalStaticInlineConstantSize = 0; +for (each static inline constant) +{ + TotalStaticInlineConstantSize += NumConstants * sizeof(Uint32); +} + +// Allocate single memory block for all static inline constants +m_pStaticInlineConstantData = GetRawAllocator().Allocate( + TotalStaticInlineConstantSize, "Static inline constant data", __FILE__, __LINE__); +memset(m_pStaticInlineConstantData, 0, TotalStaticInlineConstantSize); + +// Assign memory to each static inline constant +Uint8* pCurrentDataPtr = static_cast(m_pStaticInlineConstantData); +for (each static inline constant) +{ + m_pStaticResCache->InitializeInlineConstantBuffer( + 0, CacheOffset, NumConstants, pCurrentDataPtr); + pCurrentDataPtr += DataSize; +} +``` + +#### For Mutable/Dynamic Inline Constants (SRB) + +**File**: `PipelineResourceSignatureVkImpl.cpp` - `InitSRBResourceCache()` + +```cpp +void PipelineResourceSignatureVkImpl::InitSRBResourceCache(ShaderResourceCacheVk& ResourceCache) +{ + // Initialize inline constant buffers + if (m_NumInlineConstantBuffers > 0) + { + // Calculate total memory size + Uint32 TotalInlineConstantSize = 0; + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) + { + TotalInlineConstantSize += m_InlineConstantBuffers[i].NumConstants * sizeof(Uint32); + } + + // Allocate single memory block + void* pInlineConstantMemory = CacheMemAllocator.Allocate( + TotalInlineConstantSize, "Inline constant data", __FILE__, __LINE__); + memset(pInlineConstantMemory, 0, TotalInlineConstantSize); + + // Pass ownership to resource cache for proper cleanup + ResourceCache.SetInlineConstantMemory(CacheMemAllocator, pInlineConstantMemory); + + // Assign memory to each inline constant buffer + Uint8* pCurrentDataPtr = static_cast(pInlineConstantMemory); + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) + { + ResourceCache.InitializeInlineConstantBuffer( + DescrSet, CacheOffset, NumConstants, pCurrentDataPtr); + pCurrentDataPtr += InlineCBAttr.NumConstants * sizeof(Uint32); + } + } + + // Bind internal uniform buffers to the resource cache + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) + { + ResourceCache.SetResource( + pLogicalDevice, // nullptr for dynamic variables + DescrSet, CacheOffset, + { + BindingIndex, 0, // ArrayIndex + RefCntAutoPtr{InlineCBAttr.pBuffer}, + 0, // BufferBaseOffset + InlineCBAttr.NumConstants * sizeof(Uint32) // BufferRangeSize + }); + } +} +``` + +**Memory Management**: +- Static inline constants: Memory allocated via `GetRawAllocator()`, freed in `Destruct()` +- SRB inline constants: Memory allocated via `CacheMemAllocator`, owned by `ShaderResourceCacheVk` (freed in destructor via `m_pInlineConstantMemory`) + +### Step 3: SetInlineConstants Implementation + +**File**: `ShaderVariableManagerVk.cpp` + +```cpp +void ShaderVariableManagerVk::SetInlineConstants(Uint32 ResIndex, + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants) +{ + const PipelineResourceAttribsVk& Attribs = m_pSignature->GetResourceAttribs(ResIndex); + const ResourceCacheContentType CacheType = m_ResourceCache.GetContentType(); + +#ifdef DILIGENT_DEVELOPMENT + const PipelineResourceDesc& ResDesc = m_pSignature->GetResourceDesc(ResIndex); + VerifyInlineConstants(ResDesc, pConstants, FirstConstant, NumConstants); +#endif + + m_ResourceCache.SetInlineConstants( + Attribs.DescrSet, + Attribs.CacheOffset(CacheType), + pConstants, + FirstConstant, + NumConstants); +} +``` + +**File**: `ShaderResourceCacheVk.cpp` + +```cpp +void ShaderResourceCacheVk::SetInlineConstants(Uint32 DescrSetIndex, + Uint32 CacheOffset, + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants) +{ + DescriptorSet& DescrSet = GetDescriptorSet(DescrSetIndex); + Resource& DstRes = DescrSet.GetResource(CacheOffset); + + VERIFY(DstRes.pInlineConstantData != nullptr, + "Inline constant data pointer is null."); + + // Copy to CPU-side staging buffer + Uint32* pDstConstants = reinterpret_cast(DstRes.pInlineConstantData); + memcpy(pDstConstants + FirstConstant, pConstants, NumConstants * sizeof(Uint32)); + + UpdateRevision(); // Mark cache as dirty +} +``` + +### Step 4: Updating GPU Buffer + +During draw/dispatch commands, inline constant buffers are updated: + +**File**: `DeviceContextVkImpl.cpp` + +```cpp +void DeviceContextVkImpl::PrepareForDraw(DRAW_FLAGS Flags) +{ + // Update inline constant buffers before committing descriptor sets + UpdateInlineConstantBuffers(BindInfo); + + // Commit descriptor sets + CommitDescriptorSets(BindInfo, /* ... */); +} + +void DeviceContextVkImpl::UpdateInlineConstantBuffers(ResourceBindInfo& BindInfo) +{ + for (Uint32 SignIdx = 0; SignIdx < BindInfo.ActiveSRBCount; ++SignIdx) + { + const auto& SignInfo = BindInfo.pSignatures[SignIdx]; + if (SignInfo.pSignature->GetNumInlineConstantBuffers() > 0) + { + SignInfo.pSignature->UpdateInlineConstantBuffers( + *SignInfo.pResourceCache, *this); + } + } +} +``` + +**File**: `PipelineResourceSignatureVkImpl.cpp` + +```cpp +void PipelineResourceSignatureVkImpl::UpdateInlineConstantBuffers( + const ShaderResourceCacheVk& ResourceCache, + DeviceContextVkImpl& Ctx) const +{ + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) + { + const InlineConstantBufferAttribsVk& InlineCBAttr = m_InlineConstantBuffers[i]; + + // Get the inline constant data from the resource cache + const void* pInlineConstantData = ResourceCache.GetInlineConstantData( + InlineCBAttr.DescrSet, InlineCBAttr.BindingIndex); + + if (pInlineConstantData == nullptr) + continue; + + // Map the buffer and copy the data + void* pMappedData = nullptr; + Ctx.MapBuffer(InlineCBAttr.pBuffer, MAP_WRITE, MAP_FLAG_DISCARD, pMappedData); + if (pMappedData != nullptr) + { + memcpy(pMappedData, pInlineConstantData, + InlineCBAttr.NumConstants * sizeof(Uint32)); + Ctx.UnmapBuffer(InlineCBAttr.pBuffer, MAP_WRITE); + } + } +} +``` + +### Step 5: Handling ArraySize for ShaderResourceVariableDesc + +When inline constants are specified via `ShaderResourceVariableDesc` (not `PipelineResourceDesc`), the `ArraySize` must be determined from SPIR-V: + +**File**: `PipelineStateVkImpl.cpp` - `GetDefaultResourceSignatureDesc()` + +```cpp +// For inline constants, ArraySize specifies the number of 32-bit constants, +// not the array dimension. Calculate it from the buffer size. +Uint32 ArraySize = Attribs.ArraySize; +if ((Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0) +{ + // For inline constants specified via ShaderResourceVariableDesc, + // the SPIRV ArraySize is 1 (buffer is not an array), but we need + // the number of 32-bit constants which is BufferStaticSize / sizeof(Uint32) + if (Attribs.BufferStaticSize > 0) + { + ArraySize = Attribs.BufferStaticSize / sizeof(Uint32); + } + else + { + LOG_ERROR_AND_THROW("Resource '", Attribs.Name, "' is marked as inline constants, " + "but has zero buffer size."); + } +} +``` + +### Multiple Inline Constants Support + +Vulkan backend fully supports multiple inline constant buffers: + +- Each inline constant resource creates its own `USAGE_DYNAMIC` uniform buffer +- Each has separate CPU-side staging buffer (`pInlineConstantData`) +- All inline constant buffers are updated together during `PrepareForDraw`/`PrepareForDispatch` + +**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 uniform buffers. + +### Memory Leak Prevention + +The implementation carefully manages memory to prevent leaks: + +1. **Static inline constants**: + - Single allocation in `CreateSetLayouts()` stored in `m_pStaticInlineConstantData` + - Freed in `PipelineResourceSignatureVkImpl::Destruct()` + +2. **SRB inline constants**: + - Single allocation in `InitSRBResourceCache()` + - Ownership transferred to `ShaderResourceCacheVk::m_pInlineConstantMemory` + - Automatically freed by `std::unique_ptr` in `~ShaderResourceCacheVk()` + +### Performance Considerations + +**Vulkan Inline Constants Performance**: + +1. **Overhead**: Similar to D3D11 due to `Map`/`Unmap` operations +2. **Update frequency**: Best for per-draw updates +3. **Buffer sharing**: All SRBs share the same buffer, reducing memory +4. **Future optimization**: True push constants for `layout(push_constant)` blocks (planned) + +--- + +## 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) +- Performanc: 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 + +- Currently emulated using `USAGE_DYNAMIC` uniform buffers +- Requires `MapBuffer` + `memcpy` + `UnmapBuffer` for each update +- Buffer shared across all SRBs (reduces memory, increases update frequency) +- Performance: Similar to D3D11, moderate overhead due to Map/Unmap +- Future: True push constants for `layout(push_constant)` blocks (planned) + +### Performance Comparison + +| Backend | Implementation | Update Cost | Best Use Case | +|---------|---------------------|-------------|----------------------------------| +| D3D12 | Root constants | Very Low | Per-draw, high-frequency updates | +| D3D11 | Dynamic CB + Map | Moderate | Per-draw, moderate frequency | +| Vulkan | Dynamic UB + Map | Moderate | Per-draw, moderate frequency | + +### 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 (each as separate dynamic buffer) + - Vulkan: Full support for multiple inline constants (each as separate dynamic uniform buffer) +5. **D3D11 specific**: Use `DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT` when constants unchanged between draws + +--- + +## 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) + From a627e363cf3ffe9c54414c6992b9d72a6b0255af Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 6 Dec 2025 22:30:44 +0800 Subject: [PATCH 2/5] fix formatting. --- .../include/PipelineResourceSignatureVkImpl.hpp | 8 ++++---- .../src/PipelineResourceSignatureVkImpl.cpp | 14 +++++++------- .../src/ShaderResourceCacheVk.cpp | 2 +- .../src/ShaderVariableManagerVk.cpp | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Graphics/GraphicsEngineVulkan/include/PipelineResourceSignatureVkImpl.hpp b/Graphics/GraphicsEngineVulkan/include/PipelineResourceSignatureVkImpl.hpp index 8bae852beb..f48dc0bd4d 100644 --- a/Graphics/GraphicsEngineVulkan/include/PipelineResourceSignatureVkImpl.hpp +++ b/Graphics/GraphicsEngineVulkan/include/PipelineResourceSignatureVkImpl.hpp @@ -64,10 +64,10 @@ ASSERT_SIZEOF(ImmutableSamplerAttribsVk, 8, "The struct is used in serialization /// are emulated using dynamic uniform buffers (similar to D3D11 backend). struct InlineConstantBufferAttribsVk { - Uint32 DescrSet = 0; // Descriptor set index - Uint32 BindingIndex = 0; // Binding index within the descriptor set - Uint32 NumConstants = 0; // Number of 32-bit constants - RefCntAutoPtr pBuffer; // Internal dynamic uniform buffer + Uint32 DescrSet = 0; // Descriptor set index + Uint32 BindingIndex = 0; // Binding index within the descriptor set + Uint32 NumConstants = 0; // Number of 32-bit constants + RefCntAutoPtr pBuffer; // Internal dynamic uniform buffer }; struct PipelineResourceSignatureInternalDataVk : PipelineResourceSignatureInternalData diff --git a/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp index 1edbcb484d..79169db2e5 100644 --- a/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp @@ -352,7 +352,7 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) CacheGroupOffsets[CacheGroup] += ResDesc.GetArraySize(); VkDescriptorSetLayoutBinding vkSetLayoutBinding{}; - vkSetLayoutBinding.binding = pAttribs->BindingIndex; + vkSetLayoutBinding.binding = pAttribs->BindingIndex; // For inline constants, descriptor count is 1 (single uniform buffer) vkSetLayoutBinding.descriptorCount = ResDesc.GetArraySize(); vkSetLayoutBinding.stageFlags = ShaderTypesToVkShaderStageFlags(ResDesc.ShaderStages); @@ -376,9 +376,9 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) "Only constant buffers can have INLINE_CONSTANTS flag"); InlineConstantBufferAttribsVk& InlineCBAttribs = m_InlineConstantBuffers[InlineConstantBufferIdx++]; - InlineCBAttribs.DescrSet = pAttribs->DescrSet; - InlineCBAttribs.BindingIndex = pAttribs->BindingIndex; - InlineCBAttribs.NumConstants = ResDesc.ArraySize; // For inline constants, ArraySize is the number of 32-bit constants + 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 USAGE_DYNAMIC uniform buffer for emulating inline constants // All SRBs created from this signature will share the same inline constant buffer. @@ -755,7 +755,7 @@ void PipelineResourceSignatureVkImpl::InitSRBResourceCache(ShaderResourceCacheVk Attr.BindingIndex, 0, // ArrayIndex RefCntAutoPtr{InlineCBAttr.pBuffer}, - 0, // BufferBaseOffset + 0, // BufferBaseOffset InlineCBAttr.NumConstants * sizeof(Uint32) // BufferRangeSize }); } @@ -910,8 +910,8 @@ void PipelineResourceSignatureVkImpl::CommitDynamicResources(const ShaderResourc const Uint32 CacheOffset = Attr.CacheOffset(CacheType); // 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(); + const Uint32 ArraySize = ResDesc.GetArraySize(); + const DescriptorType DescrType = Attr.GetDescriptorType(); #ifdef DILIGENT_DEBUG { diff --git a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp index 8d634624a7..c1ced3b95d 100644 --- a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp @@ -964,7 +964,7 @@ void ShaderResourceCacheVk::SetInlineConstants(Uint32 DescrSetIndex, Resource& DstRes = DescrSet.GetResource(CacheOffset); VERIFY(DstRes.pInlineConstantData != nullptr, "Inline constant data pointer is null. " - "Make sure InitializeInlineConstantBuffer was called for this resource."); + "Make sure InitializeInlineConstantBuffer was called for this resource."); // Copy to CPU-side staging buffer Uint32* pDstConstants = reinterpret_cast(DstRes.pInlineConstantData); diff --git a/Graphics/GraphicsEngineVulkan/src/ShaderVariableManagerVk.cpp b/Graphics/GraphicsEngineVulkan/src/ShaderVariableManagerVk.cpp index 27aac2377c..f575af3f6d 100644 --- a/Graphics/GraphicsEngineVulkan/src/ShaderVariableManagerVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/ShaderVariableManagerVk.cpp @@ -656,8 +656,8 @@ void ShaderVariableManagerVk::SetInlineConstants(Uint32 ResIndex, Uint32 FirstConstant, Uint32 NumConstants) { - const PipelineResourceAttribsVk& Attribs = m_pSignature->GetResourceAttribs(ResIndex); - const ResourceCacheContentType CacheType = m_ResourceCache.GetContentType(); + const PipelineResourceAttribsVk& Attribs = m_pSignature->GetResourceAttribs(ResIndex); + const ResourceCacheContentType CacheType = m_ResourceCache.GetContentType(); const Uint32 CacheOffset = Attribs.CacheOffset(CacheType); #ifdef DILIGENT_DEVELOPMENT From f20c19efab6b84dea14dbde456059fb7851826cd Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 6 Dec 2025 22:32:53 +0800 Subject: [PATCH 3/5] fix formatting. --- .../src/PipelineResourceSignatureVkImpl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp index 79169db2e5..ac3ed9e909 100644 --- a/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp @@ -739,7 +739,7 @@ void PipelineResourceSignatureVkImpl::InitSRBResourceCache(ShaderResourceCacheVk Attr.BindingIndex, 0, // ArrayIndex RefCntAutoPtr{InlineCBAttr.pBuffer}, - 0, // BufferBaseOffset + 0, // BufferBaseOffset InlineCBAttr.NumConstants * sizeof(Uint32) // BufferRangeSize }); } From 98bf2194c81d998c600c86136a2fd7e2843b8ac3 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 6 Dec 2025 22:35:44 +0800 Subject: [PATCH 4/5] fix typo --- doc/SetInlineConstants.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/SetInlineConstants.md b/doc/SetInlineConstants.md index 249158c150..92c863f322 100644 --- a/doc/SetInlineConstants.md +++ b/doc/SetInlineConstants.md @@ -990,7 +990,7 @@ pSRB->GetVariableByName(SHADER_TYPE_VERTEX, "Constants") - 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) -- Performanc: Fastest +- Performance: Fastest ### D3D11 From 437a56216aa608fa78e9d3eb8a999539dc79e2fa Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 6 Dec 2025 22:39:14 +0800 Subject: [PATCH 5/5] remove unused var --- .../src/PipelineResourceSignatureVkImpl.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp index ac3ed9e909..e465fb545d 100644 --- a/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/PipelineResourceSignatureVkImpl.cpp @@ -540,8 +540,7 @@ void PipelineResourceSignatureVkImpl::CreateSetLayouts(const bool IsSerialized) // This must be done after the resources are initialized above if (m_pStaticResCache != nullptr && m_NumInlineConstantBuffers > 0) { - const Uint32 TotalResources = GetTotalResourceCount(); - constexpr ResourceCacheContentType StaticCacheType = ResourceCacheContentType::Signature; + const Uint32 TotalResources = GetTotalResourceCount(); // First pass: calculate total memory size needed for all static inline constants Uint32 TotalStaticInlineConstantSize = 0;