From 2d24d64f070e305f5976380d418689b7eb1e9d08 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Tue, 13 Jan 2026 22:27:47 +0800 Subject: [PATCH 1/5] Implement inline constants in WebGPU --- .../include/DeviceContextWebGPUImpl.hpp | 2 +- .../PipelineResourceSignatureWebGPUImpl.hpp | 16 +- .../include/ShaderResourceCacheWebGPU.hpp | 52 +++++- .../src/DeviceContextWebGPUImpl.cpp | 25 ++- .../PipelineResourceSignatureWebGPUImpl.cpp | 150 +++++++++++++++--- .../src/PipelineStateWebGPUImpl.cpp | 26 ++- .../src/ShaderResourceCacheWebGPU.cpp | 117 ++++++++++++-- .../src/ShaderVariableManagerWebGPU.cpp | 12 +- .../src/InlineConstantsTest.cpp | 10 +- 9 files changed, 364 insertions(+), 46 deletions(-) diff --git a/Graphics/GraphicsEngineWebGPU/include/DeviceContextWebGPUImpl.hpp b/Graphics/GraphicsEngineWebGPU/include/DeviceContextWebGPUImpl.hpp index 336eb488d1..8c9b94884a 100644 --- a/Graphics/GraphicsEngineWebGPU/include/DeviceContextWebGPUImpl.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/DeviceContextWebGPUImpl.hpp @@ -381,7 +381,7 @@ class DeviceContextWebGPUImpl final : public DeviceContextBase - void CommitBindGroups(CmdEncoderType CmdEncoder, Uint32 CommitSRBMask); + void CommitBindGroups(CmdEncoderType CmdEncoder, Uint32 CommitSRBMask, bool InlineConstantsIntact = false); UploadMemoryManagerWebGPU::Allocation AllocateUploadMemory(size_t Size, size_t Alignment = 16); DynamicMemoryManagerWebGPU::Allocation AllocateDynamicMemory(size_t Size, size_t Alignment = 16); diff --git a/Graphics/GraphicsEngineWebGPU/include/PipelineResourceSignatureWebGPUImpl.hpp b/Graphics/GraphicsEngineWebGPU/include/PipelineResourceSignatureWebGPUImpl.hpp index f4987212e1..4745846e71 100644 --- a/Graphics/GraphicsEngineWebGPU/include/PipelineResourceSignatureWebGPUImpl.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/PipelineResourceSignatureWebGPUImpl.hpp @@ -50,6 +50,7 @@ namespace Diligent struct WGSLShaderResourceAttribs; class DeviceContextWebGPUImpl; +class BufferWebGPUImpl; struct ImmutableSamplerAttribsWebGPU { @@ -65,9 +66,17 @@ struct ImmutableSamplerAttribsWebGPU }; ASSERT_SIZEOF(ImmutableSamplerAttribsWebGPU, 16, "The struct is used in serialization and must be tightly packed"); +/// Inline constant buffer attributes for WebGPU backend. +/// Similar to Vulkan's InlineConstantBufferAttribsVk. struct InlineConstantBufferAttribsWebGPU { - Uint8 Dummy = 0; + Uint32 BindGroup = 0; // Bind group index (similar to Vulkan's DescrSet) + Uint32 CacheOffset = 0; // Offset within bind group (similar to Vulkan's SRBCacheOffset) + Uint32 NumConstants = 0; // Number of 32-bit constants (= ResDesc.ArraySize) + + // Shared dynamic UBO created in the Signature. + // All SRBs reference this same buffer to reduce memory usage. + RefCntAutoPtr pBuffer; }; struct PipelineResourceSignatureInternalDataWebGPU : PipelineResourceSignatureInternalData @@ -137,6 +146,11 @@ class PipelineResourceSignatureWebGPUImpl final : public PipelineResourceSignatu // Make the base class method visible using TPipelineResourceSignatureBase::CopyStaticResources; + // Updates inline constant buffers before draw/dispatch. + // Must be called when SRB is stale or DRAW_FLAG_INLINE_CONSTANTS_INTACT is not set. + void UpdateInlineConstantBuffers(const ShaderResourceCacheWebGPU& ResourceCache, + DeviceContextWebGPUImpl* pCtx) const; + // Returns the bind group index in the resource cache template Uint32 GetBindGroupIndex() const; diff --git a/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp b/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp index a09a470a20..36314c813d 100644 --- a/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp @@ -58,16 +58,23 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase ~ShaderResourceCacheWebGPU(); - static size_t GetRequiredMemorySize(Uint32 NumGroups, const Uint32* GroupSizes); + static size_t GetRequiredMemorySize(Uint32 NumGroups, const Uint32* GroupSizes, Uint32 TotalInlineConstants = 0); - void InitializeGroups(IMemoryAllocator& MemAllocator, Uint32 NumGroups, const Uint32* GroupSizes); - void InitializeResources(Uint32 GroupIdx, Uint32 Offset, Uint32 ArraySize, BindGroupEntryType Type, bool HasImmutableSampler); + void InitializeGroups(IMemoryAllocator& MemAllocator, Uint32 NumGroups, const Uint32* GroupSizes, Uint32 TotalInlineConstants = 0); + void InitializeResources(Uint32 GroupIdx, + Uint32 Offset, + Uint32 ArraySize, + BindGroupEntryType Type, + bool HasImmutableSampler, + Uint32 InlineConstantOffset = ~0u, + Uint32 DbgNumInlineConstants = 0); struct Resource { - explicit Resource(BindGroupEntryType _Type, bool _HasImmutableSampler) noexcept : + explicit Resource(BindGroupEntryType _Type, bool _HasImmutableSampler, void* _pInlineConstantData = nullptr) noexcept : Type{_Type}, - HasImmutableSampler{_HasImmutableSampler} + HasImmutableSampler{_HasImmutableSampler}, + pInlineConstantData{_pInlineConstantData} { VERIFY(Type == BindGroupEntryType::Texture || Type == BindGroupEntryType::Sampler || !HasImmutableSampler, "Immutable sampler can only be assigned to a textre or a sampler"); @@ -88,11 +95,22 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase // For uniform and storage buffers only /*16 */ Uint64 BufferBaseOffset = 0; /*24 */ Uint64 BufferRangeSize = 0; + // For inline constant buffers only - points to staging data in cache tail +/*32 */ void* const pInlineConstantData = nullptr; // clang-format on void SetUniformBuffer(RefCntAutoPtr&& _pBuffer, Uint64 _RangeOffset, Uint64 _RangeSize); void SetStorageBuffer(RefCntAutoPtr&& _pBufferView); + // Writes inline constant data to the staging buffer. + // IMPORTANT: Does NOT call UpdateRevision() - inline constants can change after SRB commit. + void SetInlineConstants(const void* pData, Uint32 FirstConstant, Uint32 NumConstants) + { + VERIFY(pInlineConstantData != nullptr, "Inline constant data pointer is not initialized"); + VERIFY(pData != nullptr, "Source data is null"); + memcpy(static_cast(pInlineConstantData) + FirstConstant, pData, NumConstants * sizeof(Uint32)); + } + template Uint32 GetDynamicBufferOffset(const DeviceContextWebGPUImpl* pCtx) const; @@ -178,7 +196,7 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase bool HasInlineConstants() const { - return false; + return m_HasInlineConstants; } ResourceCacheContentType GetContentType() const { return static_cast(m_ContentType); } @@ -240,9 +258,31 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase // Indicates what types of resources are stored in the cache const Uint32 m_ContentType : 1; + // Indicates if this cache has inline constants + bool m_HasInlineConstants = false; + + // Pointer to the start of inline constant staging data in the memory tail + Uint32* m_pInlineConstantData = nullptr; + +public: + // Initializes an inline constant buffer resource in the cache + void InitInlineConstantBuffer(Uint32 BindGroupIdx, Uint32 CacheOffset, IDeviceObject* pBuffer, Uint32 NumConstants, Uint32 InlineConstantOffset); + + // Writes inline constant data to the staging buffer + void SetInlineConstants(Uint32 BindGroupIdx, Uint32 CacheOffset, const void* pConstants, Uint32 FirstConstant, Uint32 NumConstants); + + // Copies inline constant data from source cache to this cache + void CopyInlineConstants(const ShaderResourceCacheWebGPU& SrcCache, + Uint32 BindGroupIdx, + Uint32 SrcCacheOffset, + Uint32 DstCacheOffset, + Uint32 NumConstants); + +private: #ifdef DILIGENT_DEBUG // Debug array that stores flags indicating if resources in the cache have been initialized std::vector> m_DbgInitializedResources; + std::vector m_DbgAssignedInlineConstants; Uint8* m_DbgMemoryEnd = nullptr; #endif }; diff --git a/Graphics/GraphicsEngineWebGPU/src/DeviceContextWebGPUImpl.cpp b/Graphics/GraphicsEngineWebGPU/src/DeviceContextWebGPUImpl.cpp index e0231c747e..28898845d9 100644 --- a/Graphics/GraphicsEngineWebGPU/src/DeviceContextWebGPUImpl.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/DeviceContextWebGPUImpl.cpp @@ -218,7 +218,7 @@ void SetBindGroup(WGPUComputePassEncoder Encoder, uint32_t GroupIndex, WGPUBindG } template -void DeviceContextWebGPUImpl::CommitBindGroups(CmdEncoderType CmdEncoder, Uint32 CommitSRBMask) +void DeviceContextWebGPUImpl::CommitBindGroups(CmdEncoderType CmdEncoder, Uint32 CommitSRBMask, bool InlineConstantsIntact) { VERIFY(CommitSRBMask != 0, "This method should not be called when there is nothing to commit"); @@ -232,6 +232,25 @@ void DeviceContextWebGPUImpl::CommitBindGroups(CmdEncoderType CmdEncoder, Uint32 if ((CommitSRBMask & SRBBit) == 0) continue; + const ShaderResourceCacheWebGPU* ResourceCache = m_BindInfo.ResourceCaches[sign]; + VERIFY_EXPR(ResourceCache != nullptr); + + // Update inline constant buffers if needed + const bool SRBStale = (m_BindInfo.StaleSRBMask & SRBBit) != 0; + if ((m_BindInfo.InlineConstantsSRBMask & SRBBit) != 0) + { + VERIFY(ResourceCache->HasInlineConstants(), + "Shader resource cache does not contain inline constants, but the corresponding bit in InlineConstantsSRBMask is set."); + // Update inline constant buffers if the SRB is stale or inline constants have changed + if (SRBStale || !InlineConstantsIntact) + { + if (PipelineResourceSignatureWebGPUImpl* pSign = m_pPipelineState->GetResourceSignature(sign)) + { + pSign->UpdateInlineConstantBuffers(*ResourceCache, this); + } + } + } + Uint32 BindGroupCacheIndex = 0; for (PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID BindGroupId : {PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID_STATIC_MUTABLE, PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID_DYNAMIC}) @@ -240,8 +259,6 @@ void DeviceContextWebGPUImpl::CommitBindGroups(CmdEncoderType CmdEncoder, Uint32 if (!BindGroup.IsActive()) continue; - const ShaderResourceCacheWebGPU* ResourceCache = m_BindInfo.ResourceCaches[sign]; - VERIFY_EXPR(ResourceCache != nullptr); bool DynamicOffsetsChanged = false; if (!BindGroup.DynamicBufferOffsets.empty()) { @@ -249,7 +266,7 @@ void DeviceContextWebGPUImpl::CommitBindGroups(CmdEncoderType CmdEncoder, Uint32 } ++BindGroupCacheIndex; - if ((m_BindInfo.StaleSRBMask & SRBBit) == 0 && !DynamicOffsetsChanged) + if (!SRBStale && !DynamicOffsetsChanged) { continue; } diff --git a/Graphics/GraphicsEngineWebGPU/src/PipelineResourceSignatureWebGPUImpl.cpp b/Graphics/GraphicsEngineWebGPU/src/PipelineResourceSignatureWebGPUImpl.cpp index 03177217f9..c9ae737c42 100644 --- a/Graphics/GraphicsEngineWebGPU/src/PipelineResourceSignatureWebGPUImpl.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/PipelineResourceSignatureWebGPUImpl.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2024-2025 Diligent Graphics LLC + * Copyright 2024-2026 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -331,7 +331,7 @@ PipelineResourceSignatureWebGPUImpl::PipelineResourceSignatureWebGPUImpl(IRefere }, [this]() // { - return ShaderResourceCacheWebGPU::GetRequiredMemorySize(GetNumBindGroups(), m_BindGroupSizes.data()); + return ShaderResourceCacheWebGPU::GetRequiredMemorySize(GetNumBindGroups(), m_BindGroupSizes.data(), m_TotalInlineConstants); }); } catch (...) @@ -368,7 +368,8 @@ void PipelineResourceSignatureWebGPUImpl::CreateBindGroupLayouts(const bool IsSe }; // The total number of static resources in all stages accounting for array sizes. - Uint32 StaticResourceCount = 0; + Uint32 StaticResourceCount = 0; + Uint32 InlineConstantBufferIdx = 0; // Index of the immutable sampler for every sampler in m_Desc.Resources, or InvalidImmutableSamplerIndex. std::vector ResourceToImmutableSamplerInd(m_Desc.NumResources, InvalidImmutableSamplerIndex); @@ -400,9 +401,10 @@ void PipelineResourceSignatureWebGPUImpl::CreateBindGroupLayouts(const bool IsSe // We allocate bindings for immutable samplers separately if (!IsImmutableSampler) { - ReserveBindings(CacheGroup, ResDesc.ArraySize); + // Use GetArraySize() which returns 1 for inline constants (ArraySize is constant count) + ReserveBindings(CacheGroup, ResDesc.GetArraySize()); if (ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) - StaticResourceCount += ResDesc.ArraySize; + StaticResourceCount += ResDesc.GetArraySize(); } } @@ -420,7 +422,7 @@ void PipelineResourceSignatureWebGPUImpl::CreateBindGroupLayouts(const bool IsSe if (StaticResourceCount != 0) { VERIFY_EXPR(m_pStaticResCache != nullptr); - m_pStaticResCache->InitializeGroups(GetRawAllocator(), 1, &StaticResourceCount); + m_pStaticResCache->InitializeGroups(GetRawAllocator(), 1, &StaticResourceCount, m_TotalStaticInlineConstants); } // Bind group mapping (static/mutable (0) or dynamic (1) -> bind group index). @@ -509,6 +511,9 @@ void PipelineResourceSignatureWebGPUImpl::CreateBindGroupLayouts(const bool IsSe // Current offset in the static resource cache Uint32 StaticCacheOffset = 0; + // Current offset in static inline constant storage + Uint32 StaticInlineConstantOffset = 0; + for (Uint32 i = 0; i < m_Desc.NumResources; ++i) { const PipelineResourceDesc& ResDesc = m_Desc.Resources[i]; @@ -545,7 +550,8 @@ void PipelineResourceSignatureWebGPUImpl::CreateBindGroupLayouts(const bool IsSe else { const BIND_GROUP_ID GroupId = VarTypeToBindGroupId(ResDesc.VarType); - AllocateBindings(GroupId, GetResourceCacheGroup(ResDesc), ResDesc.ArraySize, BindGroupIndex, BindingIndex, CacheOffset); + // Use GetArraySize() which returns 1 for inline constants + AllocateBindings(GroupId, GetResourceCacheGroup(ResDesc), ResDesc.GetArraySize(), BindGroupIndex, BindingIndex, CacheOffset); } VERIFY_EXPR(BindGroupIndex != ~0u && BindingIndex != ~0u && CacheOffset != ~0u); @@ -555,7 +561,7 @@ void PipelineResourceSignatureWebGPUImpl::CreateBindGroupLayouts(const bool IsSe new (pAttribs) ResourceAttribs{ BindingIndex, AssignedSamplerInd, - ResDesc.ArraySize, + ResDesc.GetArraySize(), // Use GetArraySize() which returns 1 for inline constants EntryType, BindGroupIndex, SrcImmutableSamplerInd != InvalidImmutableSamplerIndex, @@ -569,8 +575,8 @@ void PipelineResourceSignatureWebGPUImpl::CreateBindGroupLayouts(const bool IsSe "Deserialized binding index (", pAttribs->BindingIndex, ") is invalid: ", BindingIndex, " is expected."); DEV_CHECK_ERR(pAttribs->SamplerInd == AssignedSamplerInd, "Deserialized sampler index (", pAttribs->SamplerInd, ") is invalid: ", AssignedSamplerInd, " is expected."); - DEV_CHECK_ERR(pAttribs->ArraySize == ResDesc.ArraySize, - "Deserialized array size (", pAttribs->ArraySize, ") is invalid: ", ResDesc.ArraySize, " is expected."); + DEV_CHECK_ERR(pAttribs->ArraySize == ResDesc.GetArraySize(), + "Deserialized array size (", pAttribs->ArraySize, ") is invalid: ", ResDesc.GetArraySize(), " is expected."); DEV_CHECK_ERR(pAttribs->GetBindGroupEntryType() == EntryType, "Deserialized bind group entry type is invalid"); DEV_CHECK_ERR(pAttribs->BindGroup == BindGroupIndex, "Deserialized bind group index (", pAttribs->BindGroup, ") is invalid: ", BindGroupIndex, " is expected."); @@ -583,23 +589,45 @@ void PipelineResourceSignatureWebGPUImpl::CreateBindGroupLayouts(const bool IsSe if (!IsImmutableSampler) { - for (Uint32 elem = 0; elem < ResDesc.ArraySize; ++elem) + const Uint32 ArraySize = ResDesc.GetArraySize(); + for (Uint32 elem = 0; elem < ArraySize; ++elem) { WGPUBindGroupLayoutEntry wgpuBGLayoutEntry = GetWGPUBindGroupLayoutEntry(*pAttribs, ResDesc, elem); wgpuBGLayoutEntries[BindGroupIndex].push_back(wgpuBGLayoutEntry); } + // Handle inline constant buffer creation + if (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + { + VERIFY_EXPR(InlineConstantBufferIdx < m_NumInlineConstantBuffers); + InlineConstantBufferAttribsWebGPU& InlineAttrib = m_pInlineConstantBuffers[InlineConstantBufferIdx++]; + InlineAttrib.BindGroup = BindGroupIndex; + InlineAttrib.CacheOffset = CacheOffset; + InlineAttrib.NumConstants = ResDesc.ArraySize; // ArraySize is the number of 32-bit constants for inline constants + InlineAttrib.pBuffer = CreateInlineConstantBuffer(ResDesc.Name, ResDesc.ArraySize); + } + if (ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) { + const bool IsInlineConst = (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0; + VERIFY(pAttribs->BindGroup == 0, "Static resources must always be allocated in bind group 0"); - m_pStaticResCache->InitializeResources(pAttribs->BindGroup, StaticCacheOffset, ResDesc.ArraySize, - pAttribs->GetBindGroupEntryType(), pAttribs->IsImmutableSamplerAssigned()); - StaticCacheOffset += ResDesc.ArraySize; + m_pStaticResCache->InitializeResources(pAttribs->BindGroup, StaticCacheOffset, ArraySize, + pAttribs->GetBindGroupEntryType(), pAttribs->IsImmutableSamplerAssigned(), + IsInlineConst ? StaticInlineConstantOffset : ~0u, + IsInlineConst ? ResDesc.ArraySize : 0); + StaticCacheOffset += ArraySize; + + if (IsInlineConst) + StaticInlineConstantOffset += ResDesc.ArraySize; // For inline constants, ArraySize is the number of 32-bit constants } } } + VERIFY_EXPR(InlineConstantBufferIdx == m_NumInlineConstantBuffers); + VERIFY_EXPR(StaticCacheOffset == StaticResourceCount); + VERIFY_EXPR(StaticInlineConstantOffset == m_TotalStaticInlineConstants); #ifdef DILIGENT_DEBUG if (m_pStaticResCache != nullptr) @@ -710,10 +738,12 @@ void PipelineResourceSignatureWebGPUImpl::InitSRBResourceCache(ShaderResourceCac #endif IMemoryAllocator& CacheMemAllocator = m_SRBMemAllocator.GetResourceCacheDataAllocator(0); - ResourceCache.InitializeGroups(CacheMemAllocator, NumGroups, m_BindGroupSizes.data()); + ResourceCache.InitializeGroups(CacheMemAllocator, NumGroups, m_BindGroupSizes.data(), m_TotalInlineConstants); const Uint32 TotalResources = GetTotalResourceCount(); const ResourceCacheContentType CacheType = ResourceCache.GetContentType(); + + Uint32 InlineConstantOffset = 0; for (Uint32 r = 0; r < TotalResources; ++r) { const PipelineResourceDesc& ResDesc = GetResourceDesc(r); @@ -725,8 +755,42 @@ void PipelineResourceSignatureWebGPUImpl::InitSRBResourceCache(ShaderResourceCac continue; } - ResourceCache.InitializeResources(Attr.BindGroup, Attr.CacheOffset(CacheType), ResDesc.ArraySize, - Attr.GetBindGroupEntryType(), Attr.IsImmutableSamplerAssigned()); + // For inline constants, GetArraySize() returns 1 (actual array size), + // while ArraySize contains the number of 32-bit constants + const Uint32 ResArraySize = ResDesc.GetArraySize(); + const bool IsInlineConst = (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0; + + ResourceCache.InitializeResources(Attr.BindGroup, Attr.CacheOffset(CacheType), ResArraySize, + Attr.GetBindGroupEntryType(), Attr.IsImmutableSamplerAssigned(), + IsInlineConst ? InlineConstantOffset : ~0u, + IsInlineConst ? ResDesc.ArraySize : 0); + if (IsInlineConst) + InlineConstantOffset += ResDesc.ArraySize; + } + VERIFY_EXPR(InlineConstantOffset == m_TotalInlineConstants); + + // Initialize inline constant buffers. + // Each inline constant buffer shares a single dynamic UBO created in CreateBindGroupLayouts(). + // The staging data is stored contiguously at the tail of the resource cache memory. + if (m_NumInlineConstantBuffers > 0) + { + InlineConstantOffset = 0; + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) + { + const InlineConstantBufferAttribsWebGPU& InlineCBAttr = GetInlineConstantBufferAttribs(i); + VERIFY_EXPR(InlineCBAttr.pBuffer); + VERIFY_EXPR(InlineCBAttr.NumConstants > 0); + + ResourceCache.InitInlineConstantBuffer( + InlineCBAttr.BindGroup, + InlineCBAttr.CacheOffset, + InlineCBAttr.pBuffer, + InlineCBAttr.NumConstants, + InlineConstantOffset); + + InlineConstantOffset += InlineCBAttr.NumConstants; + } + VERIFY_EXPR(InlineConstantOffset == m_TotalInlineConstants); } // Initialize immutable samplers @@ -788,6 +852,23 @@ void PipelineResourceSignatureWebGPUImpl::CopyStaticResources(ShaderResourceCach continue; } + // Handle inline constants separately - copy staging data instead of buffer reference + if (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + { + // For inline constants, the buffer is already bound during InitSRBResourceCache. + // We only need to copy the staging data. + if (DstCacheType == ResourceCacheContentType::SRB) + { + DstResourceCache.CopyInlineConstants( + SrcResourceCache, + StaticGroupIdx, + Attr.CacheOffset(SrcCacheType), + Attr.CacheOffset(DstCacheType), + ResDesc.ArraySize); // ArraySize is the number of 32-bit constants for inline constants + } + continue; + } + for (Uint32 ArrInd = 0; ArrInd < ResDesc.ArraySize; ++ArrInd) { const Uint32 SrcCacheOffset = Attr.CacheOffset(SrcCacheType) + ArrInd; @@ -836,6 +917,31 @@ Uint32 PipelineResourceSignatureWebGPUImpl::GetBindGroupIndex(); + VERIFY(pBuffer != nullptr, "Inline constant buffer is null in SRB cache"); + + const Uint32 DataSize = InlineAttrib.NumConstants * sizeof(Uint32); + + // Update the buffer from SRB cache staging data + PVoid pMappedData = nullptr; + pCtx->MapBuffer(pBuffer, MAP_WRITE, MAP_FLAG_DISCARD, pMappedData); + memcpy(pMappedData, CachedRes.pInlineConstantData, DataSize); + pCtx->UnmapBuffer(pBuffer, MAP_WRITE); + } +} + PipelineResourceSignatureWebGPUImpl::PipelineResourceSignatureWebGPUImpl(IReferenceCounters* pRefCounters, RenderDeviceWebGPUImpl* pDevice, const PipelineResourceSignatureDesc& Desc, @@ -852,7 +958,7 @@ PipelineResourceSignatureWebGPUImpl::PipelineResourceSignatureWebGPUImpl(IRefere }, [this]() // { - return ShaderResourceCacheWebGPU::GetRequiredMemorySize(GetNumBindGroups(), m_BindGroupSizes.data()); + return ShaderResourceCacheWebGPU::GetRequiredMemorySize(GetNumBindGroups(), m_BindGroupSizes.data(), m_TotalInlineConstants); }); } catch (...) @@ -931,7 +1037,13 @@ bool PipelineResourceSignatureWebGPUImpl::DvpValidateCommittedResource(const Dev // is bound. It will be null if the type is incorrect. if (const BufferWebGPUImpl* pBufferWebGPU = Res.pObject.RawPtr()) { - pDeviceCtx->DvpVerifyDynamicAllocation(pBufferWebGPU); + // 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. + if ((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) == 0) + { + pDeviceCtx->DvpVerifyDynamicAllocation(pBufferWebGPU); + } if ((WGSLAttribs.BufferStaticSize != 0) && (GetDevice()->GetValidationFlags() & VALIDATION_FLAG_CHECK_SHADER_BUFFER_SIZE) != 0 && diff --git a/Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp b/Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp index 70ca2924d0..9358f573ff 100644 --- a/Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2024-2025 Diligent Graphics LLC + * Copyright 2024-2026 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -778,7 +778,29 @@ PipelineResourceSignatureDescWrapper PipelineStateWebGPUImpl::GetDefaultResource const SHADER_RESOURCE_TYPE ResType = WGSLShaderResourceAttribs::GetShaderResourceType(Attribs.Type); const PIPELINE_RESOURCE_FLAGS Flags = WGSLShaderResourceAttribs::GetPipelineResourceFlags(Attribs.Type) | ShaderVariableFlagsToPipelineResourceFlags(VarDesc.Flags); const WebGPUResourceAttribs WebGPUAttribs = Attribs.GetWebGPUAttribs(VarDesc.Flags); - SignDesc.AddResource(VarDesc.ShaderStages, Attribs.Name, Attribs.ArraySize, ResType, VarDesc.Type, Flags, WebGPUAttribs); + + 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."); + // For inline constants, ArraySize must be the number of 32-bit constants + if (Attribs.BufferStaticSize == 0) + { + LOG_ERROR_AND_THROW("Unable to determine inline constant count for uniform buffer '", + Attribs.Name, "'. Please enable ShaderCreateInfo.LoadConstantBufferReflection."); + } + VERIFY(Attribs.BufferStaticSize % sizeof(Uint32) == 0, "Buffer size must be a multiple of 4 bytes"); + ArraySize = Attribs.BufferStaticSize / sizeof(Uint32); + + if (ArraySize > MAX_INLINE_CONSTANTS) + { + LOG_ERROR_AND_THROW("Inline constants resource '", Attribs.Name, "' has ", + ArraySize, " constants. The maximum supported number of inline constants is ", + MAX_INLINE_CONSTANTS, '.'); + } + } + + SignDesc.AddResource(VarDesc.ShaderStages, Attribs.Name, ArraySize, ResType, VarDesc.Type, Flags, WebGPUAttribs); } else { diff --git a/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp b/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp index 69c4bbd521..ff5a6e0e78 100644 --- a/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp @@ -37,7 +37,7 @@ namespace Diligent { -static size_t GetMemorySize(Uint32 NumGroups, Uint32 TotalResources) +static size_t GetMemorySize(Uint32 NumGroups, Uint32 TotalResources, Uint32 TotalInlineConstants) { size_t MemorySize = NumGroups * sizeof(ShaderResourceCacheWebGPU::BindGroup); @@ -47,17 +47,20 @@ static size_t GetMemorySize(Uint32 NumGroups, Uint32 TotalResources) MemorySize = AlignUp(MemorySize, alignof(WGPUBindGroupEntry)); MemorySize += TotalResources * sizeof(WGPUBindGroupEntry); + MemorySize = AlignUp(MemorySize, alignof(Uint32)); + MemorySize += TotalInlineConstants * sizeof(Uint32); + return MemorySize; } -size_t ShaderResourceCacheWebGPU::GetRequiredMemorySize(Uint32 NumGroups, const Uint32* GroupSizes) +size_t ShaderResourceCacheWebGPU::GetRequiredMemorySize(Uint32 NumGroups, const Uint32* GroupSizes, Uint32 TotalInlineConstants) { Uint32 TotalResources = 0; for (Uint32 t = 0; t < NumGroups; ++t) { TotalResources += GroupSizes[t]; } - return GetMemorySize(NumGroups, TotalResources); + return GetMemorySize(NumGroups, TotalResources, TotalInlineConstants); } ShaderResourceCacheWebGPU::ShaderResourceCacheWebGPU(ResourceCacheContentType ContentType) noexcept : @@ -77,7 +80,7 @@ ShaderResourceCacheWebGPU::~ShaderResourceCacheWebGPU() } } -void ShaderResourceCacheWebGPU::InitializeGroups(IMemoryAllocator& MemAllocator, Uint32 NumGroups, const Uint32* GroupSizes) +void ShaderResourceCacheWebGPU::InitializeGroups(IMemoryAllocator& MemAllocator, Uint32 NumGroups, const Uint32* GroupSizes, Uint32 TotalInlineConstants) { VERIFY(!m_pMemory, "Memory has already been allocated"); @@ -86,7 +89,7 @@ void ShaderResourceCacheWebGPU::InitializeGroups(IMemoryAllocator& MemAllocator, // m_pMemory // | // V - // || BindGroup[0] | .... | BindGroup[Ng-1] | Res[0] | ... | Res[n-1] | .... | Res[0] | ... | Res[m-1] | wgpuEntry[0] | ... | wgpuEntry[n-1] | .... | wgpuEntry[0] | ... | wgpuEntry[m-1] | DynOffset[0] | ... | DynOffset[k-1] || + // || BindGroup[0] | .... | BindGroup[Ng-1] | Res[0] | ... | Res[n-1] | .... | Res[0] | ... | Res[m-1] | wgpuEntry[0] | ... | wgpuEntry[n-1] | .... | wgpuEntry[0] | ... | wgpuEntry[m-1] | InlineConstantData[0] | ... | InlineConstantData[k-1] || // // // Ng = m_NumBindGroups @@ -101,12 +104,16 @@ void ShaderResourceCacheWebGPU::InitializeGroups(IMemoryAllocator& MemAllocator, m_TotalResources += GroupSizes[t]; } - const size_t MemorySize = GetMemorySize(NumGroups, m_TotalResources); - VERIFY_EXPR(MemorySize == GetRequiredMemorySize(NumGroups, GroupSizes)); + const size_t MemorySize = GetMemorySize(NumGroups, m_TotalResources, TotalInlineConstants); + VERIFY_EXPR(MemorySize == GetRequiredMemorySize(NumGroups, GroupSizes, TotalInlineConstants)); #ifdef DILIGENT_DEBUG m_DbgInitializedResources.resize(m_NumBindGroups); + m_DbgAssignedInlineConstants.resize(TotalInlineConstants); #endif + // Set flag indicating whether this cache has inline constants + m_HasInlineConstants = (TotalInlineConstants > 0); + if (MemorySize > 0) { m_pMemory = decltype(m_pMemory){ @@ -143,6 +150,12 @@ void ShaderResourceCacheWebGPU::InitializeGroups(IMemoryAllocator& MemAllocator, m_DbgInitializedResources[t].resize(GroupSize); #endif } + + // Set pointer to inline constant staging data at the tail of the memory block + if (TotalInlineConstants > 0) + { + m_pInlineConstantData = reinterpret_cast(pCurrWGPUEntryPtr); + } } } @@ -261,16 +274,37 @@ Uint32 ShaderResourceCacheWebGPU::Resource::GetDynamicBufferOffset(m_pInlineConstantData + InlineConstantOffset)); + + // Bind the emulation buffer as a uniform buffer + // Use full buffer size (NumConstants * sizeof(Uint32)) + RefCntAutoPtr pBufferRef{pBuffer}; + SetResource(BindGroupIdx, CacheOffset, std::move(pBufferRef), 0, NumConstants * sizeof(Uint32)); +} + +void ShaderResourceCacheWebGPU::SetInlineConstants(Uint32 BindGroupIdx, + Uint32 CacheOffset, + const void* pConstants, + Uint32 FirstConstant, + Uint32 NumConstants) +{ + BindGroup& Group = GetBindGroup(BindGroupIdx); + Resource& Res = Group.GetResource(CacheOffset); + + VERIFY_EXPR(Res.pInlineConstantData != nullptr); + VERIFY_EXPR(pConstants != nullptr); + + // Copy constants to the staging buffer + // IMPORTANT: Do NOT call UpdateRevision() - inline constants can change after SRB commit + Res.SetInlineConstants(pConstants, FirstConstant, NumConstants); +} + +void ShaderResourceCacheWebGPU::CopyInlineConstants(const ShaderResourceCacheWebGPU& SrcCache, + Uint32 BindGroupIdx, + Uint32 SrcCacheOffset, + Uint32 DstCacheOffset, + Uint32 NumConstants) +{ + const BindGroup& SrcGroup = SrcCache.GetBindGroup(BindGroupIdx); + const Resource& SrcRes = SrcGroup.GetResource(SrcCacheOffset); + + const BindGroup& DstGroup = GetBindGroup(BindGroupIdx); + const Resource& DstRes = DstGroup.GetResource(DstCacheOffset); + + VERIFY_EXPR(SrcRes.pInlineConstantData != nullptr); + VERIFY_EXPR(DstRes.pInlineConstantData != nullptr); + + // Copy inline constant staging data from source cache to destination cache + memcpy(DstRes.pInlineConstantData, SrcRes.pInlineConstantData, NumConstants * sizeof(Uint32)); +} + } // namespace Diligent diff --git a/Graphics/GraphicsEngineWebGPU/src/ShaderVariableManagerWebGPU.cpp b/Graphics/GraphicsEngineWebGPU/src/ShaderVariableManagerWebGPU.cpp index 9c829b4dea..d47c29cf20 100644 --- a/Graphics/GraphicsEngineWebGPU/src/ShaderVariableManagerWebGPU.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/ShaderVariableManagerWebGPU.cpp @@ -534,7 +534,17 @@ void ShaderVariableManagerWebGPU::SetInlineConstants(Uint32 ResIndex, Uint32 FirstConstant, Uint32 NumConstants) { - UNSUPPORTED("Not implemented yet"); + const PipelineResourceDesc& ResDesc = GetResourceDesc(ResIndex); + const PipelineResourceAttribsWebGPU& Attribs = GetResourceAttribs(ResIndex); + + VERIFY_EXPR(ResDesc.ResourceType == SHADER_RESOURCE_TYPE_CONSTANT_BUFFER); + +#ifdef DILIGENT_DEVELOPMENT + VerifyInlineConstants(ResDesc, pConstants, FirstConstant, NumConstants); +#endif + + m_ResourceCache.SetInlineConstants(Attribs.BindGroup, Attribs.CacheOffset(m_ResourceCache.GetContentType()), + pConstants, FirstConstant, NumConstants); } IDeviceObject* ShaderVariableManagerWebGPU::Get(Uint32 ArrayIndex, Uint32 ResIndex) const diff --git a/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp b/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp index 83d5792675..2c652a80bc 100644 --- a/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp +++ b/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp @@ -213,7 +213,10 @@ class InlineConstants : public ::testing::Test GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); IRenderDevice* pDevice = pEnv->GetDevice(); - if (!pDevice->GetDeviceInfo().IsD3DDevice() && !pDevice->GetDeviceInfo().IsVulkanDevice() && !pDevice->GetDeviceInfo().IsGLDevice()) + if (!pDevice->GetDeviceInfo().IsD3DDevice() && + !pDevice->GetDeviceInfo().IsVulkanDevice() && + !pDevice->GetDeviceInfo().IsGLDevice() && + !pDevice->GetDeviceInfo().IsWebGPUDevice()) { GTEST_SKIP(); } @@ -1166,9 +1169,10 @@ TEST_F(InlineConstants, RenderStateCache) PresentInCache = true; } #endif - if (pDevice->GetDeviceInfo().IsVulkanDevice()) + if (pDevice->GetDeviceInfo().IsVulkanDevice() || + pDevice->GetDeviceInfo().IsWebGPUDevice()) { - // For some reason, pUncachedVS and pVS1 got same hash computation result on Vulkan. + // For some reason, pUncachedVS and pVS1 got same hash computation result on Vulkan and WebGPU. PresentInCache = true; } CreatePSOFromCache(pCache, PresentInCache, pUncachedVS, pUncachedPS, &pPSO2); From 230d6c5c0a177a754612f092ff279ae258fe67dc Mon Sep 17 00:00:00 2001 From: assiduous Date: Wed, 14 Jan 2026 20:26:41 -0800 Subject: [PATCH 2/5] ShaderResourceCacheWebGPU: remove m_HasInlineConstants member --- .../include/ShaderResourceCacheWebGPU.hpp | 5 +---- .../GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp | 4 ---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp b/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp index 36314c813d..c19cfa0c46 100644 --- a/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp @@ -196,7 +196,7 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase bool HasInlineConstants() const { - return m_HasInlineConstants; + return m_pInlineConstantData != nullptr; } ResourceCacheContentType GetContentType() const { return static_cast(m_ContentType); } @@ -258,9 +258,6 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase // Indicates what types of resources are stored in the cache const Uint32 m_ContentType : 1; - // Indicates if this cache has inline constants - bool m_HasInlineConstants = false; - // Pointer to the start of inline constant staging data in the memory tail Uint32* m_pInlineConstantData = nullptr; diff --git a/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp b/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp index ff5a6e0e78..af39c4bfa8 100644 --- a/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp @@ -111,9 +111,6 @@ void ShaderResourceCacheWebGPU::InitializeGroups(IMemoryAllocator& MemAllocator, m_DbgAssignedInlineConstants.resize(TotalInlineConstants); #endif - // Set flag indicating whether this cache has inline constants - m_HasInlineConstants = (TotalInlineConstants > 0); - if (MemorySize > 0) { m_pMemory = decltype(m_pMemory){ @@ -618,7 +615,6 @@ void ShaderResourceCacheWebGPU::InitInlineConstantBuffer(Uint32 BindGrou Uint32 NumConstants, Uint32 InlineConstantOffset) { - VERIFY_EXPR(m_HasInlineConstants); VERIFY_EXPR(m_pInlineConstantData != nullptr); VERIFY_EXPR(pBuffer != nullptr); From cd97bf65196248e4a16baf3ba000d24236dfc577 Mon Sep 17 00:00:00 2001 From: assiduous Date: Sat, 17 Jan 2026 14:21:08 -0800 Subject: [PATCH 3/5] ShaderResourceCacheWebGPU: remove InitInlineConstantBuffer Use SetResource instead --- .../include/ShaderResourceCacheWebGPU.hpp | 3 -- .../PipelineResourceSignatureWebGPUImpl.cpp | 32 +++++++------------ .../src/ShaderResourceCacheWebGPU.cpp | 24 -------------- 3 files changed, 12 insertions(+), 47 deletions(-) diff --git a/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp b/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp index c19cfa0c46..e94917a625 100644 --- a/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp @@ -262,9 +262,6 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase Uint32* m_pInlineConstantData = nullptr; public: - // Initializes an inline constant buffer resource in the cache - void InitInlineConstantBuffer(Uint32 BindGroupIdx, Uint32 CacheOffset, IDeviceObject* pBuffer, Uint32 NumConstants, Uint32 InlineConstantOffset); - // Writes inline constant data to the staging buffer void SetInlineConstants(Uint32 BindGroupIdx, Uint32 CacheOffset, const void* pConstants, Uint32 FirstConstant, Uint32 NumConstants); diff --git a/Graphics/GraphicsEngineWebGPU/src/PipelineResourceSignatureWebGPUImpl.cpp b/Graphics/GraphicsEngineWebGPU/src/PipelineResourceSignatureWebGPUImpl.cpp index c9ae737c42..3b5782aa6f 100644 --- a/Graphics/GraphicsEngineWebGPU/src/PipelineResourceSignatureWebGPUImpl.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/PipelineResourceSignatureWebGPUImpl.cpp @@ -770,27 +770,19 @@ void PipelineResourceSignatureWebGPUImpl::InitSRBResourceCache(ShaderResourceCac VERIFY_EXPR(InlineConstantOffset == m_TotalInlineConstants); // Initialize inline constant buffers. - // Each inline constant buffer shares a single dynamic UBO created in CreateBindGroupLayouts(). - // The staging data is stored contiguously at the tail of the resource cache memory. - if (m_NumInlineConstantBuffers > 0) + for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) { - InlineConstantOffset = 0; - for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) - { - const InlineConstantBufferAttribsWebGPU& InlineCBAttr = GetInlineConstantBufferAttribs(i); - VERIFY_EXPR(InlineCBAttr.pBuffer); - VERIFY_EXPR(InlineCBAttr.NumConstants > 0); - - ResourceCache.InitInlineConstantBuffer( - InlineCBAttr.BindGroup, - InlineCBAttr.CacheOffset, - InlineCBAttr.pBuffer, - InlineCBAttr.NumConstants, - InlineConstantOffset); - - InlineConstantOffset += InlineCBAttr.NumConstants; - } - VERIFY_EXPR(InlineConstantOffset == m_TotalInlineConstants); + const InlineConstantBufferAttribsWebGPU& InlineCBAttr = GetInlineConstantBufferAttribs(i); + VERIFY_EXPR(InlineCBAttr.pBuffer); + VERIFY_EXPR(InlineCBAttr.NumConstants > 0); + // Note that since we use SetResource, the buffer will count towards the number of + // dynamic buffers in the cache. + ResourceCache.SetResource( + InlineCBAttr.BindGroup, + InlineCBAttr.CacheOffset, + InlineCBAttr.pBuffer, + 0, + InlineCBAttr.NumConstants * sizeof(Uint32)); } // Initialize immutable samplers diff --git a/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp b/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp index af39c4bfa8..ef48c4b9a2 100644 --- a/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp @@ -609,30 +609,6 @@ void ShaderResourceCacheWebGPU::DbgVerifyDynamicBuffersCounter() const } #endif -void ShaderResourceCacheWebGPU::InitInlineConstantBuffer(Uint32 BindGroupIdx, - Uint32 CacheOffset, - IDeviceObject* pBuffer, - Uint32 NumConstants, - Uint32 InlineConstantOffset) -{ - VERIFY_EXPR(m_pInlineConstantData != nullptr); - VERIFY_EXPR(pBuffer != nullptr); - - BindGroup& Group = GetBindGroup(BindGroupIdx); - Resource& Res = Group.GetResource(CacheOffset); - - VERIFY_EXPR(Res.Type == BindGroupEntryType::UniformBuffer || - Res.Type == BindGroupEntryType::UniformBufferDynamic); - - // Inline constant staging pointer must be initialized in InitializeResources(). - VERIFY_EXPR(Res.pInlineConstantData == static_cast(m_pInlineConstantData + InlineConstantOffset)); - - // Bind the emulation buffer as a uniform buffer - // Use full buffer size (NumConstants * sizeof(Uint32)) - RefCntAutoPtr pBufferRef{pBuffer}; - SetResource(BindGroupIdx, CacheOffset, std::move(pBufferRef), 0, NumConstants * sizeof(Uint32)); -} - void ShaderResourceCacheWebGPU::SetInlineConstants(Uint32 BindGroupIdx, Uint32 CacheOffset, const void* pConstants, From c8078493558301507746c259955c827751f896ce Mon Sep 17 00:00:00 2001 From: assiduous Date: Sat, 17 Jan 2026 14:43:39 -0800 Subject: [PATCH 4/5] ShaderResourceCacheWebGPU: remove m_pInlineConstantData --- .../include/ShaderResourceCacheWebGPU.hpp | 39 +++++++++++-------- .../src/ShaderResourceCacheWebGPU.cpp | 9 ++--- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp b/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp index e94917a625..645de407cf 100644 --- a/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp @@ -95,7 +95,7 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase // For uniform and storage buffers only /*16 */ Uint64 BufferBaseOffset = 0; /*24 */ Uint64 BufferRangeSize = 0; - // For inline constant buffers only - points to staging data in cache tail + // For inline constant buffers only - pointer to the staging data /*32 */ void* const pInlineConstantData = nullptr; // clang-format on @@ -196,7 +196,7 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase bool HasInlineConstants() const { - return m_pInlineConstantData != nullptr; + return m_HasInlineConstants != 0; } ResourceCacheContentType GetContentType() const { return static_cast(m_ContentType); } @@ -208,6 +208,16 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase std::vector& Offsets, Uint32 GroupIdx) const; + // Writes inline constant data to the staging buffer + void SetInlineConstants(Uint32 BindGroupIdx, Uint32 CacheOffset, const void* pConstants, Uint32 FirstConstant, Uint32 NumConstants); + + // Copies inline constant data from source cache to this cache + void CopyInlineConstants(const ShaderResourceCacheWebGPU& SrcCache, + Uint32 BindGroupIdx, + Uint32 SrcCacheOffset, + Uint32 DstCacheOffset, + Uint32 NumConstants); + #ifdef DILIGENT_DEBUG // For debug purposes only void DbgVerifyResourceInitialization() const; @@ -239,6 +249,13 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase "WGPUBindGroupEntry storage exceeds allocated memory. This indicates a bug in memory calculation logic"); return pFirstWGPUEntry; } + Uint32* GetInlineConstantDataPtr(Uint32 Offset = 0) + { + Uint32* pFirstInlineConstantData = AlignUpPtr(reinterpret_cast(GetFirstWGPUEntryPtr() + m_TotalResources)); + VERIFY(reinterpret_cast(pFirstInlineConstantData + Offset) <= m_DbgMemoryEnd, + "Inline constant storage exceeds allocated memory. This indicates a bug in memory calculation logic"); + return pFirstInlineConstantData + Offset; + } BindGroup& GetBindGroup(Uint32 Index) { VERIFY_EXPR(Index < m_NumBindGroups); @@ -253,26 +270,14 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase // The total actual number of dynamic buffers (that were created with USAGE_DYNAMIC) bound in the resource cache // regardless of the variable type. 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; - // Pointer to the start of inline constant staging data in the memory tail - Uint32* m_pInlineConstantData = nullptr; + // Indicates whether the cache has inline constants + Uint32 m_HasInlineConstants : 1; -public: - // Writes inline constant data to the staging buffer - void SetInlineConstants(Uint32 BindGroupIdx, Uint32 CacheOffset, const void* pConstants, Uint32 FirstConstant, Uint32 NumConstants); - - // Copies inline constant data from source cache to this cache - void CopyInlineConstants(const ShaderResourceCacheWebGPU& SrcCache, - Uint32 BindGroupIdx, - Uint32 SrcCacheOffset, - Uint32 DstCacheOffset, - Uint32 NumConstants); - -private: #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/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp b/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp index ef48c4b9a2..74af4dc911 100644 --- a/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp @@ -148,11 +148,7 @@ void ShaderResourceCacheWebGPU::InitializeGroups(IMemoryAllocator& MemAllocator, #endif } - // Set pointer to inline constant staging data at the tail of the memory block - if (TotalInlineConstants > 0) - { - m_pInlineConstantData = reinterpret_cast(pCurrWGPUEntryPtr); - } + m_HasInlineConstants = (TotalInlineConstants > 0) ? 1u : 0u; } } @@ -282,10 +278,11 @@ void ShaderResourceCacheWebGPU::InitializeResources(Uint32 GroupIdx, BindGroup& Group = GetBindGroup(GroupIdx); for (Uint32 res = 0; res < ArraySize; ++res) { + VERIFY(InlineConstantOffset == ~0u || ArraySize == 1, "InlineConstantOffset can only be specified for single resources, but ArraySize is ", ArraySize); new (&Group.GetResource(Offset + res)) Resource{ Type, HasImmutableSampler, - InlineConstantOffset != ~0u ? m_pInlineConstantData + InlineConstantOffset : nullptr, + InlineConstantOffset != ~0u ? GetInlineConstantDataPtr(InlineConstantOffset) : nullptr, }; #ifdef DILIGENT_DEBUG m_DbgInitializedResources[GroupIdx][size_t{Offset} + res] = true; From 24b516f4e6dd191e6d05a269f247adca77fa46e4 Mon Sep 17 00:00:00 2001 From: assiduous Date: Sun, 18 Jan 2026 09:18:13 -0800 Subject: [PATCH 5/5] ShaderResourceCacheWebGPU: rework inline constant buffer initialization --- .../include/ShaderResourceCacheWebGPU.hpp | 24 +++++-- .../PipelineResourceSignatureWebGPUImpl.cpp | 72 ++++++++++--------- .../src/ShaderResourceCacheWebGPU.cpp | 50 +++++++++---- 3 files changed, 94 insertions(+), 52 deletions(-) diff --git a/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp b/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp index 645de407cf..60dc6a600c 100644 --- a/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/ShaderResourceCacheWebGPU.hpp @@ -65,21 +65,31 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase Uint32 Offset, Uint32 ArraySize, BindGroupEntryType Type, - bool HasImmutableSampler, - Uint32 InlineConstantOffset = ~0u, - Uint32 DbgNumInlineConstants = 0); + bool HasImmutableSampler); + void InitializeInlineConstantBuffer(Uint32 GroupIdx, + Uint32 Offset, + Uint32 InlineConstantOffset, + Uint32 NumInlineConstants, + RefCntAutoPtr pObject = {}); struct Resource { - explicit Resource(BindGroupEntryType _Type, bool _HasImmutableSampler, void* _pInlineConstantData = nullptr) noexcept : + explicit Resource(BindGroupEntryType _Type, bool _HasImmutableSampler) noexcept : Type{_Type}, - HasImmutableSampler{_HasImmutableSampler}, - pInlineConstantData{_pInlineConstantData} + HasImmutableSampler{_HasImmutableSampler} { VERIFY(Type == BindGroupEntryType::Texture || Type == BindGroupEntryType::Sampler || !HasImmutableSampler, "Immutable sampler can only be assigned to a textre or a sampler"); } + explicit Resource(BindGroupEntryType _Type, void* _pInlineConstantData = nullptr) noexcept : + Type{_Type}, + HasImmutableSampler{false}, + pInlineConstantData{_pInlineConstantData} + { + VERIFY(Type == BindGroupEntryType::UniformBufferDynamic, "Inline constant buffer must be of type UniformBufferDynamic"); + } + // clang-format off Resource (const Resource&) = delete; Resource ( Resource&&) = delete; @@ -108,6 +118,8 @@ class ShaderResourceCacheWebGPU : public ShaderResourceCacheBase { VERIFY(pInlineConstantData != nullptr, "Inline constant data pointer is not initialized"); VERIFY(pData != nullptr, "Source data is null"); + VERIFY(FirstConstant + NumConstants <= BufferRangeSize / sizeof(Uint32), + "Too many constants (", FirstConstant + NumConstants, ") for the allocated space (", BufferRangeSize / sizeof(Uint32), ")"); memcpy(static_cast(pInlineConstantData) + FirstConstant, pData, NumConstants * sizeof(Uint32)); } diff --git a/Graphics/GraphicsEngineWebGPU/src/PipelineResourceSignatureWebGPUImpl.cpp b/Graphics/GraphicsEngineWebGPU/src/PipelineResourceSignatureWebGPUImpl.cpp index 3b5782aa6f..a7202dd56e 100644 --- a/Graphics/GraphicsEngineWebGPU/src/PipelineResourceSignatureWebGPUImpl.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/PipelineResourceSignatureWebGPUImpl.cpp @@ -609,17 +609,25 @@ void PipelineResourceSignatureWebGPUImpl::CreateBindGroupLayouts(const bool IsSe if (ResDesc.VarType == SHADER_RESOURCE_VARIABLE_TYPE_STATIC) { - const bool IsInlineConst = (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0; - VERIFY(pAttribs->BindGroup == 0, "Static resources must always be allocated in bind group 0"); - m_pStaticResCache->InitializeResources(pAttribs->BindGroup, StaticCacheOffset, ArraySize, - pAttribs->GetBindGroupEntryType(), pAttribs->IsImmutableSamplerAssigned(), - IsInlineConst ? StaticInlineConstantOffset : ~0u, - IsInlineConst ? ResDesc.ArraySize : 0); - StaticCacheOffset += ArraySize; + if ((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0) + { + VERIFY(pAttribs->GetBindGroupEntryType() == BindGroupEntryType::UniformBufferDynamic, + "Only dynamic uniform buffers can be inline constant buffers"); + VERIFY(!pAttribs->IsImmutableSamplerAssigned(), + "Inline constant buffers cannot have immutable samplers assigned"); + m_pStaticResCache->InitializeInlineConstantBuffer(pAttribs->BindGroup, StaticCacheOffset, + StaticInlineConstantOffset, ResDesc.ArraySize); - if (IsInlineConst) StaticInlineConstantOffset += ResDesc.ArraySize; // For inline constants, ArraySize is the number of 32-bit constants + } + else + { + m_pStaticResCache->InitializeResources(pAttribs->BindGroup, StaticCacheOffset, ArraySize, + pAttribs->GetBindGroupEntryType(), + pAttribs->IsImmutableSamplerAssigned()); + } + StaticCacheOffset += ArraySize; } } } @@ -743,7 +751,8 @@ void PipelineResourceSignatureWebGPUImpl::InitSRBResourceCache(ShaderResourceCac const Uint32 TotalResources = GetTotalResourceCount(); const ResourceCacheContentType CacheType = ResourceCache.GetContentType(); - Uint32 InlineConstantOffset = 0; + Uint32 InlineConstantBufferIdx = 0; + Uint32 InlineConstantOffset = 0; for (Uint32 r = 0; r < TotalResources; ++r) { const PipelineResourceDesc& ResDesc = GetResourceDesc(r); @@ -757,33 +766,30 @@ void PipelineResourceSignatureWebGPUImpl::InitSRBResourceCache(ShaderResourceCac // For inline constants, GetArraySize() returns 1 (actual array size), // while ArraySize contains the number of 32-bit constants - const Uint32 ResArraySize = ResDesc.GetArraySize(); - const bool IsInlineConst = (ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0; - - ResourceCache.InitializeResources(Attr.BindGroup, Attr.CacheOffset(CacheType), ResArraySize, - Attr.GetBindGroupEntryType(), Attr.IsImmutableSamplerAssigned(), - IsInlineConst ? InlineConstantOffset : ~0u, - IsInlineConst ? ResDesc.ArraySize : 0); - if (IsInlineConst) + const Uint32 ResArraySize = ResDesc.GetArraySize(); + if ((ResDesc.Flags & PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) != 0) + { + const InlineConstantBufferAttribsWebGPU& InlineCBAttr = GetInlineConstantBufferAttribs(InlineConstantBufferIdx++); + VERIFY_EXPR(InlineCBAttr.BindGroup == Attr.BindGroup); + VERIFY_EXPR(InlineCBAttr.CacheOffset == Attr.CacheOffset(CacheType)); + VERIFY_EXPR(InlineCBAttr.pBuffer); + VERIFY_EXPR(InlineCBAttr.NumConstants > 0); + + VERIFY(Attr.GetBindGroupEntryType() == BindGroupEntryType::UniformBufferDynamic, "Only dynamic uniform buffers can be inline constant buffers"); + VERIFY(!Attr.IsImmutableSamplerAssigned(), "Inline constant buffers cannot have immutable samplers assigned"); + ResourceCache.InitializeInlineConstantBuffer(Attr.BindGroup, Attr.CacheOffset(CacheType), + InlineConstantOffset, ResDesc.ArraySize, InlineCBAttr.pBuffer); + InlineConstantOffset += ResDesc.ArraySize; + } + else + { + ResourceCache.InitializeResources(Attr.BindGroup, Attr.CacheOffset(CacheType), ResArraySize, + Attr.GetBindGroupEntryType(), Attr.IsImmutableSamplerAssigned()); + } } VERIFY_EXPR(InlineConstantOffset == m_TotalInlineConstants); - - // Initialize inline constant buffers. - for (Uint32 i = 0; i < m_NumInlineConstantBuffers; ++i) - { - const InlineConstantBufferAttribsWebGPU& InlineCBAttr = GetInlineConstantBufferAttribs(i); - VERIFY_EXPR(InlineCBAttr.pBuffer); - VERIFY_EXPR(InlineCBAttr.NumConstants > 0); - // Note that since we use SetResource, the buffer will count towards the number of - // dynamic buffers in the cache. - ResourceCache.SetResource( - InlineCBAttr.BindGroup, - InlineCBAttr.CacheOffset, - InlineCBAttr.pBuffer, - 0, - InlineCBAttr.NumConstants * sizeof(Uint32)); - } + VERIFY_EXPR(InlineConstantBufferIdx == m_NumInlineConstantBuffers); // Initialize immutable samplers for (Uint32 i = 0; i < m_Desc.NumImmutableSamplers; ++i) diff --git a/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp b/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp index 74af4dc911..040350bb0d 100644 --- a/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/ShaderResourceCacheWebGPU.cpp @@ -271,34 +271,56 @@ void ShaderResourceCacheWebGPU::InitializeResources(Uint32 GroupIdx, Uint32 Offset, Uint32 ArraySize, BindGroupEntryType Type, - bool HasImmutableSampler, - Uint32 InlineConstantOffset, - Uint32 DbgNumInlineConstants) + bool HasImmutableSampler) { BindGroup& Group = GetBindGroup(GroupIdx); for (Uint32 res = 0; res < ArraySize; ++res) { - VERIFY(InlineConstantOffset == ~0u || ArraySize == 1, "InlineConstantOffset can only be specified for single resources, but ArraySize is ", ArraySize); new (&Group.GetResource(Offset + res)) Resource{ Type, HasImmutableSampler, - InlineConstantOffset != ~0u ? GetInlineConstantDataPtr(InlineConstantOffset) : nullptr, }; #ifdef DILIGENT_DEBUG m_DbgInitializedResources[GroupIdx][size_t{Offset} + res] = true; +#endif + } +} + +void ShaderResourceCacheWebGPU::InitializeInlineConstantBuffer(Uint32 GroupIdx, + Uint32 Offset, + Uint32 InlineConstantOffset, + Uint32 NumInlineConstants, + RefCntAutoPtr pObject) +{ + VERIFY_EXPR(InlineConstantOffset != ~0u); + BindGroup& Group = GetBindGroup(GroupIdx); + + Resource* pRes = new (&Group.GetResource(Offset)) Resource{ + BindGroupEntryType::UniformBufferDynamic, + GetInlineConstantDataPtr(InlineConstantOffset), + }; +#ifdef DILIGENT_DEBUG + m_DbgInitializedResources[GroupIdx][size_t{Offset}] = true; - if (InlineConstantOffset != ~0u) + if (InlineConstantOffset != ~0u) + { + VERIFY(InlineConstantOffset + NumInlineConstants <= m_DbgAssignedInlineConstants.size(), "Inline constant storage overflow"); + for (Uint32 i = 0; i < NumInlineConstants; ++i) { - VERIFY(InlineConstantOffset + DbgNumInlineConstants <= m_DbgAssignedInlineConstants.size(), "Inline constant storage overflow"); - for (Uint32 i = 0; i < DbgNumInlineConstants; ++i) - { - VERIFY(!m_DbgAssignedInlineConstants[InlineConstantOffset + i], "Inline constant storage at offset ", InlineConstantOffset + i, " has already been assigned"); - m_DbgAssignedInlineConstants[InlineConstantOffset + i] = true; - } + VERIFY(!m_DbgAssignedInlineConstants[InlineConstantOffset + i], "Inline constant storage at offset ", InlineConstantOffset + i, " has already been assigned"); + m_DbgAssignedInlineConstants[InlineConstantOffset + i] = true; } + } #endif + + pRes->BufferRangeSize = NumInlineConstants * sizeof(Uint32); + + if (pObject) + { + // Note that since we use SetResource, the buffer will count towards the number of + // dynamic buffers in the cache. + SetResource(GroupIdx, Offset, std::move(pObject), 0, NumInlineConstants * sizeof(Uint32)); } - (void)DbgNumInlineConstants; } @@ -637,6 +659,8 @@ void ShaderResourceCacheWebGPU::CopyInlineConstants(const ShaderResourceCacheWeb VERIFY_EXPR(SrcRes.pInlineConstantData != nullptr); VERIFY_EXPR(DstRes.pInlineConstantData != nullptr); + VERIFY(SrcRes.BufferRangeSize == NumConstants * sizeof(Uint32), "Source inline constant buffer size mismatch"); + VERIFY(DstRes.BufferRangeSize == NumConstants * sizeof(Uint32), "Destination inline constant buffer size mismatch"); // Copy inline constant staging data from source cache to destination cache memcpy(DstRes.pInlineConstantData, SrcRes.pInlineConstantData, NumConstants * sizeof(Uint32));