1// Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB).
2// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "renderview_p.h"
6#include <Qt3DRender/qmaterial.h>
7#include <Qt3DRender/qrenderaspect.h>
8#include <Qt3DRender/qrendertarget.h>
9#include <Qt3DRender/qabstractlight.h>
10#include <Qt3DRender/private/sphere_p.h>
11
12#include <Qt3DRender/private/cameraselectornode_p.h>
13#include <Qt3DRender/private/framegraphnode_p.h>
14#include <Qt3DRender/private/layerfilternode_p.h>
15#include <Qt3DRender/private/qparameter_p.h>
16#include <Qt3DRender/private/cameralens_p.h>
17#include <Qt3DRender/private/effect_p.h>
18#include <Qt3DRender/private/entity_p.h>
19#include <Qt3DRender/private/nodemanagers_p.h>
20#include <Qt3DRender/private/layer_p.h>
21#include <Qt3DRender/private/renderlogging_p.h>
22#include <Qt3DRender/private/renderpassfilternode_p.h>
23#include <Qt3DRender/private/renderpass_p.h>
24#include <Qt3DRender/private/geometryrenderer_p.h>
25#include <Qt3DRender/private/techniquefilternode_p.h>
26#include <Qt3DRender/private/viewportnode_p.h>
27#include <Qt3DRender/private/buffermanager_p.h>
28#include <Qt3DRender/private/geometryrenderermanager_p.h>
29#include <Qt3DRender/private/rendercapture_p.h>
30#include <Qt3DRender/private/buffercapture_p.h>
31#include <Qt3DRender/private/stringtoint_p.h>
32#include <Qt3DRender/private/renderlogging_p.h>
33#include <Qt3DRender/private/renderstateset_p.h>
34#include <Qt3DRender/private/uniformblockbuilder_p.h>
35#include <Qt3DRender/private/clearbuffers_p.h>
36#include <Qt3DRender/private/rendertargetselectornode_p.h>
37#include <Qt3DRender/private/sortpolicy_p.h>
38#include <Qt3DRender/private/techniquefilternode_p.h>
39#include <Qt3DRender/private/managers_p.h>
40#include <Qt3DRender/private/shaderdata_p.h>
41#include <Qt3DRender/private/statesetnode_p.h>
42#include <Qt3DRender/private/dispatchcompute_p.h>
43#include <Qt3DRender/private/rendersurfaceselector_p.h>
44#include <Qt3DRender/private/rendercapture_p.h>
45#include <Qt3DRender/private/buffercapture_p.h>
46#include <Qt3DRender/private/techniquemanager_p.h>
47#include <Qt3DRender/private/blitframebuffer_p.h>
48#include <Qt3DRender/private/rendercapture_p.h>
49
50#include <rendercommand_p.h>
51#include <renderer_p.h>
52#include <submissioncontext_p.h>
53#include <rhiresourcemanagers_p.h>
54#include <Qt3DCore/qentity.h>
55#include <QtGui/qsurface.h>
56#include <algorithm>
57#include <atomic>
58#include <cstdlib>
59#include <QDebug>
60#if defined(QT3D_RENDER_VIEW_JOB_TIMINGS)
61#include <QElapsedTimer>
62#endif
63
64QT_BEGIN_NAMESPACE
65
66namespace Qt3DRender {
67namespace Render {
68namespace Rhi {
69
70namespace {
71
72// register our QNodeId's as a metatype during program loading
73Q_DECL_UNUSED const int qNodeIdTypeId = qMetaTypeId<Qt3DCore::QNodeId>();
74
75const int MAX_LIGHTS = 8;
76
77#define LIGHT_POSITION_NAME QLatin1String(".position")
78#define LIGHT_TYPE_NAME QLatin1String(".type")
79#define LIGHT_COLOR_NAME QLatin1String(".color")
80#define LIGHT_INTENSITY_NAME QLatin1String(".intensity")
81
82int LIGHT_COUNT_NAME_ID = 0;
83int LIGHT_POSITION_NAMES[MAX_LIGHTS];
84int LIGHT_TYPE_NAMES[MAX_LIGHTS];
85int LIGHT_COLOR_NAMES[MAX_LIGHTS];
86int LIGHT_INTENSITY_NAMES[MAX_LIGHTS];
87QString LIGHT_STRUCT_NAMES[MAX_LIGHTS];
88
89int LIGHT_POSITION_UNROLL_NAMES[MAX_LIGHTS];
90int LIGHT_TYPE_UNROLL_NAMES[MAX_LIGHTS];
91int LIGHT_COLOR_UNROLL_NAMES[MAX_LIGHTS];
92int LIGHT_INTENSITY_UNROLL_NAMES[MAX_LIGHTS];
93QString LIGHT_STRUCT_UNROLL_NAMES[MAX_LIGHTS];
94
95std::atomic_bool wasInitialized {};
96
97} // anonymous namespace
98
99// TODO: Move this somewhere global where GraphicsContext::setViewport() can use it too
100static QRectF resolveViewport(const QRectF &fractionalViewport, const QSize &surfaceSize)
101{
102 return QRectF(fractionalViewport.x() * surfaceSize.width(),
103 (1.0 - fractionalViewport.y() - fractionalViewport.height())
104 * surfaceSize.height(),
105 fractionalViewport.width() * surfaceSize.width(),
106 fractionalViewport.height() * surfaceSize.height());
107}
108
109static Matrix4x4 getProjectionMatrix(const CameraLens *lens)
110{
111 Matrix4x4 m;
112 if (lens)
113 m = lens->projection();
114 return m;
115}
116
117/*!
118 \internal
119 Walks up the framegraph tree from \a fgLeaf and builds up as much state
120 as possible and populates \a rv. For cases where we can't get the specific state
121 (e.g. because it depends upon more than just the framegraph) we store the data from
122 the framegraph that will be needed to later when the rest of the data becomes available
123*/
124void RenderView::setRenderViewConfigFromFrameGraphLeafNode(RenderView *rv, const FrameGraphNode *fgLeaf)
125{
126 // The specific RenderPass to be used is also dependent upon the Effect and TechniqueFilter
127 // which is referenced by the Material which is referenced by the RenderMesh. So we can
128 // only store the filter info in the RenderView structure and use it to do the resolving
129 // when we build the RenderCommand list.
130 const NodeManagers *manager = rv->nodeManagers();
131 const FrameGraphNode *node = fgLeaf;
132
133 while (node) {
134 FrameGraphNode::FrameGraphNodeType type = node->nodeType();
135 if (node->isEnabled())
136 switch (type) {
137 case FrameGraphNode::InvalidNodeType:
138 // A base FrameGraphNode, can be used for grouping purposes
139 break;
140 case FrameGraphNode::CameraSelector:
141 // Can be set only once and we take camera nearest to the leaf node
142 if (!rv->renderCameraLens()) {
143 const CameraSelector *cameraSelector =
144 static_cast<const CameraSelector *>(node);
145 Entity *camNode = manager->renderNodesManager()->lookupResource(
146 id: cameraSelector->cameraUuid());
147 if (camNode) {
148 CameraLens *lens = camNode->renderComponent<CameraLens>();
149 rv->setRenderCameraEntity(camNode);
150 if (lens && lens->isEnabled()) {
151 rv->setRenderCameraLens(lens);
152 // ViewMatrix and ProjectionMatrix are computed
153 // later in updateMatrices()
154 // since at this point the transformation matrices
155 // may not yet have been updated
156 }
157 }
158 }
159 break;
160
161 case FrameGraphNode::LayerFilter: // Can be set multiple times in the tree
162 rv->appendLayerFilter(layerFilterId: static_cast<const LayerFilterNode *>(node)->peerId());
163 break;
164
165 case FrameGraphNode::ProximityFilter: // Can be set multiple times in the tree
166 rv->appendProximityFilterId(proximityFilterId: node->peerId());
167 break;
168
169 case FrameGraphNode::RenderPassFilter:
170 // Can be set once
171 // TODO: Amalgamate all render pass filters from leaf to root
172 if (!rv->renderPassFilter())
173 rv->setRenderPassFilter(static_cast<const RenderPassFilter *>(node));
174 break;
175
176 case FrameGraphNode::RenderTarget: {
177 // Can be set once and we take render target nearest to the leaf node
178 const RenderTargetSelector *targetSelector =
179 static_cast<const RenderTargetSelector *>(node);
180 Qt3DCore::QNodeId renderTargetUid = targetSelector->renderTargetUuid();
181
182 // Note: we ignore the render target outputs the RenderTargetSelector
183 // might specify as we can't handle that with RHI
184
185 // Add renderTarget Handle
186 if (!rv->renderTargetId()) {
187 rv->setRenderTargetId(renderTargetUid);
188 }
189 break;
190 }
191
192 case FrameGraphNode::ClearBuffers: {
193 const ClearBuffers *cbNode = static_cast<const ClearBuffers *>(node);
194 rv->addClearBuffers(cb: cbNode);
195 break;
196 }
197
198 case FrameGraphNode::TechniqueFilter:
199 // Can be set once
200 // TODO Amalgamate all technique filters from leaf to root
201 if (!rv->techniqueFilter())
202 rv->setTechniqueFilter(static_cast<const TechniqueFilter *>(node));
203 break;
204
205 case FrameGraphNode::Viewport: {
206 // If the Viewport has already been set in a lower node
207 // Make it so that the new viewport is actually
208 // a subregion relative to that of the parent viewport
209 const ViewportNode *vpNode = static_cast<const ViewportNode *>(node);
210 rv->setViewport(ViewportNode::computeViewport(childViewport: rv->viewport(), parentViewport: vpNode));
211 rv->setGamma(vpNode->gamma());
212 break;
213 }
214
215 case FrameGraphNode::SortMethod: {
216 const Render::SortPolicy *sortPolicy =
217 static_cast<const Render::SortPolicy *>(node);
218 rv->addSortType(sortTypes: sortPolicy->sortTypes());
219 break;
220 }
221
222 case FrameGraphNode::SubtreeEnabler:
223 // Has no meaning here. SubtreeEnabler was used
224 // in a prior step to filter the list of RenderViewJobs
225 break;
226
227 case FrameGraphNode::StateSet: {
228 const Render::StateSetNode *rStateSet = static_cast<const Render::StateSetNode *>(node);
229 // Add states from new stateSet we might be missing
230 // but don' t override existing states (lower StateSetNode always has priority)
231 if (rStateSet->hasRenderStates()) {
232 // Create global RenderStateSet for renderView if no stateSet was set before
233 RenderStateSet *stateSet = rv->getOrCreateStateSet();
234 addStatesToRenderStateSet(stateSet, stateIds: rStateSet->renderStates(), manager: manager->renderStateManager());
235 }
236 break;
237 }
238
239 case FrameGraphNode::NoDraw: {
240 rv->setNoDraw(true);
241 break;
242 }
243
244 case FrameGraphNode::FrustumCulling: {
245 rv->setFrustumCulling(true);
246 break;
247 }
248
249 case FrameGraphNode::ComputeDispatch: {
250 const Render::DispatchCompute *dispatchCompute =
251 static_cast<const Render::DispatchCompute *>(node);
252 rv->setCompute(true);
253 rv->setComputeWorkgroups(x: dispatchCompute->x(), y: dispatchCompute->y(),
254 z: dispatchCompute->z());
255 break;
256 }
257
258 case FrameGraphNode::Lighting: {
259 // TODO
260 break;
261 }
262
263 case FrameGraphNode::Surface: {
264 // Use the surface closest to leaf node
265 if (rv->surface() == nullptr) {
266 const Render::RenderSurfaceSelector *surfaceSelector =
267 static_cast<const Render::RenderSurfaceSelector *>(node);
268 rv->setSurface(surfaceSelector->surface());
269 rv->setSurfaceSize(surfaceSelector->renderTargetSize()
270 * surfaceSelector->devicePixelRatio());
271 rv->setDevicePixelRatio(surfaceSelector->devicePixelRatio());
272 }
273 break;
274 }
275 case FrameGraphNode::RenderCapture: {
276 auto *renderCapture = const_cast<Render::RenderCapture *>(
277 static_cast<const Render::RenderCapture *>(node));
278 if (rv->renderCaptureNodeId().isNull() && renderCapture->wasCaptureRequested()) {
279 rv->setRenderCaptureNodeId(renderCapture->peerId());
280 rv->setRenderCaptureRequest(renderCapture->takeCaptureRequest());
281 }
282 break;
283 }
284
285 case FrameGraphNode::MemoryBarrier: {
286 // Not available in rhi
287 break;
288 }
289
290 case FrameGraphNode::BufferCapture: {
291 auto *bufferCapture = const_cast<Render::BufferCapture *>(
292 static_cast<const Render::BufferCapture *>(node));
293 if (bufferCapture != nullptr)
294 rv->setIsDownloadBuffersEnable(bufferCapture->isEnabled());
295 break;
296 }
297
298 case FrameGraphNode::BlitFramebuffer: {
299 const Render::BlitFramebuffer *blitFramebufferNode =
300 static_cast<const Render::BlitFramebuffer *>(node);
301 rv->setHasBlitFramebufferInfo(true);
302 BlitFramebufferInfo bfbInfo;
303 bfbInfo.sourceRenderTargetId = blitFramebufferNode->sourceRenderTargetId();
304 bfbInfo.destinationRenderTargetId =
305 blitFramebufferNode->destinationRenderTargetId();
306 bfbInfo.sourceRect = blitFramebufferNode->sourceRect();
307 bfbInfo.destinationRect = blitFramebufferNode->destinationRect();
308 bfbInfo.sourceAttachmentPoint = blitFramebufferNode->sourceAttachmentPoint();
309 bfbInfo.destinationAttachmentPoint =
310 blitFramebufferNode->destinationAttachmentPoint();
311 bfbInfo.interpolationMethod = blitFramebufferNode->interpolationMethod();
312 rv->setBlitFrameBufferInfo(bfbInfo);
313 break;
314 }
315
316 case FrameGraphNode::WaitFence: {
317 // Not available in rhi
318 break;
319 }
320
321 case FrameGraphNode::SetFence: {
322 // Not available in rhi
323 break;
324 }
325
326 case FrameGraphNode::NoPicking:
327 // Nothing to do RenderView wise for NoPicking
328 break;
329
330 case FrameGraphNode::DebugOverlay:
331 // Not supported yet with RHI
332 break;
333
334 default:
335 // Should never get here
336 qCWarning(Backend) << "Unhandled FrameGraphNode type";
337 }
338
339 node = node->parent();
340 }
341}
342
343RenderView::RenderView()
344{
345 if (Q_UNLIKELY(!wasInitialized.exchange(true))) {
346 // Needed as we can control the init order of static/global variables across compile units
347 // and this hash relies on the static StringToInt class
348
349 LIGHT_COUNT_NAME_ID = StringToInt::lookupId(str: QLatin1String("lightCount"));
350 for (int i = 0; i < MAX_LIGHTS; ++i) {
351 Q_STATIC_ASSERT_X(MAX_LIGHTS < 10, "can't use the QChar trick anymore");
352 LIGHT_STRUCT_NAMES[i] =
353 QLatin1String("lights[") + QLatin1Char(char('0' + i)) + QLatin1Char(']');
354 LIGHT_POSITION_NAMES[i] =
355 StringToInt::lookupId(str: LIGHT_STRUCT_NAMES[i] + LIGHT_POSITION_NAME);
356 LIGHT_TYPE_NAMES[i] = StringToInt::lookupId(str: LIGHT_STRUCT_NAMES[i] + LIGHT_TYPE_NAME);
357 LIGHT_COLOR_NAMES[i] = StringToInt::lookupId(str: LIGHT_STRUCT_NAMES[i] + LIGHT_COLOR_NAME);
358 LIGHT_INTENSITY_NAMES[i] =
359 StringToInt::lookupId(str: LIGHT_STRUCT_NAMES[i] + LIGHT_INTENSITY_NAME);
360
361 LIGHT_STRUCT_UNROLL_NAMES[i] =
362 QLatin1String("light_") + QLatin1Char(char('0' + i));
363 LIGHT_POSITION_UNROLL_NAMES[i] =
364 StringToInt::lookupId(str: LIGHT_STRUCT_UNROLL_NAMES[i] + LIGHT_POSITION_NAME);
365 LIGHT_TYPE_UNROLL_NAMES[i] = StringToInt::lookupId(str: LIGHT_STRUCT_UNROLL_NAMES[i] + LIGHT_TYPE_NAME);
366 LIGHT_COLOR_UNROLL_NAMES[i] = StringToInt::lookupId(str: LIGHT_STRUCT_UNROLL_NAMES[i] + LIGHT_COLOR_NAME);
367 LIGHT_INTENSITY_UNROLL_NAMES[i] =
368 StringToInt::lookupId(str: LIGHT_STRUCT_UNROLL_NAMES[i] + LIGHT_INTENSITY_NAME);
369 }
370 }
371}
372
373RenderView::~RenderView()
374{
375}
376
377namespace {
378
379template<int SortType>
380struct AdjacentSubRangeFinder
381{
382 static bool adjacentSubRange(const RenderCommand &, const RenderCommand &)
383 {
384 Q_UNREACHABLE_RETURN(false);
385 }
386};
387
388template<>
389struct AdjacentSubRangeFinder<QSortPolicy::StateChangeCost>
390{
391 static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b)
392 {
393 return a.m_changeCost == b.m_changeCost;
394 }
395};
396
397template<>
398struct AdjacentSubRangeFinder<QSortPolicy::BackToFront>
399{
400 static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b)
401 {
402 return qFuzzyCompare(p1: a.m_depth, p2: b.m_depth);
403 }
404};
405
406template<>
407struct AdjacentSubRangeFinder<QSortPolicy::Material>
408{
409 static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b)
410 {
411 return a.m_rhiShader == b.m_rhiShader;
412 }
413};
414
415template<>
416struct AdjacentSubRangeFinder<QSortPolicy::FrontToBack>
417{
418 static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b)
419 {
420 return qFuzzyCompare(p1: a.m_depth, p2: b.m_depth);
421 }
422};
423
424template<>
425struct AdjacentSubRangeFinder<QSortPolicy::Texture>
426{
427 static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b)
428 {
429 // Two renderCommands are adjacent if one contains all the other command's textures
430 const std::vector<ShaderParameterPack::NamedResource> &texturesA = a.m_parameterPack.textures();
431 const std::vector<ShaderParameterPack::NamedResource> &texturesB = b.m_parameterPack.textures();
432
433 const bool bBigger = texturesB.size() > texturesA.size();
434 const std::vector<ShaderParameterPack::NamedResource> &smallestVector = bBigger ? texturesA : texturesB;
435 const std::vector<ShaderParameterPack::NamedResource> &biggestVector = bBigger ? texturesB : texturesA;
436
437 const auto e = biggestVector.cend();
438 for (const ShaderParameterPack::NamedResource &tex : smallestVector) {
439 if (std::find(first: biggestVector.begin(), last: e, val: tex) == e)
440 return false;
441 }
442
443 return true;
444 }
445};
446
447template<typename Predicate>
448int advanceUntilNonAdjacent(const EntityRenderCommandDataView *view,
449 const size_t beg, const size_t end, Predicate pred)
450{
451 const std::vector<size_t> &commandIndices = view->indices;
452 const std::vector<RenderCommand> &commands = view->data.commands;
453 size_t i = beg + 1;
454 if (i < end) {
455 const size_t startIdx = commandIndices[beg];
456 while (i < end) {
457 const size_t targetIdx = commandIndices[i];
458 if (!pred(commands[startIdx], commands[targetIdx]))
459 break;
460 ++i;
461 }
462 }
463 return int(i);
464}
465
466
467template<int SortType>
468struct SubRangeSorter
469{
470 static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end)
471 {
472 Q_UNUSED(view);
473 Q_UNUSED(begin);
474 Q_UNUSED(end);
475 Q_UNREACHABLE();
476 }
477};
478
479template<>
480struct SubRangeSorter<QSortPolicy::StateChangeCost>
481{
482 static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end)
483 {
484 std::vector<size_t> &commandIndices = view->indices;
485 const std::vector<RenderCommand> &commands = view->data.commands;
486 std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end,
487 comp: [&commands] (const size_t &iA, const size_t &iB) {
488 const RenderCommand &a = commands[iA];
489 const RenderCommand &b = commands[iB];
490 return a.m_changeCost > b.m_changeCost;
491 });
492 }
493};
494
495template<>
496struct SubRangeSorter<QSortPolicy::BackToFront>
497{
498 static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end)
499 {
500 std::vector<size_t> &commandIndices = view->indices;
501 const std::vector<RenderCommand> &commands = view->data.commands;
502 std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end,
503 comp: [&commands] (const size_t &iA, const size_t &iB) {
504 const RenderCommand &a = commands[iA];
505 const RenderCommand &b = commands[iB];
506 return a.m_depth > b.m_depth;
507 });
508 }
509};
510
511template<>
512struct SubRangeSorter<QSortPolicy::Material>
513{
514 static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end)
515 {
516 std::vector<size_t> &commandIndices = view->indices;
517 const std::vector<RenderCommand> &commands = view->data.commands;
518 std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end,
519 comp: [&commands] (const size_t &iA, const size_t &iB) {
520 const RenderCommand &a = commands[iA];
521 const RenderCommand &b = commands[iB];
522 return a.m_rhiShader > b.m_rhiShader;
523 });
524 }
525};
526
527template<>
528struct SubRangeSorter<QSortPolicy::FrontToBack>
529{
530 static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end)
531 {
532 std::vector<size_t> &commandIndices = view->indices;
533 const std::vector<RenderCommand> &commands = view->data.commands;
534 std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end,
535 comp: [&commands] (const size_t &iA, const size_t &iB) {
536 const RenderCommand &a = commands[iA];
537 const RenderCommand &b = commands[iB];
538 return a.m_depth < b.m_depth;
539 });
540 }
541};
542
543template<>
544struct SubRangeSorter<QSortPolicy::Texture>
545{
546 static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end)
547 {
548#ifndef Q_OS_WIN
549 std::vector<size_t> &commandIndices = view->indices;
550 const std::vector<RenderCommand> &commands = view->data.commands;
551 std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end,
552 comp: [&commands] (const int &iA, const int &iB) {
553 const RenderCommand &a = commands[iA];
554 const RenderCommand &b = commands[iB];
555 const std::vector<ShaderParameterPack::NamedResource> &texturesA = a.m_parameterPack.textures();
556 const std::vector<ShaderParameterPack::NamedResource> &texturesB = b.m_parameterPack.textures();
557
558 const bool bBigger = texturesB.size() > texturesA.size();
559 const std::vector<ShaderParameterPack::NamedResource> &smallestVector = bBigger ? texturesA : texturesB;
560 const std::vector<ShaderParameterPack::NamedResource> &biggestVector = bBigger ? texturesB : texturesA;
561
562 size_t identicalTextureCount = 0;
563 const auto e = biggestVector.cend();
564 for (const ShaderParameterPack::NamedResource &tex : smallestVector) {
565 if (std::find(first: biggestVector.begin(), last: e, val: tex) != e)
566 ++identicalTextureCount;
567 }
568
569 return identicalTextureCount < smallestVector.size();
570 });
571#else
572 Q_UNUSED(view);
573 Q_UNUSED(begin);
574 Q_UNUSED(end);
575#endif
576 }
577};
578
579int findSubRange(const EntityRenderCommandDataView *view,
580 const int begin, const int end,
581 const QSortPolicy::SortType sortType)
582{
583 switch (sortType) {
584 case QSortPolicy::StateChangeCost:
585 return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::StateChangeCost>::adjacentSubRange);
586 case QSortPolicy::BackToFront:
587 return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::BackToFront>::adjacentSubRange);
588 case QSortPolicy::Material:
589 return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange);
590 case QSortPolicy::FrontToBack:
591 return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::FrontToBack>::adjacentSubRange);
592 case QSortPolicy::Texture:
593 return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::Texture>::adjacentSubRange);
594 case QSortPolicy::Uniform:
595 return end;
596 default:
597 Q_UNREACHABLE_RETURN(end);
598 }
599}
600
601void sortByMaterial(EntityRenderCommandDataView *view, int begin, const int end)
602{
603 // We try to arrange elements so that their rendering cost is minimized for a given shader
604 std::vector<size_t> &commandIndices = view->indices;
605 const std::vector<RenderCommand> &commands = view->data.commands;
606 int rangeEnd = advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange);
607 while (begin != end) {
608 if (begin + 1 < rangeEnd) {
609 std::stable_sort(first: commandIndices.begin() + begin + 1, last: commandIndices.begin() + rangeEnd,
610 comp: [&commands] (const size_t &iA, const size_t &iB) {
611 const RenderCommand &a = commands[iA];
612 const RenderCommand &b = commands[iB];
613 return a.m_material.handle() < b.m_material.handle();
614 });
615 }
616 begin = rangeEnd;
617 rangeEnd = advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange);
618 }
619}
620
621void sortCommandRange(EntityRenderCommandDataView *view, int begin, int end, const size_t level,
622 const std::vector<Qt3DRender::QSortPolicy::SortType> &sortingTypes)
623{
624 if (level >= sortingTypes.size())
625 return;
626
627 switch (sortingTypes.at(n: level)) {
628 case QSortPolicy::StateChangeCost:
629 SubRangeSorter<QSortPolicy::StateChangeCost>::sortSubRange(view, begin, end);
630 break;
631 case QSortPolicy::BackToFront:
632 SubRangeSorter<QSortPolicy::BackToFront>::sortSubRange(view, begin, end);
633 break;
634 case QSortPolicy::Material:
635 // Groups all same shader DNA together
636 SubRangeSorter<QSortPolicy::Material>::sortSubRange(view, begin, end);
637 // Group all same material together (same parameters most likely)
638 sortByMaterial(view, begin, end);
639 break;
640 case QSortPolicy::FrontToBack:
641 SubRangeSorter<QSortPolicy::FrontToBack>::sortSubRange(view, begin, end);
642 break;
643 case QSortPolicy::Texture:
644 SubRangeSorter<QSortPolicy::Texture>::sortSubRange(view, begin, end);
645 break;
646 case QSortPolicy::Uniform:
647 break;
648 default:
649 Q_UNREACHABLE();
650 }
651
652 // For all sub ranges of adjacent item for sortType[i]
653 // Perform filtering with sortType[i + 1]
654 int rangeEnd = findSubRange(view, begin, end, sortType: sortingTypes.at(n: level));
655 while (begin != end) {
656 sortCommandRange(view, begin, end: rangeEnd, level: level + 1, sortingTypes);
657 begin = rangeEnd;
658 rangeEnd = findSubRange(view, begin, end, sortType: sortingTypes.at(n: level));
659 }
660}
661
662} // anonymous
663
664void RenderView::sort()
665{
666 assert(m_renderCommandDataView);
667 // Compares the bitsetKey of the RenderCommands
668 // Key[Depth | StateCost | Shader]
669 sortCommandRange(view: m_renderCommandDataView.data(), begin: 0, end: int(m_renderCommandDataView->size()), level: 0, sortingTypes: m_sortingTypes);
670
671 // For RenderCommand with the same shader
672 // We compute the adjacent change cost
673
674 // Only perform uniform minimization if we explicitly asked for it
675 if (!Qt3DCore::contains(destination: m_sortingTypes, element: QSortPolicy::Uniform))
676 return;
677
678 // Minimize uniform changes
679 size_t i = 0;
680 std::vector<RenderCommand> &commands = m_renderCommandDataView->data.commands;
681 const std::vector<size_t> &indices = m_renderCommandDataView->indices;
682 const size_t commandSize = indices.size();
683
684 while (i < commandSize) {
685 size_t j = i;
686
687 // Advance while commands share the same shader
688 while (i < commandSize &&
689 commands[indices[j]].m_rhiShader == commands[indices[i]].m_rhiShader)
690 ++i;
691
692 if (i - j > 0) { // Several commands have the same shader, so we minimize uniform changes
693 PackUniformHash cachedUniforms = commands[indices[j++]].m_parameterPack.uniforms();
694
695 while (j < i) {
696 // We need the reference here as we are modifying the original container
697 // not the copy
698 PackUniformHash &uniforms = commands[indices[j]].m_parameterPack.m_uniforms;
699
700 for (size_t u = 0; u < uniforms.keys.size();) {
701 // We are comparing the values:
702 // - raw uniform values
703 // - the texture Node id if the uniform represents a texture
704 // since all textures are assigned texture units before the RenderCommands
705 // sharing the same material (shader) are rendered, we can't have the case
706 // where two uniforms, referencing the same texture eventually have 2 different
707 // texture unit values
708 const int uniformNameId = uniforms.keys.at(n: u);
709 const UniformValue &refValue = cachedUniforms.value(key: uniformNameId);
710 const UniformValue &newValue = uniforms.values.at(n: u);
711 if (newValue == refValue) {
712 uniforms.erase(idx: int(u));
713 } else {
714 // Record updated value so that subsequent comparison
715 // for the next command will be made againts latest
716 // uniform value
717 cachedUniforms.insert(key: uniformNameId, value: newValue);
718 ++u;
719 }
720 }
721 ++j;
722 }
723 }
724 }
725}
726
727void RenderView::setRenderer(Renderer *renderer)
728{
729 m_renderer = renderer;
730 m_manager = renderer->nodeManagers();
731}
732
733RenderStateSet *RenderView::getOrCreateStateSet()
734{
735 if (!m_stateSet)
736 m_stateSet.reset(other: new RenderStateSet());
737 return m_stateSet.data();
738}
739
740void RenderView::addClearBuffers(const ClearBuffers *cb)
741{
742 QClearBuffers::BufferTypeFlags type = cb->type();
743
744 if (type & QClearBuffers::StencilBuffer) {
745 m_clearStencilValue = cb->clearStencilValue();
746 m_clearBuffer |= QClearBuffers::StencilBuffer;
747 }
748 if (type & QClearBuffers::DepthBuffer) {
749 m_clearDepthValue = cb->clearDepthValue();
750 m_clearBuffer |= QClearBuffers::DepthBuffer;
751 }
752 // keep track of global ClearColor (if set) and collect all DrawBuffer-specific
753 // ClearColors
754 if (type & QClearBuffers::ColorBuffer) {
755 ClearBufferInfo clearBufferInfo;
756 clearBufferInfo.clearColor = cb->clearColor();
757
758 if (cb->clearsAllColorBuffers()) {
759 m_globalClearColorBuffer = clearBufferInfo;
760 m_clearBuffer |= QClearBuffers::ColorBuffer;
761 } else {
762 if (cb->bufferId()) {
763 const RenderTargetOutput *targetOutput =
764 m_manager->attachmentManager()->lookupResource(id: cb->bufferId());
765 if (targetOutput) {
766 clearBufferInfo.attchmentPoint = targetOutput->point();
767 // Note: a job is later performed to find the drawIndex from the buffer
768 // attachment point using the AttachmentPack
769 m_specificClearColorBuffers.push_back(x: clearBufferInfo);
770 }
771 }
772 }
773 }
774}
775
776// If we are there, we know that entity had a GeometryRenderer + Material
777EntityRenderCommandData RenderView::buildDrawRenderCommands(const Entity **entities,
778 int offset, int count) const
779{
780 EntityRenderCommandData commands;
781
782 commands.reserve(size: count);
783
784 for (int i = 0; i < count; ++i) {
785 const int idx = offset + i;
786 const Entity *entity = entities[idx];
787 GeometryRenderer *geometryRenderer = nullptr;
788 HGeometryRenderer geometryRendererHandle = entity->componentHandle<GeometryRenderer>();
789
790 // There is a geometry renderer with geometry
791 if ((geometryRenderer = m_manager->geometryRendererManager()->data(handle: geometryRendererHandle))
792 != nullptr
793 && geometryRenderer->isEnabled() && !geometryRenderer->geometryId().isNull()) {
794
795 const Qt3DCore::QNodeId materialComponentId = entity->componentUuid<Material>();
796 const HMaterial materialHandle = entity->componentHandle<Material>();
797 const std::vector<RenderPassParameterData> renderPassData =
798 m_parameters.value(key: materialComponentId);
799
800 HGeometry geometryHandle =
801 m_manager->geometryManager()->lookupHandle(id: geometryRenderer->geometryId());
802 Geometry *geometry = m_manager->geometryManager()->data(handle: geometryHandle);
803
804 if (geometry == nullptr)
805 continue;
806
807 // 1 RenderCommand per RenderPass pass on an Entity with a Mesh
808 for (const RenderPassParameterData &passData : renderPassData) {
809 // Add the RenderPass Parameters
810 RenderCommand command = {};
811 command.m_geometryRenderer = geometryRendererHandle;
812 command.m_geometry = geometryHandle;
813
814 command.m_material = materialHandle;
815 // For RenderPass based states we use the globally set RenderState
816 // if no renderstates are defined as part of the pass. That means:
817 // RenderPass { renderStates: [] } will use the states defined by
818 // StateSet in the FrameGraph
819 RenderPass *pass = passData.pass;
820 if (pass->hasRenderStates()) {
821 command.m_stateSet = RenderStateSetPtr::create();
822 addStatesToRenderStateSet(stateSet: command.m_stateSet.data(), stateIds: pass->renderStates(),
823 manager: m_manager->renderStateManager());
824 if (m_stateSet != nullptr)
825 command.m_stateSet->merge(other: m_stateSet.get());
826 command.m_changeCost =
827 m_renderer->defaultRenderState()->changeCost(previousState: command.m_stateSet.data());
828 }
829 command.m_shaderId = pass->shaderProgram();
830
831 // At submission time, shaderId is used to retrieve a RHIShader
832 // No point in continuing if shaderId is null
833 if (!command.m_shaderId)
834 continue;
835
836 // We try to resolve the m_rhiShader here. If the shader exist,
837 // it won't be null and will allow us to full process the
838 // command over a single frame. Otherwise, the shader will be
839 // loaded at the next submission time and the command will only
840 // be fully valid on the next frame. Additionally, that way, if
841 // a commands keeps being rebuilt, frame after frame, it will
842 // still be visible on screen as long as the shader exists
843 RHIShaderManager *rhiShaderManager = m_renderer->rhiResourceManagers()->rhiShaderManager();
844 command.m_rhiShader = rhiShaderManager->lookupResource(shaderId: command.m_shaderId);
845
846 { // Scoped to show extent
847
848 // Build of list of Attribute Layout information which
849 // allows use to compare the layout of geometries against
850 // one another.
851 // { name, classification, stride, offset, divisor }
852
853 // Update the draw command with what's going to be needed for the drawing
854 int primitiveCount = geometryRenderer->vertexCount();
855 int estimatedCount = 0;
856 Attribute *indexAttribute = nullptr;
857 Attribute *indirectAttribute = nullptr;
858
859 const QList<Qt3DCore::QNodeId> attributeIds = geometry->attributes();
860 command.m_attributeInfo.clear();
861 command.m_attributeInfo.reserve(n: attributeIds.size());
862 for (Qt3DCore::QNodeId attributeId : attributeIds) {
863 using namespace Qt3DCore;
864
865 Attribute *attribute =
866 m_manager->attributeManager()->lookupResource(id: attributeId);
867 switch (attribute->attributeType()) {
868 case QAttribute::IndexAttribute:
869 indexAttribute = attribute;
870 break;
871 case QAttribute::DrawIndirectAttribute:
872 indirectAttribute = attribute;
873 break;
874 case QAttribute::VertexAttribute:
875 estimatedCount = std::max(a: int(attribute->count()), b: estimatedCount);
876 break;
877 default:
878 Q_UNREACHABLE();
879 break;
880 }
881
882 if (attribute->attributeType() == QAttribute::VertexAttribute) {
883 command.m_attributeInfo.push_back(x: { .nameId: attribute->nameId(),
884 .classification: attribute->divisor() == 0 ? QRhiVertexInputBinding::PerVertex : QRhiVertexInputBinding::PerInstance,
885 .stride: size_t(attribute->byteStride()),
886 .offset: size_t(attribute->byteOffset()),
887 .divisor: size_t(attribute->divisor()) });
888 }
889 }
890
891 // Sort attributes by name so that same attributes added
892 // in different order would still result in the same geometeyLayout key
893 std::sort(first: command.m_attributeInfo.begin(),
894 last: command.m_attributeInfo.end(),
895 comp: [] (const AttributeInfo &a, const AttributeInfo &b) {
896 return a.nameId < b.nameId;
897 });
898
899 command.m_drawIndexed = (indexAttribute != nullptr);
900 command.m_drawIndirect = (indirectAttribute != nullptr);
901 command.indexAttribute = nullptr;
902 command.indexBuffer = nullptr;
903 command.pipeline = std::monostate{};
904
905 // Update the draw command with all the information required for the drawing
906 if (command.m_drawIndexed) {
907 command.m_indexAttributeDataType = indexAttribute->vertexBaseType();
908 command.m_indexAttributeByteOffset = indexAttribute->byteOffset()
909 + geometryRenderer->indexBufferByteOffset();
910 }
911
912 // Note: we only care about the primitiveCount when using direct draw calls
913 // For indirect draw calls it is assumed the buffer was properly set already
914 if (command.m_drawIndirect) {
915 command.m_indirectAttributeByteOffset = indirectAttribute->byteOffset();
916 command.m_indirectDrawBuffer = m_manager->bufferManager()->lookupHandle(
917 id: indirectAttribute->bufferId());
918 } else {
919 // Use the count specified by the GeometryRender
920 // If not specify use the indexAttribute count if present
921 // Otherwise tries to use the count from the attribute with the highest
922 // count
923 if (primitiveCount == 0) {
924 if (indexAttribute)
925 primitiveCount = indexAttribute->count();
926 else
927 primitiveCount = estimatedCount;
928 }
929 }
930
931 command.m_primitiveCount = primitiveCount;
932 command.m_primitiveType = geometryRenderer->primitiveType();
933 command.m_primitiveRestartEnabled = geometryRenderer->primitiveRestartEnabled();
934 command.m_restartIndexValue = geometryRenderer->restartIndexValue();
935 command.m_firstInstance = geometryRenderer->firstInstance();
936 command.m_instanceCount = geometryRenderer->instanceCount();
937 command.m_firstVertex = geometryRenderer->firstVertex();
938 command.m_indexOffset = geometryRenderer->indexOffset();
939 command.m_verticesPerPatch = geometryRenderer->verticesPerPatch();
940 } // scope
941
942 commands.push_back(e: entity, c: std::move(command), p: passData);
943 }
944 }
945 }
946
947 return commands;
948}
949
950EntityRenderCommandData RenderView::buildComputeRenderCommands(const Entity **entities,
951 int offset, int count) const
952{
953 // If the RenderView contains only a ComputeDispatch then it cares about
954 // A ComputeDispatch is also implicitely a NoDraw operation
955 // enabled flag
956 // layer component
957 // material/effect/technique/parameters/filters/
958 EntityRenderCommandData commands;
959
960 commands.reserve(size: count);
961
962 for (int i = 0; i < count; ++i) {
963 const int idx = offset + i;
964 const Entity *entity = entities[idx];
965 ComputeCommand *computeJob = nullptr;
966 HComputeCommand computeCommandHandle = entity->componentHandle<ComputeCommand>();
967 if ((computeJob = nodeManagers()->computeJobManager()->data(handle: computeCommandHandle))
968 != nullptr
969 && computeJob->isEnabled()) {
970
971 const Qt3DCore::QNodeId materialComponentId = entity->componentUuid<Material>();
972 const std::vector<RenderPassParameterData> &renderPassData =
973 m_parameters.value(key: materialComponentId);
974
975 // 1 RenderCommand per RenderPass pass on an Entity with a Mesh
976 for (const RenderPassParameterData &passData : renderPassData) {
977 // Add the RenderPass Parameters
978 RenderCommand command = {};
979 RenderPass *pass = passData.pass;
980
981 if (pass->hasRenderStates()) {
982 command.m_stateSet = RenderStateSetPtr::create();
983 addStatesToRenderStateSet(stateSet: command.m_stateSet.data(), stateIds: pass->renderStates(),
984 manager: m_manager->renderStateManager());
985
986 // Merge per pass stateset with global stateset
987 // so that the local stateset only overrides
988 if (m_stateSet != nullptr)
989 command.m_stateSet->merge(other: m_stateSet.get());
990 command.m_changeCost =
991 m_renderer->defaultRenderState()->changeCost(previousState: command.m_stateSet.data());
992 }
993 command.m_shaderId = pass->shaderProgram();
994
995 // At submission time, shaderId is used to retrieve a RHIShader
996 // No point in continuing if shaderId is null
997 if (!command.m_shaderId)
998 continue;
999
1000 // We try to resolve the m_rhiShader here. If the shader exist,
1001 // it won't be null and will allow us to full process the
1002 // command over a single frame. Otherwise, the shader will be
1003 // loaded at the next submission time and the command will only
1004 // be fully valid on the next frame. Additionally, that way, if
1005 // a commands keeps being rebuilt, frame after frame, it will
1006 // still be visible on screen as long as the shader exists
1007 RHIShaderManager *rhiShaderManager = m_renderer->rhiResourceManagers()->rhiShaderManager();
1008 command.m_rhiShader = rhiShaderManager->lookupResource(shaderId: command.m_shaderId);
1009
1010 command.m_computeCommand = computeCommandHandle;
1011 command.m_type = RenderCommand::Compute;
1012 command.m_workGroups[0] = std::max(a: m_workGroups[0], b: computeJob->x());
1013 command.m_workGroups[1] = std::max(a: m_workGroups[1], b: computeJob->y());
1014 command.m_workGroups[2] = std::max(a: m_workGroups[2], b: computeJob->z());
1015
1016 commands.push_back(e: entity, c: std::move(command), p: passData);
1017 }
1018 }
1019 }
1020
1021 return commands;
1022}
1023
1024namespace
1025{
1026void copyNormalMatrix(float(&destination)[12], const float* src) noexcept {
1027 destination[0] = src[0 * 3 + 0];
1028 destination[1] = src[0 * 3 + 1];
1029 destination[2] = src[0 * 3 + 2];
1030
1031 destination[4] = src[1 * 3 + 0];
1032 destination[5] = src[1 * 3 + 1];
1033 destination[6] = src[1 * 3 + 2];
1034
1035 destination[8] = src[2 * 3 + 0];
1036 destination[9] = src[2 * 3 + 1];
1037 destination[10] = src[2 * 3 + 2];
1038}
1039}
1040
1041void RenderView::updateRenderCommand(const EntityRenderCommandDataSubView &subView)
1042{
1043 // Update RenderViewUBO (Qt3D standard uniforms)
1044 const bool yIsUp = m_renderer->submissionContext()->rhi()->isYUpInNDC();
1045
1046 const Matrix4x4 clipCorrectionMatrix = Matrix4x4(m_renderer->submissionContext()->rhi()->clipSpaceCorrMatrix());
1047 const Matrix4x4 unCorrectedProjectionMatrix = getProjectionMatrix(lens: m_renderCameraLens);
1048 const Matrix4x4 projectionMatrix = clipCorrectionMatrix * unCorrectedProjectionMatrix;
1049 const Matrix4x4 inverseViewMatrix = m_viewMatrix.inverted();
1050 const Matrix4x4 inversedProjectionMatrix = projectionMatrix.inverted();
1051 const Matrix4x4 viewProjectionMatrix = (projectionMatrix * m_viewMatrix);
1052 const Matrix4x4 inversedViewProjectionMatrix = viewProjectionMatrix.inverted();
1053 {
1054 memcpy(dest: &m_renderViewUBO.viewMatrix, src: &m_viewMatrix, n: sizeof(Matrix4x4));
1055 memcpy(dest: &m_renderViewUBO.projectionMatrix, src: &projectionMatrix, n: sizeof(Matrix4x4));
1056 memcpy(dest: &m_renderViewUBO.clipCorrectionMatrix, src: &clipCorrectionMatrix, n: sizeof(Matrix4x4));
1057 memcpy(dest: &m_renderViewUBO.uncorrectedProjectionMatrix, src: &unCorrectedProjectionMatrix, n: sizeof(Matrix4x4));
1058 memcpy(dest: &m_renderViewUBO.viewProjectionMatrix, src: &viewProjectionMatrix, n: sizeof(Matrix4x4));
1059 memcpy(dest: &m_renderViewUBO.inverseViewMatrix, src: &inverseViewMatrix, n: sizeof(Matrix4x4));
1060 memcpy(dest: &m_renderViewUBO.inverseProjectionMatrix, src: &inversedProjectionMatrix,
1061 n: sizeof(Matrix4x4));
1062 memcpy(dest: &m_renderViewUBO.inverseViewProjectionMatrix, src: &inversedViewProjectionMatrix,
1063 n: sizeof(Matrix4x4));
1064 {
1065 QMatrix4x4 viewportMatrix;
1066 // TO DO: Implement on Matrix4x4
1067 viewportMatrix.viewport(rect: resolveViewport(fractionalViewport: m_viewport, surfaceSize: m_surfaceSize));
1068 Matrix4x4 vpMatrix(viewportMatrix);
1069 Matrix4x4 invVpMatrix = vpMatrix.inverted();
1070 memcpy(dest: &m_renderViewUBO.viewportMatrix, src: &vpMatrix, n: sizeof(Matrix4x4));
1071 memcpy(dest: &m_renderViewUBO.inverseViewportMatrix, src: &invVpMatrix, n: sizeof(Matrix4x4));
1072 }
1073 memcpy(dest: &m_renderViewUBO.textureTransformMatrix, src: m_renderer->textureTransform(),
1074 n: sizeof(float) * 4);
1075
1076 memcpy(dest: &m_renderViewUBO.eyePosition, src: &m_eyePos, n: sizeof(float) * 3);
1077 const float ratio =
1078 float(m_surfaceSize.width()) / std::max(a: 1.f, b: float(m_surfaceSize.height()));
1079 memcpy(dest: &m_renderViewUBO.aspectRatio, src: &ratio, n: sizeof(float));
1080 memcpy(dest: &m_renderViewUBO.gamma, src: &m_gamma, n: sizeof(float));
1081 const float exposure =
1082 m_renderCameraLens ? m_renderCameraLens->exposure() : 0.0f;
1083 memcpy(dest: &m_renderViewUBO.exposure, src: &exposure, n: sizeof(float));
1084 const float timeValue = float(m_renderer->time() / 1000000000.0f);
1085 memcpy(dest: &m_renderViewUBO.time, src: &timeValue, n: sizeof(float));
1086
1087 const float yUpNDC = yIsUp ? 1.0f : 0.0f;
1088 const float yUpFBO = m_renderer->submissionContext()->rhi()->isYUpInFramebuffer() ? 1.0f : 0.0f;
1089 memcpy(dest: &m_renderViewUBO.yUpInNDC, src: &yUpNDC, n: sizeof(float));
1090 memcpy(dest: &m_renderViewUBO.yUpInFBO, src: &yUpFBO, n: sizeof(float));
1091 }
1092
1093 subView.forEach(func: [&] (const Entity *entity,
1094 const RenderPassParameterData &passData,
1095 RenderCommand &command) {
1096
1097 // Pick which lights to take in to account.
1098 // For now decide based on the distance by taking the MAX_LIGHTS closest lights.
1099 // Replace with more sophisticated mechanisms later.
1100 // Copy vector so that we can sort it concurrently and we only want to sort the one for the
1101 // current command
1102 std::vector<LightSource> lightSources;
1103 EnvironmentLight *environmentLight = nullptr;
1104
1105 if (command.m_type == RenderCommand::Draw) {
1106 // Project the camera-to-object-center vector onto the camera
1107 // view vector. This gives a depth value suitable as the key
1108 // for BackToFront sorting.
1109 command.m_depth = Vector3D::dotProduct(
1110 a: entity->worldBoundingVolume()->center() - m_eyePos, b: m_eyeViewDir);
1111
1112 auto geometryRenderer = m_manager->geometryRendererManager()->data(handle: command.m_geometryRenderer);
1113 if (geometryRenderer && !qFuzzyCompare(p1: geometryRenderer->sortIndex(), p2: -1.f))
1114 command.m_depth = geometryRenderer->sortIndex();
1115
1116 environmentLight = m_environmentLight;
1117 lightSources = m_lightSources;
1118
1119 if (lightSources.size() > 1) {
1120 const Vector3D entityCenter = entity->worldBoundingVolume()->center();
1121 std::sort(first: lightSources.begin(), last: lightSources.end(),
1122 comp: [&](const LightSource &a, const LightSource &b) {
1123 const float distA = entityCenter.distanceToPoint(
1124 point: a.entity->worldBoundingVolume()->center());
1125 const float distB = entityCenter.distanceToPoint(
1126 point: b.entity->worldBoundingVolume()->center());
1127 return distA < distB;
1128 });
1129 m_lightSources = {lightSources.begin(), lightSources.begin() + std::min(a: lightSources.size(), b: size_t(MAX_LIGHTS)) };
1130 }
1131 } else { // Compute
1132 // Note: if frameCount has reached 0 in the previous frame, isEnabled
1133 // would be false
1134 ComputeCommand *computeJob =
1135 m_manager->computeJobManager()->data(handle: command.m_computeCommand);
1136 if (computeJob->runType() == QComputeCommand::Manual)
1137 computeJob->updateFrameCount();
1138 }
1139
1140 ParameterInfoList globalParameters = passData.parameterInfo;
1141 // setShaderAndUniforms can initialize a localData
1142 // make sure this is cleared before we leave this function
1143
1144 setShaderAndUniforms(command: &command, parameters&: globalParameters, entity, activeLightSources: lightSources, environmentLight);
1145
1146 // Update CommandUBO (Qt3D standard uniforms)
1147 const Matrix4x4 worldTransform = *(entity->worldTransform());
1148 const Matrix4x4 inverseWorldTransform = worldTransform.inverted();
1149 const QMatrix3x3 modelNormalMatrix = convertToQMatrix4x4(v: worldTransform).normalMatrix();
1150 const Matrix4x4 modelViewMatrix = m_viewMatrix * worldTransform;
1151 const QMatrix3x3 modelViewNormalMatrix = convertToQMatrix4x4(v: modelViewMatrix).normalMatrix();
1152 const Matrix4x4 inverseModelViewMatrix = modelViewMatrix.inverted();
1153 const Matrix4x4 mvp = projectionMatrix * modelViewMatrix;
1154 const Matrix4x4 inverseModelViewProjection = mvp.inverted();
1155 {
1156 memcpy(dest: &command.m_commandUBO.modelMatrix, src: &worldTransform, n: sizeof(Matrix4x4));
1157 memcpy(dest: &command.m_commandUBO.inverseModelMatrix, src: &inverseWorldTransform,
1158 n: sizeof(Matrix4x4));
1159 memcpy(dest: &command.m_commandUBO.modelViewMatrix, src: &modelViewMatrix, n: sizeof(Matrix4x4));
1160 copyNormalMatrix(destination&: command.m_commandUBO.modelNormalMatrix, src: modelNormalMatrix.constData());
1161 memcpy(dest: &command.m_commandUBO.inverseModelViewMatrix, src: &inverseModelViewMatrix,
1162 n: sizeof(Matrix4x4));
1163 memcpy(dest: &command.m_commandUBO.mvp, src: &mvp, n: sizeof(Matrix4x4));
1164 memcpy(dest: &command.m_commandUBO.inverseModelViewProjectionMatrix,
1165 src: &inverseModelViewProjection, n: sizeof(Matrix4x4));
1166 copyNormalMatrix(destination&: command.m_commandUBO.modelViewNormalMatrix, src: modelViewNormalMatrix.constData());
1167
1168 const Armature *armature = entity->renderComponent<Armature>();
1169 if (armature) {
1170 const UniformValue &skinningPalette = armature->skinningPaletteUniform();
1171 memcpy(dest: &command.m_commandUBO.skinningPalette, src: skinningPalette.constData<float>(),
1172 n: qMin<size_t>(a: skinningPalette.byteSize(), b: 100 * 16 * sizeof(float)));
1173 }
1174 }
1175 });
1176}
1177
1178void RenderView::updateMatrices()
1179{
1180 if (m_renderCameraNode && m_renderCameraLens
1181 && m_renderCameraLens->isEnabled()) {
1182 auto transform = m_renderCameraNode->renderComponent<Transform>();
1183 if (m_renderCameraNode->isParentLessTransform() && transform && transform->hasViewMatrix()) {
1184 // optimization: if the entity is a QCamera and it doesn't have a parent with a transform component,
1185 // then we use the frontend version of the viewMatrix to avoid extra calculations that may introduce
1186 // rounding errors
1187 setViewMatrix(transform->viewMatrix());
1188 } else {
1189 const Matrix4x4 cameraWorld = *(m_renderCameraNode->worldTransform());
1190 setViewMatrix(m_renderCameraLens->viewMatrix(worldTransform: cameraWorld));
1191 }
1192
1193 setViewProjectionMatrix(m_renderCameraLens->projection() * viewMatrix());
1194 // To get the eyePosition of the camera, we need to use the inverse of the
1195 // camera's worldTransform matrix.
1196 const Matrix4x4 inverseWorldTransform = viewMatrix().inverted();
1197 const Vector3D eyePosition(inverseWorldTransform.column(index: 3));
1198 setEyePosition(eyePosition);
1199
1200 // Get the viewing direction of the camera. Use the normal matrix to
1201 // ensure non-uniform scale works too.
1202 const QMatrix3x3 normalMat = convertToQMatrix4x4(v: m_viewMatrix).normalMatrix();
1203 // dir = normalize(QVector3D(0, 0, -1) * normalMat)
1204 setEyeViewDirection(
1205 Vector3D(-normalMat(2, 0), -normalMat(2, 1), -normalMat(2, 2)).normalized());
1206 }
1207}
1208
1209void RenderView::setUniformValue(ShaderParameterPack &uniformPack, int nameId,
1210 const UniformValue &value) const
1211{
1212 // At this point a uniform value can only be a scalar type
1213 // or a Qt3DCore::QNodeId corresponding to a Texture or Image
1214 // ShaderData/Buffers would be handled as UBO/SSBO and would therefore
1215 // not be in the default uniform block
1216 if (value.valueType() == UniformValue::NodeId) {
1217 const Qt3DCore::QNodeId *nodeIds = value.constData<Qt3DCore::QNodeId>();
1218
1219 const int uniformArraySize = value.byteSize() / sizeof(Qt3DCore::QNodeId);
1220 UniformValue::ValueType resourceType = UniformValue::TextureValue;
1221
1222 for (int i = 0; i < uniformArraySize; ++i) {
1223 const Qt3DCore::QNodeId resourceId = nodeIds[i];
1224
1225 const Texture *tex = m_manager->textureManager()->lookupResource(id: resourceId);
1226 if (tex != nullptr) {
1227 uniformPack.setTexture(glslNameId: nameId, uniformArrayIndex: i, id: resourceId);
1228 } else {
1229 const ShaderImage *img =
1230 m_manager->shaderImageManager()->lookupResource(id: resourceId);
1231 if (img != nullptr) {
1232 resourceType = UniformValue::ShaderImageValue;
1233 uniformPack.setImage(glslNameId: nameId, uniformArrayIndex: i, id: resourceId);
1234 }
1235 }
1236 }
1237
1238 // This uniform will be overridden in SubmissionContext::setParameters
1239 // and -1 values will be replaced by valid Texture or Image units
1240 UniformValue uniformValue(uniformArraySize * sizeof(int), resourceType);
1241 std::fill(first: uniformValue.data<int>(), last: uniformValue.data<int>() + uniformArraySize, value: -1);
1242 uniformPack.setUniform(glslNameId: nameId, val: uniformValue);
1243 } else {
1244 uniformPack.setUniform(glslNameId: nameId, val: value);
1245 }
1246}
1247
1248void RenderView::setUniformBlockValue(ShaderParameterPack &uniformPack, const RHIShader *shader,
1249 const ShaderUniformBlock &block,
1250 const UniformValue &value) const
1251{
1252 Q_UNUSED(shader);
1253
1254 if (value.valueType() == UniformValue::NodeId) {
1255
1256 Buffer *buffer = nullptr;
1257 if ((buffer = m_manager->bufferManager()->lookupResource(
1258 id: *value.constData<Qt3DCore::QNodeId>()))
1259 != nullptr) {
1260 BlockToUBO uniformBlockUBO;
1261 uniformBlockUBO.m_blockIndex = block.m_index;
1262 uniformBlockUBO.m_bindingIndex = block.m_binding;
1263 uniformBlockUBO.m_bufferID = buffer->peerId();
1264 uniformBlockUBO.m_needsUpdate = false;
1265 uniformPack.setUniformBuffer(std::move(uniformBlockUBO));
1266 // Buffer update to GL buffer will be done at render time
1267 }
1268 }
1269}
1270
1271void RenderView::setShaderStorageValue(ShaderParameterPack &uniformPack, const RHIShader *shader,
1272 const ShaderStorageBlock &block,
1273 const UniformValue &value) const
1274{
1275 Q_UNUSED(shader);
1276 if (value.valueType() == UniformValue::NodeId) {
1277 Buffer *buffer = nullptr;
1278 if ((buffer = m_manager->bufferManager()->lookupResource(
1279 id: *value.constData<Qt3DCore::QNodeId>()))
1280 != nullptr) {
1281 BlockToSSBO shaderStorageBlock;
1282 shaderStorageBlock.m_blockIndex = block.m_index;
1283 shaderStorageBlock.m_bufferID = buffer->peerId();
1284 shaderStorageBlock.m_bindingIndex = block.m_binding;
1285 uniformPack.setShaderStorageBuffer(shaderStorageBlock);
1286 // Buffer update to RHI buffer will be done at render time
1287 }
1288 }
1289}
1290
1291void RenderView::setShaderDataValue(ShaderParameterPack &uniformPack,
1292 const ShaderUniformBlock &block,
1293 const Qt3DCore::QNodeId &shaderDataId) const
1294{
1295 if (block.m_binding >= 0) {
1296 ShaderDataForUBO shaderDataForUBO;
1297 shaderDataForUBO.m_shaderDataID = shaderDataId;
1298 shaderDataForUBO.m_bindingIndex = block.m_binding;
1299 uniformPack.setShaderDataForUBO(shaderDataForUBO);
1300 }
1301}
1302
1303void RenderView::setDefaultUniformBlockShaderDataValue(ShaderParameterPack &uniformPack,
1304 const RHIShader *shader,
1305 ShaderData *shaderData,
1306 const QString &structName) const
1307{
1308 UniformBlockValueBuilder builder(shader->uniformsNamesIds(),
1309 m_manager->shaderDataManager(),
1310 m_manager->textureManager(),
1311 m_viewMatrix);
1312
1313 // Build name-value map for the block
1314 builder.buildActiveUniformNameValueMapStructHelper(rShaderData: shaderData, blockName: structName);
1315 // Set uniform values for each entrie of the block name-value map
1316 QHash<int, QVariant>::const_iterator activeValuesIt =
1317 builder.activeUniformNamesToValue.constBegin();
1318 const QHash<int, QVariant>::const_iterator activeValuesEnd =
1319 builder.activeUniformNamesToValue.constEnd();
1320
1321 // TO DO: Make the ShaderData store UniformValue
1322 while (activeValuesIt != activeValuesEnd) {
1323 setUniformValue(uniformPack, nameId: activeValuesIt.key(),
1324 value: UniformValue::fromVariant(variant: activeValuesIt.value()));
1325 ++activeValuesIt;
1326 }
1327}
1328
1329void RenderView::applyParameter(const Parameter *param, RenderCommand *command,
1330 const RHIShader *shader) const noexcept
1331{
1332 const int nameId = param->nameId();
1333 const UniformValue &uniformValue = param->uniformValue();
1334 const RHIShader::ParameterKind parameterKind = shader->categorizeVariable(nameId);
1335
1336 switch (parameterKind) {
1337 case RHIShader::Uniform: {
1338 setUniformValue(uniformPack&: command->m_parameterPack, nameId, value: uniformValue);
1339 break;
1340 }
1341 case RHIShader::UBO: {
1342 setUniformBlockValue(uniformPack&: command->m_parameterPack, shader,
1343 block: shader->uniformBlockForBlockNameId(blockIndex: nameId), value: uniformValue);
1344 break;
1345 }
1346 case RHIShader::SSBO: {
1347 setShaderStorageValue(uniformPack&: command->m_parameterPack, shader,
1348 block: shader->storageBlockForBlockNameId(blockNameId: nameId), value: uniformValue);
1349 break;
1350 }
1351 case RHIShader::Struct: {
1352 // Structs will have to be converted to UBOs later on
1353 ShaderData *shaderData = nullptr;
1354
1355 if (uniformValue.valueType() == UniformValue::NodeId
1356 && (shaderData = m_manager->shaderDataManager()->lookupResource(
1357 id: *uniformValue.constData<Qt3DCore::QNodeId>()))
1358 != nullptr) {
1359 // We need to find the Block associated with the uniform name
1360 setShaderDataValue(uniformPack&: command->m_parameterPack,
1361 block: shader->uniformBlockForInstanceNameId(instanceNameId: nameId), shaderDataId: shaderData->peerId());
1362 }
1363 break;
1364 }
1365 }
1366}
1367
1368void RenderView::setShaderAndUniforms(RenderCommand *command, ParameterInfoList &parameters,
1369 const Entity *entity,
1370 const std::vector<LightSource> &activeLightSources,
1371 EnvironmentLight *environmentLight) const
1372{
1373 Q_UNUSED(entity);
1374
1375 // The VAO Handle is set directly in the renderer thread so as to avoid having to use a mutex
1376 // here Set shader, technique, and effect by basically doing :
1377 // ShaderProgramManager[MaterialManager[frontentEntity->id()]->Effect->Techniques[TechniqueFilter->name]->RenderPasses[RenderPassFilter->name]];
1378 // The Renderer knows that if one of those is null, a default material / technique / effect as
1379 // to be used
1380
1381 // Find all RenderPasses (in order) matching values set in the RenderPassFilter
1382 // Get list of parameters for the Material, Effect, and Technique
1383 // For each ParameterBinder in the RenderPass -> create a QUniformPack
1384 // Once that works, improve that to try and minimize QUniformPack updates
1385
1386 RHIShader *shader = command->m_rhiShader;
1387 if (shader == nullptr || !shader->isLoaded())
1388 return;
1389
1390 // If we have already build the uniforms previously, we should
1391 // only update values of uniforms that have changed
1392 // If parameters add been added/removed, the command would have been rebuild
1393 // and the parameter pack would be empty
1394 const bool updateUniformsOnly = !command->m_parameterPack.submissionUniformIndices().empty();
1395
1396 if (!updateUniformsOnly) {
1397 // Builds the QUniformPack, sets shader standard uniforms and store attributes name / glname
1398 // bindings If a parameter is defined and not found in the bindings it is assumed to be a
1399 // binding of Uniform type with the glsl name equals to the parameter name
1400
1401 // Set fragData Name and index
1402 // Later on we might want to relink the shader if attachments have changed
1403 // But for now we set them once and for all
1404 if (!m_renderTarget.isNull() && !shader->isLoaded()) {
1405 QHash<QString, int> fragOutputs;
1406 const auto atts = m_attachmentPack.attachments();
1407 for (const Attachment &att : atts) {
1408 if (att.m_point <= QRenderTargetOutput::Color15)
1409 fragOutputs.insert(key: att.m_name, value: att.m_point);
1410 }
1411 // Set frag outputs in the shaders if hash not empty
1412 if (!fragOutputs.isEmpty())
1413 shader->setFragOutputs(fragOutputs);
1414 }
1415 // Set default attributes
1416 command->m_activeAttributes = shader->attributeNamesIds();
1417
1418 // At this point we know whether the command is a valid draw command or not
1419 // We still need to process the uniforms as the command could be a compute command
1420 command->m_isValid = (!command->m_activeAttributes.empty()) || (command->m_type == RenderCommand::CommandType::Compute);
1421 }
1422
1423 if (shader->hasActiveVariables()) {
1424 // Unlike the GL engine, the standard uniforms are set a bit before this function,
1425 // in RenderView::updateRenderCommand
1426
1427 // Parameters remaining could be
1428 // -> uniform scalar / vector
1429 // -> uniform struct / arrays
1430 // -> uniform block / array (4.3)
1431 // -> ssbo block / array (4.3)
1432
1433 ParameterInfoList::const_iterator it = parameters.cbegin();
1434 const ParameterInfoList::const_iterator parametersEnd = parameters.cend();
1435
1436 while (it != parametersEnd) {
1437 const Parameter *param = m_manager->data<Parameter, ParameterManager>(handle: it->handle);
1438 applyParameter(param, command, shader);
1439 ++it;
1440 }
1441
1442 // Lights
1443 int lightIdx = 0;
1444 for (const LightSource &lightSource : activeLightSources) {
1445 if (lightIdx == MAX_LIGHTS)
1446 break;
1447 const Entity *lightEntity = lightSource.entity;
1448 const Matrix4x4 lightWorldTransform = *(lightEntity->worldTransform());
1449 const Vector3D worldPos = lightWorldTransform.map(point: Vector3D(0.0f, 0.0f, 0.0f));
1450 for (Light *light : lightSource.lights) {
1451 if (!light->isEnabled())
1452 continue;
1453
1454 ShaderData *shaderData =
1455 m_manager->shaderDataManager()->lookupResource(id: light->shaderData());
1456 if (!shaderData)
1457 continue;
1458
1459 if (lightIdx == MAX_LIGHTS)
1460 break;
1461
1462 // Note: implicit conversion of values to UniformValue
1463 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_POSITION_NAMES[lightIdx],
1464 value: worldPos);
1465 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_TYPE_NAMES[lightIdx],
1466 value: int(QAbstractLight::PointLight));
1467 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COLOR_NAMES[lightIdx],
1468 value: Vector3D(1.0f, 1.0f, 1.0f));
1469 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_INTENSITY_NAMES[lightIdx],
1470 value: 0.5f);
1471 // Unrolled
1472 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_POSITION_UNROLL_NAMES[lightIdx],
1473 value: worldPos);
1474 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_TYPE_UNROLL_NAMES[lightIdx],
1475 value: int(QAbstractLight::PointLight));
1476 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COLOR_UNROLL_NAMES[lightIdx],
1477 value: Vector3D(1.0f, 1.0f, 1.0f));
1478 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_INTENSITY_UNROLL_NAMES[lightIdx], value: 0.5f);
1479
1480 // There is no risk in doing that even if multithreaded
1481 // since we are sure that a shaderData is unique for a given light
1482 // and won't ever be referenced as a Component either
1483 const Matrix4x4 *worldTransform = lightEntity->worldTransform();
1484 if (worldTransform)
1485 shaderData->updateWorldTransform(worldMatrix: *worldTransform);
1486
1487 setDefaultUniformBlockShaderDataValue(uniformPack&: command->m_parameterPack, shader,
1488 shaderData, structName: LIGHT_STRUCT_NAMES[lightIdx]);
1489 ++lightIdx;
1490 }
1491 }
1492
1493 if (shader->hasUniform(nameId: LIGHT_COUNT_NAME_ID))
1494 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COUNT_NAME_ID,
1495 value: UniformValue(qMax(a: (environmentLight ? 0 : 1), b: lightIdx)));
1496
1497 // If no active light sources and no environment light, add a default light
1498 if (activeLightSources.empty() && !environmentLight) {
1499 // Note: implicit conversion of values to UniformValue
1500 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_POSITION_NAMES[0],
1501 value: Vector3D(10.0f, 10.0f, 0.0f));
1502 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_TYPE_NAMES[0],
1503 value: int(QAbstractLight::PointLight));
1504 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COLOR_NAMES[0],
1505 value: Vector3D(1.0f, 1.0f, 1.0f));
1506 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_INTENSITY_NAMES[0], value: 0.5f);
1507 // Unrolled
1508 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_POSITION_UNROLL_NAMES[0],
1509 value: Vector3D(10.0f, 10.0f, 0.0f));
1510 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_TYPE_UNROLL_NAMES[0],
1511 value: int(QAbstractLight::PointLight));
1512 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COLOR_UNROLL_NAMES[0],
1513 value: Vector3D(1.0f, 1.0f, 1.0f));
1514 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_INTENSITY_UNROLL_NAMES[0], value: 0.5f);
1515 }
1516
1517 // Environment Light
1518 int envLightCount = 0;
1519 if (environmentLight && environmentLight->isEnabled()) {
1520 static const int irradianceStructId =
1521 StringToInt::lookupId(str: QLatin1String("envLight_irradiance"));
1522 static const int specularStructId =
1523 StringToInt::lookupId(str: QLatin1String("envLight_specular"));
1524 static const int irradianceId =
1525 StringToInt::lookupId(str: QLatin1String("envLightIrradiance"));
1526 static const int specularId =
1527 StringToInt::lookupId(str: QLatin1String("envLightSpecular"));
1528 ShaderData *shaderData = m_manager->shaderDataManager()->lookupResource(
1529 id: environmentLight->shaderData());
1530 if (shaderData) {
1531 envLightCount = 1;
1532
1533 // ("specularSize", "irradiance", "irradianceSize", "specular")
1534 auto irr =
1535 shaderData->properties()["irradiance"].value.value<Qt3DCore::QNodeId>();
1536 auto spec =
1537 shaderData->properties()["specular"].value.value<Qt3DCore::QNodeId>();
1538
1539 setUniformValue(uniformPack&: command->m_parameterPack, nameId: irradianceId, value: irr);
1540 setUniformValue(uniformPack&: command->m_parameterPack, nameId: irradianceStructId, value: irr);
1541 setUniformValue(uniformPack&: command->m_parameterPack, nameId: specularId, value: spec);
1542 setUniformValue(uniformPack&: command->m_parameterPack, nameId: specularStructId, value: spec);
1543 }
1544 }
1545 setUniformValue(uniformPack&: command->m_parameterPack,
1546 nameId: StringToInt::lookupId(QStringLiteral("envLightCount")), value: envLightCount);
1547 }
1548}
1549
1550bool RenderView::hasBlitFramebufferInfo() const
1551{
1552 return m_hasBlitFramebufferInfo;
1553}
1554
1555void RenderView::setHasBlitFramebufferInfo(bool hasBlitFramebufferInfo)
1556{
1557 m_hasBlitFramebufferInfo = hasBlitFramebufferInfo;
1558}
1559
1560BlitFramebufferInfo RenderView::blitFrameBufferInfo() const
1561{
1562 return m_blitFrameBufferInfo;
1563}
1564
1565void RenderView::setBlitFrameBufferInfo(const BlitFramebufferInfo &blitFrameBufferInfo)
1566{
1567 m_blitFrameBufferInfo = blitFrameBufferInfo;
1568}
1569
1570bool RenderView::isDownloadBuffersEnable() const
1571{
1572 return m_isDownloadBuffersEnable;
1573}
1574
1575void RenderView::setIsDownloadBuffersEnable(bool isDownloadBuffersEnable)
1576{
1577 m_isDownloadBuffersEnable = isDownloadBuffersEnable;
1578}
1579
1580} // namespace Rhi
1581} // namespace Render
1582} // namespace Qt3DRender
1583
1584QT_END_NAMESPACE
1585

source code of qt3d/src/plugins/renderers/rhi/renderer/renderview.cpp