1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "node_context_pso_manager.h"
17 
18 #include <cstdint>
19 
20 #include <base/containers/vector.h>
21 #include <base/util/hash.h>
22 #include <render/namespace.h>
23 #include <render/nodecontext/intf_node_context_pso_manager.h>
24 
25 #include "device/device.h"
26 #include "device/gpu_program.h"
27 #include "device/gpu_program_util.h"
28 #include "device/gpu_resource_handle_util.h"
29 #include "device/gpu_resource_manager.h"
30 #include "device/pipeline_state_object.h"
31 #include "device/shader_manager.h"
32 #include "util/log.h"
33 
34 template<>
hash(const RENDER_NS::ShaderSpecializationConstantDataView & specialization)35 uint64_t BASE_NS::hash(const RENDER_NS::ShaderSpecializationConstantDataView& specialization)
36 {
37     uint64_t seed = BASE_NS::FNV_OFFSET_BASIS;
38     if ((!specialization.data.empty()) && (!specialization.constants.empty())) {
39         const size_t minSize = BASE_NS::Math::min(specialization.constants.size(), specialization.data.size());
40         for (size_t idx = 0; idx < minSize; ++idx) {
41             const auto& currConstant = specialization.constants[idx];
42             uint64_t v = 0;
43             const auto constantSize = RENDER_NS::GpuProgramUtil::SpecializationByteSize(currConstant.type);
44             if ((currConstant.offset + constantSize) <= specialization.data.size_bytes()) {
45                 uint8_t const* data = (uint8_t const*)specialization.data.data() + currConstant.offset;
46                 size_t const bytes = sizeof(v) < constantSize ? sizeof(v) : constantSize;
47                 HashCombine(seed, array_view(data, bytes));
48             }
49 #if (RENDER_VALIDATION_ENABLED == 1)
50             else {
51                 PLUGIN_LOG_E("RENDER_VALIDATION: shader specialization issue with constant and data size mismatch");
52             }
53 #endif
54         }
55     }
56     return seed;
57 }
58 
59 using namespace BASE_NS;
60 
61 RENDER_BEGIN_NAMESPACE()
62 namespace {
HashComputeShader(const RenderHandle shaderHandle,const ShaderSpecializationConstantDataView & shaderSpecialization)63 uint64_t HashComputeShader(
64     const RenderHandle shaderHandle, const ShaderSpecializationConstantDataView& shaderSpecialization)
65 {
66     return Hash(shaderHandle.id, shaderSpecialization);
67 }
68 
HashGraphicsShader(const RenderHandle shaderHandle,const RenderHandle graphicsStateHandle,const array_view<const DynamicStateEnum> dynamicStates,const ShaderSpecializationConstantDataView & shaderSpecialization,const uint64_t customGraphicsStateHash)69 uint64_t HashGraphicsShader(const RenderHandle shaderHandle, const RenderHandle graphicsStateHandle,
70     const array_view<const DynamicStateEnum> dynamicStates,
71     const ShaderSpecializationConstantDataView& shaderSpecialization, const uint64_t customGraphicsStateHash)
72 {
73     uint64_t hash = 0;
74     for (const auto& ref : dynamicStates) {
75         HashCombine(hash, static_cast<uint64_t>(ref));
76     }
77     return Hash(hash, shaderHandle.id, graphicsStateHandle.id, shaderSpecialization, customGraphicsStateHash);
78 }
79 
80 #if (RENDER_VALIDATION_ENABLED == 1)
validateSSO(ShaderManager & shaderMgr,const RenderHandle shaderHandle,const VertexInputDeclarationDataWrapper & vidw)81 void validateSSO(
82     ShaderManager& shaderMgr, const RenderHandle shaderHandle, const VertexInputDeclarationDataWrapper& vidw)
83 {
84     const GpuShaderProgram* gsp = shaderMgr.GetGpuShaderProgram(shaderHandle);
85     if (gsp) {
86         const auto& reflection = gsp->GetReflection();
87         const bool hasBindings = (reflection.vertexInputDeclarationView.bindingDescriptions.size() > 0);
88         const bool vidHasBindings = (vidw.bindingDescriptions.size() > 0);
89         if (hasBindings != vidHasBindings) {
90             PLUGIN_LOG_E(
91                 "RENDER_VALIDATION: vertex input declaration pso (bindings: %u) mismatch with shader reflection "
92                 "(bindings: %u)",
93                 static_cast<uint32_t>(vidw.bindingDescriptions.size()),
94                 static_cast<uint32_t>(reflection.vertexInputDeclarationView.bindingDescriptions.size()));
95         }
96     }
97 }
98 #endif
99 } // namespace
100 
NodeContextPsoManager(Device & device,ShaderManager & shaderManager)101 NodeContextPsoManager::NodeContextPsoManager(Device& device, ShaderManager& shaderManager)
102     : device_ { device }, shaderMgr_ { shaderManager }
103 {}
104 
BeginBackendFrame()105 void NodeContextPsoManager::BeginBackendFrame()
106 {
107     // destroy pending
108     const uint64_t frameCount = device_.GetFrameCount();
109     constexpr uint64_t additionalFrameCount { 2u };
110     const auto minAge = device_.GetCommandBufferingCount() + additionalFrameCount;
111     const auto ageLimit = (frameCount < minAge) ? 0 : (frameCount - minAge);
112     {
113         auto& gpCache = computePipelineStateCache_;
114         for (auto iter = gpCache.pendingPsoDestroys.begin(); iter != gpCache.pendingPsoDestroys.end();) {
115             if (iter->frameIndex < ageLimit) {
116                 iter = gpCache.pendingPsoDestroys.erase(iter);
117             } else {
118                 ++iter;
119             }
120         }
121     }
122     {
123         auto& gpCache = graphicsPipelineStateCache_;
124         for (auto iter = gpCache.pendingPsoDestroys.begin(); iter != gpCache.pendingPsoDestroys.end();) {
125             if (iter->frameIndex < ageLimit) {
126                 iter = gpCache.pendingPsoDestroys.erase(iter);
127             } else {
128                 ++iter;
129             }
130         }
131     }
132 
133     // check for shader manager reloaded shaders -> re-create psos
134     if (shaderMgr_.HasReloadedShaderForBackend()) {
135         const auto reloadedShaders = shaderMgr_.GetReloadedShadersForBackend();
136         if (!reloadedShaders.empty()) {
137             // find if using reloaded shader handles
138             {
139                 auto& gpCache = computePipelineStateCache_;
140                 for (size_t idx = 0U; idx < gpCache.psoCreationData.size(); ++idx) {
141                     const auto& ref = gpCache.psoCreationData[idx];
142                     for (const auto& refHandle : reloadedShaders) {
143                         if (ref.shaderHandle.id == refHandle.id) {
144                             // move pso and set as null
145                             gpCache.pendingPsoDestroys.push_back(
146                                 { move(gpCache.pipelineStateObjects[idx]), frameCount });
147                             gpCache.pipelineStateObjects[idx] = nullptr;
148                             break;
149                         }
150                     }
151                 }
152             }
153             {
154                 auto& gpCache = graphicsPipelineStateCache_;
155                 auto& pso = gpCache.pipelineStateObjects;
156                 for (auto iter = pso.begin(); iter != pso.end();) {
157                     bool erase = false;
158                     for (const auto& refHandle : reloadedShaders) {
159                         if (iter->second.shaderHandle.id == refHandle.id) {
160                             erase = true;
161                             break;
162                         }
163                     }
164                     if (erase) {
165                         // move pso and erase
166                         graphicsPipelineStateCache_.pendingPsoDestroys.push_back(
167                             { move(iter->second.pso), frameCount });
168                         iter = pso.erase(iter);
169                     } else {
170                         ++iter;
171                     }
172                 }
173             }
174         }
175     }
176 }
177 
GetComputePsoHandle(const RenderHandle shaderHandle,const PipelineLayout & pipelineLayout,const ShaderSpecializationConstantDataView & shaderSpecialization)178 RenderHandle NodeContextPsoManager::GetComputePsoHandle(const RenderHandle shaderHandle,
179     const PipelineLayout& pipelineLayout, const ShaderSpecializationConstantDataView& shaderSpecialization)
180 {
181     if (RenderHandleUtil::GetHandleType(shaderHandle) != RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) {
182 #if (RENDER_VALIDATION_ENABLED == 1)
183         PLUGIN_LOG_E("RENDER_VALIDATION: invalid shader handle given to compute pso creation");
184 #endif
185         return {}; // early out
186     }
187     // if not matching pso -> deferred creation in render backend
188     RenderHandle psoHandle;
189 
190     auto& cache = computePipelineStateCache_;
191 
192     const uint64_t hash = HashComputeShader(shaderHandle, shaderSpecialization);
193     const auto iter = cache.hashToHandle.find(hash);
194     const bool needsNewPso = (iter == cache.hashToHandle.cend());
195     if (needsNewPso) {
196         PLUGIN_ASSERT(cache.psoCreationData.size() == cache.pipelineStateObjects.size());
197 
198         // reserve slot for new pso
199         const uint32_t index = static_cast<uint32_t>(cache.psoCreationData.size());
200         cache.pipelineStateObjects.emplace_back(nullptr);
201         // add pipeline layout descriptor set mask to pso handle for fast evaluation
202         uint32_t descriptorSetBitmask = 0;
203         for (uint32_t idx = 0; idx < PipelineLayoutConstants::MAX_DESCRIPTOR_SET_COUNT; ++idx) {
204             if (pipelineLayout.descriptorSetLayouts[idx].set != PipelineLayoutConstants::INVALID_INDEX) {
205                 descriptorSetBitmask |= (1 << idx);
206             }
207         }
208         psoHandle = RenderHandleUtil::CreateHandle(RenderHandleType::COMPUTE_PSO, index, 0, descriptorSetBitmask);
209         cache.hashToHandle[hash] = psoHandle;
210 
211 #if (RENDER_VALIDATION_ENABLED == 1)
212         cache.handleToPipelineLayout[psoHandle] = pipelineLayout;
213 #endif
214 
215         // store needed data for render backend pso creation
216         ShaderSpecializationConstantDataWrapper ssw {
217             vector<ShaderSpecialization::Constant>(
218                 shaderSpecialization.constants.begin(), shaderSpecialization.constants.end()),
219             vector<uint32_t>(shaderSpecialization.data.begin(), shaderSpecialization.data.end()),
220         };
221         cache.psoCreationData.push_back({ shaderHandle, pipelineLayout, move(ssw) });
222     } else {
223         psoHandle = iter->second;
224     }
225 
226     return psoHandle;
227 }
228 
GetComputePsoHandle(const RenderHandle shaderHandle,const RenderHandle pipelineLayoutHandle,const ShaderSpecializationConstantDataView & shaderSpecialization)229 RenderHandle NodeContextPsoManager::GetComputePsoHandle(const RenderHandle shaderHandle,
230     const RenderHandle pipelineLayoutHandle, const ShaderSpecializationConstantDataView& shaderSpecialization)
231 {
232     RenderHandle psoHandle;
233     if (RenderHandleUtil::GetHandleType(pipelineLayoutHandle) == RenderHandleType::PIPELINE_LAYOUT) {
234         const PipelineLayout& pl = shaderMgr_.GetPipelineLayoutRef(pipelineLayoutHandle);
235         psoHandle = GetComputePsoHandle(shaderHandle, pl, shaderSpecialization);
236     } else {
237         PLUGIN_LOG_E("NodeContextPsoManager: invalid pipeline layout handle given to GetComputePsoHandle()");
238     }
239     return psoHandle;
240 }
241 
GetGraphicsPsoHandleImpl(const RenderHandle shader,const RenderHandle graphicsState,const PipelineLayout & pipelineLayout,const VertexInputDeclarationView & vertexInputDeclarationView,const ShaderSpecializationConstantDataView & shaderSpecialization,const array_view<const DynamicStateEnum> dynamicStates,const GraphicsState * customGraphicsState)242 RenderHandle NodeContextPsoManager::GetGraphicsPsoHandleImpl(const RenderHandle shader,
243     const RenderHandle graphicsState, const PipelineLayout& pipelineLayout,
244     const VertexInputDeclarationView& vertexInputDeclarationView,
245     const ShaderSpecializationConstantDataView& shaderSpecialization,
246     const array_view<const DynamicStateEnum> dynamicStates, const GraphicsState* customGraphicsState)
247 {
248 #if (RENDER_VALIDATION_ENABLED == 1)
249     if (RenderHandleUtil::GetHandleType(shader) != RenderHandleType::SHADER_STATE_OBJECT) {
250         PLUGIN_LOG_E("RENDER_VALIDATION: invalid shader handle given to graphics pso creation");
251     }
252     if (RenderHandleUtil::IsValid(graphicsState) &&
253         RenderHandleUtil::GetHandleType(graphicsState) != RenderHandleType::GRAPHICS_STATE) {
254         PLUGIN_LOG_E("RENDER_VALIDATION: invalid graphics state handle given to graphics pso creation");
255     }
256 #endif
257     if (RenderHandleUtil::GetHandleType(shader) != RenderHandleType::SHADER_STATE_OBJECT) {
258         return {}; // early out
259     }
260     // if not matching pso -> deferred creation in render backend
261     RenderHandle psoHandle;
262 
263     auto& cache = graphicsPipelineStateCache_;
264     uint64_t cGfxHash = 0;
265     if ((!RenderHandleUtil::IsValid(graphicsState)) && customGraphicsState) {
266         cGfxHash = shaderMgr_.HashGraphicsState(*customGraphicsState);
267     }
268     const uint64_t hash = HashGraphicsShader(shader, graphicsState, dynamicStates, shaderSpecialization, cGfxHash);
269     const auto iter = cache.hashToHandle.find(hash);
270     const bool needsNewPso = (iter == cache.hashToHandle.cend());
271     if (needsNewPso) {
272         const uint32_t index = static_cast<uint32_t>(cache.psoCreationData.size());
273         // add pipeline layout descriptor set mask to pso handle for fast evaluation
274         uint32_t descriptorSetBitmask = 0;
275         for (uint32_t idx = 0; idx < PipelineLayoutConstants::MAX_DESCRIPTOR_SET_COUNT; ++idx) {
276             if (pipelineLayout.descriptorSetLayouts[idx].set != PipelineLayoutConstants::INVALID_INDEX) {
277                 descriptorSetBitmask |= (1 << idx);
278             }
279         }
280         psoHandle = RenderHandleUtil::CreateHandle(RenderHandleType::GRAPHICS_PSO, index, 0, descriptorSetBitmask);
281         cache.hashToHandle[hash] = psoHandle;
282 
283         // store needed data for render backend pso creation
284         ShaderSpecializationConstantDataWrapper ssw {
285             { shaderSpecialization.constants.begin(), shaderSpecialization.constants.end() },
286             { shaderSpecialization.data.begin(), shaderSpecialization.data.end() },
287         };
288         VertexInputDeclarationDataWrapper vidw {
289             { vertexInputDeclarationView.bindingDescriptions.begin(),
290                 vertexInputDeclarationView.bindingDescriptions.end() },
291             { vertexInputDeclarationView.attributeDescriptions.begin(),
292                 vertexInputDeclarationView.attributeDescriptions.end() },
293         };
294 #if (RENDER_VALIDATION_ENABLED == 1)
295         validateSSO(shaderMgr_, shader, vidw);
296         cache.handleToPipelineLayout[psoHandle] = pipelineLayout;
297 #endif
298         // custom graphics state or null
299         unique_ptr<GraphicsState> customGraphicsStatePtr =
300             customGraphicsState ? make_unique<GraphicsState>(*customGraphicsState) : nullptr;
301         GraphicsPipelineStateCreationData psoCreationData;
302         psoCreationData.shaderHandle = shader;
303         psoCreationData.graphicsStateHandle = graphicsState;
304         psoCreationData.pipelineLayout = pipelineLayout;
305         psoCreationData.vertexInputDeclaration = move(vidw);
306         psoCreationData.shaderSpecialization = move(ssw);
307         psoCreationData.customGraphicsState = move(customGraphicsStatePtr);
308         psoCreationData.dynamicStates = { dynamicStates.cbegin(), dynamicStates.cend() };
309         cache.psoCreationData.push_back(move(psoCreationData));
310     } else {
311         psoHandle = iter->second;
312     }
313 
314     return psoHandle;
315 }
316 
GetGraphicsPsoHandle(const RenderHandle shaderHandle,const RenderHandle graphicsState,const RenderHandle pipelineLayoutHandle,const RenderHandle vertexInputDeclarationHandle,const ShaderSpecializationConstantDataView & shaderSpecialization,const array_view<const DynamicStateEnum> dynamicStates)317 RenderHandle NodeContextPsoManager::GetGraphicsPsoHandle(const RenderHandle shaderHandle,
318     const RenderHandle graphicsState, const RenderHandle pipelineLayoutHandle,
319     const RenderHandle vertexInputDeclarationHandle, const ShaderSpecializationConstantDataView& shaderSpecialization,
320     const array_view<const DynamicStateEnum> dynamicStates)
321 {
322     RenderHandle psoHandle;
323     const PipelineLayout& pl = shaderMgr_.GetPipelineLayout(pipelineLayoutHandle);
324     VertexInputDeclarationView vidView = shaderMgr_.GetVertexInputDeclarationView(vertexInputDeclarationHandle);
325     const RenderHandle gfxStateHandle =
326         (RenderHandleUtil::GetHandleType(graphicsState) == RenderHandleType::GRAPHICS_STATE)
327             ? graphicsState
328             : shaderMgr_.GetGraphicsStateHandleByShaderHandle(shaderHandle).GetHandle();
329     psoHandle = GetGraphicsPsoHandleImpl(
330         shaderHandle, gfxStateHandle, pl, vidView, shaderSpecialization, dynamicStates, nullptr);
331     return psoHandle;
332 }
333 
GetGraphicsPsoHandle(const RenderHandle shader,const RenderHandle graphicsState,const PipelineLayout & pipelineLayout,const VertexInputDeclarationView & vertexInputDeclarationView,const ShaderSpecializationConstantDataView & shaderSpecialization,const array_view<const DynamicStateEnum> dynamicStates)334 RenderHandle NodeContextPsoManager::GetGraphicsPsoHandle(const RenderHandle shader, const RenderHandle graphicsState,
335     const PipelineLayout& pipelineLayout, const VertexInputDeclarationView& vertexInputDeclarationView,
336     const ShaderSpecializationConstantDataView& shaderSpecialization,
337     const array_view<const DynamicStateEnum> dynamicStates)
338 {
339     return GetGraphicsPsoHandleImpl(shader, graphicsState, pipelineLayout, vertexInputDeclarationView,
340         shaderSpecialization, dynamicStates, nullptr);
341 }
342 
GetGraphicsPsoHandle(const RenderHandle shader,const GraphicsState & graphicsState,const PipelineLayout & pipelineLayout,const VertexInputDeclarationView & vertexInputDeclarationView,const ShaderSpecializationConstantDataView & shaderSpecialization,const array_view<const DynamicStateEnum> dynamicStates)343 RenderHandle NodeContextPsoManager::GetGraphicsPsoHandle(const RenderHandle shader, const GraphicsState& graphicsState,
344     const PipelineLayout& pipelineLayout, const VertexInputDeclarationView& vertexInputDeclarationView,
345     const ShaderSpecializationConstantDataView& shaderSpecialization,
346     const array_view<const DynamicStateEnum> dynamicStates)
347 {
348     return GetGraphicsPsoHandleImpl(shader, RenderHandle {}, pipelineLayout, vertexInputDeclarationView,
349         shaderSpecialization, dynamicStates, &graphicsState);
350 }
351 
352 #if (RENDER_VALIDATION_ENABLED == 1)
GetComputePsoPipelineLayout(const RenderHandle handle) const353 const PipelineLayout& NodeContextPsoManager::GetComputePsoPipelineLayout(const RenderHandle handle) const
354 {
355     auto& handleToPl = computePipelineStateCache_.handleToPipelineLayout;
356     if (const auto iter = handleToPl.find(handle); iter != handleToPl.cend()) {
357         return iter->second;
358     } else {
359         static PipelineLayout pl;
360         return pl;
361     }
362 }
363 #endif
364 
365 #if (RENDER_VALIDATION_ENABLED == 1)
GetGraphicsPsoPipelineLayout(const RenderHandle handle) const366 const PipelineLayout& NodeContextPsoManager::GetGraphicsPsoPipelineLayout(const RenderHandle handle) const
367 {
368     auto& handleToPl = graphicsPipelineStateCache_.handleToPipelineLayout;
369     if (const auto iter = handleToPl.find(handle); iter != handleToPl.cend()) {
370         return iter->second;
371     } else {
372         static PipelineLayout pl;
373         return pl;
374     }
375 }
376 #endif
377 
GetComputePso(const RenderHandle handle,const LowLevelPipelineLayoutData * pipelineLayoutData)378 const ComputePipelineStateObject* NodeContextPsoManager::GetComputePso(
379     const RenderHandle handle, const LowLevelPipelineLayoutData* pipelineLayoutData)
380 {
381     PLUGIN_ASSERT(RenderHandleUtil::GetHandleType(handle) == RenderHandleType::COMPUTE_PSO);
382     const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
383     PLUGIN_ASSERT_MSG(index < static_cast<uint32_t>(computePipelineStateCache_.psoCreationData.size()),
384         "Check that IRenderNode::InitNode clears cached handles.");
385 
386     auto& cache = computePipelineStateCache_;
387     if (cache.pipelineStateObjects[index] == nullptr) { // pso needs to be created
388         PLUGIN_ASSERT(index < static_cast<uint32_t>(cache.psoCreationData.size()));
389         const auto& psoDataRef = cache.psoCreationData[index];
390         if (const GpuComputeProgram* gcp = shaderMgr_.GetGpuComputeProgram(psoDataRef.shaderHandle); gcp) {
391             const ShaderSpecializationConstantDataView sscdv {
392                 psoDataRef.shaderSpecialization.constants,
393                 psoDataRef.shaderSpecialization.data,
394             };
395             cache.pipelineStateObjects[index] =
396                 device_.CreateComputePipelineStateObject(*gcp, psoDataRef.pipelineLayout, sscdv, pipelineLayoutData);
397         }
398     }
399     return cache.pipelineStateObjects[index].get();
400 }
401 
GetGraphicsPso(const RenderHandle handle,const RenderPassDesc & renderPassDesc,const array_view<const RenderPassSubpassDesc> renderPassSubpassDescs,const uint32_t subpassIndex,const uint64_t psoStateHash,const LowLevelRenderPassData * renderPassData,const LowLevelPipelineLayoutData * pipelineLayoutData)402 const GraphicsPipelineStateObject* NodeContextPsoManager::GetGraphicsPso(const RenderHandle handle,
403     const RenderPassDesc& renderPassDesc, const array_view<const RenderPassSubpassDesc> renderPassSubpassDescs,
404     const uint32_t subpassIndex, const uint64_t psoStateHash, const LowLevelRenderPassData* renderPassData,
405     const LowLevelPipelineLayoutData* pipelineLayoutData)
406 {
407     PLUGIN_ASSERT(RenderHandleUtil::GetHandleType(handle) == RenderHandleType::GRAPHICS_PSO);
408     const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
409     PLUGIN_ASSERT_MSG(index < static_cast<uint32_t>(graphicsPipelineStateCache_.psoCreationData.size()),
410         "Check that IRenderNode::InitNode clears cached handles.");
411 
412     auto& cache = graphicsPipelineStateCache_;
413     const uint64_t hash =
414         (device_.GetBackendType() == DeviceBackendType::VULKAN) ? Hash(handle.id, psoStateHash) : handle.id;
415     if (const auto iter = cache.pipelineStateObjects.find(hash); iter != cache.pipelineStateObjects.cend()) {
416         return iter->second.pso.get();
417     } else {
418         PLUGIN_ASSERT(index < static_cast<uint32_t>(cache.psoCreationData.size()));
419         const auto& psoDataRef = cache.psoCreationData[index];
420         if (const GpuShaderProgram* gsp = shaderMgr_.GetGpuShaderProgram(psoDataRef.shaderHandle); gsp) {
421             const GraphicsState* customGraphicsState = psoDataRef.customGraphicsState.get();
422             const GraphicsState& graphicsState = (customGraphicsState)
423                                                      ? *customGraphicsState
424                                                      : shaderMgr_.GetGraphicsStateRef(psoDataRef.graphicsStateHandle);
425 #if (RENDER_VALIDATION_ENABLED == 1)
426             if (subpassIndex >= renderPassSubpassDescs.size()) {
427                 PLUGIN_LOG_ONCE_I("node_context_pso_subpass_index",
428                     "RENDER_VALIDATION: subpassIndex (%u) out-of-bounds (%zu)",
429                     subpassIndex, renderPassSubpassDescs.size());
430             } else if (graphicsState.colorBlendState.colorAttachmentCount !=
431                 renderPassSubpassDescs[subpassIndex].colorAttachmentCount) {
432                 PLUGIN_LOG_ONCE_I("node_context_pso_output_info",
433                     "RENDER_VALIDATION: graphics state color attachment count (%u) does not match "
434                     "render pass subpass color attachment count (%u). (Output not consumed info)",
435                     graphicsState.colorBlendState.colorAttachmentCount,
436                     renderPassSubpassDescs[subpassIndex].colorAttachmentCount);
437             }
438 #endif
439             const auto& vertexInput = psoDataRef.vertexInputDeclaration;
440             const VertexInputDeclarationView vidv { vertexInput.bindingDescriptions,
441                 vertexInput.attributeDescriptions };
442 
443             const auto& shaderSpec = psoDataRef.shaderSpecialization;
444             const ShaderSpecializationConstantDataView sscdv { shaderSpec.constants, shaderSpec.data };
445 
446             auto& newPsoRef = cache.pipelineStateObjects[hash];
447             newPsoRef.shaderHandle = psoDataRef.shaderHandle;
448             newPsoRef.pso = device_.CreateGraphicsPipelineStateObject(*gsp, graphicsState, psoDataRef.pipelineLayout,
449                 vidv, sscdv, psoDataRef.dynamicStates, renderPassDesc, renderPassSubpassDescs, subpassIndex,
450                 renderPassData, pipelineLayoutData);
451             return newPsoRef.pso.get();
452         }
453     }
454     return nullptr;
455 }
456 RENDER_END_NAMESPACE()
457