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 });
1169}
1170
1171void RenderView::updateMatrices()
1172{
1173 if (m_renderCameraNode && m_renderCameraLens
1174 && m_renderCameraLens->isEnabled()) {
1175 const Matrix4x4 cameraWorld = *(m_renderCameraNode->worldTransform());
1176 setViewMatrix(m_renderCameraLens->viewMatrix(worldTransform: cameraWorld));
1177
1178 setViewProjectionMatrix(m_renderCameraLens->projection() * viewMatrix());
1179 // To get the eyePosition of the camera, we need to use the inverse of the
1180 // camera's worldTransform matrix.
1181 const Matrix4x4 inverseWorldTransform = viewMatrix().inverted();
1182 const Vector3D eyePosition(inverseWorldTransform.column(index: 3));
1183 setEyePosition(eyePosition);
1184
1185 // Get the viewing direction of the camera. Use the normal matrix to
1186 // ensure non-uniform scale works too.
1187 const QMatrix3x3 normalMat = convertToQMatrix4x4(v: m_viewMatrix).normalMatrix();
1188 // dir = normalize(QVector3D(0, 0, -1) * normalMat)
1189 setEyeViewDirection(
1190 Vector3D(-normalMat(2, 0), -normalMat(2, 1), -normalMat(2, 2)).normalized());
1191 }
1192}
1193
1194void RenderView::setUniformValue(ShaderParameterPack &uniformPack, int nameId,
1195 const UniformValue &value) const
1196{
1197 // At this point a uniform value can only be a scalar type
1198 // or a Qt3DCore::QNodeId corresponding to a Texture or Image
1199 // ShaderData/Buffers would be handled as UBO/SSBO and would therefore
1200 // not be in the default uniform block
1201 if (value.valueType() == UniformValue::NodeId) {
1202 const Qt3DCore::QNodeId *nodeIds = value.constData<Qt3DCore::QNodeId>();
1203
1204 const int uniformArraySize = value.byteSize() / sizeof(Qt3DCore::QNodeId);
1205 UniformValue::ValueType resourceType = UniformValue::TextureValue;
1206
1207 for (int i = 0; i < uniformArraySize; ++i) {
1208 const Qt3DCore::QNodeId resourceId = nodeIds[i];
1209
1210 const Texture *tex = m_manager->textureManager()->lookupResource(id: resourceId);
1211 if (tex != nullptr) {
1212 uniformPack.setTexture(glslNameId: nameId, uniformArrayIndex: i, id: resourceId);
1213 } else {
1214 const ShaderImage *img =
1215 m_manager->shaderImageManager()->lookupResource(id: resourceId);
1216 if (img != nullptr) {
1217 resourceType = UniformValue::ShaderImageValue;
1218 uniformPack.setImage(glslNameId: nameId, uniformArrayIndex: i, id: resourceId);
1219 }
1220 }
1221 }
1222
1223 // This uniform will be overridden in SubmissionContext::setParameters
1224 // and -1 values will be replaced by valid Texture or Image units
1225 UniformValue uniformValue(uniformArraySize * sizeof(int), resourceType);
1226 std::fill(first: uniformValue.data<int>(), last: uniformValue.data<int>() + uniformArraySize, value: -1);
1227 uniformPack.setUniform(glslNameId: nameId, val: uniformValue);
1228 } else {
1229 uniformPack.setUniform(glslNameId: nameId, val: value);
1230 }
1231}
1232
1233void RenderView::setUniformBlockValue(ShaderParameterPack &uniformPack, const RHIShader *shader,
1234 const ShaderUniformBlock &block,
1235 const UniformValue &value) const
1236{
1237 Q_UNUSED(shader);
1238
1239 if (value.valueType() == UniformValue::NodeId) {
1240
1241 Buffer *buffer = nullptr;
1242 if ((buffer = m_manager->bufferManager()->lookupResource(
1243 id: *value.constData<Qt3DCore::QNodeId>()))
1244 != nullptr) {
1245 BlockToUBO uniformBlockUBO;
1246 uniformBlockUBO.m_blockIndex = block.m_index;
1247 uniformBlockUBO.m_bindingIndex = block.m_binding;
1248 uniformBlockUBO.m_bufferID = buffer->peerId();
1249 uniformBlockUBO.m_needsUpdate = false;
1250 uniformPack.setUniformBuffer(std::move(uniformBlockUBO));
1251 // Buffer update to GL buffer will be done at render time
1252 }
1253 }
1254}
1255
1256void RenderView::setShaderStorageValue(ShaderParameterPack &uniformPack, const RHIShader *shader,
1257 const ShaderStorageBlock &block,
1258 const UniformValue &value) const
1259{
1260 Q_UNUSED(shader);
1261 if (value.valueType() == UniformValue::NodeId) {
1262 Buffer *buffer = nullptr;
1263 if ((buffer = m_manager->bufferManager()->lookupResource(
1264 id: *value.constData<Qt3DCore::QNodeId>()))
1265 != nullptr) {
1266 BlockToSSBO shaderStorageBlock;
1267 shaderStorageBlock.m_blockIndex = block.m_index;
1268 shaderStorageBlock.m_bufferID = buffer->peerId();
1269 shaderStorageBlock.m_bindingIndex = block.m_binding;
1270 uniformPack.setShaderStorageBuffer(shaderStorageBlock);
1271 // Buffer update to RHI buffer will be done at render time
1272 }
1273 }
1274}
1275
1276void RenderView::setShaderDataValue(ShaderParameterPack &uniformPack,
1277 const ShaderUniformBlock &block,
1278 const Qt3DCore::QNodeId &shaderDataId) const
1279{
1280 if (block.m_binding >= 0) {
1281 ShaderDataForUBO shaderDataForUBO;
1282 shaderDataForUBO.m_shaderDataID = shaderDataId;
1283 shaderDataForUBO.m_bindingIndex = block.m_binding;
1284 uniformPack.setShaderDataForUBO(shaderDataForUBO);
1285 }
1286}
1287
1288void RenderView::setDefaultUniformBlockShaderDataValue(ShaderParameterPack &uniformPack,
1289 const RHIShader *shader,
1290 ShaderData *shaderData,
1291 const QString &structName) const
1292{
1293 UniformBlockValueBuilder builder(shader->uniformsNamesIds(),
1294 m_manager->shaderDataManager(),
1295 m_manager->textureManager(),
1296 m_viewMatrix);
1297
1298 // Build name-value map for the block
1299 builder.buildActiveUniformNameValueMapStructHelper(rShaderData: shaderData, blockName: structName);
1300 // Set uniform values for each entrie of the block name-value map
1301 QHash<int, QVariant>::const_iterator activeValuesIt =
1302 builder.activeUniformNamesToValue.constBegin();
1303 const QHash<int, QVariant>::const_iterator activeValuesEnd =
1304 builder.activeUniformNamesToValue.constEnd();
1305
1306 // TO DO: Make the ShaderData store UniformValue
1307 while (activeValuesIt != activeValuesEnd) {
1308 setUniformValue(uniformPack, nameId: activeValuesIt.key(),
1309 value: UniformValue::fromVariant(variant: activeValuesIt.value()));
1310 ++activeValuesIt;
1311 }
1312}
1313
1314void RenderView::applyParameter(const Parameter *param, RenderCommand *command,
1315 const RHIShader *shader) const noexcept
1316{
1317 const int nameId = param->nameId();
1318 const UniformValue &uniformValue = param->uniformValue();
1319 const RHIShader::ParameterKind parameterKind = shader->categorizeVariable(nameId);
1320
1321 switch (parameterKind) {
1322 case RHIShader::Uniform: {
1323 setUniformValue(uniformPack&: command->m_parameterPack, nameId, value: uniformValue);
1324 break;
1325 }
1326 case RHIShader::UBO: {
1327 setUniformBlockValue(uniformPack&: command->m_parameterPack, shader,
1328 block: shader->uniformBlockForBlockNameId(blockIndex: nameId), value: uniformValue);
1329 break;
1330 }
1331 case RHIShader::SSBO: {
1332 setShaderStorageValue(uniformPack&: command->m_parameterPack, shader,
1333 block: shader->storageBlockForBlockNameId(blockNameId: nameId), value: uniformValue);
1334 break;
1335 }
1336 case RHIShader::Struct: {
1337 // Structs will have to be converted to UBOs later on
1338 ShaderData *shaderData = nullptr;
1339
1340 if (uniformValue.valueType() == UniformValue::NodeId
1341 && (shaderData = m_manager->shaderDataManager()->lookupResource(
1342 id: *uniformValue.constData<Qt3DCore::QNodeId>()))
1343 != nullptr) {
1344 // We need to find the Block associated with the uniform name
1345 setShaderDataValue(uniformPack&: command->m_parameterPack,
1346 block: shader->uniformBlockForInstanceNameId(instanceNameId: nameId), shaderDataId: shaderData->peerId());
1347 }
1348 break;
1349 }
1350 }
1351}
1352
1353void RenderView::setShaderAndUniforms(RenderCommand *command, ParameterInfoList &parameters,
1354 const Entity *entity,
1355 const std::vector<LightSource> &activeLightSources,
1356 EnvironmentLight *environmentLight) const
1357{
1358 Q_UNUSED(entity);
1359
1360 // The VAO Handle is set directly in the renderer thread so as to avoid having to use a mutex
1361 // here Set shader, technique, and effect by basically doing :
1362 // ShaderProgramManager[MaterialManager[frontentEntity->id()]->Effect->Techniques[TechniqueFilter->name]->RenderPasses[RenderPassFilter->name]];
1363 // The Renderer knows that if one of those is null, a default material / technique / effect as
1364 // to be used
1365
1366 // Find all RenderPasses (in order) matching values set in the RenderPassFilter
1367 // Get list of parameters for the Material, Effect, and Technique
1368 // For each ParameterBinder in the RenderPass -> create a QUniformPack
1369 // Once that works, improve that to try and minimize QUniformPack updates
1370
1371 RHIShader *shader = command->m_rhiShader;
1372 if (shader == nullptr || !shader->isLoaded())
1373 return;
1374
1375 // If we have already build the uniforms previously, we should
1376 // only update values of uniforms that have changed
1377 // If parameters add been added/removed, the command would have been rebuild
1378 // and the parameter pack would be empty
1379 const bool updateUniformsOnly = command->m_parameterPack.submissionUniformIndices().size() > 0;
1380
1381 if (!updateUniformsOnly) {
1382 // Builds the QUniformPack, sets shader standard uniforms and store attributes name / glname
1383 // bindings If a parameter is defined and not found in the bindings it is assumed to be a
1384 // binding of Uniform type with the glsl name equals to the parameter name
1385
1386 // Set fragData Name and index
1387 // Later on we might want to relink the shader if attachments have changed
1388 // But for now we set them once and for all
1389 if (!m_renderTarget.isNull() && !shader->isLoaded()) {
1390 QHash<QString, int> fragOutputs;
1391 const auto atts = m_attachmentPack.attachments();
1392 for (const Attachment &att : atts) {
1393 if (att.m_point <= QRenderTargetOutput::Color15)
1394 fragOutputs.insert(key: att.m_name, value: att.m_point);
1395 }
1396 // Set frag outputs in the shaders if hash not empty
1397 if (!fragOutputs.isEmpty())
1398 shader->setFragOutputs(fragOutputs);
1399 }
1400 // Set default attributes
1401 command->m_activeAttributes = shader->attributeNamesIds();
1402
1403 // At this point we know whether the command is a valid draw command or not
1404 // We still need to process the uniforms as the command could be a compute command
1405 command->m_isValid = (!command->m_activeAttributes.empty()) || (command->m_type == RenderCommand::CommandType::Compute);
1406 }
1407
1408 if (shader->hasActiveVariables()) {
1409 // Unlike the GL engine, the standard uniforms are set a bit before this function,
1410 // in RenderView::updateRenderCommand
1411
1412 // Parameters remaining could be
1413 // -> uniform scalar / vector
1414 // -> uniform struct / arrays
1415 // -> uniform block / array (4.3)
1416 // -> ssbo block / array (4.3)
1417
1418 ParameterInfoList::const_iterator it = parameters.cbegin();
1419 const ParameterInfoList::const_iterator parametersEnd = parameters.cend();
1420
1421 while (it != parametersEnd) {
1422 const Parameter *param = m_manager->data<Parameter, ParameterManager>(handle: it->handle);
1423 applyParameter(param, command, shader);
1424 ++it;
1425 }
1426
1427 // Lights
1428 int lightIdx = 0;
1429 for (const LightSource &lightSource : activeLightSources) {
1430 if (lightIdx == MAX_LIGHTS)
1431 break;
1432 const Entity *lightEntity = lightSource.entity;
1433 const Matrix4x4 lightWorldTransform = *(lightEntity->worldTransform());
1434 const Vector3D worldPos = lightWorldTransform.map(point: Vector3D(0.0f, 0.0f, 0.0f));
1435 for (Light *light : lightSource.lights) {
1436 if (!light->isEnabled())
1437 continue;
1438
1439 ShaderData *shaderData =
1440 m_manager->shaderDataManager()->lookupResource(id: light->shaderData());
1441 if (!shaderData)
1442 continue;
1443
1444 if (lightIdx == MAX_LIGHTS)
1445 break;
1446
1447 // Note: implicit conversion of values to UniformValue
1448 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_POSITION_NAMES[lightIdx],
1449 value: worldPos);
1450 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_TYPE_NAMES[lightIdx],
1451 value: int(QAbstractLight::PointLight));
1452 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COLOR_NAMES[lightIdx],
1453 value: Vector3D(1.0f, 1.0f, 1.0f));
1454 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_INTENSITY_NAMES[lightIdx],
1455 value: 0.5f);
1456 // Unrolled
1457 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_POSITION_UNROLL_NAMES[lightIdx],
1458 value: worldPos);
1459 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_TYPE_UNROLL_NAMES[lightIdx],
1460 value: int(QAbstractLight::PointLight));
1461 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COLOR_UNROLL_NAMES[lightIdx],
1462 value: Vector3D(1.0f, 1.0f, 1.0f));
1463 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_INTENSITY_UNROLL_NAMES[lightIdx], value: 0.5f);
1464
1465 // There is no risk in doing that even if multithreaded
1466 // since we are sure that a shaderData is unique for a given light
1467 // and won't ever be referenced as a Component either
1468 const Matrix4x4 *worldTransform = lightEntity->worldTransform();
1469 if (worldTransform)
1470 shaderData->updateWorldTransform(worldMatrix: *worldTransform);
1471
1472 setDefaultUniformBlockShaderDataValue(uniformPack&: command->m_parameterPack, shader,
1473 shaderData, structName: LIGHT_STRUCT_NAMES[lightIdx]);
1474 ++lightIdx;
1475 }
1476 }
1477
1478 if (shader->hasUniform(nameId: LIGHT_COUNT_NAME_ID))
1479 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COUNT_NAME_ID,
1480 value: UniformValue(qMax(a: (environmentLight ? 0 : 1), b: lightIdx)));
1481
1482 // If no active light sources and no environment light, add a default light
1483 if (activeLightSources.empty() && !environmentLight) {
1484 // Note: implicit conversion of values to UniformValue
1485 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_POSITION_NAMES[0],
1486 value: Vector3D(10.0f, 10.0f, 0.0f));
1487 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_TYPE_NAMES[0],
1488 value: int(QAbstractLight::PointLight));
1489 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COLOR_NAMES[0],
1490 value: Vector3D(1.0f, 1.0f, 1.0f));
1491 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_INTENSITY_NAMES[0], value: 0.5f);
1492 // Unrolled
1493 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_POSITION_UNROLL_NAMES[0],
1494 value: Vector3D(10.0f, 10.0f, 0.0f));
1495 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_TYPE_UNROLL_NAMES[0],
1496 value: int(QAbstractLight::PointLight));
1497 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COLOR_UNROLL_NAMES[0],
1498 value: Vector3D(1.0f, 1.0f, 1.0f));
1499 setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_INTENSITY_UNROLL_NAMES[0], value: 0.5f);
1500 }
1501
1502 // Environment Light
1503 int envLightCount = 0;
1504 if (environmentLight && environmentLight->isEnabled()) {
1505 static const int irradianceStructId =
1506 StringToInt::lookupId(str: QLatin1String("envLight_irradiance"));
1507 static const int specularStructId =
1508 StringToInt::lookupId(str: QLatin1String("envLight_specular"));
1509 static const int irradianceId =
1510 StringToInt::lookupId(str: QLatin1String("envLightIrradiance"));
1511 static const int specularId =
1512 StringToInt::lookupId(str: QLatin1String("envLightSpecular"));
1513 ShaderData *shaderData = m_manager->shaderDataManager()->lookupResource(
1514 id: environmentLight->shaderData());
1515 if (shaderData) {
1516 envLightCount = 1;
1517
1518 // ("specularSize", "irradiance", "irradianceSize", "specular")
1519 auto irr =
1520 shaderData->properties()["irradiance"].value.value<Qt3DCore::QNodeId>();
1521 auto spec =
1522 shaderData->properties()["specular"].value.value<Qt3DCore::QNodeId>();
1523
1524 setUniformValue(uniformPack&: command->m_parameterPack, nameId: irradianceId, value: irr);
1525 setUniformValue(uniformPack&: command->m_parameterPack, nameId: irradianceStructId, value: irr);
1526 setUniformValue(uniformPack&: command->m_parameterPack, nameId: specularId, value: spec);
1527 setUniformValue(uniformPack&: command->m_parameterPack, nameId: specularStructId, value: spec);
1528 }
1529 }
1530 setUniformValue(uniformPack&: command->m_parameterPack,
1531 nameId: StringToInt::lookupId(QStringLiteral("envLightCount")), value: envLightCount);
1532 }
1533}
1534
1535bool RenderView::hasBlitFramebufferInfo() const
1536{
1537 return m_hasBlitFramebufferInfo;
1538}
1539
1540void RenderView::setHasBlitFramebufferInfo(bool hasBlitFramebufferInfo)
1541{
1542 m_hasBlitFramebufferInfo = hasBlitFramebufferInfo;
1543}
1544
1545BlitFramebufferInfo RenderView::blitFrameBufferInfo() const
1546{
1547 return m_blitFrameBufferInfo;
1548}
1549
1550void RenderView::setBlitFrameBufferInfo(const BlitFramebufferInfo &blitFrameBufferInfo)
1551{
1552 m_blitFrameBufferInfo = blitFrameBufferInfo;
1553}
1554
1555bool RenderView::isDownloadBuffersEnable() const
1556{
1557 return m_isDownloadBuffersEnable;
1558}
1559
1560void RenderView::setIsDownloadBuffersEnable(bool isDownloadBuffersEnable)
1561{
1562 m_isDownloadBuffersEnable = isDownloadBuffersEnable;
1563}
1564
1565} // namespace Rhi
1566} // namespace Render
1567} // namespace Qt3DRender
1568
1569QT_END_NAMESPACE
1570

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