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/GraphicsEngineVulkan/include/DeviceContextVkImpl.hpp b/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.hpp index d86ffd775d..08207dd541 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 { @@ -374,6 +377,8 @@ 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 +567,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/PipelineResourceSignatureVkImpl.hpp b/Graphics/GraphicsEngineVulkan/include/PipelineResourceSignatureVkImpl.hpp index d138b8e495..6465adad90 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,11 @@ class PipelineResourceSignatureVkImpl final : public PipelineResourceSignatureBa // The total number storage buffers with dynamic offsets in both descriptor sets, // accounting for array size. Uint16 m_DynamicStorageBufferCount = 0; + + // Number of inline constant buffers + Uint32 m_NumInlineConstantBufferAttribs = 0; + // Inline constant buffer attributes + std::unique_ptr m_InlineConstantBufferAttribs; }; template <> Uint32 PipelineResourceSignatureVkImpl::GetDescriptorSetIndex() const; diff --git a/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.hpp b/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.hpp index 8923ac1fe9..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 26bf336e4d..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,13 +250,44 @@ 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 false; + return m_HasInlineConstants; } ResourceCacheContentType GetContentType() const { return static_cast(m_ContentType); } @@ -287,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/ShaderVkImpl.hpp b/Graphics/GraphicsEngineVulkan/include/ShaderVkImpl.hpp index 1da2b141f1..722e7973d7 100644 --- a/Graphics/GraphicsEngineVulkan/include/ShaderVkImpl.hpp +++ b/Graphics/GraphicsEngineVulkan/include/ShaderVkImpl.hpp @@ -92,7 +92,11 @@ class ShaderVkImpl final : public ShaderBase 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); @@ -117,8 +123,11 @@ class ShaderVkImpl final : public ShaderBase private: std::shared_ptr m_pShaderResources; + std::string m_CIEntryPoint; std::string m_EntryPoint; std::vector m_SPIRV; + + bool m_CILoadConstantBufferReflection = false; }; } // namespace Diligent diff --git a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp index 01bb51d029..00f2617e23 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, Flags & DRAW_FLAG_INLINE_CONSTANTS_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..63265be875 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,10 @@ void PipelineResourceSignatureVkImpl::CopyStaticResources(ShaderResourceCacheVk& if (!HasDescriptorSet(DESCRIPTOR_SET_ID_STATIC_MUTABLE) || m_pStaticResCache == nullptr) return; + if (m_pStaticResCache->GetNumDescriptorSets() == 0) + return; + + // 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. @@ -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 731b4b83ab..e14b2c5400 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,20 @@ void PipelineStateVkImpl::RemapOrVerifyShaderResources( // remap resource bindings. for (size_t s = 0; s < ShaderStages.size(); ++s) { - const std::vector& Shaders = ShaderStages[s].Shaders; - std::vector>& SPIRVs = ShaderStages[s].SPIRVs; - const SHADER_TYPE ShaderType = ShaderStages[s].Type; + const std::vector& Shaders = ShaderStages[s].Shaders; + std::vector>& SPIRVs = ShaderStages[s].SPIRVs; + const SHADER_TYPE ShaderType = ShaderStages[s].Type; + std::vector>& ShaderResources = ShaderStages[s].ShaderResources; VERIFY_EXPR(Shaders.size() == SPIRVs.size()); + VERIFY_EXPR(Shaders.size() == ShaderResources.size()); for (size_t i = 0; i < Shaders.size(); ++i) { - const ShaderVkImpl* pShader = Shaders[i]; - std::vector& SPIRV = SPIRVs[i]; + const ShaderVkImpl* pShader = Shaders[i]; + std::vector& SPIRV = SPIRVs[i]; + const std::shared_ptr& pShaderResources = ShaderResources[i]; - const auto& pShaderResources = pShader->GetShaderResources(); VERIFY_EXPR(pShaderResources); if (pDvpShaderResources) @@ -690,6 +707,10 @@ void PipelineStateVkImpl::RemapOrVerifyShaderResources( pShaderResources->ProcessResources( [&](const SPIRVShaderResourceAttribs& SPIRVAttribs, Uint32) // { + // Push constants don't use descriptor sets and don't need binding/set remapping. + // They are handled via vkCmdPushConstants. We still verify they exist in the signature. + const bool IsPushConstant = (SPIRVAttribs.Type == SPIRVShaderResourceAttribs::ResourceType::PushConstant); + const ResourceAttribution ResAttribution = GetResourceAttribution(SPIRVAttribs.Name, ShaderType, pSignatures, SignatureCount); if (!ResAttribution) { @@ -702,6 +723,22 @@ void PipelineStateVkImpl::RemapOrVerifyShaderResources( const SHADER_RESOURCE_TYPE ResType = SPIRVShaderResourceAttribs::GetShaderResourceType(SPIRVAttribs.Type); const PIPELINE_RESOURCE_FLAGS Flags = SPIRVShaderResourceAttribs::GetPipelineResourceFlags(SPIRVAttribs.Type); + // For push constants, skip descriptor set operations but validate the resource exists. + if (IsPushConstant) + { + if (ResAttribution.ResourceIndex != ResourceAttribution::InvalidResourceIndex) + { + const PipelineResourceDesc& ResDesc = ResAttribution.pSignature->GetResourceDesc(ResAttribution.ResourceIndex); + ValidatePipelineResourceCompatibility(ResDesc, ResType, Flags, SPIRVAttribs.ArraySize, + pShader->GetDesc().Name, SignDesc.Name); + } + if (pDvpResourceAttibutions) + pDvpResourceAttibutions->emplace_back(ResAttribution); + + // Skip descriptor set remapping for push constants + return; + } + Uint32 ResourceBinding = ~0u; Uint32 DescriptorSet = ~0u; if (ResAttribution.ResourceIndex != ResourceAttribution::InvalidResourceIndex) @@ -784,6 +821,98 @@ void PipelineStateVkImpl::RemapOrVerifyShaderResources( } } +bool PipelineStateVkImpl::InitPushConstantInfoFromSignatures(PushConstantInfoVk& PushConstant, + TShaderStages& ShaderStages) const noexcept(false) +{ + // Vulkan allows only one push constant range per pipeline layout. + // DiligentCore allows multiple inline constant resources, so we promote only the first inline constant + // from resource signatures to push constants. Other inline constants remain uniform buffers. + const PipelineResourceDesc* pResDesc = nullptr; + const char* PushConstantName = nullptr; + Uint32 PushConstantSize = 0; + Uint32 PushConstantSignIdx = INVALID_PUSH_CONSTANT_INDEX; + Uint32 PushConstantResIdx = INVALID_PUSH_CONSTANT_INDEX; + + 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 ((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) == 0) + continue; + + // For inline constants, ArraySize contains the number of 32-bit constants. + PushConstantSize = ResDesc.ArraySize * sizeof(Uint32); + PushConstantSignIdx = s; + PushConstantResIdx = r; + PushConstantName = ResDesc.Name; + pResDesc = &ResDesc; + break; + } + + if (pResDesc != nullptr) + break; + } + + // Validate shader-declared push constants against the selected inline constant (if any). + for (const ShaderStageInfo& Stage : ShaderStages) + { + for (const ShaderVkImpl* pShader : Stage.Shaders) + { + const auto& pShaderResources = pShader->GetShaderResources(); + if (!pShaderResources) + continue; + + for (Uint32 pc = 0; pc < pShaderResources->GetNumPushConstants(); ++pc) + { + const SPIRVShaderResourceAttribs& PCAttribs = pShaderResources->GetPushConstant(pc); + + if (pResDesc == nullptr) + { + LOG_ERROR_AND_THROW("Shader '", pShader->GetDesc().Name, "' defines push constants block '", PCAttribs.Name, + "', but the pipeline resource signatures define no inline constant resource to promote to push constants."); + } + + if (strcmp(PushConstantName, PCAttribs.Name) != 0) + { + LOG_ERROR_AND_THROW("Shader '", pShader->GetDesc().Name, "' defines push constants block '", PCAttribs.Name, + "', but the pipeline resource signatures select inline constant resource '", PushConstantName, + "' as push constants (only the first inline constant is promoted in Vulkan)."); + } + + if (PCAttribs.BufferStaticSize != PushConstantSize) + { + LOG_ERROR_AND_THROW("Push constants block size mismatch for resource '", PushConstantName, "' in shader '", + pShader->GetDesc().Name, "': SPIR-V declares ", PCAttribs.BufferStaticSize, + " bytes, but the pipeline resource signature defines ", PushConstantSize, " bytes."); + } + } + } + } + + if (pResDesc == nullptr) + { + // No inline constants found - PushConstant remains empty. + return false; + } + + PushConstant.Size = PushConstantSize; + PushConstant.SignatureIndex = PushConstantSignIdx; + PushConstant.ResourceIndex = PushConstantResIdx; + + for (SHADER_TYPE ShaderTypes = pResDesc->ShaderStages; ShaderTypes != SHADER_TYPE_UNKNOWN;) + { + const SHADER_TYPE ShaderType = ExtractLSB(ShaderTypes); + PushConstant.StageFlags |= ShaderTypeToVkShaderStageFlagBit(ShaderType); + } + + return true; +} + 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. + // Promote the first inline constant from signatures to push constants (and validate any shader-declared push constants). + 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 = ConvertUBOToPushConstants( + 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; } // @@ -986,7 +1221,7 @@ void PipelineStateVkImpl::DvpValidateResourceLimits() const const PipelineResourceSignatureVkImpl::ResourceAttribs& ResAttr = pSignature->GetResourceAttribs(r); const Uint32 DescIndex = static_cast(ResAttr.DescrType); - DescriptorCount[DescIndex] += ResAttr.ArraySize; + DescriptorCount[DescIndex] += ResDesc.GetArraySize(); for (SHADER_TYPE ShaderStages = ResDesc.ShaderStages; ShaderStages != 0;) { diff --git a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp index e186c946c3..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 f3d870cb08..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 @@ -655,7 +656,20 @@ void ShaderVariableManagerVk::SetInlineConstants(Uint32 ResIndex, Uint32 FirstConstant, Uint32 NumConstants) { - UNSUPPORTED("Not yet implemented"); + 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 @@ -664,7 +678,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/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp index d588a3d6d1..b9fc64ef88 100644 --- a/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp @@ -229,47 +229,63 @@ void ShaderVkImpl::Initialize(const ShaderCreateInfo& ShaderCI, // We cannot create shader module here because resource bindings are assigned when // pipeline state is created + m_CILoadConstantBufferReflection = ShaderCI.LoadConstantBufferReflection; + + m_CIEntryPoint = ShaderCI.EntryPoint; // 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 // - }; + std::shared_ptr pShaderResources = CreateSPIRVShaderResources(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)); - m_pShaderResources.reset(static_cast(pRawMem.release()), STDDeleterRawMem(Allocator)); - if (LoadShaderInputs && m_pShaderResources->IsHLSLSource()) + m_pShaderResources = pShaderResources; + + if (m_Desc.ShaderType == SHADER_TYPE_VERTEX && m_pShaderResources->IsHLSLSource()) { m_pShaderResources->MapHLSLVertexShaderInputs(m_SPIRV); } } else { - m_EntryPoint = ShaderCI.EntryPoint; + m_EntryPoint = m_CIEntryPoint; } m_Status.store(SHADER_STATUS_READY); } } +std::shared_ptr ShaderVkImpl::CreateSPIRVShaderResources(const std::vector& SPIRV) noexcept(false) +{ + //This ensures m_EntryPoint always initialized from ShaderCI.EntryPoint when reconstructing shader resources with CreateSPIRVShaderResources + m_EntryPoint = m_CIEntryPoint; + + 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_CILoadConstantBufferReflection, + 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/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp b/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp index 729d421603..656f74bd43 100644 --- a/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp +++ b/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp @@ -129,7 +129,7 @@ class InlineConstants : public ::testing::Test GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); IRenderDevice* pDevice = pEnv->GetDevice(); - if (!pDevice->GetDeviceInfo().IsD3DDevice()) + if (!pDevice->GetDeviceInfo().IsD3DDevice() && !pDevice->GetDeviceInfo().IsVulkanDevice()) { GTEST_SKIP(); } @@ -341,7 +341,7 @@ void InlineConstants::TestSignatures(Uint32 NumSignatures) PipelineResourceSignatureDescX SignDesc{"Inline constants test"}; SignDesc - .AddResource(SHADER_TYPE_VERTEX, "cb0_stat", 1u, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + .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) @@ -360,7 +360,7 @@ void InlineConstants::TestSignatures(Uint32 NumSignatures) SignDesc.BindingIndex = 1; } - SignDesc.AddResource(SHADER_TYPE_VERTEX, "cb1_stat", 1u, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_STATIC) + 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) @@ -369,7 +369,7 @@ void InlineConstants::TestSignatures(Uint32 NumSignatures) .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) + .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) @@ -750,6 +750,11 @@ TEST_F(InlineConstants, RenderStateCache) 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);