From 34adf82bdba12cc6c257b0a6b4678cd77a51e1db Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 17 Oct 2026 08:42:40 +0800 Subject: [PATCH 1/9] D3D12: enable persistent PSO cache reuse for archived and mesh pipelines. Retain serialized pipeline library data, use stable content keys, forward caches during archive unpacking, and cache mesh pipelines with explicit topology. --- .../include/PipelineStateCacheD3D12Impl.hpp | 9 +- .../src/PipelineStateCacheD3D12Impl.cpp | 75 ++++++++-- .../src/PipelineStateD3D12Impl.cpp | 133 ++++++++++++++---- .../src/RenderStateCacheImpl.cpp | 1 + 4 files changed, 175 insertions(+), 43 deletions(-) diff --git a/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp b/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp index 11d7c6e7bd..ab78bc6558 100644 --- a/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp +++ b/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp @@ -31,6 +31,7 @@ #include "EngineD3D12ImplTraits.hpp" #include "PipelineStateCacheBase.hpp" +#include namespace Diligent { @@ -53,11 +54,17 @@ class PipelineStateCacheD3D12Impl final : public PipelineStateCacheBase LoadComputePipeline(const wchar_t* Name, const D3D12_COMPUTE_PIPELINE_STATE_DESC& Desc); CComPtr LoadGraphicsPipeline(const wchar_t* Name, const D3D12_GRAPHICS_PIPELINE_STATE_DESC& Desc); +#ifdef D3D12_H_HAS_MESH_SHADER + CComPtr LoadPipeline(const wchar_t* Name, const D3D12_PIPELINE_STATE_STREAM_DESC& Desc); +#endif bool StorePipeline(const wchar_t* Name, ID3D12DeviceChild* pPSO); private: - CComPtr m_pLibrary; + RefCntAutoPtr m_pInitialData; + CComPtr m_pLibrary; + CComPtr m_pLibrary1; + std::mutex m_LibraryMtx; }; } // namespace Diligent diff --git a/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp index 7a8736babe..3e860b7dd4 100644 --- a/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp @@ -46,15 +46,39 @@ PipelineStateCacheD3D12Impl::PipelineStateCacheD3D12Impl(IReferenceCounters* } // clang-format on { - HRESULT hr = pRenderDeviceD3D12->GetD3D12Device1()->CreatePipelineLibrary(nullptr, 0, IID_PPV_ARGS(&m_pLibrary)); + auto* pDevice1 = pRenderDeviceD3D12->GetD3D12Device1(); + if (pDevice1 == nullptr) + { + LOG_WARNING_MESSAGE("D3D12 pipeline libraries are unavailable; native caching is disabled."); + return; + } + + // D3D12 does not copy the serialized data: retain our own copy until the library is released. + if (CreateInfo.pCacheData != nullptr && CreateInfo.CacheDataSize != 0) + { + m_pInitialData = DataBlobImpl::Create(CreateInfo.CacheDataSize, CreateInfo.pCacheData); + const HRESULT LoadResult = pDevice1->CreatePipelineLibrary(m_pInitialData->GetConstDataPtr(), m_pInitialData->GetSize(), IID_PPV_ARGS(&m_pLibrary)); + if (FAILED(LoadResult)) + { + LOG_WARNING_MESSAGE("D3D12 pipeline library data is invalid or incompatible; using an empty cache (HRESULT ", LoadResult, ")."); + m_pInitialData.Release(); + } + } + + HRESULT hr = m_pLibrary ? S_OK : pDevice1->CreatePipelineLibrary(nullptr, 0, IID_PPV_ARGS(&m_pLibrary)); if (FAILED(hr)) - LOG_ERROR_AND_THROW("Failed to create D3D12 pipeline library"); + { + LOG_WARNING_MESSAGE("D3D12 pipeline library is unavailable; native caching is disabled (HRESULT ", hr, ")."); + return; + } + m_pLibrary->QueryInterface(IID_PPV_ARGS(&m_pLibrary1)); } PipelineStateCacheD3D12Impl::~PipelineStateCacheD3D12Impl() { // D3D12 object can only be destroyed when it is no longer used by the GPU - GetDevice()->SafeReleaseDeviceObject(std::move(m_pLibrary), ~Uint64{0}); + m_pLibrary1.Release(); + GetDevice()->SafeReleaseDeviceObject(std::make_pair(std::move(m_pInitialData), std::move(m_pLibrary)), ~Uint64{0}); } CComPtr PipelineStateCacheD3D12Impl::LoadComputePipeline(const wchar_t* Name, const D3D12_COMPUTE_PIPELINE_STATE_DESC& Desc) @@ -66,11 +90,12 @@ CComPtr PipelineStateCacheD3D12Impl::LoadComputePipeline(cons } CComPtr d3d12PSO; - if ((m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) + if (m_pLibrary && (m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) { - HRESULT hr = m_pLibrary->LoadComputePipeline(Name, &Desc, IID_PPV_ARGS(&d3d12PSO)); - if (FAILED(hr) && (m_Desc.Flags & PSO_CACHE_FLAG_VERBOSE) != 0) - LOG_ERROR_MESSAGE("Failed to load compute pipeline '", NarrowString(Name), "' from the library"); + std::lock_guard Lock{m_LibraryMtx}; + HRESULT hr = m_pLibrary->LoadComputePipeline(Name, &Desc, IID_PPV_ARGS(&d3d12PSO)); + if ((m_Desc.Flags & PSO_CACHE_FLAG_VERBOSE) != 0) + LOG_INFO_MESSAGE(SUCCEEDED(hr) ? "PSO cache hit (compute): " : "PSO cache miss (compute): ", NarrowString(Name)); } return d3d12PSO; } @@ -84,24 +109,42 @@ CComPtr PipelineStateCacheD3D12Impl::LoadGraphicsPipeline(con } CComPtr d3d12PSO; - if ((m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) + if (m_pLibrary && (m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) { - HRESULT hr = m_pLibrary->LoadGraphicsPipeline(Name, &Desc, IID_PPV_ARGS(&d3d12PSO)); - if (FAILED(hr) && (m_Desc.Flags & PSO_CACHE_FLAG_VERBOSE) != 0) - LOG_ERROR_MESSAGE("Failed to load graphics pipeline '", NarrowString(Name), "' from the library"); + std::lock_guard Lock{m_LibraryMtx}; + HRESULT hr = m_pLibrary->LoadGraphicsPipeline(Name, &Desc, IID_PPV_ARGS(&d3d12PSO)); + if ((m_Desc.Flags & PSO_CACHE_FLAG_VERBOSE) != 0) + LOG_INFO_MESSAGE(SUCCEEDED(hr) ? "PSO cache hit (graphics): " : "PSO cache miss (graphics): ", NarrowString(Name)); } return d3d12PSO; } +#ifdef D3D12_H_HAS_MESH_SHADER +CComPtr PipelineStateCacheD3D12Impl::LoadPipeline(const wchar_t* Name, const D3D12_PIPELINE_STATE_STREAM_DESC& Desc) +{ + VERIFY_EXPR(Name != nullptr); + CComPtr d3d12PSO; + if (m_pLibrary1 && (m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) + { + std::lock_guard Lock{m_LibraryMtx}; + const HRESULT hr = m_pLibrary1->LoadPipeline(Name, &Desc, IID_PPV_ARGS(&d3d12PSO)); + if ((m_Desc.Flags & PSO_CACHE_FLAG_VERBOSE) != 0) + LOG_INFO_MESSAGE(SUCCEEDED(hr) ? "PSO cache hit (mesh): " : "PSO cache miss (mesh): ", NarrowString(Name), " (HRESULT ", hr, ")"); + } + return CComPtr{d3d12PSO.p}; +} +#endif + bool PipelineStateCacheD3D12Impl::StorePipeline(const wchar_t* Name, ID3D12DeviceChild* pPSO) { VERIFY_EXPR(Name != nullptr); - if ((m_Desc.Mode & PSO_CACHE_MODE_STORE) == 0) + if (!m_pLibrary || (m_Desc.Mode & PSO_CACHE_MODE_STORE) == 0) return false; - HRESULT hr = m_pLibrary->StorePipeline(Name, static_cast(pPSO)); + std::lock_guard Lock{m_LibraryMtx}; + HRESULT hr = m_pLibrary->StorePipeline(Name, static_cast(pPSO)); if (FAILED(hr) && (m_Desc.Flags & PSO_CACHE_FLAG_VERBOSE) != 0) - LOG_ERROR_MESSAGE("Failed to add pipeline '", NarrowString(Name), "' to the library"); + LOG_INFO_MESSAGE("PSO cache store failed: ", NarrowString(Name), " (HRESULT ", hr, "; it may already be stored)."); return SUCCEEDED(hr); } @@ -110,6 +153,10 @@ void PipelineStateCacheD3D12Impl::GetData(IDataBlob** ppBlob) { DEV_CHECK_ERR(ppBlob != nullptr, "ppBlob must not be null"); *ppBlob = nullptr; + if (!m_pLibrary) + return; + + std::lock_guard Lock{m_LibraryMtx}; RefCntAutoPtr pDataBlob = DataBlobImpl::Create(m_pLibrary->GetSerializedSize()); diff --git a/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp index fc696006e3..008e6481f5 100644 --- a/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp @@ -51,6 +51,7 @@ #include "DynamicLinearAllocator.hpp" #include "D3DShaderResourceValidation.hpp" #include "DataBlobImpl.hpp" +#include "HashUtils.hpp" #include "DXBCUtils.hpp" #include "DXCompiler.hpp" @@ -63,6 +64,66 @@ constexpr INTERFACE_ID PipelineStateD3D12Impl::IID_InternalImpl; namespace { +struct PipelineCacheHasher +{ + template + void Update(const T& Value) + { + if constexpr (std::is_fundamental::value || std::is_enum::value) + UpdateRaw(&Value, sizeof(Value)); + else + HashCombiner{*this}(Value); + } + + void Update(const char* Value) + { + const Uint64 Length = Value != nullptr ? std::strlen(Value) : 0; + Update(Length); + UpdateRaw(Value, Length); + } + + template + void operator()(const Args&... Values) + { + (Update(Values), ...); + } + + void UpdateRaw(const void* pData, Uint64 Size) + { + const auto* Bytes = static_cast(pData); + for (Uint64 i = 0; i < Size; ++i) + { + m_Hash ^= Bytes[i]; + m_Hash *= FNVPrime; + } + } + + Uint64 Get() const { return m_Hash; } + +private: + static constexpr Uint64 FNVOffsetBasis = 14695981039346656037ull; + static constexpr Uint64 FNVPrime = 1099511628211ull; + Uint64 m_Hash = FNVOffsetBasis; +}; + +template +std::wstring GetPipelineCacheName(const RootSignatureD3D12& RootSig, const ShaderStagesType& ShaderStages, const GraphicsPipelineDesc* pGraphics = nullptr) +{ + // Hash the effective pipeline after binding remapping. Archive unpacking changes + // create-info flags/signatures and shader bindings, but not the native pipeline. + PipelineCacheHasher Hasher; + Hasher(RootSig.GetHash(), pGraphics != nullptr); + if (pGraphics != nullptr) + Hasher(*pGraphics); + for (const auto& Stage : ShaderStages) + { + Hasher(Stage.Type); + for (const auto& Bytecode : Stage.ByteCodes) + Hasher.UpdateRaw(Bytecode->GetConstDataPtr(), Bytecode->GetSize()); + } + return L"PSO_2_" + std::to_wstring(Hasher.Get()); +} + #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable : 4324) // warning C4324: structure was padded due to alignment specifier @@ -641,6 +702,9 @@ void PipelineStateD3D12Impl::InitializePipeline(const GraphicsPipelineStateCreat TShaderStages ShaderStages; InitInternalObjects(CreateInfo, ShaderStages); + PipelineStateCacheD3D12Impl* const pPSOCacheD3D12 = ClassPtrCast(CreateInfo.pPSOCache); + const std::wstring CacheName = pPSOCacheD3D12 ? GetPipelineCacheName(*m_RootSig, ShaderStages, &m_pGraphicsPipelineData->Desc) : std::wstring{}; + ID3D12Device* pd3d12Device = m_pDevice->GetD3D12Device(); if (m_Desc.PipelineType == PIPELINE_TYPE_GRAPHICS) { @@ -727,9 +791,8 @@ void PipelineStateD3D12Impl::InitializePipeline(const GraphicsPipelineStateCreat d3d12PSODesc.Flags = D3D12_PIPELINE_STATE_FLAG_NONE; // Try to load from the cache - PipelineStateCacheD3D12Impl* const pPSOCacheD3D12 = ClassPtrCast(CreateInfo.pPSOCache); - if (pPSOCacheD3D12 != nullptr && !WName.empty()) - m_pd3d12PSO = pPSOCacheD3D12->LoadGraphicsPipeline(WName.c_str(), d3d12PSODesc); + if (pPSOCacheD3D12 != nullptr) + m_pd3d12PSO = pPSOCacheD3D12->LoadGraphicsPipeline(CacheName.c_str(), d3d12PSODesc); if (!m_pd3d12PSO) { // Note: renderdoc frame capture fails if any interface but IID_ID3D12PipelineState is requested @@ -738,8 +801,8 @@ void PipelineStateD3D12Impl::InitializePipeline(const GraphicsPipelineStateCreat LOG_ERROR_AND_THROW("Failed to create pipeline state"); // Add to the cache - if (pPSOCacheD3D12 != nullptr && !WName.empty()) - pPSOCacheD3D12->StorePipeline(WName.c_str(), m_pd3d12PSO); + if (pPSOCacheD3D12 != nullptr) + pPSOCacheD3D12->StorePipeline(CacheName.c_str(), m_pd3d12PSO); } } #ifdef D3D12_H_HAS_MESH_SHADER @@ -749,20 +812,21 @@ void PipelineStateD3D12Impl::InitializePipeline(const GraphicsPipelineStateCreat struct MESH_SHADER_PIPELINE_STATE_DESC { - PSS_SubObject Flags; - PSS_SubObject NodeMask; - PSS_SubObject pRootSignature; - PSS_SubObject PS; - PSS_SubObject AS; - PSS_SubObject MS; - PSS_SubObject BlendState; - PSS_SubObject DepthStencilState; - PSS_SubObject RasterizerState; - PSS_SubObject SampleDesc; - PSS_SubObject SampleMask; - PSS_SubObject DSVFormat; - PSS_SubObject RTVFormatArray; - PSS_SubObject CachedPSO; + PSS_SubObject Flags; + PSS_SubObject NodeMask; + PSS_SubObject pRootSignature; + PSS_SubObject PS; + PSS_SubObject AS; + PSS_SubObject MS; + PSS_SubObject BlendState; + PSS_SubObject DepthStencilState; + PSS_SubObject RasterizerState; + PSS_SubObject SampleDesc; + PSS_SubObject SampleMask; + PSS_SubObject PrimitiveTopologyType; + PSS_SubObject DSVFormat; + PSS_SubObject RTVFormatArray; + PSS_SubObject CachedPSO; }; MESH_SHADER_PIPELINE_STATE_DESC d3d12PSODesc = {}; @@ -791,6 +855,11 @@ void PipelineStateD3D12Impl::InitializePipeline(const GraphicsPipelineStateCreat BlendStateDesc_To_D3D12_BLEND_DESC(GraphicsPipeline.BlendDesc, *d3d12PSODesc.BlendState); d3d12PSODesc.SampleMask = GraphicsPipeline.SampleMask; + // Explicit topology keeps CreatePipelineState and PipelineLibrary::LoadPipeline + // descriptions identical; creation may infer topology from the mesh shader. + static const PrimitiveTopology_To_D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimTopologyToD3D12TopologyType; + d3d12PSODesc.PrimitiveTopologyType = PrimTopologyToD3D12TopologyType[GraphicsPipeline.PrimitiveTopology]; + RasterizerStateDesc_To_D3D12_RASTERIZER_DESC(GraphicsPipeline.RasterizerDesc, *d3d12PSODesc.RasterizerState); DepthStencilStateDesc_To_D3D12_DEPTH_STENCIL_DESC(GraphicsPipeline.DepthStencilDesc, *d3d12PSODesc.DepthStencilState); @@ -819,11 +888,18 @@ void PipelineStateD3D12Impl::InitializePipeline(const GraphicsPipelineStateCreat streamDesc.SizeInBytes = sizeof(d3d12PSODesc); streamDesc.pPipelineStateSubobjectStream = &d3d12PSODesc; - ID3D12Device2* pd3d12Device2 = m_pDevice->GetD3D12Device2(); - // Note: renderdoc frame capture fails if any interface but IID_ID3D12PipelineState is requested - HRESULT hr = pd3d12Device2->CreatePipelineState(&streamDesc, __uuidof(ID3D12PipelineState), IID_PPV_ARGS_Helper(&m_pd3d12PSO)); - if (FAILED(hr)) - LOG_ERROR_AND_THROW("Failed to create pipeline state"); + if (pPSOCacheD3D12 != nullptr) + m_pd3d12PSO = pPSOCacheD3D12->LoadPipeline(CacheName.c_str(), streamDesc); + if (!m_pd3d12PSO) + { + ID3D12Device2* pd3d12Device2 = m_pDevice->GetD3D12Device2(); + // Note: renderdoc frame capture fails if any interface but IID_ID3D12PipelineState is requested + HRESULT hr = pd3d12Device2->CreatePipelineState(&streamDesc, __uuidof(ID3D12PipelineState), IID_PPV_ARGS_Helper(&m_pd3d12PSO)); + if (FAILED(hr)) + LOG_ERROR_AND_THROW("Failed to create pipeline state"); + if (pPSOCacheD3D12 != nullptr) + pPSOCacheD3D12->StorePipeline(CacheName.c_str(), m_pd3d12PSO); + } } #endif // D3D12_H_HAS_MESH_SHADER else @@ -868,8 +944,9 @@ void PipelineStateD3D12Impl::InitializePipeline(const ComputePipelineStateCreate // Try to load from the cache const std::wstring WName = WidenString(m_Desc.Name); PipelineStateCacheD3D12Impl* const pPSOCacheD3D12 = ClassPtrCast(CreateInfo.pPSOCache); - if (pPSOCacheD3D12 != nullptr && !WName.empty()) - m_pd3d12PSO = pPSOCacheD3D12->LoadComputePipeline(WName.c_str(), d3d12PSODesc); + const std::wstring CacheName = pPSOCacheD3D12 ? GetPipelineCacheName(*m_RootSig, ShaderStages) : std::wstring{}; + if (pPSOCacheD3D12 != nullptr) + m_pd3d12PSO = pPSOCacheD3D12->LoadComputePipeline(CacheName.c_str(), d3d12PSODesc); if (!m_pd3d12PSO) { // Note: renderdoc frame capture fails if any interface but IID_ID3D12PipelineState is requested @@ -878,8 +955,8 @@ void PipelineStateD3D12Impl::InitializePipeline(const ComputePipelineStateCreate LOG_ERROR_AND_THROW("Failed to create pipeline state"); // Add to the cache - if (pPSOCacheD3D12 != nullptr && !WName.empty()) - pPSOCacheD3D12->StorePipeline(WName.c_str(), m_pd3d12PSO); + if (pPSOCacheD3D12 != nullptr) + pPSOCacheD3D12->StorePipeline(CacheName.c_str(), m_pd3d12PSO); } if (!WName.empty()) diff --git a/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp b/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp index 58c87b7ca7..4c178c7b1b 100644 --- a/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp +++ b/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp @@ -834,6 +834,7 @@ bool RenderStateCacheImpl::CreatePipelineStateInternal(const CreateInfoType& PSO UnpackInfo.PipelineType = PSOCreateInfo.PSODesc.PipelineType; UnpackInfo.Name = HashStr.c_str(); UnpackInfo.pDevice = m_pDevice; + UnpackInfo.pCache = PSOCreateInfo.pPSOCache; UnpackInfo.ModifyPipelineStateCreateInfo = Callback; UnpackInfo.pUserData = Callback; RefCntAutoPtr pPSO; From 0bf170dbc5aa312c142f6b54896fb493bb0580ea Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sun, 25 Oct 2026 14:43:07 +0800 Subject: [PATCH 2/9] D3D12: request pipeline state interface on native cache hits Load cached graphics and compute PSOs as ID3D12PipelineState, then explicitly upcast the returned raw pointer for shared DeviceChild storage, matching the mesh pipeline path. A separately queried DeviceChild interface is not guaranteed to be the base subobject of the PipelineState interface expected by downstream static casts and SetPipelineState. --- .../src/PipelineStateCacheD3D12Impl.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp index 3e860b7dd4..79a52aa37f 100644 --- a/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp @@ -89,7 +89,7 @@ CComPtr PipelineStateCacheD3D12Impl::LoadComputePipeline(cons return {}; } - CComPtr d3d12PSO; + CComPtr d3d12PSO; if (m_pLibrary && (m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) { std::lock_guard Lock{m_LibraryMtx}; @@ -97,7 +97,7 @@ CComPtr PipelineStateCacheD3D12Impl::LoadComputePipeline(cons if ((m_Desc.Flags & PSO_CACHE_FLAG_VERBOSE) != 0) LOG_INFO_MESSAGE(SUCCEEDED(hr) ? "PSO cache hit (compute): " : "PSO cache miss (compute): ", NarrowString(Name)); } - return d3d12PSO; + return CComPtr{d3d12PSO.p}; } CComPtr PipelineStateCacheD3D12Impl::LoadGraphicsPipeline(const wchar_t* Name, const D3D12_GRAPHICS_PIPELINE_STATE_DESC& Desc) @@ -108,7 +108,7 @@ CComPtr PipelineStateCacheD3D12Impl::LoadGraphicsPipeline(con return {}; } - CComPtr d3d12PSO; + CComPtr d3d12PSO; if (m_pLibrary && (m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) { std::lock_guard Lock{m_LibraryMtx}; @@ -116,7 +116,7 @@ CComPtr PipelineStateCacheD3D12Impl::LoadGraphicsPipeline(con if ((m_Desc.Flags & PSO_CACHE_FLAG_VERBOSE) != 0) LOG_INFO_MESSAGE(SUCCEEDED(hr) ? "PSO cache hit (graphics): " : "PSO cache miss (graphics): ", NarrowString(Name)); } - return d3d12PSO; + return CComPtr{d3d12PSO.p}; } #ifdef D3D12_H_HAS_MESH_SHADER From 78f0940df81c4bab0f936dcfa805f7c484d02c86 Mon Sep 17 00:00:00 2001 From: assiduous Date: Tue, 27 Oct 2026 21:05:55 +0000 Subject: [PATCH 3/9] D3D12: Use XXH128 for pipeline cache keys --- .../src/PipelineStateD3D12Impl.cpp | 50 ++----------------- 1 file changed, 4 insertions(+), 46 deletions(-) diff --git a/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp index 008e6481f5..3644da38e4 100644 --- a/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,7 +51,7 @@ #include "DynamicLinearAllocator.hpp" #include "D3DShaderResourceValidation.hpp" #include "DataBlobImpl.hpp" -#include "HashUtils.hpp" +#include "XXH128Hasher.hpp" #include "DXBCUtils.hpp" #include "DXCompiler.hpp" @@ -64,54 +64,12 @@ constexpr INTERFACE_ID PipelineStateD3D12Impl::IID_InternalImpl; namespace { -struct PipelineCacheHasher -{ - template - void Update(const T& Value) - { - if constexpr (std::is_fundamental::value || std::is_enum::value) - UpdateRaw(&Value, sizeof(Value)); - else - HashCombiner{*this}(Value); - } - - void Update(const char* Value) - { - const Uint64 Length = Value != nullptr ? std::strlen(Value) : 0; - Update(Length); - UpdateRaw(Value, Length); - } - - template - void operator()(const Args&... Values) - { - (Update(Values), ...); - } - - void UpdateRaw(const void* pData, Uint64 Size) - { - const auto* Bytes = static_cast(pData); - for (Uint64 i = 0; i < Size; ++i) - { - m_Hash ^= Bytes[i]; - m_Hash *= FNVPrime; - } - } - - Uint64 Get() const { return m_Hash; } - -private: - static constexpr Uint64 FNVOffsetBasis = 14695981039346656037ull; - static constexpr Uint64 FNVPrime = 1099511628211ull; - Uint64 m_Hash = FNVOffsetBasis; -}; - template std::wstring GetPipelineCacheName(const RootSignatureD3D12& RootSig, const ShaderStagesType& ShaderStages, const GraphicsPipelineDesc* pGraphics = nullptr) { // Hash the effective pipeline after binding remapping. Archive unpacking changes // create-info flags/signatures and shader bindings, but not the native pipeline. - PipelineCacheHasher Hasher; + XXH128State Hasher; Hasher(RootSig.GetHash(), pGraphics != nullptr); if (pGraphics != nullptr) Hasher(*pGraphics); @@ -121,7 +79,7 @@ std::wstring GetPipelineCacheName(const RootSignatureD3D12& RootSig, const Shade for (const auto& Bytecode : Stage.ByteCodes) Hasher.UpdateRaw(Bytecode->GetConstDataPtr(), Bytecode->GetSize()); } - return L"PSO_2_" + std::to_wstring(Hasher.Get()); + return L"PSO_2_" + WidenString(Hasher.Digest().ToString()); } #ifdef _MSC_VER From 6be72bb84e4bd1f4cedd9fb1f9fa11d5476edc5b Mon Sep 17 00:00:00 2001 From: assiduous Date: Tue, 27 Oct 2026 21:30:35 +0000 Subject: [PATCH 4/9] D3D12: Simplify pipeline cache name helper --- .../GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp index 3644da38e4..63ba13af2f 100644 --- a/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp @@ -64,13 +64,18 @@ constexpr INTERFACE_ID PipelineStateD3D12Impl::IID_InternalImpl; namespace { -template -std::wstring GetPipelineCacheName(const RootSignatureD3D12& RootSig, const ShaderStagesType& ShaderStages, const GraphicsPipelineDesc* pGraphics = nullptr) +std::wstring GetPipelineCacheName(const RootSignatureD3D12& RootSig, + const PipelineStateD3D12Impl::TShaderStages& ShaderStages, + const GraphicsPipelineDesc* pGraphics = nullptr) { // Hash the effective pipeline after binding remapping. Archive unpacking changes // create-info flags/signatures and shader bindings, but not the native pipeline. XXH128State Hasher; Hasher(RootSig.GetHash(), pGraphics != nullptr); + + // Graphics and mesh pipelines have additional fixed-function state. Compute + // pipelines are fully described by the root signature and shader bytecode. + // Ray tracing uses a separate state-object creation path and is not cached here. if (pGraphics != nullptr) Hasher(*pGraphics); for (const auto& Stage : ShaderStages) From b6a60b67760b1dbeec06f2719256ab73777fb9a7 Mon Sep 17 00:00:00 2001 From: assiduous Date: Tue, 27 Oct 2026 21:38:42 +0000 Subject: [PATCH 5/9] D3D12: Extract shared pipeline cache creation flow Use LoadOrCreatePipeline for graphics, compute, and mesh pipelines. Centralize cache lookup, creation fallback, error handling, and storage while preserving the interface requirement for RenderDoc capture. --- .../src/PipelineStateD3D12Impl.cpp | 115 ++++++++++-------- 1 file changed, 65 insertions(+), 50 deletions(-) diff --git a/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp index 63ba13af2f..4839d9bb99 100644 --- a/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp @@ -64,20 +64,23 @@ constexpr INTERFACE_ID PipelineStateD3D12Impl::IID_InternalImpl; namespace { -std::wstring GetPipelineCacheName(const RootSignatureD3D12& RootSig, - const PipelineStateD3D12Impl::TShaderStages& ShaderStages, - const GraphicsPipelineDesc* pGraphics = nullptr) +std::wstring GetPSOKey(const RootSignatureD3D12& RootSig, + const PipelineStateD3D12Impl::TShaderStages& ShaderStages, + const GraphicsPipelineDesc* pGraphics = nullptr) { // Hash the effective pipeline after binding remapping. Archive unpacking changes // create-info flags/signatures and shader bindings, but not the native pipeline. XXH128State Hasher; Hasher(RootSig.GetHash(), pGraphics != nullptr); - // Graphics and mesh pipelines have additional fixed-function state. Compute - // pipelines are fully described by the root signature and shader bytecode. - // Ray tracing uses a separate state-object creation path and is not cached here. if (pGraphics != nullptr) + { + // Graphics and mesh pipelines have additional fixed-function state. Compute + // pipelines are fully described by the root signature and shader bytecode. + // Ray tracing uses a separate state-object creation path and is not cached here. Hasher(*pGraphics); + } + for (const auto& Stage : ShaderStages) { Hasher(Stage.Type); @@ -87,6 +90,37 @@ std::wstring GetPipelineCacheName(const RootSignatureD3D12& R return L"PSO_2_" + WidenString(Hasher.Digest().ToString()); } +template +CComPtr LoadOrCreatePipeline(IPipelineStateCache* pPSOCache, + const RootSignatureD3D12& RootSig, + const PipelineStateD3D12Impl::TShaderStages& ShaderStages, + const GraphicsPipelineDesc* pGraphics, + const LoadPipelineType& LoadPipeline, + const CreatePipelineType& CreatePipeline) +{ + PipelineStateCacheD3D12Impl* const pCacheD3D12 = ClassPtrCast(pPSOCache); + + std::wstring PSOKey; + CComPtr pPSO; + if (pCacheD3D12 != nullptr) + { + PSOKey = GetPSOKey(RootSig, ShaderStages, pGraphics); + pPSO = LoadPipeline(*pCacheD3D12, PSOKey.c_str()); + } + + if (!pPSO) + { + // RenderDoc frame capture requires requesting IID_ID3D12PipelineState. + const HRESULT hr = CreatePipeline(__uuidof(ID3D12PipelineState), IID_PPV_ARGS_Helper(&pPSO)); + if (FAILED(hr)) + LOG_ERROR_AND_THROW("Failed to create pipeline state"); + + if (pCacheD3D12 != nullptr) + pCacheD3D12->StorePipeline(PSOKey.c_str(), pPSO); + } + return pPSO; +} + #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable : 4324) // warning C4324: structure was padded due to alignment specifier @@ -665,9 +699,6 @@ void PipelineStateD3D12Impl::InitializePipeline(const GraphicsPipelineStateCreat TShaderStages ShaderStages; InitInternalObjects(CreateInfo, ShaderStages); - PipelineStateCacheD3D12Impl* const pPSOCacheD3D12 = ClassPtrCast(CreateInfo.pPSOCache); - const std::wstring CacheName = pPSOCacheD3D12 ? GetPipelineCacheName(*m_RootSig, ShaderStages, &m_pGraphicsPipelineData->Desc) : std::wstring{}; - ID3D12Device* pd3d12Device = m_pDevice->GetD3D12Device(); if (m_Desc.PipelineType == PIPELINE_TYPE_GRAPHICS) { @@ -753,20 +784,14 @@ void PipelineStateD3D12Impl::InitializePipeline(const GraphicsPipelineStateCreat // The only valid bit is D3D12_PIPELINE_STATE_FLAG_TOOL_DEBUG, which can only be set on WARP devices. d3d12PSODesc.Flags = D3D12_PIPELINE_STATE_FLAG_NONE; - // Try to load from the cache - if (pPSOCacheD3D12 != nullptr) - m_pd3d12PSO = pPSOCacheD3D12->LoadGraphicsPipeline(CacheName.c_str(), d3d12PSODesc); - if (!m_pd3d12PSO) - { - // Note: renderdoc frame capture fails if any interface but IID_ID3D12PipelineState is requested - HRESULT hr = pd3d12Device->CreateGraphicsPipelineState(&d3d12PSODesc, __uuidof(ID3D12PipelineState), IID_PPV_ARGS_Helper(&m_pd3d12PSO)); - if (FAILED(hr)) - LOG_ERROR_AND_THROW("Failed to create pipeline state"); - - // Add to the cache - if (pPSOCacheD3D12 != nullptr) - pPSOCacheD3D12->StorePipeline(CacheName.c_str(), m_pd3d12PSO); - } + m_pd3d12PSO = LoadOrCreatePipeline( + CreateInfo.pPSOCache, *m_RootSig, ShaderStages, &GraphicsPipeline, + [&](PipelineStateCacheD3D12Impl& Cache, const wchar_t* Name) { + return Cache.LoadGraphicsPipeline(Name, d3d12PSODesc); + }, + [&](REFIID IID, void** ppPSO) { + return pd3d12Device->CreateGraphicsPipelineState(&d3d12PSODesc, IID, ppPSO); + }); } #ifdef D3D12_H_HAS_MESH_SHADER else if (m_Desc.PipelineType == PIPELINE_TYPE_MESH) @@ -851,18 +876,14 @@ void PipelineStateD3D12Impl::InitializePipeline(const GraphicsPipelineStateCreat streamDesc.SizeInBytes = sizeof(d3d12PSODesc); streamDesc.pPipelineStateSubobjectStream = &d3d12PSODesc; - if (pPSOCacheD3D12 != nullptr) - m_pd3d12PSO = pPSOCacheD3D12->LoadPipeline(CacheName.c_str(), streamDesc); - if (!m_pd3d12PSO) - { - ID3D12Device2* pd3d12Device2 = m_pDevice->GetD3D12Device2(); - // Note: renderdoc frame capture fails if any interface but IID_ID3D12PipelineState is requested - HRESULT hr = pd3d12Device2->CreatePipelineState(&streamDesc, __uuidof(ID3D12PipelineState), IID_PPV_ARGS_Helper(&m_pd3d12PSO)); - if (FAILED(hr)) - LOG_ERROR_AND_THROW("Failed to create pipeline state"); - if (pPSOCacheD3D12 != nullptr) - pPSOCacheD3D12->StorePipeline(CacheName.c_str(), m_pd3d12PSO); - } + m_pd3d12PSO = LoadOrCreatePipeline( + CreateInfo.pPSOCache, *m_RootSig, ShaderStages, &GraphicsPipeline, + [&](PipelineStateCacheD3D12Impl& Cache, const wchar_t* Name) { + return Cache.LoadPipeline(Name, streamDesc); + }, + [&](REFIID IID, void** ppPSO) { + return m_pDevice->GetD3D12Device2()->CreatePipelineState(&streamDesc, IID, ppPSO); + }); } #endif // D3D12_H_HAS_MESH_SHADER else @@ -905,22 +926,16 @@ void PipelineStateD3D12Impl::InitializePipeline(const ComputePipelineStateCreate d3d12PSODesc.pRootSignature = m_RootSig->GetD3D12RootSignature(); // Try to load from the cache - const std::wstring WName = WidenString(m_Desc.Name); - PipelineStateCacheD3D12Impl* const pPSOCacheD3D12 = ClassPtrCast(CreateInfo.pPSOCache); - const std::wstring CacheName = pPSOCacheD3D12 ? GetPipelineCacheName(*m_RootSig, ShaderStages) : std::wstring{}; - if (pPSOCacheD3D12 != nullptr) - m_pd3d12PSO = pPSOCacheD3D12->LoadComputePipeline(CacheName.c_str(), d3d12PSODesc); - if (!m_pd3d12PSO) - { - // Note: renderdoc frame capture fails if any interface but IID_ID3D12PipelineState is requested - HRESULT hr = pd3d12Device->CreateComputePipelineState(&d3d12PSODesc, __uuidof(ID3D12PipelineState), IID_PPV_ARGS_Helper(&m_pd3d12PSO)); - if (FAILED(hr)) - LOG_ERROR_AND_THROW("Failed to create pipeline state"); + const std::wstring WName = WidenString(m_Desc.Name); - // Add to the cache - if (pPSOCacheD3D12 != nullptr) - pPSOCacheD3D12->StorePipeline(CacheName.c_str(), m_pd3d12PSO); - } + m_pd3d12PSO = LoadOrCreatePipeline( + CreateInfo.pPSOCache, *m_RootSig, ShaderStages, nullptr, + [&](PipelineStateCacheD3D12Impl& Cache, const wchar_t* Name) { + return Cache.LoadComputePipeline(Name, d3d12PSODesc); + }, + [&](REFIID IID, void** ppPSO) { + return pd3d12Device->CreateComputePipelineState(&d3d12PSODesc, IID, ppPSO); + }); if (!WName.empty()) { From 634263f814018ab3e2dab9a3d86332d389d79cc2 Mon Sep 17 00:00:00 2001 From: assiduous Date: Tue, 27 Oct 2026 22:10:39 +0000 Subject: [PATCH 6/9] D3D12: Clarify pipeline library data lifetime --- .../include/PipelineStateCacheD3D12Impl.hpp | 9 ++++++--- .../src/PipelineStateCacheD3D12Impl.cpp | 10 +++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp b/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp index ab78bc6558..38b5b96ae4 100644 --- a/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp +++ b/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 Diligent Graphics LLC + * Copyright 2019-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. @@ -61,10 +61,13 @@ class PipelineStateCacheD3D12Impl final : public PipelineStateCacheBase m_pInitialData; + // CreatePipelineLibrary retains the raw data pointer but neither copies the bytes + // nor takes ownership of the memory. + RefCntAutoPtr m_pInitialData; + + std::mutex m_LibraryMtx; CComPtr m_pLibrary; CComPtr m_pLibrary1; - std::mutex m_LibraryMtx; }; } // namespace Diligent diff --git a/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp index 79a52aa37f..def68d732a 100644 --- a/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp @@ -46,17 +46,20 @@ PipelineStateCacheD3D12Impl::PipelineStateCacheD3D12Impl(IReferenceCounters* } // clang-format on { - auto* pDevice1 = pRenderDeviceD3D12->GetD3D12Device1(); + ID3D12Device1* pDevice1 = pRenderDeviceD3D12->GetD3D12Device1(); if (pDevice1 == nullptr) { LOG_WARNING_MESSAGE("D3D12 pipeline libraries are unavailable; native caching is disabled."); return; } - // D3D12 does not copy the serialized data: retain our own copy until the library is released. if (CreateInfo.pCacheData != nullptr && CreateInfo.CacheDataSize != 0) { - m_pInitialData = DataBlobImpl::Create(CreateInfo.CacheDataSize, CreateInfo.pCacheData); + // CreatePipelineLibrary retains the raw data pointer but neither copies the bytes + // nor takes ownership of the memory. Keeping the library alive does not keep the + // IDataBlob alive, so own a copy and retain it until the library is destroyed. + m_pInitialData = DataBlobImpl::Create(CreateInfo.CacheDataSize, CreateInfo.pCacheData); + const HRESULT LoadResult = pDevice1->CreatePipelineLibrary(m_pInitialData->GetConstDataPtr(), m_pInitialData->GetSize(), IID_PPV_ARGS(&m_pLibrary)); if (FAILED(LoadResult)) { @@ -78,6 +81,7 @@ PipelineStateCacheD3D12Impl::~PipelineStateCacheD3D12Impl() { // D3D12 object can only be destroyed when it is no longer used by the GPU m_pLibrary1.Release(); + // Keep the backing data alive through deferred destruction of the library. GetDevice()->SafeReleaseDeviceObject(std::make_pair(std::move(m_pInitialData), std::move(m_pLibrary)), ~Uint64{0}); } From 46201bfc14f9fa1168ff51e9ed8a2fd16ddff53c Mon Sep 17 00:00:00 2001 From: assiduous Date: Tue, 27 Oct 2026 22:23:58 +0000 Subject: [PATCH 7/9] D3D12: Preserve shader-declared mesh topology Omit the primitive topology subobject from mesh pipeline streams and document why GraphicsPipeline.PrimitiveTopology is ignored. --- .../src/PipelineStateD3D12Impl.cpp | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp index 4839d9bb99..9ce0c2d73f 100644 --- a/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp @@ -798,23 +798,24 @@ void PipelineStateD3D12Impl::InitializePipeline(const GraphicsPipelineStateCreat { const GraphicsPipelineDesc& GraphicsPipeline = m_pGraphicsPipelineData->Desc; + // Omit the primitive topology subobject: mesh shaders declare their output topology + // with [outputtopology], and GraphicsPipeline.PrimitiveTopology is ignored. struct MESH_SHADER_PIPELINE_STATE_DESC { - PSS_SubObject Flags; - PSS_SubObject NodeMask; - PSS_SubObject pRootSignature; - PSS_SubObject PS; - PSS_SubObject AS; - PSS_SubObject MS; - PSS_SubObject BlendState; - PSS_SubObject DepthStencilState; - PSS_SubObject RasterizerState; - PSS_SubObject SampleDesc; - PSS_SubObject SampleMask; - PSS_SubObject PrimitiveTopologyType; - PSS_SubObject DSVFormat; - PSS_SubObject RTVFormatArray; - PSS_SubObject CachedPSO; + PSS_SubObject Flags; + PSS_SubObject NodeMask; + PSS_SubObject pRootSignature; + PSS_SubObject PS; + PSS_SubObject AS; + PSS_SubObject MS; + PSS_SubObject BlendState; + PSS_SubObject DepthStencilState; + PSS_SubObject RasterizerState; + PSS_SubObject SampleDesc; + PSS_SubObject SampleMask; + PSS_SubObject DSVFormat; + PSS_SubObject RTVFormatArray; + PSS_SubObject CachedPSO; }; MESH_SHADER_PIPELINE_STATE_DESC d3d12PSODesc = {}; @@ -843,11 +844,6 @@ void PipelineStateD3D12Impl::InitializePipeline(const GraphicsPipelineStateCreat BlendStateDesc_To_D3D12_BLEND_DESC(GraphicsPipeline.BlendDesc, *d3d12PSODesc.BlendState); d3d12PSODesc.SampleMask = GraphicsPipeline.SampleMask; - // Explicit topology keeps CreatePipelineState and PipelineLibrary::LoadPipeline - // descriptions identical; creation may infer topology from the mesh shader. - static const PrimitiveTopology_To_D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimTopologyToD3D12TopologyType; - d3d12PSODesc.PrimitiveTopologyType = PrimTopologyToD3D12TopologyType[GraphicsPipeline.PrimitiveTopology]; - RasterizerStateDesc_To_D3D12_RASTERIZER_DESC(GraphicsPipeline.RasterizerDesc, *d3d12PSODesc.RasterizerState); DepthStencilStateDesc_To_D3D12_DEPTH_STENCIL_DESC(GraphicsPipeline.DepthStencilDesc, *d3d12PSODesc.DepthStencilState); From c555e4d26de3f414eaf792fa16c0c24827619b17 Mon Sep 17 00:00:00 2001 From: assiduous Date: Tue, 27 Oct 2026 22:31:53 +0000 Subject: [PATCH 8/9] D3D12: Consolidate pipeline cache loading Extract shared validation, locking, and logging into LoadPipelineImpl for graphics, compute, and mesh pipelines. --- .../include/PipelineStateCacheD3D12Impl.hpp | 6 +++ .../src/PipelineStateCacheD3D12Impl.cpp | 52 ++++++++----------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp b/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp index 38b5b96ae4..8ad318584c 100644 --- a/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp +++ b/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp @@ -61,6 +61,12 @@ class PipelineStateCacheD3D12Impl final : public PipelineStateCacheBase + CComPtr LoadPipelineImpl(ID3D12PipelineLibrary* pLibrary, + const wchar_t* Name, + const char* PipelineType, + const LoadPipelineType& LoadPipeline); + // CreatePipelineLibrary retains the raw data pointer but neither copies the bytes // nor takes ownership of the memory. RefCntAutoPtr m_pInitialData; diff --git a/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp index def68d732a..d77c098218 100644 --- a/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-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. @@ -85,7 +85,11 @@ PipelineStateCacheD3D12Impl::~PipelineStateCacheD3D12Impl() GetDevice()->SafeReleaseDeviceObject(std::make_pair(std::move(m_pInitialData), std::move(m_pLibrary)), ~Uint64{0}); } -CComPtr PipelineStateCacheD3D12Impl::LoadComputePipeline(const wchar_t* Name, const D3D12_COMPUTE_PIPELINE_STATE_DESC& Desc) +template +CComPtr PipelineStateCacheD3D12Impl::LoadPipelineImpl(ID3D12PipelineLibrary* pLibrary, + const wchar_t* Name, + const char* PipelineType, + const LoadPipelineType& LoadPipeline) { if (Name == nullptr) { @@ -94,48 +98,36 @@ CComPtr PipelineStateCacheD3D12Impl::LoadComputePipeline(cons } CComPtr d3d12PSO; - if (m_pLibrary && (m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) + if (pLibrary != nullptr && (m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) { std::lock_guard Lock{m_LibraryMtx}; - HRESULT hr = m_pLibrary->LoadComputePipeline(Name, &Desc, IID_PPV_ARGS(&d3d12PSO)); + const HRESULT hr = LoadPipeline(IID_PPV_ARGS(&d3d12PSO)); if ((m_Desc.Flags & PSO_CACHE_FLAG_VERBOSE) != 0) - LOG_INFO_MESSAGE(SUCCEEDED(hr) ? "PSO cache hit (compute): " : "PSO cache miss (compute): ", NarrowString(Name)); + LOG_INFO_MESSAGE("PSO cache ", SUCCEEDED(hr) ? "hit" : "miss", " (", PipelineType, "): ", NarrowString(Name), " (HRESULT ", hr, ")"); } return CComPtr{d3d12PSO.p}; } -CComPtr PipelineStateCacheD3D12Impl::LoadGraphicsPipeline(const wchar_t* Name, const D3D12_GRAPHICS_PIPELINE_STATE_DESC& Desc) +CComPtr PipelineStateCacheD3D12Impl::LoadComputePipeline(const wchar_t* Name, const D3D12_COMPUTE_PIPELINE_STATE_DESC& Desc) { - if (Name == nullptr) - { - DEV_ERROR("Pipeline name must not be null"); - return {}; - } + return LoadPipelineImpl(m_pLibrary, Name, "compute", [&](REFIID IID, void** ppPSO) { + return m_pLibrary->LoadComputePipeline(Name, &Desc, IID, ppPSO); + }); +} - CComPtr d3d12PSO; - if (m_pLibrary && (m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) - { - std::lock_guard Lock{m_LibraryMtx}; - HRESULT hr = m_pLibrary->LoadGraphicsPipeline(Name, &Desc, IID_PPV_ARGS(&d3d12PSO)); - if ((m_Desc.Flags & PSO_CACHE_FLAG_VERBOSE) != 0) - LOG_INFO_MESSAGE(SUCCEEDED(hr) ? "PSO cache hit (graphics): " : "PSO cache miss (graphics): ", NarrowString(Name)); - } - return CComPtr{d3d12PSO.p}; +CComPtr PipelineStateCacheD3D12Impl::LoadGraphicsPipeline(const wchar_t* Name, const D3D12_GRAPHICS_PIPELINE_STATE_DESC& Desc) +{ + return LoadPipelineImpl(m_pLibrary, Name, "graphics", [&](REFIID IID, void** ppPSO) { + return m_pLibrary->LoadGraphicsPipeline(Name, &Desc, IID, ppPSO); + }); } #ifdef D3D12_H_HAS_MESH_SHADER CComPtr PipelineStateCacheD3D12Impl::LoadPipeline(const wchar_t* Name, const D3D12_PIPELINE_STATE_STREAM_DESC& Desc) { - VERIFY_EXPR(Name != nullptr); - CComPtr d3d12PSO; - if (m_pLibrary1 && (m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) - { - std::lock_guard Lock{m_LibraryMtx}; - const HRESULT hr = m_pLibrary1->LoadPipeline(Name, &Desc, IID_PPV_ARGS(&d3d12PSO)); - if ((m_Desc.Flags & PSO_CACHE_FLAG_VERBOSE) != 0) - LOG_INFO_MESSAGE(SUCCEEDED(hr) ? "PSO cache hit (mesh): " : "PSO cache miss (mesh): ", NarrowString(Name), " (HRESULT ", hr, ")"); - } - return CComPtr{d3d12PSO.p}; + return LoadPipelineImpl(m_pLibrary1, Name, "mesh", [&](REFIID IID, void** ppPSO) { + return m_pLibrary1->LoadPipeline(Name, &Desc, IID, ppPSO); + }); } #endif From 295dca89fc4f12a41addb9aedfcdbfc093da0020 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 21 Oct 2026 12:25:56 +0800 Subject: [PATCH 9/9] WebGPU: preserve shader entry points in render state cache archives --- .../include/ShaderWebGPUImpl.hpp | 3 +- .../interface/ShaderWebGPU.h | 5 +- .../interface/GraphicsUtilities.h | 6 +- .../GraphicsTools/src/GraphicsUtilities.cpp | 7 +- .../src/GraphicsUtilitiesWebGPU.cpp | 11 +- .../src/RenderStateCacheImpl.cpp | 1 + .../src/RenderStateCacheTest.cpp | 143 +++++++++++++++++- 7 files changed, 170 insertions(+), 6 deletions(-) diff --git a/Graphics/GraphicsEngineWebGPU/include/ShaderWebGPUImpl.hpp b/Graphics/GraphicsEngineWebGPU/include/ShaderWebGPUImpl.hpp index c82cd77fbd..91a8c66c9f 100644 --- a/Graphics/GraphicsEngineWebGPU/include/ShaderWebGPUImpl.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/ShaderWebGPUImpl.hpp @@ -87,7 +87,8 @@ class ShaderWebGPUImpl final : public ShaderBase return m_pShaderResources->GetEmulatedArrayIndexSuffix(); } - const char* GetEntryPoint() const; + /// Implementation of IShaderWebGPU::GetEntryPoint(). + const char* DILIGENT_CALL_TYPE GetEntryPoint() const override final; const std::shared_ptr& GetShaderResources() const { diff --git a/Graphics/GraphicsEngineWebGPU/interface/ShaderWebGPU.h b/Graphics/GraphicsEngineWebGPU/interface/ShaderWebGPU.h index 3d3c9198ab..d176e9840a 100644 --- a/Graphics/GraphicsEngineWebGPU/interface/ShaderWebGPU.h +++ b/Graphics/GraphicsEngineWebGPU/interface/ShaderWebGPU.h @@ -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. @@ -63,6 +63,9 @@ class IShaderWebGPU : public IShader /// This value is defined by `ShaderCI.WebGPUEmulatedArrayIndexSuffix`, /// see Diligent::ShaderCreateInfo::WebGPUEmulatedArrayIndexSuffix. virtual const char* DILIGENT_CALL_TYPE GetEmulatedArrayIndexSuffix() const = 0; + + /// Returns the name of the selected entry point in the WGSL source code. + virtual const char* DILIGENT_CALL_TYPE GetEntryPoint() const = 0; }; #endif diff --git a/Graphics/GraphicsTools/interface/GraphicsUtilities.h b/Graphics/GraphicsTools/interface/GraphicsUtilities.h index c0398fa8cc..859352c76c 100644 --- a/Graphics/GraphicsTools/interface/GraphicsUtilities.h +++ b/Graphics/GraphicsTools/interface/GraphicsUtilities.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -227,6 +227,10 @@ IBufferView* DILIGENT_GLOBAL_FUNCTION(GetBufferDefaultUAV)(IObject* pBuffer); /// For other shader types, returns null. const char* DILIGENT_GLOBAL_FUNCTION(GetWebGPUEmulatedArrayIndexSuffix)(IShader* pShader); +/// For WebGPU shaders, returns the name of the selected entry point in the WGSL source code. +/// For other shader types, returns null. +const char* DILIGENT_GLOBAL_FUNCTION(GetWebGPUShaderEntryPoint)(IShader* pShader); + /// Returns the native texture format (e.g. DXGI_FORMAT, VkFormat) for the given texture format and device type. int64_t DILIGENT_GLOBAL_FUNCTION(GetNativeTextureFormat)(TEXTURE_FORMAT TexFormat, enum RENDER_DEVICE_TYPE DeviceType); diff --git a/Graphics/GraphicsTools/src/GraphicsUtilities.cpp b/Graphics/GraphicsTools/src/GraphicsUtilities.cpp index 2114b07279..c5c1efc34d 100644 --- a/Graphics/GraphicsTools/src/GraphicsUtilities.cpp +++ b/Graphics/GraphicsTools/src/GraphicsUtilities.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -578,6 +578,11 @@ const char* GetWebGPUEmulatedArrayIndexSuffix(IShader* pShader) { return nullptr; } + +const char* GetWebGPUShaderEntryPoint(IShader* pShader) +{ + return nullptr; +} #endif int64_t GetNativeTextureFormat(TEXTURE_FORMAT TexFormat, RENDER_DEVICE_TYPE DeviceType) diff --git a/Graphics/GraphicsTools/src/GraphicsUtilitiesWebGPU.cpp b/Graphics/GraphicsTools/src/GraphicsUtilitiesWebGPU.cpp index 47201a94ad..ed88b60c39 100644 --- a/Graphics/GraphicsTools/src/GraphicsUtilitiesWebGPU.cpp +++ b/Graphics/GraphicsTools/src/GraphicsUtilitiesWebGPU.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2024 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. @@ -49,6 +49,15 @@ const char* GetWebGPUEmulatedArrayIndexSuffix(IShader* pShader) return nullptr; } +const char* GetWebGPUShaderEntryPoint(IShader* pShader) +{ + if (RefCntAutoPtr pShaderWGPU{pShader, IID_ShaderWebGPU}) + { + return pShaderWGPU->GetEntryPoint(); + } + return nullptr; +} + int64_t GetNativeTextureFormatWebGPU(TEXTURE_FORMAT TexFormat) { return static_cast(TextureFormatToWGPUFormat(TexFormat)); diff --git a/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp b/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp index 4c178c7b1b..cac1f3defb 100644 --- a/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp +++ b/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp @@ -595,6 +595,7 @@ struct RenderStateCacheImpl::SerializedPsoCIWrapperBase ShaderCI.Source = static_cast(ShaderCI.ByteCode); ShaderCI.ByteCode = nullptr; ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.EntryPoint = GetWebGPUShaderEntryPoint(pShader); ShaderCI.WebGPUEmulatedArrayIndexSuffix = GetWebGPUEmulatedArrayIndexSuffix(pShader); } ShaderArchiveInfo ArchiveInfo; diff --git a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp index a96ca8ff9c..9ae4a4446e 100644 --- a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp +++ b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-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. @@ -847,6 +847,147 @@ void TestGraphicsPSO(bool UseRenderPass, bool CompileAsync = false) } } +void TestWebGPUEntryPoints(bool LoadShadersFromArchive, bool MultipleEntryPoints) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pDevice = pEnv->GetDevice(); + if (!pDevice->GetDeviceInfo().IsWebGPUDevice()) + GTEST_SKIP() << "WGSL entry points require WebGPU"; + if (pEnv->GetArchiverFactory() == nullptr) + GTEST_SKIP() << "Archiver library is not loaded"; + + GPUTestingEnvironment::ScopedReset AutoReset; + + const SHADER_TYPE ShaderTypes[] = {SHADER_TYPE_VERTEX, SHADER_TYPE_PIXEL, SHADER_TYPE_COMPUTE}; + const char* EntryPoints[] = {"vertexMain", "fragmentMain", "computeMain"}; + const char* Sources[] = { + "@vertex fn vertexMain() -> @builtin(position) vec4f { return vec4f(0.0, 0.0, 0.0, 1.0); }", + "@fragment fn fragmentMain() -> @location(0) vec4f { return vec4f(1.0); }", + "@compute @workgroup_size(1) fn computeMain() {}", + }; + const char* OtherEntryPoints[] = { + "@group(0) @binding(0) var unusedValue: vec4f;\n" + "@vertex fn unusedVertex() -> @builtin(position) vec4f { return unusedValue; }\n", + "@group(0) @binding(0) var unusedValue: vec4f;\n" + "@fragment fn unusedFragment() -> @location(0) vec4f { return unusedValue; }\n", + "@group(0) @binding(0) var unusedValue: array;\n" + "@compute @workgroup_size(1) fn unusedCompute() { unusedValue[0] = 1u; }\n", + }; + + std::string ShaderSources[_countof(ShaderTypes)]; + ShaderCreateInfo ShaderCIs[_countof(ShaderTypes)]; + for (Uint32 i = 0; i < _countof(ShaderTypes); ++i) + { + // Put another entry point of the same stage first, so inferring a single + // entry point or selecting the first one cannot replace the selected name. + ShaderSources[i] = MultipleEntryPoints ? OtherEntryPoints[i] : ""; + ShaderSources[i] += Sources[i]; + ShaderCIs[i].Desc = {EntryPoints[i], ShaderTypes[i]}; + ShaderCIs[i].SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCIs[i].Source = ShaderSources[i].c_str(); + ShaderCIs[i].EntryPoint = EntryPoints[i]; + } + + RefCntAutoPtr pData; + if (LoadShadersFromArchive) + { + // Save shaders without a PSO. Reloading them must exercise device-shader + // reserialization when the PSO is first added to the new cache. + auto pCache = CreateCache(pDevice, false); + ASSERT_NE(nullptr, pCache); + for (const auto& ShaderCI : ShaderCIs) + { + RefCntAutoPtr pShader; + EXPECT_FALSE(pCache->CreateShader(ShaderCI, &pShader)); + ASSERT_NE(nullptr, pShader); + ASSERT_EQ(SHADER_STATUS_READY, pShader->GetStatus()); + } + ASSERT_TRUE(pCache->WriteToBlob(ContentVersion, &pData)); + ASSERT_NE(nullptr, pData); + } + + for (Uint32 Pass = 0; Pass < 2; ++Pass) + { + SCOPED_TRACE(Pass); + auto pCache = CreateCache(pDevice, false, pData); + ASSERT_NE(nullptr, pCache); + + RefCntAutoPtr Shaders[_countof(ShaderTypes)]; + for (Uint32 i = 0; i < _countof(ShaderTypes); ++i) + { + if (LoadShadersFromArchive) + EXPECT_TRUE(pCache->CreateShader(ShaderCIs[i], &Shaders[i])); + else + pDevice->CreateShader(ShaderCIs[i], &Shaders[i]); + ASSERT_NE(nullptr, Shaders[i]); + ASSERT_EQ(SHADER_STATUS_READY, Shaders[i]->GetStatus()); + } + + GraphicsPipelineStateCreateInfo GraphicsCI; + GraphicsCI.PSODesc.Name = "RenderStateCache - WGSL Graphics"; + GraphicsCI.GraphicsPipeline.NumRenderTargets = 1; + GraphicsCI.GraphicsPipeline.RTVFormats[0] = pEnv->GetSwapChain()->GetDesc().ColorBufferFormat; + GraphicsCI.GraphicsPipeline.PrimitiveTopology = PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + GraphicsCI.GraphicsPipeline.RasterizerDesc.CullMode = CULL_MODE_NONE; + GraphicsCI.GraphicsPipeline.DepthStencilDesc.DepthEnable = False; + GraphicsCI.pVS = Shaders[0]; + GraphicsCI.pPS = Shaders[1]; + + RefCntAutoPtr pGraphicsPSO; + EXPECT_EQ(Pass != 0, pCache->CreateGraphicsPipelineState(GraphicsCI, &pGraphicsPSO)); + ASSERT_NE(nullptr, pGraphicsPSO); + ASSERT_EQ(PIPELINE_STATE_STATUS_READY, pGraphicsPSO->GetStatus()); + + ComputePipelineStateCreateInfo ComputeCI; + ComputeCI.PSODesc.Name = "RenderStateCache - WGSL Compute"; + ComputeCI.PSODesc.PipelineType = PIPELINE_TYPE_COMPUTE; + ComputeCI.pCS = Shaders[2]; + + RefCntAutoPtr pComputePSO; + EXPECT_EQ(Pass != 0, pCache->CreateComputePipelineState(ComputeCI, &pComputePSO)); + ASSERT_NE(nullptr, pComputePSO); + ASSERT_EQ(PIPELINE_STATE_STATUS_READY, pComputePSO->GetStatus()); + + // Only the unused entry points access resources. Selecting one of them + // while serializing would change the restored pipeline's resource layout. + for (IPipelineState* pPSO : {pGraphicsPSO.RawPtr(), pComputePSO.RawPtr()}) + { + for (Uint32 i = 0; i < pPSO->GetResourceSignatureCount(); ++i) + { + if (auto* pSignature = pPSO->GetResourceSignature(i)) + EXPECT_EQ(0u, pSignature->GetDesc().NumResources); + } + } + + // A ready device PSO alone is insufficient: serialization failures are + // caught by the cache. Require archive hits after destroying this cache. + RefCntAutoPtr pNextData; + ASSERT_TRUE(pCache->WriteToBlob(ContentVersion, &pNextData)); + ASSERT_NE(nullptr, pNextData); + pData = std::move(pNextData); + } +} + +TEST(RenderStateCacheTest, WebGPUEntryPoints_DeviceShaders) +{ + TestWebGPUEntryPoints(/*LoadShadersFromArchive = */ false, /*MultipleEntryPoints = */ false); +} + +TEST(RenderStateCacheTest, WebGPUEntryPoints_ArchivedShaders) +{ + TestWebGPUEntryPoints(/*LoadShadersFromArchive = */ true, /*MultipleEntryPoints = */ false); +} + +TEST(RenderStateCacheTest, WebGPUEntryPoints_MultipleDeviceShaders) +{ + TestWebGPUEntryPoints(/*LoadShadersFromArchive = */ false, /*MultipleEntryPoints = */ true); +} + +TEST(RenderStateCacheTest, WebGPUEntryPoints_MultipleArchivedShaders) +{ + TestWebGPUEntryPoints(/*LoadShadersFromArchive = */ true, /*MultipleEntryPoints = */ true); +} + TEST(RenderStateCacheTest, CreateGraphicsPSO) { TestGraphicsPSO(/*UseRenderPass = */ false);