diff --git a/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp b/Graphics/GraphicsEngineD3D12/include/PipelineStateCacheD3D12Impl.hpp index 11d7c6e7bd..8ad318584c 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. @@ -31,6 +31,7 @@ #include "EngineD3D12ImplTraits.hpp" #include "PipelineStateCacheBase.hpp" +#include namespace Diligent { @@ -53,11 +54,26 @@ 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; + template + 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; + + std::mutex m_LibraryMtx; + CComPtr m_pLibrary; + CComPtr m_pLibrary1; }; } // namespace Diligent diff --git a/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp index 7a8736babe..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. @@ -46,18 +46,50 @@ PipelineStateCacheD3D12Impl::PipelineStateCacheD3D12Impl(IReferenceCounters* } // clang-format on { - HRESULT hr = pRenderDeviceD3D12->GetD3D12Device1()->CreatePipelineLibrary(nullptr, 0, IID_PPV_ARGS(&m_pLibrary)); + ID3D12Device1* pDevice1 = pRenderDeviceD3D12->GetD3D12Device1(); + if (pDevice1 == nullptr) + { + LOG_WARNING_MESSAGE("D3D12 pipeline libraries are unavailable; native caching is disabled."); + return; + } + + if (CreateInfo.pCacheData != nullptr && CreateInfo.CacheDataSize != 0) + { + // 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)) + { + 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(); + // 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}); } -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) { @@ -65,43 +97,50 @@ CComPtr PipelineStateCacheD3D12Impl::LoadComputePipeline(cons return {}; } - CComPtr d3d12PSO; - if ((m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) + CComPtr d3d12PSO; + if (pLibrary != nullptr && (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}; + const HRESULT hr = LoadPipeline(IID_PPV_ARGS(&d3d12PSO)); + if ((m_Desc.Flags & PSO_CACHE_FLAG_VERBOSE) != 0) + LOG_INFO_MESSAGE("PSO cache ", SUCCEEDED(hr) ? "hit" : "miss", " (", PipelineType, "): ", NarrowString(Name), " (HRESULT ", hr, ")"); } - return d3d12PSO; + return CComPtr{d3d12PSO.p}; +} + +CComPtr PipelineStateCacheD3D12Impl::LoadComputePipeline(const wchar_t* Name, const D3D12_COMPUTE_PIPELINE_STATE_DESC& Desc) +{ + return LoadPipelineImpl(m_pLibrary, Name, "compute", [&](REFIID IID, void** ppPSO) { + return m_pLibrary->LoadComputePipeline(Name, &Desc, IID, ppPSO); + }); } CComPtr PipelineStateCacheD3D12Impl::LoadGraphicsPipeline(const wchar_t* Name, const D3D12_GRAPHICS_PIPELINE_STATE_DESC& Desc) { - if (Name == nullptr) - { - DEV_ERROR("Pipeline name must not be null"); - return {}; - } + return LoadPipelineImpl(m_pLibrary, Name, "graphics", [&](REFIID IID, void** ppPSO) { + return m_pLibrary->LoadGraphicsPipeline(Name, &Desc, IID, ppPSO); + }); +} - CComPtr d3d12PSO; - if ((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"); - } - return d3d12PSO; +#ifdef D3D12_H_HAS_MESH_SHADER +CComPtr PipelineStateCacheD3D12Impl::LoadPipeline(const wchar_t* Name, const D3D12_PIPELINE_STATE_STREAM_DESC& Desc) +{ + return LoadPipelineImpl(m_pLibrary1, Name, "mesh", [&](REFIID IID, void** ppPSO) { + return m_pLibrary1->LoadPipeline(Name, &Desc, IID, ppPSO); + }); } +#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 +149,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..9ce0c2d73f 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,6 +51,7 @@ #include "DynamicLinearAllocator.hpp" #include "D3DShaderResourceValidation.hpp" #include "DataBlobImpl.hpp" +#include "XXH128Hasher.hpp" #include "DXBCUtils.hpp" #include "DXCompiler.hpp" @@ -63,6 +64,63 @@ constexpr INTERFACE_ID PipelineStateD3D12Impl::IID_InternalImpl; namespace { +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); + + 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); + for (const auto& Bytecode : Stage.ByteCodes) + Hasher.UpdateRaw(Bytecode->GetConstDataPtr(), Bytecode->GetSize()); + } + 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 @@ -726,27 +784,22 @@ 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 - PipelineStateCacheD3D12Impl* const pPSOCacheD3D12 = ClassPtrCast(CreateInfo.pPSOCache); - if (pPSOCacheD3D12 != nullptr && !WName.empty()) - m_pd3d12PSO = pPSOCacheD3D12->LoadGraphicsPipeline(WName.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 && !WName.empty()) - pPSOCacheD3D12->StorePipeline(WName.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) { 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; @@ -819,11 +872,14 @@ 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"); + 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 @@ -866,21 +922,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); - if (pPSOCacheD3D12 != nullptr && !WName.empty()) - m_pd3d12PSO = pPSOCacheD3D12->LoadComputePipeline(WName.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 && !WName.empty()) - pPSOCacheD3D12->StorePipeline(WName.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()) { 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;