From de321112725fe9625efff49f309aa1a7e556a726 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Mon, 28 Sep 2026 15:46:10 +0800 Subject: [PATCH 1/7] Vulkan: promoted inline constants can bind stale, misaligned dynamic UBO offsets after buffer ID reuse. A fix need to be applied to `WriteDynamicBufferOffsets` to mitigate the issue. --- .../include/ShaderResourceCacheVk.hpp | 3 +- .../src/DeviceContextVkImpl.cpp | 37 ++++++------------- .../src/ShaderResourceCacheVk.cpp | 19 ++++++++-- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp b/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp index 33ec890026..256b2126c7 100644 --- a/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp +++ b/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp @@ -324,7 +324,8 @@ class ShaderResourceCacheVk : public ShaderResourceCacheBase WriteDynamicBufferOffsetsResult WriteDynamicBufferOffsets( DeviceContextVkImpl* pCtx, std::vector& Offsets, - Uint32 StartInd) const; + Uint32 StartInd, + const Resource* pPushConstantResource) const; private: Resource* GetFirstResourcePtr() diff --git a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp index 6f2e844824..311bfc50fd 100644 --- a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp @@ -475,6 +475,7 @@ void DeviceContextVkImpl::CommitDescriptorSets(ResourceBindInfo& BindInfo, Uint3 uint32_t TotalSetCount = 0; const Uint32 FirstSetToBind = BindInfo.SetInfo[FirstSign].BaseInd; const Uint16 FirstDynamicOffset = BindInfo.SetInfo[FirstSign].FirstDynamicOffset; + const auto& PushConstantInfo = m_pPipelineState->GetPipelineLayout().GetPushConstantInfo(); // Note that in current implementation, if any of the dynamic offsets change, // all descriptor sets are rebound. This may be further optimized to only rebind @@ -511,7 +512,16 @@ void DeviceContextVkImpl::CommitDescriptorSets(ResourceBindInfo& BindInfo, Uint3 VERIFY(m_DynamicBufferOffsets.size() >= size_t{FirstDynamicOffset} + size_t{DynamicOffsetCount} + size_t{SetInfo.DynamicOffsetCount}, "m_DynamicBufferOffsets must've been resized by SetPipelineState() to have enough space"); - auto WriteResult = pResourceCache->WriteDynamicBufferOffsets(this, m_DynamicBufferOffsets, FirstDynamicOffset + DynamicOffsetCount); + const ShaderResourceCacheVk::Resource* pPushConstantResource = nullptr; + if (PushConstantInfo && sign == PushConstantInfo.SignatureIndex) + { + // Resolve through the bound SRB cache: it may come from a compatible + // signature instance with different backing buffer objects. + const auto& Attribs = m_pPipelineState->GetResourceSignature(sign)->GetResourceAttribs(PushConstantInfo.ResourceIndex); + pPushConstantResource = &pResourceCache->GetDescriptorSet(Attribs.DescrSet).GetResource(Attribs.CacheOffset(ResourceCacheContentType::SRB)); + } + auto WriteResult = pResourceCache->WriteDynamicBufferOffsets(this, m_DynamicBufferOffsets, + FirstDynamicOffset + DynamicOffsetCount, pPushConstantResource); VERIFY_EXPR(WriteResult.NumOffsetsWritten == SetInfo.DynamicOffsetCount); DynamicOffsetCount += SetInfo.DynamicOffsetCount; @@ -2454,30 +2464,7 @@ void DeviceContextVkImpl::UpdateTexture(ITexture* pTexture, if (SubresData.pSrcBuffer != nullptr) { - BufferVkImpl* pSrcBuffVk = ClassPtrCast(SubresData.pSrcBuffer); - const TextureDesc& DstTexDesc = pTexVk->GetDesc(); - const TextureFormatAttribs& FmtAttribs = GetTextureFormatAttribs(DstTexDesc.Format); - - TransitionOrVerifyBufferState(*pSrcBuffVk, SrcBufferStateTransitionMode, RESOURCE_STATE_COPY_SOURCE, VK_ACCESS_TRANSFER_READ_BIT, - "Using buffer as copy source (DeviceContextVkImpl::UpdateTexture)"); - - // We must unbind the texture from framebuffer because we will transition its state. - // If we later try to commit it as a render target (e.g. from SetPipelineState()), a - // state mismatch error will occur. - UnbindTextureFromFramebuffer(pTexVk, true); - - const Uint32 SrcBufferRowStrideInTexels = (FmtAttribs.ComponentType == COMPONENT_TYPE_COMPRESSED) ? - StaticCast(SubresData.Stride / Uint64{FmtAttribs.ComponentSize} * Uint64{FmtAttribs.BlockWidth}) : - StaticCast(SubresData.Stride / (Uint64{FmtAttribs.ComponentSize} * Uint64{FmtAttribs.NumComponents})); - - CopyBufferToTexture(pSrcBuffVk->GetVkBuffer(), - SubresData.SrcOffset, - SrcBufferRowStrideInTexels, - *pTexVk, - DstBox, - MipLevel, - Slice, - TextureStateTransitionMode); + UNSUPPORTED("Copying buffer to texture is not implemented"); } else { diff --git a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp index da3c5e3531..1a450a2b25 100644 --- a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp @@ -945,7 +945,8 @@ VkWriteDescriptorSetAccelerationStructureKHR ShaderResourceCacheVk::Resource::Ge ShaderResourceCacheVk::WriteDynamicBufferOffsetsResult ShaderResourceCacheVk::WriteDynamicBufferOffsets( DeviceContextVkImpl* pCtx, std::vector& Offsets, - Uint32 StartInd) const + Uint32 StartInd, + const Resource* pPushConstantResource) const { WriteDynamicBufferOffsetsResult Result; @@ -990,8 +991,20 @@ ShaderResourceCacheVk::WriteDynamicBufferOffsetsResult ShaderResourceCacheVk::Wr const Resource& Res = DescrSet.GetResource(res); if (Res.Type == DescriptorType::UniformBufferDynamic) { - const BufferVkImpl* pBufferVk = Res.pObject.ConstPtr(); - WriteOffset(pBufferVk, Res.BufferDynamicOffset); + if (&Res == pPushConstantResource) + { + // The promoted resource still has a dynamic UBO descriptor, but its buffer + // is not mapped by CommitInlineConstants. Its recycled dynamic buffer ID + // may refer to an unrelated allocation with a different alignment. + // Keep the descriptor's offset slot, using zero for this unused UBO. + VERIFY_EXPR(Res.pInlineConstantData != nullptr); + WriteOffset(nullptr, 0); + } + else + { + const BufferVkImpl* pBufferVk = Res.pObject.ConstPtr(); + WriteOffset(pBufferVk, Res.BufferDynamicOffset); + } ++res; } else From 9fdaf9adbab5a7a6dd0286d3f8cd32d871ddf251 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Mon, 28 Sep 2026 16:15:04 +0800 Subject: [PATCH 2/7] fix(vulkan): preserve buffer-to-texture updates Keep the existing upstream UpdateTexture implementation when fixing promoted inline constant offsets. Co-Authored-By: Codex --- .../src/DeviceContextVkImpl.cpp | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp index 311bfc50fd..83c380d634 100644 --- a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp @@ -2464,7 +2464,30 @@ void DeviceContextVkImpl::UpdateTexture(ITexture* pTexture, if (SubresData.pSrcBuffer != nullptr) { - UNSUPPORTED("Copying buffer to texture is not implemented"); + BufferVkImpl* pSrcBuffVk = ClassPtrCast(SubresData.pSrcBuffer); + const TextureDesc& DstTexDesc = pTexVk->GetDesc(); + const TextureFormatAttribs& FmtAttribs = GetTextureFormatAttribs(DstTexDesc.Format); + + TransitionOrVerifyBufferState(*pSrcBuffVk, SrcBufferStateTransitionMode, RESOURCE_STATE_COPY_SOURCE, VK_ACCESS_TRANSFER_READ_BIT, + "Using buffer as copy source (DeviceContextVkImpl::UpdateTexture)"); + + // We must unbind the texture from framebuffer because we will transition its state. + // If we later try to commit it as a render target (e.g. from SetPipelineState()), a + // state mismatch error will occur. + UnbindTextureFromFramebuffer(pTexVk, true); + + const Uint32 SrcBufferRowStrideInTexels = (FmtAttribs.ComponentType == COMPONENT_TYPE_COMPRESSED) ? + StaticCast(SubresData.Stride / Uint64{FmtAttribs.ComponentSize} * Uint64{FmtAttribs.BlockWidth}) : + StaticCast(SubresData.Stride / (Uint64{FmtAttribs.ComponentSize} * Uint64{FmtAttribs.NumComponents})); + + CopyBufferToTexture(pSrcBuffVk->GetVkBuffer(), + SubresData.SrcOffset, + SrcBufferRowStrideInTexels, + *pTexVk, + DstBox, + MipLevel, + Slice, + TextureStateTransitionMode); } else { From 773a0cb5552538001deeb6b82c080ddf36587b92 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Mon, 28 Sep 2026 16:15:04 +0800 Subject: [PATCH 3/7] test(vulkan): cover recycled offsets for promoted inline constants Seed a provably misaligned vertex allocation before recycling its dynamic buffer ID into an inline UBO. Cover compatible SRBs, pipeline-specific push constant selection, UBO emulation, and ordinary dynamic UBO updates with GPU readback and Vulkan validation. Co-Authored-By: Codex --- .../src/InlineConstantsTest.cpp | 223 ++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp b/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp index ba06ed411a..e7fbde0e9e 100644 --- a/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp +++ b/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp @@ -34,6 +34,14 @@ #include "FastRand.hpp" #include "MapHelper.hpp" +#include +#include +#include + +#if VULKAN_SUPPORTED +# include "Vulkan/TestingEnvironmentVk.hpp" +#endif + namespace Diligent { @@ -1064,6 +1072,221 @@ TEST_F(InlineConstants, CrossSignatureSRB) Present(); } +// Regression for https://github.com/hzqst/DiligentCore/issues/2. +void TestPromotedUBORecycledDynamicOffset(bool UseCompatibleSignature) +{ +#if VULKAN_SUPPORTED + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + IRenderDevice* pDevice = pEnv->GetDevice(); + if (!pDevice->GetDeviceInfo().IsVulkanDevice()) + GTEST_SKIP() << "This regression requires Vulkan"; + + const auto& Limits = TestingEnvironmentVk::GetInstance()->DeviceProps.limits; + const Uint64 UniformAlignment = Limits.minUniformBufferOffsetAlignment; + const Uint64 VertexAlignment = std::max(Uint64{4}, Uint64{Limits.optimalBufferCopyOffsetAlignment}); + if (VertexAlignment >= UniformAlignment) + GTEST_SKIP() << "Dynamic vertex buffer offsets always satisfy uniform buffer alignment on this device"; + + GPUTestingEnvironment::ScopedReset AutoReset; + IDeviceContext* pContext = pEnv->GetDeviceContext(); + + constexpr Uint32 NumDispatches = 4; + constexpr Uint32 NumConstants = 4; + Uint32 Expected[NumDispatches][NumConstants] = {}; + + BufferDesc OutputDesc{"Promoted UBO regression output", sizeof(Expected), BIND_UNORDERED_ACCESS}; + OutputDesc.Mode = BUFFER_MODE_STRUCTURED; + OutputDesc.ElementByteStride = NumConstants * sizeof(Uint32); + RefCntAutoPtr pOutput = pEnv->CreateBuffer(OutputDesc, Expected); + ASSERT_TRUE(pOutput); + RefCntAutoPtr pReadback = pEnv->CreateBuffer( + {"Promoted UBO regression readback", sizeof(Expected), BIND_NONE, USAGE_STAGING, CPU_ACCESS_READ}); + ASSERT_TRUE(pReadback); + RefCntAutoPtr pDynamicCB = pEnv->CreateBuffer( + {"Ordinary dynamic UBO", NumConstants * sizeof(Uint32), BIND_UNIFORM_BUFFER, USAGE_DYNAMIC, CPU_ACCESS_WRITE}); + ASSERT_TRUE(pDynamicCB); + + PipelineResourceSignatureDescX SignADesc{"Push constants A"}; + SignADesc.AddResource(SHADER_TYPE_COMPUTE, "cbA", NumConstants, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, + SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE, PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS); + RefCntAutoPtr pSignA; + pDevice->CreatePipelineResourceSignature(SignADesc, &pSignA); + ASSERT_TRUE(pSignA); + + PipelineResourceSignatureDescX SignBDesc{"Push constants or emulated UBO B"}; + SignBDesc.BindingIndex = 1; + SignBDesc + .AddResource(SHADER_TYPE_COMPUTE, "cbDynamic", SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE) + .AddResource(SHADER_TYPE_COMPUTE, "cbB", NumConstants, SHADER_RESOURCE_TYPE_CONSTANT_BUFFER, + SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE, PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS) + .AddResource(SHADER_TYPE_COMPUTE, "g_Output", SHADER_RESOURCE_TYPE_BUFFER_UAV, SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE); + + RefCntAutoPtr pCompatibleSignB; + if (UseCompatibleSignature) + { + pDevice->CreatePipelineResourceSignature(SignBDesc, &pCompatibleSignB); + ASSERT_TRUE(pCompatibleSignB); + } + + // All dynamic buffers share the context's mapped Vulkan heap. The UBO anchor + // has a uniform-aligned offset, so a mapped address difference modulo the + // uniform alignment also gives the vertex allocation's offset modulo it. + // This works even after other tests have used the heap or a new block is allocated. + RefCntAutoPtr pAnchor = pEnv->CreateBuffer( + {"Uniform-aligned allocation anchor", UniformAlignment, BIND_UNIFORM_BUFFER, USAGE_DYNAMIC, CPU_ACCESS_WRITE}); + RefCntAutoPtr pRecycledVB = pEnv->CreateBuffer( + {"Recycled vertex buffer ID", VertexAlignment, BIND_VERTEX_BUFFER, USAGE_DYNAMIC, CPU_ACCESS_WRITE}); + ASSERT_TRUE(pAnchor); + ASSERT_TRUE(pRecycledVB); + std::uintptr_t AnchorAddress = 0; + { + MapHelper Data{pContext, pAnchor, MAP_WRITE, MAP_FLAG_DISCARD}; + Uint8* pData = Data; + ASSERT_NE(nullptr, pData); + AnchorAddress = reinterpret_cast(pData); + std::memset(pData, 0, static_cast(UniformAlignment)); + } + Uint64 OffsetRemainder = 0; + // If the first allocation is aligned (including a block rollover), the next + // small allocation advances by VertexAlignment and must be misaligned. + for (Uint32 Attempt = 0; Attempt < 2 && OffsetRemainder == 0; ++Attempt) + { + MapHelper Data{pContext, pRecycledVB, MAP_WRITE, MAP_FLAG_DISCARD}; + Uint8* pData = Data; + ASSERT_NE(nullptr, pData); + OffsetRemainder = (reinterpret_cast(pData) - AnchorAddress) % UniformAlignment; + } + ASSERT_NE(Uint64{0}, OffsetRemainder) << "Failed to seed a misaligned dynamic allocation"; + + // Dynamic buffer IDs are recycled in LIFO order. Do not create another + // dynamic buffer between releasing the VB and creating B's backing UBO. + pRecycledVB.Release(); + RefCntAutoPtr pSignB; + pDevice->CreatePipelineResourceSignature(SignBDesc, &pSignB); + ASSERT_TRUE(pSignB); + if (UseCompatibleSignature) + { + ASSERT_NE(pSignB, pCompatibleSignB); + ASSERT_TRUE(pSignB->IsCompatibleWith(pCompatibleSignB)); + } + + const char* ShaderSource = R"( +#if USE_A +cbuffer cbA { uint4 g_A; } +#endif +cbuffer cbB { uint4 g_B; } +cbuffer cbDynamic { uint4 g_Dynamic; } +RWStructuredBuffer g_Output; +[numthreads(1, 1, 1)] +void main() +{ +#if USE_A + uint A = g_A.x; +#else + uint A = 0; +#endif + g_Output[g_Dynamic.w] = uint4(A, g_B.x, g_Dynamic.x, A + 3 * g_B.x + 7 * g_Dynamic.x); +} +)"; + + RefCntAutoPtr pPSOs[2]; + for (Uint32 UseA = 0; UseA < 2; ++UseA) + { + const std::string Source = std::string{UseA ? "#define USE_A 1\n" : "#define USE_A 0\n"} + ShaderSource; + ShaderCreateInfo ShaderCI; + ShaderCI.Desc = {"Promoted UBO dynamic offset regression", SHADER_TYPE_COMPUTE, true}; + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL; + ShaderCI.ShaderCompiler = pEnv->GetDefaultCompiler(ShaderCI.SourceLanguage); + ShaderCI.EntryPoint = "main"; + ShaderCI.Source = Source.c_str(); + RefCntAutoPtr pCS; + pDevice->CreateShader(ShaderCI, &pCS); + ASSERT_TRUE(pCS); + + ComputePipelineStateCreateInfoX PsoCI{"Promoted UBO dynamic offset regression"}; + PsoCI.AddShader(pCS); + if (UseA != 0) + PsoCI.AddSignature(pSignA); + PsoCI.AddSignature(UseCompatibleSignature ? pCompatibleSignB : pSignB); + pDevice->CreateComputePipelineState(PsoCI, &pPSOs[UseA]); + ASSERT_TRUE(pPSOs[UseA]); + } + + RefCntAutoPtr pSRBA, pSRBB; + pSignA->CreateShaderResourceBinding(&pSRBA, true); + pSignB->CreateShaderResourceBinding(&pSRBB, true); + ASSERT_TRUE(pSRBA); + ASSERT_TRUE(pSRBB); + IShaderResourceVariable* pVarA = pSRBA->GetVariableByName(SHADER_TYPE_COMPUTE, "cbA"); + IShaderResourceVariable* pVarB = pSRBB->GetVariableByName(SHADER_TYPE_COMPUTE, "cbB"); + IShaderResourceVariable* pVarDynamic = pSRBB->GetVariableByName(SHADER_TYPE_COMPUTE, "cbDynamic"); + IShaderResourceVariable* pVarOutput = pSRBB->GetVariableByName(SHADER_TYPE_COMPUTE, "g_Output"); + ASSERT_TRUE(pVarA); + ASSERT_TRUE(pVarB); + ASSERT_TRUE(pVarDynamic); + ASSERT_TRUE(pVarOutput); + pVarDynamic->Set(pDynamicCB); + pVarOutput->Set(pOutput->GetDefaultView(BUFFER_VIEW_UNORDERED_ACCESS)); + + // The first dispatch leaves B's fallback UBO unmapped. Without the fix, its + // recycled ID supplies the old VB offset to vkCmdBindDescriptorSets, causing + // VUID-vkCmdBindDescriptorSets-pDynamicOffsets-01971. GPUTestingEnvironment + // enables validation and turns unexpected validation errors into test failures; + // GPU readback alone would not detect this violation. + // Repeat without-fix runs in separate processes: validation layers may limit + // the number of duplicate VUID messages emitted per instance. + for (Uint32 Pass = 0; Pass < NumDispatches; ++Pass) + { + SCOPED_TRACE(Pass); + const Uint32 UseA = Pass % 2; + const Uint32 A[NumConstants] = {11 + Pass, 0, 0, 0}; + const Uint32 B[NumConstants] = {23 + Pass, 0, 0, 0}; + const Uint32 Dynamic[NumConstants] = {37 + Pass, 0, 0, Pass}; + pVarA->SetInlineConstants(A, 0, NumConstants); + pVarB->SetInlineConstants(B, 0, NumConstants); + { + MapHelper Data{pContext, pDynamicCB, MAP_WRITE, MAP_FLAG_DISCARD}; + Uint32* pData = Data; + ASSERT_NE(nullptr, pData); + std::memcpy(pData, Dynamic, sizeof(Dynamic)); + } + + pContext->SetPipelineState(pPSOs[UseA]); + if (UseA != 0) + pContext->CommitShaderResources(pSRBA, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + pContext->CommitShaderResources(pSRBB, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + pContext->DispatchCompute({1, 1, 1}); + + Expected[Pass][0] = UseA ? A[0] : 0; + Expected[Pass][1] = B[0]; + Expected[Pass][2] = Dynamic[0]; + Expected[Pass][3] = Expected[Pass][0] + 3 * B[0] + 7 * Dynamic[0]; + } + + pContext->CopyBuffer(pOutput, 0, RESOURCE_STATE_TRANSITION_MODE_TRANSITION, + pReadback, 0, sizeof(Expected), RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + pContext->WaitForIdle(); + MapHelper Data{pContext, pReadback, MAP_READ, MAP_FLAG_DO_NOT_WAIT}; + ASSERT_NE(nullptr, static_cast(Data)); + for (Uint32 Pass = 0; Pass < NumDispatches; ++Pass) + for (Uint32 Component = 0; Component < NumConstants; ++Component) + EXPECT_EQ(Expected[Pass][Component], Data[Pass * NumConstants + Component]) + << "Dispatch " << Pass << ", component " << Component; +#else + GTEST_SKIP() << "Vulkan is not supported in this build"; +#endif +} + +TEST_F(InlineConstants, VulkanPromotedUBORecycledDynamicOffset) +{ + TestPromotedUBORecycledDynamicOffset(false); +} + +TEST_F(InlineConstants, VulkanPromotedUBOCompatibleSRB) +{ + TestPromotedUBORecycledDynamicOffset(true); +} + constexpr Uint32 kCacheContentVersion = 7; RefCntAutoPtr CreateCache(IRenderDevice* pDevice, From 3c6a547c575011d6deda097ec59d1d04e2745244 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Mon, 28 Sep 2026 16:19:30 +0800 Subject: [PATCH 4/7] style(vulkan): align promoted resource assignments Co-Authored-By: Codex --- Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp index 83c380d634..ecab4249e7 100644 --- a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp @@ -517,7 +517,7 @@ void DeviceContextVkImpl::CommitDescriptorSets(ResourceBindInfo& BindInfo, Uint3 { // Resolve through the bound SRB cache: it may come from a compatible // signature instance with different backing buffer objects. - const auto& Attribs = m_pPipelineState->GetResourceSignature(sign)->GetResourceAttribs(PushConstantInfo.ResourceIndex); + const auto& Attribs = m_pPipelineState->GetResourceSignature(sign)->GetResourceAttribs(PushConstantInfo.ResourceIndex); pPushConstantResource = &pResourceCache->GetDescriptorSet(Attribs.DescrSet).GetResource(Attribs.CacheOffset(ResourceCacheContentType::SRB)); } auto WriteResult = pResourceCache->WriteDynamicBufferOffsets(this, m_DynamicBufferOffsets, From 322fd0c4a334b9f9efd0484645bfae5711782919 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Mon, 28 Sep 2026 16:22:47 +0800 Subject: [PATCH 5/7] docs(tests): reference upstream inline constants issue Co-Authored-By: Codex --- Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp b/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp index e7fbde0e9e..b91e9f2716 100644 --- a/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp +++ b/Tests/DiligentCoreAPITest/src/InlineConstantsTest.cpp @@ -1072,7 +1072,7 @@ TEST_F(InlineConstants, CrossSignatureSRB) Present(); } -// Regression for https://github.com/hzqst/DiligentCore/issues/2. +// Regression for https://github.com/DiligentGraphics/DiligentCore/issues/808. void TestPromotedUBORecycledDynamicOffset(bool UseCompatibleSignature) { #if VULKAN_SUPPORTED From 2b2d649d7cd61ca16b704ab713e5e549487b9b6c Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 30 Sep 2026 12:50:28 +0800 Subject: [PATCH 6/7] =?UTF-8?q?Refine=20WriteDynamicBufferOffsets:=20Pass?= =?UTF-8?q?=20promoted=20resource=E2=80=99s=20descriptor-set=20index=20and?= =?UTF-8?q?=20SRB=20cache=20offset=20to=20WriteDynamicBufferOffsets.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../include/ShaderResourceCacheVk.hpp | 3 ++- .../src/DeviceContextVkImpl.cpp | 15 +++++++++------ .../src/ShaderResourceCacheVk.cpp | 5 +++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp b/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp index 256b2126c7..b761a04047 100644 --- a/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp +++ b/Graphics/GraphicsEngineVulkan/include/ShaderResourceCacheVk.hpp @@ -325,7 +325,8 @@ class ShaderResourceCacheVk : public ShaderResourceCacheBase DeviceContextVkImpl* pCtx, std::vector& Offsets, Uint32 StartInd, - const Resource* pPushConstantResource) const; + Uint32 PushConstantSet, + Uint32 PushConstantCacheOffset) const; private: Resource* GetFirstResourcePtr() diff --git a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp index ecab4249e7..40e58542d4 100644 --- a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp @@ -512,16 +512,19 @@ void DeviceContextVkImpl::CommitDescriptorSets(ResourceBindInfo& BindInfo, Uint3 VERIFY(m_DynamicBufferOffsets.size() >= size_t{FirstDynamicOffset} + size_t{DynamicOffsetCount} + size_t{SetInfo.DynamicOffsetCount}, "m_DynamicBufferOffsets must've been resized by SetPipelineState() to have enough space"); - const ShaderResourceCacheVk::Resource* pPushConstantResource = nullptr; + Uint32 PushConstantSet = ~0u; + Uint32 PushConstantCacheOffset = ~0u; if (PushConstantInfo && sign == PushConstantInfo.SignatureIndex) { - // Resolve through the bound SRB cache: it may come from a compatible - // signature instance with different backing buffer objects. - const auto& Attribs = m_pPipelineState->GetResourceSignature(sign)->GetResourceAttribs(PushConstantInfo.ResourceIndex); - pPushConstantResource = &pResourceCache->GetDescriptorSet(Attribs.DescrSet).GetResource(Attribs.CacheOffset(ResourceCacheContentType::SRB)); + // Compatible signatures have matching cache layouts, so these indices + // identify the promoted resource in the bound SRB cache as well. + const auto& Attribs = m_pPipelineState->GetResourceSignature(sign)->GetResourceAttribs(PushConstantInfo.ResourceIndex); + PushConstantSet = Attribs.DescrSet; + PushConstantCacheOffset = Attribs.CacheOffset(ResourceCacheContentType::SRB); } auto WriteResult = pResourceCache->WriteDynamicBufferOffsets(this, m_DynamicBufferOffsets, - FirstDynamicOffset + DynamicOffsetCount, pPushConstantResource); + FirstDynamicOffset + DynamicOffsetCount, + PushConstantSet, PushConstantCacheOffset); VERIFY_EXPR(WriteResult.NumOffsetsWritten == SetInfo.DynamicOffsetCount); DynamicOffsetCount += SetInfo.DynamicOffsetCount; diff --git a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp index 1a450a2b25..b6b68e3ed9 100644 --- a/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/ShaderResourceCacheVk.cpp @@ -946,7 +946,8 @@ ShaderResourceCacheVk::WriteDynamicBufferOffsetsResult ShaderResourceCacheVk::Wr DeviceContextVkImpl* pCtx, std::vector& Offsets, Uint32 StartInd, - const Resource* pPushConstantResource) const + Uint32 PushConstantSet, + Uint32 PushConstantCacheOffset) const { WriteDynamicBufferOffsetsResult Result; @@ -991,7 +992,7 @@ ShaderResourceCacheVk::WriteDynamicBufferOffsetsResult ShaderResourceCacheVk::Wr const Resource& Res = DescrSet.GetResource(res); if (Res.Type == DescriptorType::UniformBufferDynamic) { - if (&Res == pPushConstantResource) + if (set == PushConstantSet && res == PushConstantCacheOffset) { // The promoted resource still has a dynamic UBO descriptor, but its buffer // is not mapped by CommitInlineConstants. Its recycled dynamic buffer ID From 0789fc9470b41b0d640f4457e3b6ef34e21c84d0 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 30 Sep 2026 13:09:48 +0800 Subject: [PATCH 7/7] Refine Vulkan's CommitDescriptorSets: Cache DescrSet and SRBCacheOffset in PushConstantInfo, no more lookup in CommitDescriptorSets(). --- .../include/PipelineLayoutVk.hpp | 6 ++++++ .../src/DeviceContextVkImpl.cpp | 16 ++++------------ .../src/PipelineLayoutVk.cpp | 4 ++++ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Graphics/GraphicsEngineVulkan/include/PipelineLayoutVk.hpp b/Graphics/GraphicsEngineVulkan/include/PipelineLayoutVk.hpp index efebe70bd5..531f50e2c1 100644 --- a/Graphics/GraphicsEngineVulkan/include/PipelineLayoutVk.hpp +++ b/Graphics/GraphicsEngineVulkan/include/PipelineLayoutVk.hpp @@ -71,6 +71,12 @@ class PipelineLayoutVk Uint32 SignatureIndex = ~0u; Uint32 ResourceIndex = ~0u; + // Descriptor-set index local to the signature's SRB cache, not the pipeline-global index. + // Compatible SRBs have matching cache layouts and can use the same indices. + Uint32 DescrSet = ~0u; + // Resource offset within that descriptor set in the SRB cache. + Uint32 SRBCacheOffset = ~0u; + constexpr explicit operator bool() const { return vkRange.size != 0; } }; static PushConstantInfo GetPushConstantInfo(const RefCntAutoPtr* ppSignatures, diff --git a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp index 40e58542d4..2b7ffb75ec 100644 --- a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp @@ -512,19 +512,11 @@ void DeviceContextVkImpl::CommitDescriptorSets(ResourceBindInfo& BindInfo, Uint3 VERIFY(m_DynamicBufferOffsets.size() >= size_t{FirstDynamicOffset} + size_t{DynamicOffsetCount} + size_t{SetInfo.DynamicOffsetCount}, "m_DynamicBufferOffsets must've been resized by SetPipelineState() to have enough space"); - Uint32 PushConstantSet = ~0u; - Uint32 PushConstantCacheOffset = ~0u; - if (PushConstantInfo && sign == PushConstantInfo.SignatureIndex) - { - // Compatible signatures have matching cache layouts, so these indices - // identify the promoted resource in the bound SRB cache as well. - const auto& Attribs = m_pPipelineState->GetResourceSignature(sign)->GetResourceAttribs(PushConstantInfo.ResourceIndex); - PushConstantSet = Attribs.DescrSet; - PushConstantCacheOffset = Attribs.CacheOffset(ResourceCacheContentType::SRB); - } - auto WriteResult = pResourceCache->WriteDynamicBufferOffsets(this, m_DynamicBufferOffsets, + const bool HasPushConstant = PushConstantInfo && sign == PushConstantInfo.SignatureIndex; + auto WriteResult = pResourceCache->WriteDynamicBufferOffsets(this, m_DynamicBufferOffsets, FirstDynamicOffset + DynamicOffsetCount, - PushConstantSet, PushConstantCacheOffset); + HasPushConstant ? PushConstantInfo.DescrSet : ~0u, + HasPushConstant ? PushConstantInfo.SRBCacheOffset : ~0u); VERIFY_EXPR(WriteResult.NumOffsetsWritten == SetInfo.DynamicOffsetCount); DynamicOffsetCount += SetInfo.DynamicOffsetCount; diff --git a/Graphics/GraphicsEngineVulkan/src/PipelineLayoutVk.cpp b/Graphics/GraphicsEngineVulkan/src/PipelineLayoutVk.cpp index d246ed13e9..8c02bcc64c 100644 --- a/Graphics/GraphicsEngineVulkan/src/PipelineLayoutVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/PipelineLayoutVk.cpp @@ -92,6 +92,10 @@ PipelineLayoutVk::PushConstantInfo PipelineLayoutVk::GetPushConstantInfo( PCInfo.SignatureIndex = BindInd; PCInfo.ResourceIndex = r; + const auto& Attribs = pSignature->GetResourceAttribs(r); + PCInfo.DescrSet = Attribs.DescrSet; + PCInfo.SRBCacheOffset = Attribs.CacheOffset(ResourceCacheContentType::SRB); + break; } }