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..79a52aa37f 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) @@ -65,14 +89,15 @@ CComPtr PipelineStateCacheD3D12Impl::LoadComputePipeline(cons return {}; } - CComPtr d3d12PSO; - if ((m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) + CComPtr d3d12PSO; + 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; + return CComPtr{d3d12PSO.p}; } CComPtr PipelineStateCacheD3D12Impl::LoadGraphicsPipeline(const wchar_t* Name, const D3D12_GRAPHICS_PIPELINE_STATE_DESC& Desc) @@ -83,25 +108,43 @@ CComPtr PipelineStateCacheD3D12Impl::LoadGraphicsPipeline(con return {}; } - CComPtr d3d12PSO; - if ((m_Desc.Mode & PSO_CACHE_MODE_LOAD) != 0) + CComPtr d3d12PSO; + 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; + return CComPtr{d3d12PSO.p}; } +#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 abd42b858e..65d1fed688 100644 --- a/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp +++ b/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp @@ -833,6 +833,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;