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 "render_node_util.h"
17
18 #include <algorithm>
19
20 #include <render/device/intf_gpu_resource_manager.h>
21 #include <render/device/intf_shader_manager.h>
22 #include <render/device/pipeline_state_desc.h>
23 #include <render/namespace.h>
24 #include <render/nodecontext/intf_node_context_descriptor_set_manager.h>
25 #include <render/nodecontext/intf_pipeline_descriptor_set_binder.h>
26 #include <render/nodecontext/intf_render_node.h>
27 #include <render/nodecontext/intf_render_node_context_manager.h>
28 #include <render/nodecontext/intf_render_node_graph_share_manager.h>
29 #include <render/nodecontext/intf_render_node_util.h>
30
31 #include "datastore/render_data_store_post_process.h"
32 #include "device/gpu_resource_handle_util.h"
33 #include "nodecontext/pipeline_descriptor_set_binder.h"
34 #include "util/log.h"
35
36 using namespace BASE_NS;
37
38 RENDER_BEGIN_NAMESPACE()
39 namespace {
GetRoutedResource(const IRenderNodeGpuResourceManager & gpuResourceMgr,const IRenderNodeGraphShareManager & rngShareMgr,const RenderDataConstants::RenderDataFixedString & name,const RenderDataConstants::RenderDataFixedString & nodeName,const RenderNodeGraphResourceLocationType resourceLocation,const uint32_t resourceIndex,const RenderHandleType handleType)40 RenderHandle GetRoutedResource(const IRenderNodeGpuResourceManager& gpuResourceMgr,
41 const IRenderNodeGraphShareManager& rngShareMgr, const RenderDataConstants::RenderDataFixedString& name,
42 const RenderDataConstants::RenderDataFixedString& nodeName,
43 const RenderNodeGraphResourceLocationType resourceLocation, const uint32_t resourceIndex,
44 const RenderHandleType handleType)
45 {
46 RenderHandle handle;
47 switch (resourceLocation) {
48 case (RenderNodeGraphResourceLocationType::DEFAULT):
49 if (handleType == RenderHandleType::GPU_BUFFER) {
50 handle = gpuResourceMgr.GetBufferHandle(name);
51 } else if (handleType == RenderHandleType::GPU_IMAGE) {
52 handle = gpuResourceMgr.GetImageHandle(name);
53 } else {
54 handle = gpuResourceMgr.GetSamplerHandle(name);
55 }
56 break;
57 case (RenderNodeGraphResourceLocationType::FROM_RENDER_GRAPH_INPUT):
58 handle = rngShareMgr.GetRenderNodeGraphInput(resourceIndex);
59 break;
60 case (RenderNodeGraphResourceLocationType::FROM_RENDER_GRAPH_OUTPUT):
61 handle = rngShareMgr.GetRenderNodeGraphOutput(resourceIndex);
62 break;
63 case (RenderNodeGraphResourceLocationType::FROM_PREVIOUS_RENDER_NODE_OUTPUT):
64 handle = rngShareMgr.GetRegisteredPrevRenderNodeOutput(resourceIndex);
65 break;
66 case (RenderNodeGraphResourceLocationType::FROM_NAMED_RENDER_NODE_OUTPUT):
67 if (!name.empty()) {
68 handle = rngShareMgr.GetRegisteredRenderNodeOutput(nodeName, name);
69 } else {
70 handle = rngShareMgr.GetRegisteredRenderNodeOutput(nodeName, resourceIndex);
71 }
72 break;
73 case (RenderNodeGraphResourceLocationType::FROM_PREVIOUS_RENDER_NODE_GRAPH_OUTPUT):
74 if (!name.empty()) {
75 handle = rngShareMgr.GetNamedPrevRenderNodeGraphOutput(name);
76 } else {
77 handle = rngShareMgr.GetPrevRenderNodeGraphOutput(resourceIndex);
78 }
79 break;
80 default:
81 break;
82 }
83 #if (RENDER_VALIDATION_ENABLED == 1)
84 if (!RenderHandleUtil::IsValid(handle)) {
85 PLUGIN_LOG_ONCE_W(string(nodeName + name),
86 "RENDER_VALIDATION: named render node GPU resource handle not found (name:%s) (type:%u)", name.c_str(),
87 static_cast<uint32_t>(handleType));
88 }
89 #endif
90 return handle;
91 }
92
SetupRenderNodeResourceHandles(const IRenderNodeGpuResourceManager & gpuResourceMgr,const IRenderNodeGraphShareManager & rngShareMgr,const RenderNodeGraphInputs::InputResources & resources,RenderNodeHandles::InputResources & res)93 void SetupRenderNodeResourceHandles(const IRenderNodeGpuResourceManager& gpuResourceMgr,
94 const IRenderNodeGraphShareManager& rngShareMgr, const RenderNodeGraphInputs::InputResources& resources,
95 RenderNodeHandles::InputResources& res)
96 {
97 const auto setHandles = [](const IRenderNodeGpuResourceManager& gpuResourceMgr,
98 const IRenderNodeGraphShareManager& rngShareMgr, const RenderHandleType handleType,
99 const auto& input, auto& output) {
100 output.reserve(input.size());
101 for (const auto& ref : input) {
102 const RenderHandle handle = GetRoutedResource(gpuResourceMgr, rngShareMgr, ref.name, ref.nodeName,
103 ref.resourceLocation, ref.resourceIndex, handleType);
104 output.push_back(RenderNodeResource { ref.set, ref.binding, handle, {}, ref.mip, ref.layer });
105 }
106 };
107 const auto setImageHandles = [](const IRenderNodeGpuResourceManager& gpuResourceMgr,
108 const IRenderNodeGraphShareManager& rngShareMgr,
109 vector<RenderNodeResource>& optionalSamplers, auto& input, auto& output) {
110 output.reserve(input.size());
111 for (const auto& ref : input) {
112 const RenderHandle handle = GetRoutedResource(gpuResourceMgr, rngShareMgr, ref.name, ref.nodeName,
113 ref.resourceLocation, ref.resourceIndex, RenderHandleType::GPU_IMAGE);
114 RenderHandle secondHandle;
115 for (auto sampIter = optionalSamplers.begin(); sampIter != optionalSamplers.end();) {
116 if ((sampIter->set == ref.set) && (sampIter->binding == ref.binding)) {
117 secondHandle = sampIter->handle;
118 optionalSamplers.erase(sampIter);
119 } else {
120 sampIter++;
121 }
122 }
123 output.push_back(RenderNodeResource { ref.set, ref.binding, handle, secondHandle, ref.mip, ref.layer });
124 }
125 };
126
127 setHandles(gpuResourceMgr, rngShareMgr, RenderHandleType::GPU_BUFFER, resources.buffers, res.buffers);
128 setHandles(gpuResourceMgr, rngShareMgr, RenderHandleType::GPU_SAMPLER, resources.samplers, res.samplers);
129 // loops through samplers for possible combined image sampler (this method is usually only called in
130 // RenderNodeInit())
131 // If found, moves the sampler handle from res.samplers to res.images.secondHandle
132 setImageHandles(gpuResourceMgr, rngShareMgr, res.samplers, resources.images, res.images);
133
134 setHandles(gpuResourceMgr, rngShareMgr, RenderHandleType::GPU_BUFFER, resources.customInputBuffers,
135 res.customInputBuffers);
136 setHandles(gpuResourceMgr, rngShareMgr, RenderHandleType::GPU_BUFFER, resources.customOutputBuffers,
137 res.customOutputBuffers);
138
139 vector<RenderNodeResource> sams;
140 setImageHandles(gpuResourceMgr, rngShareMgr, sams, resources.customInputImages, res.customInputImages);
141 setImageHandles(gpuResourceMgr, rngShareMgr, sams, resources.customOutputImages, res.customOutputImages);
142 }
143 } // namespace
144
RenderNodeUtil(IRenderNodeContextManager & renderNodeContextMgr)145 RenderNodeUtil::RenderNodeUtil(IRenderNodeContextManager& renderNodeContextMgr)
146 : renderNodeContextMgr_(renderNodeContextMgr)
147 {}
148
CreateInputRenderPass(const RenderNodeGraphInputs::InputRenderPass & renderPass) const149 RenderNodeHandles::InputRenderPass RenderNodeUtil::CreateInputRenderPass(
150 const RenderNodeGraphInputs::InputRenderPass& renderPass) const
151 {
152 RenderNodeHandles::InputRenderPass rp;
153 const auto& gpuResourceMgr = renderNodeContextMgr_.GetGpuResourceManager();
154 const auto& rngShareMgr = renderNodeContextMgr_.GetRenderNodeGraphShareManager();
155 if (!renderPass.attachments.empty()) {
156 rp.attachments.reserve(renderPass.attachments.size());
157 for (const auto& ref : renderPass.attachments) {
158 const RenderHandle handle = GetRoutedResource(gpuResourceMgr, rngShareMgr, ref.name, ref.nodeName,
159 ref.resourceLocation, ref.resourceIndex, RenderHandleType::GPU_IMAGE);
160 rp.attachments.push_back(RenderNodeAttachment { handle, ref.loadOp, ref.storeOp, ref.stencilLoadOp,
161 ref.stencilStoreOp, ref.clearValue, ref.mip, ref.layer });
162 }
163
164 rp.subpassIndex = renderPass.subpassIndex;
165 rp.subpassCount = renderPass.subpassCount;
166 rp.subpassContents = renderPass.subpassContents;
167
168 rp.depthAttachmentIndex = renderPass.depthAttachmentIndex;
169 rp.depthResolveAttachmentIndex = renderPass.depthResolveAttachmentIndex;
170 rp.inputAttachmentIndices = renderPass.inputAttachmentIndices;
171 rp.colorAttachmentIndices = renderPass.colorAttachmentIndices;
172 rp.resolveAttachmentIndices = renderPass.resolveAttachmentIndices;
173 rp.fragmentShadingRateAttachmentIndex = renderPass.fragmentShadingRateAttachmentIndex;
174 rp.depthResolveModeFlagBit = renderPass.depthResolveModeFlagBit;
175 rp.stencilResolveModeFlagBit = renderPass.stencilResolveModeFlagBit;
176 rp.shadingRateTexelSize = renderPass.shadingRateTexelSize;
177 rp.viewMask = renderPass.viewMask;
178 }
179
180 return rp;
181 }
182
CreateInputResources(const RenderNodeGraphInputs::InputResources & resources) const183 RenderNodeHandles::InputResources RenderNodeUtil::CreateInputResources(
184 const RenderNodeGraphInputs::InputResources& resources) const
185 {
186 RenderNodeHandles::InputResources res;
187 const auto& gpuResourceMgr = renderNodeContextMgr_.GetGpuResourceManager();
188 const auto& rngShareMgr = renderNodeContextMgr_.GetRenderNodeGraphShareManager();
189 SetupRenderNodeResourceHandles(gpuResourceMgr, rngShareMgr, resources, res);
190 return res;
191 }
192
CreatePipelineLayout(const RenderHandle & shaderHandle) const193 PipelineLayout RenderNodeUtil::CreatePipelineLayout(const RenderHandle& shaderHandle) const
194 {
195 const auto& shaderMgr = renderNodeContextMgr_.GetShaderManager();
196 RenderHandle plHandle = shaderMgr.GetPipelineLayoutHandleByShaderHandle(shaderHandle);
197 if (RenderHandleUtil::IsValid(plHandle)) {
198 return shaderMgr.GetPipelineLayout(plHandle);
199 } else {
200 return shaderMgr.GetReflectionPipelineLayout(shaderHandle);
201 }
202 }
203
CreatePipelineDescriptorSetBinder(const PipelineLayout & pipelineLayout) const204 IPipelineDescriptorSetBinder::Ptr RenderNodeUtil::CreatePipelineDescriptorSetBinder(
205 const PipelineLayout& pipelineLayout) const
206 {
207 auto& descriptorSetMgr = renderNodeContextMgr_.GetDescriptorSetManager();
208 return descriptorSetMgr.CreatePipelineDescriptorSetBinder(pipelineLayout);
209 }
210
BindResourcesToBinder(const RenderNodeHandles::InputResources & resources,IPipelineDescriptorSetBinder & pipelineDescriptorSetBinder) const211 void RenderNodeUtil::BindResourcesToBinder(
212 const RenderNodeHandles::InputResources& resources, IPipelineDescriptorSetBinder& pipelineDescriptorSetBinder) const
213 {
214 pipelineDescriptorSetBinder.ClearBindings();
215 for (const auto& ref : resources.buffers) {
216 if (RenderHandleUtil::IsValid(ref.handle)) {
217 BindableBuffer bindable;
218 bindable.handle = ref.handle;
219 pipelineDescriptorSetBinder.BindBuffer(ref.set, ref.binding, bindable);
220 }
221 }
222 for (const auto& ref : resources.images) {
223 if (RenderHandleUtil::IsValid(ref.handle)) {
224 BindableImage bindable;
225 bindable.handle = ref.handle;
226 bindable.samplerHandle = ref.secondHandle; // might be combined image sampler if valid handle
227 bindable.mip = ref.mip;
228 bindable.layer = ref.layer;
229 pipelineDescriptorSetBinder.BindImage(ref.set, ref.binding, bindable);
230 }
231 }
232 for (const auto& ref : resources.samplers) {
233 if (RenderHandleUtil::IsValid(ref.handle)) {
234 BindableSampler bindable;
235 bindable.handle = ref.handle;
236 pipelineDescriptorSetBinder.BindSampler(ref.set, ref.binding, bindable);
237 }
238 }
239 }
240
GetDescriptorCounts(const PipelineLayout & pipelineLayout) const241 DescriptorCounts RenderNodeUtil::GetDescriptorCounts(const PipelineLayout& pipelineLayout) const
242 {
243 DescriptorCounts dc;
244 for (uint32_t setIdx = 0; setIdx < PipelineLayoutConstants::MAX_DESCRIPTOR_SET_COUNT; ++setIdx) {
245 const auto& setRef = pipelineLayout.descriptorSetLayouts[setIdx];
246 if (setRef.set == PipelineLayoutConstants::INVALID_INDEX) {
247 continue;
248 }
249 dc.counts.reserve(dc.counts.size() + setRef.bindings.size());
250 for (const auto& bindingRef : setRef.bindings) {
251 dc.counts.push_back(DescriptorCounts::TypedCount { bindingRef.descriptorType, bindingRef.descriptorCount });
252 }
253 }
254 return dc;
255 }
256
GetDescriptorCounts(const array_view<DescriptorSetLayoutBinding> bindings) const257 DescriptorCounts RenderNodeUtil::GetDescriptorCounts(const array_view<DescriptorSetLayoutBinding> bindings) const
258 {
259 DescriptorCounts dc;
260 for (const auto& bindingRef : bindings) {
261 dc.counts.push_back(DescriptorCounts::TypedCount { bindingRef.descriptorType, bindingRef.descriptorCount });
262 }
263 return dc;
264 }
265
CreateRenderPass(const RenderNodeHandles::InputRenderPass & renderPass) const266 RenderPass RenderNodeUtil::CreateRenderPass(const RenderNodeHandles::InputRenderPass& renderPass) const
267 {
268 RenderPass rp;
269 RenderPassDesc& rpDesc = rp.renderPassDesc;
270
271 const uint32_t attachmentCount = static_cast<uint32_t>(renderPass.attachments.size());
272 PLUGIN_ASSERT(attachmentCount <= PipelineStateConstants::MAX_RENDER_PASS_ATTACHMENT_COUNT);
273 uint32_t width = ~0u;
274 uint32_t height = ~0u;
275 if (attachmentCount > 0) {
276 rp.renderPassDesc.attachmentCount = attachmentCount;
277 for (size_t idx = 0; idx < renderPass.attachments.size(); ++idx) {
278 const auto& ref = renderPass.attachments[idx];
279 rpDesc.attachments[idx] = { ref.layer, ref.mip, ref.loadOp, ref.storeOp, ref.stencilLoadOp,
280 ref.stencilStoreOp, ref.clearValue };
281 rpDesc.attachmentHandles[idx] = ref.handle;
282 if (idx == 0) { // optimization, width and height must match (will end in error later)
283 const GpuImageDesc desc = renderNodeContextMgr_.GetGpuResourceManager().GetImageDescriptor(ref.handle);
284 width = desc.width;
285 height = desc.height;
286 }
287 }
288
289 rpDesc.subpassContents = renderPass.subpassContents;
290 rpDesc.subpassCount = renderPass.subpassCount;
291 if (renderPass.subpassIndex >= renderPass.subpassCount) {
292 PLUGIN_LOG_E("Render pass subpass idx < count (%u < %u)", renderPass.subpassIndex, renderPass.subpassCount);
293 }
294 if (renderPass.subpassIndex < renderPass.subpassCount) {
295 rp.subpassStartIndex = renderPass.subpassIndex;
296 // update the one subpass described in render node graph
297 auto& spDesc = rp.subpassDesc;
298
299 spDesc.inputAttachmentCount = static_cast<uint32_t>(renderPass.inputAttachmentIndices.size());
300 spDesc.colorAttachmentCount = static_cast<uint32_t>(renderPass.colorAttachmentIndices.size());
301 spDesc.resolveAttachmentCount = static_cast<uint32_t>(renderPass.resolveAttachmentIndices.size());
302
303 spDesc.depthAttachmentCount = (renderPass.depthAttachmentIndex != ~0u) ? 1u : 0u;
304 spDesc.depthAttachmentIndex = (spDesc.depthAttachmentCount > 0) ? renderPass.depthAttachmentIndex : ~0u;
305 spDesc.depthResolveAttachmentCount = (renderPass.depthResolveAttachmentIndex != ~0u) ? 1u : 0u;
306 spDesc.depthResolveAttachmentIndex =
307 (spDesc.depthResolveAttachmentCount > 0) ? renderPass.depthResolveAttachmentIndex : ~0u;
308 spDesc.depthResolveModeFlagBit = renderPass.depthResolveModeFlagBit;
309 spDesc.stencilResolveModeFlagBit = renderPass.stencilResolveModeFlagBit;
310
311 spDesc.fragmentShadingRateAttachmentCount =
312 (renderPass.fragmentShadingRateAttachmentIndex != ~0u) ? 1u : 0u;
313 spDesc.fragmentShadingRateAttachmentIndex =
314 (spDesc.fragmentShadingRateAttachmentCount > 0) ? renderPass.fragmentShadingRateAttachmentIndex : ~0u;
315 spDesc.shadingRateTexelSize = renderPass.shadingRateTexelSize;
316 spDesc.viewMask = renderPass.viewMask;
317
318 for (uint32_t idx = 0; idx < spDesc.inputAttachmentCount; ++idx) {
319 spDesc.inputAttachmentIndices[idx] = renderPass.inputAttachmentIndices[idx];
320 }
321 for (uint32_t idx = 0; idx < spDesc.colorAttachmentCount; ++idx) {
322 spDesc.colorAttachmentIndices[idx] = renderPass.colorAttachmentIndices[idx];
323 }
324 for (uint32_t idx = 0; idx < spDesc.resolveAttachmentCount; ++idx) {
325 spDesc.resolveAttachmentIndices[idx] = renderPass.resolveAttachmentIndices[idx];
326 }
327 }
328 }
329 rpDesc.renderArea = { 0, 0, width, height };
330
331 return rp;
332 }
333
CreateDefaultViewport(const RenderPass & renderPass) const334 ViewportDesc RenderNodeUtil::CreateDefaultViewport(const RenderPass& renderPass) const
335 {
336 return ViewportDesc {
337 0.0f,
338 0.0f,
339 static_cast<float>(renderPass.renderPassDesc.renderArea.extentWidth),
340 static_cast<float>(renderPass.renderPassDesc.renderArea.extentHeight),
341 0.0f,
342 1.0f,
343 };
344 }
345
CreateDefaultScissor(const RenderPass & renderPass) const346 ScissorDesc RenderNodeUtil::CreateDefaultScissor(const RenderPass& renderPass) const
347 {
348 return ScissorDesc {
349 0,
350 0,
351 renderPass.renderPassDesc.renderArea.extentWidth,
352 renderPass.renderPassDesc.renderArea.extentHeight,
353 };
354 }
355
GetRenderPostProcessConfiguration(const PostProcessConfiguration & input) const356 RenderPostProcessConfiguration RenderNodeUtil::GetRenderPostProcessConfiguration(
357 const PostProcessConfiguration& input) const
358 {
359 RenderPostProcessConfiguration output;
360 std::fill(output.factors, output.factors + PostProcessConfiguration::INDEX_FACTOR_COUNT,
361 Math::Vec4 { 0.0f, 0.0f, 0.0f, 0.0f });
362
363 output.flags = { input.enableFlags, 0, 0, 0 };
364 output.renderTimings = renderNodeContextMgr_.GetRenderNodeGraphData().renderingConfiguration.renderTimings;
365 output.factors[PostProcessConfiguration::INDEX_TONEMAP] = PostProcessConversionHelper::GetFactorTonemap(input);
366 output.factors[PostProcessConfiguration::INDEX_VIGNETTE] = PostProcessConversionHelper::GetFactorVignette(input);
367 output.factors[PostProcessConfiguration::INDEX_DITHER] = PostProcessConversionHelper::GetFactorDither(input);
368 output.factors[PostProcessConfiguration::INDEX_COLOR_CONVERSION] =
369 PostProcessConversionHelper::GetFactorColorConversion(input);
370 output.factors[PostProcessConfiguration::INDEX_COLOR_FRINGE] = PostProcessConversionHelper::GetFactorFringe(input);
371
372 output.factors[PostProcessConfiguration::INDEX_BLUR] = PostProcessConversionHelper::GetFactorBlur(input);
373 output.factors[PostProcessConfiguration::INDEX_BLOOM] = PostProcessConversionHelper::GetFactorBloom(input);
374 output.factors[PostProcessConfiguration::INDEX_FXAA] = PostProcessConversionHelper::GetFactorFxaa(input);
375 output.factors[PostProcessConfiguration::INDEX_TAA] = PostProcessConversionHelper::GetFactorTaa(input);
376 output.factors[PostProcessConfiguration::INDEX_DOF] = PostProcessConversionHelper::GetFactorDof(input);
377 output.factors[PostProcessConfiguration::INDEX_MOTION_BLUR] =
378 PostProcessConversionHelper::GetFactorMotionBlur(input);
379
380 BASE_NS::CloneData(output.userFactors, sizeof(output.userFactors), input.userFactors, sizeof(input.userFactors));
381
382 return output;
383 }
384
GetRenderPostProcessConfiguration(const IRenderDataStorePostProcess::GlobalFactors & globalFactors) const385 RenderPostProcessConfiguration RenderNodeUtil::GetRenderPostProcessConfiguration(
386 const IRenderDataStorePostProcess::GlobalFactors& globalFactors) const
387 {
388 RenderPostProcessConfiguration output;
389
390 output.flags = { globalFactors.enableFlags, 0, 0, 0 };
391 output.renderTimings = renderNodeContextMgr_.GetRenderNodeGraphData().renderingConfiguration.renderTimings;
392
393 BASE_NS::CloneData(output.factors, sizeof(output.factors), globalFactors.factors, sizeof(globalFactors.factors));
394 BASE_NS::CloneData(
395 output.userFactors, sizeof(output.userFactors), globalFactors.userFactors, sizeof(globalFactors.userFactors));
396
397 return output;
398 }
399
HasChangeableResources(const RenderNodeGraphInputs::InputRenderPass & renderPass) const400 bool RenderNodeUtil::HasChangeableResources(const RenderNodeGraphInputs::InputRenderPass& renderPass) const
401 {
402 bool changeable = false;
403 for (const auto& ref : renderPass.attachments) {
404 if (ref.resourceLocation != RenderNodeGraphResourceLocationType::DEFAULT) {
405 changeable = true;
406 }
407 }
408 return changeable;
409 }
410
HasChangeableResources(const RenderNodeGraphInputs::InputResources & resources) const411 bool RenderNodeUtil::HasChangeableResources(const RenderNodeGraphInputs::InputResources& resources) const
412 {
413 auto HasChangingResources = [](const auto& vec) {
414 bool changeable = false;
415 for (const auto& ref : vec) {
416 if (ref.resourceLocation != RenderNodeGraphResourceLocationType::DEFAULT) {
417 changeable = true;
418 }
419 }
420 return changeable;
421 };
422 bool changeable = false;
423 changeable = changeable || HasChangingResources(resources.buffers);
424 changeable = changeable || HasChangingResources(resources.images);
425 changeable = changeable || HasChangingResources(resources.samplers);
426 changeable = changeable || HasChangingResources(resources.customInputBuffers);
427 changeable = changeable || HasChangingResources(resources.customOutputBuffers);
428 changeable = changeable || HasChangingResources(resources.customInputImages);
429 changeable = changeable || HasChangingResources(resources.customOutputImages);
430 return changeable;
431 }
432 RENDER_END_NAMESPACE()
433