From da62ad33b521591321e6b0686269ab6f89c82504 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Fri, 30 Oct 2026 04:03:37 +0800 Subject: [PATCH 1/2] RenderStateCache: unpack archived pipelines with their SRB granularity and context mask Archives do not store PipelineStateDesc::SRBAllocationGranularity and ImmediateContextMask; the dearchiver takes them from PipelineStateUnpackInfo. RenderStateCacheImpl left both at their defaults, so a pipeline created with other values never matched the one unpacked from a loaded cache: the cache logged a description mismatch and created the pipeline again. Add RenderStateCacheTest.ArchivedPSOKeepsDescription, which fails on Vulkan, D3D12, D3D11 and GL without the fix. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../src/RenderStateCacheImpl.cpp | 2 + .../src/RenderStateCacheTest.cpp | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp b/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp index da7baaeba..a0283859d 100644 --- a/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp +++ b/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp @@ -849,6 +849,8 @@ bool RenderStateCacheImpl::CreatePipelineStateInternal(const CreateInfoType& PSO UnpackInfo.PipelineType = PSOCreateInfo.PSODesc.PipelineType; UnpackInfo.Name = HashStr.c_str(); UnpackInfo.pDevice = m_pDevice; + UnpackInfo.SRBAllocationGranularity = PSOCreateInfo.PSODesc.SRBAllocationGranularity; + UnpackInfo.ImmediateContextMask = PSOCreateInfo.PSODesc.ImmediateContextMask; UnpackInfo.pCache = PSOCreateInfo.pPSOCache; UnpackInfo.ModifyPipelineStateCreateInfo = Callback; UnpackInfo.pUserData = Callback; diff --git a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp index 570650338..5f414879b 100644 --- a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp +++ b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp @@ -1345,6 +1345,55 @@ TEST(RenderStateCacheTest, CreateComputePSO_Sign_Async) TestComputePSO(/*UseSignature = */ true, /*CompileAsync = */ true); } +// The archive does not store SRBAllocationGranularity and ImmediateContextMask; the dearchiver takes them from the +// unpack info. A pipeline created with non-default values must be found in a loaded cache and keep those values. +TEST(RenderStateCacheTest, ArchivedPSOKeepsDescription) +{ + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + IRenderDevice* pDevice = pEnv->GetDevice(); + if (!pDevice->GetDeviceInfo().Features.ComputeShaders) + GTEST_SKIP() << "Compute shaders are not supported by this device"; + + GPUTestingEnvironment::ScopedReset AutoReset; + + RefCntAutoPtr pShaderSourceFactory; + pDevice->GetEngineFactory()->CreateDefaultShaderSourceStreamFactory("shaders/RenderStateCache", &pShaderSourceFactory); + ASSERT_NE(nullptr, pShaderSourceFactory); + + constexpr Uint32 SRBAllocationGranularity = 4; + // Every immediate context; it differs from the default mask when the device has more than one. + const Uint64 ImmediateContextMask = (Uint64{1} << pEnv->GetNumImmediateContexts()) - 1; + + RefCntAutoPtr pArchiveData; + for (Uint32 Pass = 0; Pass < 2; ++Pass) + { + SCOPED_TRACE(Pass); + auto pCache = CreateCache(pDevice, false, pArchiveData); + ASSERT_NE(nullptr, pCache); + + RefCntAutoPtr pCS; + CreateComputeShader(pCache, pShaderSourceFactory, SHADER_COMPILE_FLAG_NONE, pCS, Pass != 0); + ASSERT_NE(nullptr, pCS); + + ComputePipelineStateCreateInfo PSOCI; + PSOCI.PSODesc.Name = "Archived pipeline description"; + PSOCI.PSODesc.SRBAllocationGranularity = SRBAllocationGranularity; + PSOCI.PSODesc.ImmediateContextMask = ImmediateContextMask; + PSOCI.PSODesc.ResourceLayout.DefaultVariableType = SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE; + PSOCI.pCS = pCS; + RefCntAutoPtr pPSO; + EXPECT_EQ(Pass != 0, pCache->CreateComputePipelineState(PSOCI, &pPSO)); + ASSERT_NE(nullptr, pPSO); + ASSERT_EQ(PIPELINE_STATE_STATUS_READY, pPSO->GetStatus()); + EXPECT_EQ(PSOCI.PSODesc, pPSO->GetDesc()); + VerifyComputePSO(pPSO, /*UseSignature = */ true); + + pArchiveData.Release(); + ASSERT_TRUE(pCache->WriteToBlob(ContentVersion, &pArchiveData)); + ASSERT_NE(nullptr, pArchiveData); + } +} + TEST(RenderStateCacheTest, CacheByFileName) { From f6ee3d2170dd6ddc1331f7e020c94dd90c38d395 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Fri, 30 Oct 2026 04:14:03 +0800 Subject: [PATCH 2/2] RenderStateCache: accept the context mask the device keeps for an unpacked pipeline PipelineStateBase clears the bits of ImmediateContextMask that have no command queue on the device. A pipeline unpacked from the cache therefore may hold a subset of the requested mask, and comparing its description with the create info reported a mismatch and created the pipeline again. Compare with the requested mask reduced to the bits the pipeline kept. ArchivedPSOKeepsDescription now requests every context bit, so it covers the reduced mask on any device; it fails on Vulkan, D3D12, D3D11 and GL with only the previous commit. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../GraphicsTools/src/RenderStateCacheImpl.cpp | 6 +++++- .../src/RenderStateCacheTest.cpp | 14 +++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp b/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp index a0283859d..0fd0a9803 100644 --- a/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp +++ b/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp @@ -861,7 +861,11 @@ bool RenderStateCacheImpl::CreatePipelineStateInternal(const CreateInfoType& PSO const PIPELINE_STATE_STATUS Status = pPSO->GetStatus(); if (Status == PIPELINE_STATE_STATUS_READY) { - if (pPSO->GetDesc() == PSOCreateInfo.PSODesc) + // The device clears the bits of the immediate context mask that have no command queue, + // so the unpacked pipeline may hold a subset of the requested mask. + PipelineStateDesc ExpectedDesc = PSOCreateInfo.PSODesc; + ExpectedDesc.ImmediateContextMask &= pPSO->GetDesc().ImmediateContextMask; + if (pPSO->GetDesc() == ExpectedDesc) { *ppPipelineState = pPSO.Detach(); FoundInCache = true; diff --git a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp index 5f414879b..f87781e39 100644 --- a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp +++ b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp @@ -1346,7 +1346,8 @@ TEST(RenderStateCacheTest, CreateComputePSO_Sign_Async) } // The archive does not store SRBAllocationGranularity and ImmediateContextMask; the dearchiver takes them from the -// unpack info. A pipeline created with non-default values must be found in a loaded cache and keep those values. +// unpack info. A pipeline created with non-default values must be found in a loaded cache and keep those values; the +// device reduces the mask to its immediate contexts, which must not make the unpacked pipeline look different. TEST(RenderStateCacheTest, ArchivedPSOKeepsDescription) { GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); @@ -1361,8 +1362,9 @@ TEST(RenderStateCacheTest, ArchivedPSOKeepsDescription) ASSERT_NE(nullptr, pShaderSourceFactory); constexpr Uint32 SRBAllocationGranularity = 4; - // Every immediate context; it differs from the default mask when the device has more than one. - const Uint64 ImmediateContextMask = (Uint64{1} << pEnv->GetNumImmediateContexts()) - 1; + // All bits: not the default mask on any device, and the device reduces it to its immediate contexts. + constexpr Uint64 RequestedContextMask = ~Uint64{0}; + const Uint64 DeviceContextMask = (Uint64{1} << pEnv->GetNumImmediateContexts()) - 1; RefCntAutoPtr pArchiveData; for (Uint32 Pass = 0; Pass < 2; ++Pass) @@ -1378,14 +1380,16 @@ TEST(RenderStateCacheTest, ArchivedPSOKeepsDescription) ComputePipelineStateCreateInfo PSOCI; PSOCI.PSODesc.Name = "Archived pipeline description"; PSOCI.PSODesc.SRBAllocationGranularity = SRBAllocationGranularity; - PSOCI.PSODesc.ImmediateContextMask = ImmediateContextMask; + PSOCI.PSODesc.ImmediateContextMask = RequestedContextMask; PSOCI.PSODesc.ResourceLayout.DefaultVariableType = SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE; PSOCI.pCS = pCS; RefCntAutoPtr pPSO; EXPECT_EQ(Pass != 0, pCache->CreateComputePipelineState(PSOCI, &pPSO)); ASSERT_NE(nullptr, pPSO); ASSERT_EQ(PIPELINE_STATE_STATUS_READY, pPSO->GetStatus()); - EXPECT_EQ(PSOCI.PSODesc, pPSO->GetDesc()); + PipelineStateDesc ExpectedDesc = PSOCI.PSODesc; + ExpectedDesc.ImmediateContextMask = DeviceContextMask; + EXPECT_EQ(ExpectedDesc, pPSO->GetDesc()); VerifyComputePSO(pPSO, /*UseSignature = */ true); pArchiveData.Release();