Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -31,6 +31,7 @@

#include "EngineD3D12ImplTraits.hpp"
#include "PipelineStateCacheBase.hpp"
#include <mutex>

namespace Diligent
{
Expand All @@ -53,11 +54,26 @@ class PipelineStateCacheD3D12Impl final : public PipelineStateCacheBase<EngineD3

CComPtr<ID3D12DeviceChild> LoadComputePipeline(const wchar_t* Name, const D3D12_COMPUTE_PIPELINE_STATE_DESC& Desc);
CComPtr<ID3D12DeviceChild> LoadGraphicsPipeline(const wchar_t* Name, const D3D12_GRAPHICS_PIPELINE_STATE_DESC& Desc);
#ifdef D3D12_H_HAS_MESH_SHADER
CComPtr<ID3D12DeviceChild> LoadPipeline(const wchar_t* Name, const D3D12_PIPELINE_STATE_STREAM_DESC& Desc);
#endif

bool StorePipeline(const wchar_t* Name, ID3D12DeviceChild* pPSO);

private:
CComPtr<ID3D12PipelineLibrary> m_pLibrary;
template <typename LoadPipelineType>
CComPtr<ID3D12DeviceChild> 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<IDataBlob> m_pInitialData;

std::mutex m_LibraryMtx;
CComPtr<ID3D12PipelineLibrary> m_pLibrary;
CComPtr<ID3D12PipelineLibrary1> m_pLibrary1;
};

} // namespace Diligent
97 changes: 70 additions & 27 deletions Graphics/GraphicsEngineD3D12/src/PipelineStateCacheD3D12Impl.cpp
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -46,62 +46,101 @@ 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<ID3D12DeviceChild> PipelineStateCacheD3D12Impl::LoadComputePipeline(const wchar_t* Name, const D3D12_COMPUTE_PIPELINE_STATE_DESC& Desc)
template <typename LoadPipelineType>
CComPtr<ID3D12DeviceChild> PipelineStateCacheD3D12Impl::LoadPipelineImpl(ID3D12PipelineLibrary* pLibrary,
const wchar_t* Name,
const char* PipelineType,
const LoadPipelineType& LoadPipeline)
{
if (Name == nullptr)
{
DEV_ERROR("Pipeline name must not be null");
return {};
}

CComPtr<ID3D12DeviceChild> d3d12PSO;
if ((m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0)
CComPtr<ID3D12PipelineState> 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<std::mutex> 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<ID3D12DeviceChild>{d3d12PSO.p};
}

CComPtr<ID3D12DeviceChild> 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<ID3D12DeviceChild> 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<ID3D12DeviceChild> 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<ID3D12DeviceChild> 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<ID3D12PipelineState*>(pPSO));
std::lock_guard<std::mutex> Lock{m_LibraryMtx};
HRESULT hr = m_pLibrary->StorePipeline(Name, static_cast<ID3D12PipelineState*>(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);
}
Expand All @@ -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<std::mutex> Lock{m_LibraryMtx};

RefCntAutoPtr<DataBlobImpl> pDataBlob = DataBlobImpl::Create(m_pLibrary->GetSerializedSize());

Expand Down
121 changes: 86 additions & 35 deletions Graphics/GraphicsEngineD3D12/src/PipelineStateD3D12Impl.cpp
Original file line number Diff line number Diff line change
@@ -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");
Expand Down Expand Up @@ -51,6 +51,7 @@
#include "DynamicLinearAllocator.hpp"
#include "D3DShaderResourceValidation.hpp"
#include "DataBlobImpl.hpp"
#include "XXH128Hasher.hpp"

#include "DXBCUtils.hpp"
#include "DXCompiler.hpp"
Expand All @@ -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 <typename LoadPipelineType, typename CreatePipelineType>
CComPtr<ID3D12DeviceChild> LoadOrCreatePipeline(IPipelineStateCache* pPSOCache,
const RootSignatureD3D12& RootSig,
const PipelineStateD3D12Impl::TShaderStages& ShaderStages,
const GraphicsPipelineDesc* pGraphics,
const LoadPipelineType& LoadPipeline,
const CreatePipelineType& CreatePipeline)
{
PipelineStateCacheD3D12Impl* const pCacheD3D12 = ClassPtrCast<PipelineStateCacheD3D12Impl>(pPSOCache);

std::wstring PSOKey;
CComPtr<ID3D12DeviceChild> 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
Expand Down Expand Up @@ -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<PipelineStateCacheD3D12Impl>(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<D3D12_PIPELINE_STATE_FLAGS, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_FLAGS> Flags;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<PipelineStateCacheD3D12Impl>(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())
{
Expand Down
1 change: 1 addition & 0 deletions Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<IPipelineState> pPSO;
Expand Down
Loading