Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qsgbatchrenderer.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
3// Copyright (C) 2016 Robin Burchell <robin.burchell@viroteck.net>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5
7
8#include <qmath.h>
9
10#include <QtCore/QElapsedTimer>
11#include <QtCore/QtNumeric>
12
13#include <QtGui/QGuiApplication>
14
15#include <private/qnumeric_p.h>
16#include "qsgmaterialshader_p.h"
17
18#include "qsgrhivisualizer_p.h"
19
20#include <algorithm>
21
23
24#ifndef QT_NO_DEBUG
25Q_QUICK_EXPORT bool qsg_test_and_clear_material_failure();
26#endif
27
28int qt_sg_envInt(const char *name, int defaultValue);
29
31{
32
33#define DECLARE_DEBUG_VAR(variable) \
34 static bool debug_ ## variable() \
35 { static bool value = qgetenv("QSG_RENDERER_DEBUG").contains(QT_STRINGIFY(variable)); return value; }
42DECLARE_DEBUG_VAR(noalpha)
43DECLARE_DEBUG_VAR(noopaque)
45#undef DECLARE_DEBUG_VAR
46
47#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling())
48#define SHADOWNODE_TRAVERSE(NODE) for (Node *child = NODE->firstChild(); child; child = child->sibling())
49
50static inline int size_of_type(int type)
51{
52 static int sizes[] = {
53 sizeof(char),
54 sizeof(unsigned char),
55 sizeof(short),
56 sizeof(unsigned short),
57 sizeof(int),
58 sizeof(unsigned int),
59 sizeof(float),
60 2,
61 3,
62 4,
63 sizeof(double)
64 };
67}
68
69bool qsg_sort_element_increasing_order(Element *a, Element *b) { return a->order < b->order; }
70bool qsg_sort_element_decreasing_order(Element *a, Element *b) { return a->order > b->order; }
71bool qsg_sort_batch_is_valid(Batch *a, Batch *b) { return a->first && !b->first; }
72bool qsg_sort_batch_increasing_order(Batch *a, Batch *b) { return a->first->order < b->first->order; }
73bool qsg_sort_batch_decreasing_order(Batch *a, Batch *b) { return a->first->order > b->first->order; }
74
76
77static bool isTranslate(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Translation; }
78static bool isScale(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Scale; }
79static bool is2DSafe(const QMatrix4x4 &m) { return m.flags() < QMatrix4x4::Rotation; }
80
81const float OPAQUE_LIMIT = 0.999f;
82
86
87const float VIEWPORT_MIN_DEPTH = 0.0f;
88const float VIEWPORT_MAX_DEPTH = 1.0f;
89
90template <class Int>
91inline Int aligned(Int v, Int byteAlign)
92{
93 return (v + byteAlign - 1) & ~(byteAlign - 1);
94}
95
97{
98 switch (a.type) {
100 if (a.tupleSize == 4)
102 if (a.tupleSize == 3)
104 if (a.tupleSize == 2)
106 if (a.tupleSize == 1)
108 break;
110 if (a.tupleSize == 4)
112 if (a.tupleSize == 2)
114 if (a.tupleSize == 1)
116 break;
117 default:
118 break;
119 }
120 qWarning("Unsupported attribute type 0x%x with %d components", a.type, a.tupleSize);
121 Q_UNREACHABLE_RETURN(QRhiVertexInputAttribute::Float);
122}
123
125{
126 Q_ASSERT(geometry);
128 if (!sd->vertexShader) {
129 qWarning("No vertex shader in QSGMaterialShader %p", s);
130 return QRhiVertexInputLayout();
131 }
132
133 const int attrCount = geometry->attributeCount();
134 QVarLengthArray<QRhiVertexInputAttribute, 8> inputAttributes;
135 inputAttributes.reserve(attrCount + 1);
136 quint32 offset = 0;
137 for (int i = 0; i < attrCount; ++i) {
138 const QSGGeometry::Attribute &a = geometry->attributes()[i];
139 if (!sd->vertexShader->vertexInputLocations.contains(a.position)) {
140 qWarning("Vertex input %d is present in material but not in shader. This is wrong.",
141 a.position);
142 }
144 offset += a.tupleSize * size_of_type(a.type);
145 }
146 if (batchable) {
149 }
150
151 Q_ASSERT(VERTEX_BUFFER_BINDING == 0 && ZORDER_BUFFER_BINDING == 1); // not very flexible
152 QVarLengthArray<QRhiVertexInputBinding, 2> inputBindings;
153 inputBindings.append(QRhiVertexInputBinding(geometry->sizeOfVertex()));
154 if (batchable)
155 inputBindings.append(QRhiVertexInputBinding(sizeof(float)));
156
157 QRhiVertexInputLayout inputLayout;
158 inputLayout.setBindings(inputBindings.cbegin(), inputBindings.cend());
159 inputLayout.setAttributes(inputAttributes.cbegin(), inputAttributes.cend());
160
161 return inputLayout;
162}
163
165{
166 switch (geometry->indexType()) {
169 break;
172 break;
173 default:
174 Q_UNREACHABLE_RETURN(QRhiCommandBuffer::IndexUInt16);
175 }
176}
177
179{
181 switch (geomDrawMode) {
184 break;
187 break;
190 break;
193 break;
196 break;
197 default:
198 qWarning("Primitive topology 0x%x not supported", geomDrawMode);
199 break;
200 }
201 return topology;
202}
203
204void qsg_setMultiViewFlagsOnMaterial(QSGMaterial *material, int multiViewCount)
205{
206 material->setFlag(QSGMaterial::MultiView2, multiViewCount == 2);
207 material->setFlag(QSGMaterial::MultiView3, multiViewCount == 3);
208 material->setFlag(QSGMaterial::MultiView4, multiViewCount == 4);
209}
210
212 const QSGGeometry *geometry,
214 int multiViewCount)
215{
216 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
217
218 QSGMaterialType *type = material->type();
219 ShaderKey key = { type, renderMode, multiViewCount };
220 Shader *shader = rewrittenShaders.value(key, nullptr);
221 if (shader)
222 return shader;
223
224 shader = new Shader;
225 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
227 shader->materialShader = s;
228 shader->inputLayout = calculateVertexInputLayout(s, geometry, true);
230 shader->stages = {
233 };
234
235 shader->lastOpacity = 0;
236
237 rewrittenShaders[key] = shader;
238 return shader;
239}
240
242 const QSGGeometry *geometry,
244 int multiViewCount)
245{
246 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
247
248 QSGMaterialType *type = material->type();
249 ShaderKey key = { type, renderMode, multiViewCount };
250 Shader *shader = stockShaders.value(key, nullptr);
251 if (shader)
252 return shader;
253
254 shader = new Shader;
255 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
257 shader->materialShader = s;
258 shader->inputLayout = calculateVertexInputLayout(s, geometry, false);
260 shader->stages = {
263 };
264
265 shader->lastOpacity = 0;
266
267 stockShaders[key] = shader;
268
269 return shader;
270}
271
273{
274 qDeleteAll(stockShaders);
275 stockShaders.clear();
276 qDeleteAll(rewrittenShaders);
277 rewrittenShaders.clear();
278
280 pipelineCache.clear();
281
283 srbPool.clear();
284}
285
287{
288 for (ShaderManager::Shader *sms : std::as_const(stockShaders)) {
289 QSGMaterialShader *s = sms->materialShader;
290 if (s) {
293 }
294 }
295 for (ShaderManager::Shader *sms : std::as_const(rewrittenShaders)) {
296 QSGMaterialShader *s = sms->materialShader;
297 if (s) {
300 }
301 }
302}
303
305{
306 static int extraIndent = 0;
307 ++extraIndent;
308
309 QByteArray ind(indent + extraIndent + 10, ' ');
310
311 if (!i) {
312 qDebug("%s - no info", ind.constData());
313 } else {
314 qDebug() << ind.constData() << "- parent:" << i->parentRoot << "orders" << i->firstOrder << "->" << i->lastOrder << ", avail:" << i->availableOrders;
315 for (QSet<Node *>::const_iterator it = i->subRoots.constBegin();
316 it != i->subRoots.constEnd(); ++it) {
317 qDebug() << ind.constData() << "-" << *it;
318 qsg_dumpShadowRoots((*it)->rootInfo(), indent);
319 }
320 }
321
322 --extraIndent;
323}
324
326{
327#ifndef QT_NO_DEBUG_OUTPUT
328 static int indent = 0;
329 ++indent;
330
331 QByteArray ind(indent, ' ');
332
333 if (n->type() == QSGNode::ClipNodeType || n->isBatchRoot) {
334 qDebug() << ind.constData() << "[X]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
335 qsg_dumpShadowRoots(n->rootInfo(), indent);
336 } else {
337 QDebug d = qDebug();
338 d << ind.constData() << "[ ]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
339 if (n->type() == QSGNode::GeometryNodeType)
340 d << "order" << Qt::dec << n->element()->order;
341 }
342
345
346 --indent;
347#else
348 Q_UNUSED(n);
349#endif
350}
351
353 : renderer(r)
354 , m_roots(32)
355 , m_rootMatrices(8)
356{
357 m_roots.add(0);
358 m_combined_matrix_stack.add(&m_identityMatrix);
359 m_rootMatrices.add(m_identityMatrix);
360}
361
363{
364 m_current_clip = nullptr;
365
366 m_added = 0;
367 m_transformChange = 0;
368 m_opacityChange = 0;
369
370 Node *sn = renderer->m_nodes.value(n, 0);
371 Q_ASSERT(sn);
372
373 if (Q_UNLIKELY(debug_roots()))
375
376 if (Q_UNLIKELY(debug_build())) {
377 qDebug("Updater::updateStates()");
378 if (sn->dirtyState & (QSGNode::DirtyNodeAdded << 16))
379 qDebug(" - nodes have been added");
380 if (sn->dirtyState & (QSGNode::DirtyMatrix << 16))
381 qDebug(" - transforms have changed");
382 if (sn->dirtyState & (QSGNode::DirtyOpacity << 16))
383 qDebug(" - opacity has changed");
385 qDebug(" - forceupdate");
386 }
387
388 if (Q_UNLIKELY(renderer->m_visualizer->mode() == Visualizer::VisualizeChanges))
389 renderer->m_visualizer->visualizeChangesPrepare(sn);
390
391 visitNode(sn);
392}
393
395{
396 if (m_added == 0 && n->dirtyState == 0 && m_force_update == 0 && m_transformChange == 0 && m_opacityChange == 0)
397 return;
398
399 int count = m_added;
400 if (n->dirtyState & QSGNode::DirtyNodeAdded)
401 ++m_added;
402
403 int force = m_force_update;
404 if (n->dirtyState & QSGNode::DirtyForceUpdate)
406
407 switch (n->type()) {
410 break;
413 break;
416 break;
419 break;
421 if (m_added)
422 n->renderNodeElement()->root = m_roots.last();
423 Q_FALLTHROUGH(); // to visit children
424 default:
426 break;
427 }
428
429 m_added = count;
430 m_force_update = force;
431 n->dirtyState = {};
432}
433
435{
436 ClipBatchRootInfo *extra = n->clipInfo();
437
438 QSGClipNode *cn = static_cast<QSGClipNode *>(n->sgNode);
439
440 if (m_roots.last() && m_added > 0)
441 renderer->registerBatchRoot(n, m_roots.last());
442
443 cn->setRendererClipList(m_current_clip);
444 m_current_clip = cn;
445 m_roots << n;
446 m_rootMatrices.add(m_rootMatrices.last() * *m_combined_matrix_stack.last());
447 extra->matrix = m_rootMatrices.last();
448 cn->setRendererMatrix(&extra->matrix);
449 m_combined_matrix_stack << &m_identityMatrix;
450
452
453 m_current_clip = cn->clipList();
454 m_rootMatrices.pop_back();
456 m_roots.pop_back();
457}
458
460{
461 QSGOpacityNode *on = static_cast<QSGOpacityNode *>(n->sgNode);
462
463 qreal combined = m_opacity_stack.last() * on->opacity();
464 on->setCombinedOpacity(combined);
465 m_opacity_stack.add(combined);
466
467 if (m_added == 0 && n->dirtyState & QSGNode::DirtyOpacity) {
468 bool was = n->isOpaque;
469 bool is = on->opacity() > OPAQUE_LIMIT;
470 if (was != is) {
471 renderer->m_rebuild = Renderer::FullRebuild;
472 n->isOpaque = is;
473 }
474 ++m_opacityChange;
476 --m_opacityChange;
477 } else {
478 if (m_added > 0)
479 n->isOpaque = on->opacity() > OPAQUE_LIMIT;
481 }
482
484}
485
487{
488 bool popMatrixStack = false;
489 bool popRootStack = false;
490 bool dirty = n->dirtyState & QSGNode::DirtyMatrix;
491
492 QSGTransformNode *tn = static_cast<QSGTransformNode *>(n->sgNode);
493
494 if (n->isBatchRoot) {
495 if (m_added > 0 && m_roots.last())
496 renderer->registerBatchRoot(n, m_roots.last());
497 tn->setCombinedMatrix(m_rootMatrices.last() * *m_combined_matrix_stack.last() * tn->matrix());
498
499 // The only change in this subtree is ourselves and we are a batch root, so
500 // only update subroots and return, saving tons of child-processing (flickable-panning)
501
502 if (!n->becameBatchRoot && m_added == 0 && m_force_update == 0 && m_opacityChange == 0 && dirty && (n->dirtyState & ~QSGNode::DirtyMatrix) == 0) {
503 BatchRootInfo *info = renderer->batchRootInfo(n);
504 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
505 it != info->subRoots.constEnd(); ++it) {
506 updateRootTransforms(*it, n, tn->combinedMatrix());
507 }
508 return;
509 }
510
511 n->becameBatchRoot = false;
512
513 m_combined_matrix_stack.add(&m_identityMatrix);
514 m_roots.add(n);
515 m_rootMatrices.add(tn->combinedMatrix());
516
517 popMatrixStack = true;
518 popRootStack = true;
519 } else if (!tn->matrix().isIdentity()) {
520 tn->setCombinedMatrix(*m_combined_matrix_stack.last() * tn->matrix());
521 m_combined_matrix_stack.add(&tn->combinedMatrix());
522 popMatrixStack = true;
523 } else {
524 tn->setCombinedMatrix(*m_combined_matrix_stack.last());
525 }
526
527 if (dirty)
528 ++m_transformChange;
529
531
532 if (dirty)
533 --m_transformChange;
534 if (popMatrixStack)
536 if (popRootStack) {
537 m_roots.pop_back();
538 m_rootMatrices.pop_back();
539 }
540}
541
543{
544 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode);
545
547 gn->setRendererClipList(m_current_clip);
548 gn->setInheritedOpacity(m_opacity_stack.last());
549
550 if (m_added) {
551 Element *e = n->element();
552 e->root = m_roots.last();
553 e->translateOnlyToRoot = isTranslate(*gn->matrix());
554
555 if (e->root) {
556 BatchRootInfo *info = renderer->batchRootInfo(e->root);
557 while (info != nullptr) {
558 info->availableOrders--;
559 if (info->availableOrders < 0) {
560 renderer->m_rebuild |= Renderer::BuildRenderLists;
561 } else {
562 renderer->m_rebuild |= Renderer::BuildRenderListsForTaggedRoots;
563 renderer->m_taggedRoots << e->root;
564 }
565 if (info->parentRoot != nullptr)
566 info = renderer->batchRootInfo(info->parentRoot);
567 else
568 info = nullptr;
569 }
570 } else {
571 renderer->m_rebuild |= Renderer::FullRebuild;
572 }
573 } else {
574 if (m_transformChange) {
575 Element *e = n->element();
576 e->translateOnlyToRoot = isTranslate(*gn->matrix());
577 }
578 if (m_opacityChange) {
579 Element *e = n->element();
580 if (e->batch)
581 renderer->invalidateBatchAndOverlappingRenderOrders(e->batch);
582 }
583 }
584
586}
587
588void Updater::updateRootTransforms(Node *node, Node *root, const QMatrix4x4 &combined)
589{
590 BatchRootInfo *info = renderer->batchRootInfo(node);
592 Node *n = node;
593
594 while (n != root) {
595 if (n->type() == QSGNode::TransformNodeType)
596 m = static_cast<QSGTransformNode *>(n->sgNode)->matrix() * m;
597 n = n->parent();
598 }
599
600 m = combined * m;
601
602 if (node->type() == QSGNode::ClipNodeType) {
603 static_cast<ClipBatchRootInfo *>(info)->matrix = m;
604 } else {
606 static_cast<QSGTransformNode *>(node->sgNode)->setCombinedMatrix(m);
607 }
608
609 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
610 it != info->subRoots.constEnd(); ++it) {
611 updateRootTransforms(*it, node, m);
612 }
613}
614
616{
617 int vaOffset = 0;
618 for (int a=0; a<g->attributeCount(); ++a) {
619 const QSGGeometry::Attribute &attr = g->attributes()[a];
620 if (attr.isVertexCoordinate && attr.tupleSize == 2 && attr.type == QSGGeometry::FloatType) {
621 return vaOffset;
622 }
623 vaOffset += attr.tupleSize * size_of_type(attr.type);
624 }
625 return -1;
626}
627
628
630{
631 const float *m = matrix.constData();
632 if (isScale(matrix)) {
633 tl.x = tl.x * m[0] + m[12];
634 tl.y = tl.y * m[5] + m[13];
635 br.x = br.x * m[0] + m[12];
636 br.y = br.y * m[5] + m[13];
637 if (tl.x > br.x)
638 qSwap(tl.x, br.x);
639 if (tl.y > br.y)
640 qSwap(tl.y, br.y);
641 } else {
642 Pt mtl = tl;
643 Pt mtr = { br.x, tl.y };
644 Pt mbl = { tl.x, br.y };
645 Pt mbr = br;
646
647 mtl.map(matrix);
648 mtr.map(matrix);
649 mbl.map(matrix);
650 mbr.map(matrix);
651
652 set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
653 (*this) |= mtl;
654 (*this) |= mtr;
655 (*this) |= mbl;
656 (*this) |= mbr;
657 }
658}
659
660void Element::computeBounds()
661{
663 boundsComputed = true;
664
667 if (offset == -1) {
668 // No position attribute means overlaps with everything..
669 bounds.set(-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX);
670 return;
671 }
672
673 bounds.set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
674 char *vd = (char *) g->vertexData() + offset;
675 for (int i=0; i<g->vertexCount(); ++i) {
676 bounds |= *(Pt *) vd;
677 vd += g->sizeOfVertex();
678 }
679 bounds.map(*node->matrix());
680
681 if (!qt_is_finite(bounds.tl.x) || bounds.tl.x == FLT_MAX)
682 bounds.tl.x = -FLT_MAX;
683 if (!qt_is_finite(bounds.tl.y) || bounds.tl.y == FLT_MAX)
684 bounds.tl.y = -FLT_MAX;
685 if (!qt_is_finite(bounds.br.x) || bounds.br.x == -FLT_MAX)
686 bounds.br.x = FLT_MAX;
687 if (!qt_is_finite(bounds.br.y) || bounds.br.y == -FLT_MAX)
688 bounds.br.y = FLT_MAX;
689
692
694}
695
697{
698 Element *n = first;
699 // Skip to the first node other than e which has not been removed
700 while (n && (n == e || n->removed))
701 n = n->nextInBatch;
702
703 // Only 'e' in this batch, so a material change doesn't change anything as long as
704 // its blending is still in sync with this batch...
705 if (!n)
706 return BatchIsCompatible;
707
709 QSGMaterial *nm = n->node->activeMaterial();
710 return (nm->type() == m->type() && nm->viewCount() == m->viewCount() && nm->compare(m) == 0)
713}
714
715/*
716 * Marks this batch as dirty or in the case where the geometry node has
717 * changed to be incompatible with this batch, return false so that
718 * the caller can mark the entire sg for a full rebuild...
719 */
721{
722 Element *e = first;
723 Q_ASSERT_X(e, "Batch::geometryWasChanged", "Batch is expected to 'valid' at this time");
724 // 'gn' is the first node in the batch, compare against the next one.
725 while (e && (e->node == gn || e->removed))
726 e = e->nextInBatch;
727 if (!e || e->node->geometry()->attributes() == gn->geometry()->attributes()) {
728 needsUpload = true;
729 return true;
730 } else {
731 return false;
732 }
733}
734
736{
737 if (!needsPurge)
738 return;
739
740 // remove from front of batch..
741 while (first && first->removed) {
742 first = first->nextInBatch;
743 }
744
745 // Then continue and remove other nodes further out in the batch..
746 if (first) {
747 Element *e = first;
748 while (e->nextInBatch) {
749 if (e->nextInBatch->removed)
751 else
752 e = e->nextInBatch;
753
754 }
755 }
756
757 needsPurge = false;
758}
759
760/*
761 * Iterates through all geometry nodes in this batch and unsets their batch,
762 * thus forcing them to be rebuilt
763 */
765{
767 Element *e = first;
768 first = nullptr;
769 root = nullptr;
770 while (e) {
771 e->batch = nullptr;
772 Element *n = e->nextInBatch;
773 e->nextInBatch = nullptr;
774 e = n;
775 }
776}
777
779 bool only = true;
780 Element *e = first;
781 while (e && only) {
782 only &= e->translateOnlyToRoot;
783 e = e->nextInBatch;
784 }
785 return only;
786}
787
788/*
789 * Iterates through all the nodes in the batch and returns true if the
790 * nodes are all safe to batch. There are two separate criteria:
791 *
792 * - The matrix is such that the z component of the result is of no
793 * consequence.
794 *
795 * - The bounds are inside the stable floating point range. This applies
796 * to desktop only where we in this case can trigger a fallback to
797 * unmerged in which case we pass the geometry straight through and
798 * just apply the matrix.
799 *
800 * NOTE: This also means a slight performance impact for geometries which
801 * are defined to be outside the stable floating point range and still
802 * use single precision float, but given that this implicitly fixes
803 * huge lists and tables, it is worth it.
804 */
806 Element *e = first;
807 while (e) {
809 return false;
810 if (!is2DSafe(*e->node->matrix()))
811 return false;
812 e = e->nextInBatch;
813 }
814 return true;
815}
816
817static int qsg_countNodesInBatch(const Batch *batch)
818{
819 int sum = 0;
820 Element *e = batch->first;
821 while (e) {
822 ++sum;
823 e = e->nextInBatch;
824 }
825 return sum;
826}
827
828static int qsg_countNodesInBatches(const QDataBuffer<Batch *> &batches)
829{
830 int sum = 0;
831 for (int i=0; i<batches.size(); ++i) {
832 sum += qsg_countNodesInBatch(batches.at(i));
833 }
834 return sum;
835}
836
839 , m_context(ctx)
840 , m_renderMode(renderMode)
841 , m_opaqueRenderList(64)
842 , m_alphaRenderList(64)
843 , m_nextRenderOrder(0)
844 , m_partialRebuild(false)
845 , m_partialRebuildRoot(nullptr)
846 , m_forceNoDepthBuffer(false)
847 , m_opaqueBatches(16)
848 , m_alphaBatches(16)
849 , m_batchPool(16)
850 , m_elementsToDelete(64)
851 , m_tmpAlphaElements(16)
852 , m_tmpOpaqueElements(16)
853 , m_vboPool(16)
854 , m_iboPool(16)
855 , m_rebuild(FullRebuild)
856 , m_zRange(0)
857#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
858 , m_renderOrderRebuildLower(-1)
859 , m_renderOrderRebuildUpper(-1)
860#endif
861 , m_currentMaterial(nullptr)
862 , m_currentShader(nullptr)
863 , m_vertexUploadPool(256)
864 , m_indexUploadPool(64)
865{
866 m_rhi = m_context->rhi();
867 Q_ASSERT(m_rhi); // no more direct OpenGL code path in Qt 6
868
869 m_ubufAlignment = m_rhi->ubufAlignment();
870
872 if (qEnvironmentVariableIntValue("QSG_RHI_UINT32_INDEX"))
873 m_uint32IndexForRhi = true;
874
875 m_visualizer = new RhiVisualizer(this);
876
877 setNodeUpdater(new Updater(this));
878
879 // The shader manager is shared between renderers (think for example Item
880 // layers that create a new Renderer each) with the same rendercontext (and
881 // so same QRhi).
883 if (!m_shaderManager) {
884 m_shaderManager = new ShaderManager(ctx);
885 m_shaderManager->setObjectName(QStringLiteral("__qt_ShaderManager"));
886 m_shaderManager->setParent(ctx);
887 QObject::connect(ctx, SIGNAL(invalidated()), m_shaderManager, SLOT(invalidated()), Qt::DirectConnection);
888 }
889
890 m_batchNodeThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_NODE_THRESHOLD", 64);
891 m_batchVertexThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_VERTEX_THRESHOLD", 1024);
892 m_srbPoolThreshold = qt_sg_envInt("QSG_RENDERER_SRB_POOL_THRESHOLD", 1024);
893
894 if (Q_UNLIKELY(debug_build() || debug_render())) {
895 qDebug("Batch thresholds: nodes: %d vertices: %d Srb pool threshold: %d",
896 m_batchNodeThreshold, m_batchVertexThreshold, m_srbPoolThreshold);
897 }
898}
899
901{
902 delete buffer->buf;
903
904 // The free here is ok because we're in one of two situations.
905 // 1. We're using the upload pool in which case unmap will have set the
906 // data pointer to 0 and calling free on 0 is ok.
907 // 2. We're using dedicated buffers because of visualization or IBO workaround
908 // and the data something we malloced and must be freed.
909 free(buffer->data);
910}
911
912static void qsg_wipeBatch(Batch *batch)
913{
914 qsg_wipeBuffer(&batch->vbo);
915 qsg_wipeBuffer(&batch->ibo);
916 delete batch->ubuf;
917 batch->stencilClipState.reset();
918 delete batch;
919}
920
922{
923 if (m_rhi) {
924 // Clean up batches and buffers
925 for (int i = 0; i < m_opaqueBatches.size(); ++i)
926 qsg_wipeBatch(m_opaqueBatches.at(i));
927 for (int i = 0; i < m_alphaBatches.size(); ++i)
928 qsg_wipeBatch(m_alphaBatches.at(i));
929 for (int i = 0; i < m_batchPool.size(); ++i)
930 qsg_wipeBatch(m_batchPool.at(i));
931 for (int i = 0; i < m_vboPool.size(); ++i)
932 delete m_vboPool.at(i);
933 for (int i = 0; i < m_iboPool.size(); ++i)
934 delete m_iboPool.at(i);
935 }
936
937 for (Node *n : std::as_const(m_nodes)) {
938 if (n->type() == QSGNode::GeometryNodeType) {
939 Element *e = n->element();
940 if (!e->removed)
941 m_elementsToDelete.add(e);
942 }
943 m_nodeAllocator.release(n);
944 }
945
946 // Remaining elements...
947 for (int i=0; i<m_elementsToDelete.size(); ++i)
948 releaseElement(m_elementsToDelete.at(i), true);
949
950 destroyGraphicsResources();
951
952 delete m_visualizer;
953}
954
955void Renderer::destroyGraphicsResources()
956{
957 // If this is from the dtor, then the shader manager and its already
958 // prepared shaders will stay around for other renderers -> the cached data
959 // in the rhi shaders have to be purged as it may refer to samplers we
960 // are going to destroy.
961 m_shaderManager->clearCachedRendererData();
962
963 qDeleteAll(m_samplers);
964 m_stencilClipCommon.reset();
965 delete m_dummyTexture;
966 m_visualizer->releaseResources();
967}
968
970{
971 m_shaderManager->invalidated();
972
973 destroyGraphicsResources();
974
975 m_samplers.clear();
976 m_dummyTexture = nullptr;
977
979
980 m_vertexUploadPool.shrink(0);
981 m_vertexUploadPool.reset();
982 m_indexUploadPool.shrink(0);
983 m_indexUploadPool.reset();
984
985 for (int i = 0; i < m_vboPool.size(); ++i)
986 delete m_vboPool.at(i);
987 m_vboPool.reset();
988
989 for (int i = 0; i < m_iboPool.size(); ++i)
990 delete m_iboPool.at(i);
991 m_iboPool.reset();
992}
993
994void Renderer::invalidateAndRecycleBatch(Batch *b)
995{
996 if (b->vbo.buf != nullptr)
997 m_vboPool.add(b->vbo.buf);
998 if (b->ibo.buf != nullptr)
999 m_iboPool.add(b->ibo.buf);
1000 b->vbo.buf = nullptr;
1001 b->ibo.buf = nullptr;
1002 b->invalidate();
1003 for (int i=0; i<m_batchPool.size(); ++i)
1004 if (b == m_batchPool.at(i))
1005 return;
1006 m_batchPool.add(b);
1007}
1008
1009void Renderer::map(Buffer *buffer, quint32 byteSize, bool isIndexBuf)
1010{
1011 if (m_visualizer->mode() == Visualizer::VisualizeNothing) {
1012 // Common case, use a shared memory pool for uploading vertex data to avoid
1013 // excessive reevaluation
1014 QDataBuffer<char> &pool = isIndexBuf ? m_indexUploadPool : m_vertexUploadPool;
1015 if (byteSize > quint32(pool.size()))
1016 pool.resize(byteSize);
1017 buffer->data = pool.data();
1018 } else if (buffer->size != byteSize) {
1019 free(buffer->data);
1020 buffer->data = (char *) malloc(byteSize);
1021 Q_CHECK_PTR(buffer->data);
1022 }
1023 buffer->size = byteSize;
1024}
1025
1026void Renderer::unmap(Buffer *buffer, bool isIndexBuf)
1027{
1028 // Batches are pooled and reused which means the QRhiBuffer will be
1029 // still valid in a recycled Batch. We only hit the newBuffer() path
1030 // when there are no buffers to recycle.
1031 QDataBuffer<QRhiBuffer *> *bufferPool = isIndexBuf ? &m_iboPool : &m_vboPool;
1032 if (!buffer->buf && bufferPool->isEmpty()) {
1034 isIndexBuf ? QRhiBuffer::IndexBuffer : QRhiBuffer::VertexBuffer,
1035 buffer->size);
1036 if (!buffer->buf->create()) {
1037 qWarning("Failed to build vertex/index buffer of size %u", buffer->size);
1038 delete buffer->buf;
1039 buffer->buf = nullptr;
1040 }
1041 } else {
1042 if (!buffer->buf) {
1043 const quint32 expectedSize = buffer->size;
1044 qsizetype foundBufferIndex = 0;
1045 for (qsizetype i = 0; i < bufferPool->size(); ++i) {
1046 QRhiBuffer *testBuffer = bufferPool->at(i);
1047 if (!buffer->buf
1048 || (testBuffer->size() >= expectedSize && testBuffer->size() < buffer->buf->size())
1049 || (testBuffer->size() < expectedSize && testBuffer->size() > buffer->buf->size())) {
1050 foundBufferIndex = i;
1051 buffer->buf = testBuffer;
1052 if (buffer->buf->size() == expectedSize)
1053 break;
1054 }
1055 }
1056
1057 if (foundBufferIndex < bufferPool->size() - 1) {
1058 qSwap(bufferPool->data()[foundBufferIndex],
1059 bufferPool->data()[bufferPool->size() - 1]);
1060 }
1061 bufferPool->pop_back();
1062 }
1063
1064 bool needsRebuild = false;
1065 if (buffer->buf->size() < buffer->size) {
1066 buffer->buf->setSize(buffer->size);
1067 needsRebuild = true;
1068 }
1069 if (buffer->buf->type() != QRhiBuffer::Dynamic
1070 && buffer->nonDynamicChangeCount > DYNAMIC_VERTEX_INDEX_BUFFER_THRESHOLD)
1071 {
1072 buffer->buf->setType(QRhiBuffer::Dynamic);
1073 buffer->nonDynamicChangeCount = 0;
1074 needsRebuild = true;
1075 }
1076 if (needsRebuild) {
1077 if (!buffer->buf->create()) {
1078 qWarning("Failed to (re)build vertex/index buffer of size %u", buffer->size);
1079 delete buffer->buf;
1080 buffer->buf = nullptr;
1081 }
1082 }
1083 }
1084 if (buffer->buf) {
1085 if (buffer->buf->type() != QRhiBuffer::Dynamic) {
1086 m_resourceUpdates->uploadStaticBuffer(buffer->buf,
1087 0, buffer->size, buffer->data);
1088 buffer->nonDynamicChangeCount += 1;
1089 } else {
1090 m_resourceUpdates->updateDynamicBuffer(buffer->buf, 0, buffer->size,
1091 buffer->data);
1092 }
1093 }
1094 if (m_visualizer->mode() == Visualizer::VisualizeNothing)
1095 buffer->data = nullptr;
1096}
1097
1098BatchRootInfo *Renderer::batchRootInfo(Node *node)
1099{
1100 BatchRootInfo *info = node->rootInfo();
1101 if (!info) {
1102 if (node->type() == QSGNode::ClipNodeType)
1103 info = new ClipBatchRootInfo;
1104 else {
1105 Q_ASSERT(node->type() == QSGNode::TransformNodeType);
1106 info = new BatchRootInfo;
1107 }
1108 node->data = info;
1109 }
1110 return info;
1111}
1112
1113void Renderer::removeBatchRootFromParent(Node *childRoot)
1114{
1115 BatchRootInfo *childInfo = batchRootInfo(childRoot);
1116 if (!childInfo->parentRoot)
1117 return;
1118 BatchRootInfo *parentInfo = batchRootInfo(childInfo->parentRoot);
1119
1120 Q_ASSERT(parentInfo->subRoots.contains(childRoot));
1121 parentInfo->subRoots.remove(childRoot);
1122 childInfo->parentRoot = nullptr;
1123}
1124
1125void Renderer::registerBatchRoot(Node *subRoot, Node *parentRoot)
1126{
1127 BatchRootInfo *subInfo = batchRootInfo(subRoot);
1128 BatchRootInfo *parentInfo = batchRootInfo(parentRoot);
1129 subInfo->parentRoot = parentRoot;
1130 parentInfo->subRoots << subRoot;
1131}
1132
1133bool Renderer::changeBatchRoot(Node *node, Node *root)
1134{
1135 BatchRootInfo *subInfo = batchRootInfo(node);
1136 if (subInfo->parentRoot == root)
1137 return false;
1138 if (subInfo->parentRoot) {
1139 BatchRootInfo *oldRootInfo = batchRootInfo(subInfo->parentRoot);
1140 oldRootInfo->subRoots.remove(node);
1141 }
1142 BatchRootInfo *newRootInfo = batchRootInfo(root);
1143 newRootInfo->subRoots << node;
1144 subInfo->parentRoot = root;
1145 return true;
1146}
1147
1148void Renderer::nodeChangedBatchRoot(Node *node, Node *root)
1149{
1150 if (node->type() == QSGNode::ClipNodeType || node->isBatchRoot) {
1151 // When we reach a batchroot, we only need to update it. Its subtree
1152 // is relative to that root, so no need to recurse further.
1153 changeBatchRoot(node, root);
1154 return;
1155 } else if (node->type() == QSGNode::GeometryNodeType) {
1156 // Only need to change the root as nodeChanged anyway flags a full update.
1157 Element *e = node->element();
1158 if (e) {
1159 e->root = root;
1160 e->boundsComputed = false;
1161 }
1162 } else if (node->type() == QSGNode::RenderNodeType) {
1163 RenderNodeElement *e = node->renderNodeElement();
1164 if (e)
1165 e->root = root;
1166 }
1167
1169 nodeChangedBatchRoot(child, root);
1170}
1171
1172void Renderer::nodeWasTransformed(Node *node, int *vertexCount)
1173{
1174 if (node->type() == QSGNode::GeometryNodeType) {
1175 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node->sgNode);
1176 *vertexCount += gn->geometry()->vertexCount();
1177 Element *e = node->element();
1178 if (e) {
1179 e->boundsComputed = false;
1180 if (e->batch) {
1181 if (!e->batch->isOpaque) {
1182 invalidateBatchAndOverlappingRenderOrders(e->batch);
1183 } else if (e->batch->merged) {
1184 e->batch->needsUpload = true;
1185 }
1186 }
1187 }
1188 }
1189
1191 nodeWasTransformed(child, vertexCount);
1192}
1193
1194void Renderer::nodeWasAdded(QSGNode *node, Node *shadowParent)
1195{
1196 Q_ASSERT(!m_nodes.contains(node));
1197 if (node->isSubtreeBlocked())
1198 return;
1199
1200 Node *snode = m_nodeAllocator.allocate();
1201 snode->sgNode = node;
1202 m_nodes.insert(node, snode);
1203 if (shadowParent)
1204 shadowParent->append(snode);
1205
1206 if (node->type() == QSGNode::GeometryNodeType) {
1207 snode->data = m_elementAllocator.allocate();
1208 snode->element()->setNode(static_cast<QSGGeometryNode *>(node));
1209
1210 } else if (node->type() == QSGNode::ClipNodeType) {
1211 snode->data = new ClipBatchRootInfo;
1212 m_rebuild |= FullRebuild;
1213
1214 } else if (node->type() == QSGNode::RenderNodeType) {
1215 QSGRenderNode *rn = static_cast<QSGRenderNode *>(node);
1216 RenderNodeElement *e = new RenderNodeElement(rn);
1217 snode->data = e;
1218 Q_ASSERT(!m_renderNodeElements.contains(rn));
1219 m_renderNodeElements.insert(e->renderNode, e);
1220 if (!rn->flags().testFlag(QSGRenderNode::DepthAwareRendering))
1221 m_forceNoDepthBuffer = true;
1222 m_rebuild |= FullRebuild;
1223 }
1224
1225 QSGNODE_TRAVERSE(node)
1226 nodeWasAdded(child, snode);
1227}
1228
1229void Renderer::nodeWasRemoved(Node *node)
1230{
1231 // Prefix traversal as removeBatchRootFromParent below removes nodes
1232 // in a bottom-up manner. Note that we *cannot* use SHADOWNODE_TRAVERSE
1233 // here, because we delete 'child' (when recursed, down below), so we'd
1234 // have a use-after-free.
1235 {
1236 Node *child = node->firstChild();
1237 while (child) {
1238 // Remove (and delete) child
1239 node->remove(child);
1240 nodeWasRemoved(child);
1241 child = node->firstChild();
1242 }
1243 }
1244
1245 if (node->type() == QSGNode::GeometryNodeType) {
1246 Element *e = node->element();
1247 if (e) {
1248 e->removed = true;
1249 m_elementsToDelete.add(e);
1250 e->node = nullptr;
1251 if (e->root) {
1252 BatchRootInfo *info = batchRootInfo(e->root);
1253 info->availableOrders++;
1254 }
1255 if (e->batch) {
1256 e->batch->needsUpload = true;
1257 e->batch->needsPurge = true;
1258 }
1259
1260 }
1261
1262 } else if (node->type() == QSGNode::ClipNodeType) {
1263 removeBatchRootFromParent(node);
1264 delete node->clipInfo();
1265 m_rebuild |= FullRebuild;
1266 m_taggedRoots.remove(node);
1267
1268 } else if (node->isBatchRoot) {
1269 removeBatchRootFromParent(node);
1270 delete node->rootInfo();
1271 m_rebuild |= FullRebuild;
1272 m_taggedRoots.remove(node);
1273
1274 } else if (node->type() == QSGNode::RenderNodeType) {
1275 RenderNodeElement *e = m_renderNodeElements.take(static_cast<QSGRenderNode *>(node->sgNode));
1276 if (e) {
1277 e->removed = true;
1278 m_elementsToDelete.add(e);
1279 if (m_renderNodeElements.isEmpty()) {
1280 m_forceNoDepthBuffer = false;
1281 // Must have a full rebuild given useDepthBuffer() now returns
1282 // a different value than before, meaning there can once again
1283 // be an opaque pass.
1284 m_rebuild |= FullRebuild;
1285 }
1286
1287 if (e->batch != nullptr)
1288 e->batch->needsPurge = true;
1289 }
1290 }
1291
1292 Q_ASSERT(m_nodes.contains(node->sgNode));
1293
1294 m_nodeAllocator.release(m_nodes.take(node->sgNode));
1295}
1296
1297void Renderer::turnNodeIntoBatchRoot(Node *node)
1298{
1299 if (Q_UNLIKELY(debug_change())) qDebug(" - new batch root");
1300 m_rebuild |= FullRebuild;
1301 node->isBatchRoot = true;
1302 node->becameBatchRoot = true;
1303
1304 Node *p = node->parent();
1305 while (p) {
1306 if (p->type() == QSGNode::ClipNodeType || p->isBatchRoot) {
1307 registerBatchRoot(node, p);
1308 break;
1309 }
1310 p = p->parent();
1311 }
1312
1314 nodeChangedBatchRoot(child, node);
1315}
1316
1317
1318void Renderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state)
1319{
1320#ifndef QT_NO_DEBUG_OUTPUT
1321 if (Q_UNLIKELY(debug_change())) {
1322 QDebug debug = qDebug();
1323 debug << "dirty:";
1325 debug << "Geometry";
1327 debug << "Material";
1329 debug << "Matrix";
1331 debug << "Added";
1333 debug << "Removed";
1335 debug << "Opacity";
1337 debug << "SubtreeBlocked";
1339 debug << "ForceUpdate";
1340
1341 // when removed, some parts of the node could already have been destroyed
1342 // so don't debug it out.
1344 debug << (void *) node << node->type();
1345 else
1346 debug << node;
1347 }
1348#endif
1349 // As this function calls nodeChanged recursively, we do it at the top
1350 // to avoid that any of the others are processed twice.
1352 Node *sn = m_nodes.value(node);
1353
1354 // Force a batch rebuild if this includes an opacity change
1356 m_rebuild |= FullRebuild;
1357
1358 bool blocked = node->isSubtreeBlocked();
1359 if (blocked && sn) {
1361 Q_ASSERT(m_nodes.value(node) == 0);
1362 } else if (!blocked && !sn) {
1364 }
1365 return;
1366 }
1367
1369 if (nodeUpdater()->isNodeBlocked(node, rootNode())) {
1371 return;
1372 }
1373 if (node == rootNode())
1374 nodeWasAdded(node, nullptr);
1375 else
1376 nodeWasAdded(node, m_nodes.value(node->parent()));
1377 }
1378
1379 // Mark this node dirty in the shadow tree.
1380 Node *shadowNode = m_nodes.value(node);
1381
1382 // Blocked subtrees won't have shadow nodes, so we can safely abort
1383 // here..
1384 if (!shadowNode) {
1386 return;
1387 }
1388
1389 shadowNode->dirtyState |= state;
1390
1391 if (state & QSGNode::DirtyMatrix && !shadowNode->isBatchRoot) {
1393 if (node->m_subtreeRenderableCount > m_batchNodeThreshold) {
1394 turnNodeIntoBatchRoot(shadowNode);
1395 } else {
1396 int vertices = 0;
1397 nodeWasTransformed(shadowNode, &vertices);
1398 if (vertices > m_batchVertexThreshold) {
1399 turnNodeIntoBatchRoot(shadowNode);
1400 }
1401 }
1402 }
1403
1405 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1406 Element *e = shadowNode->element();
1407 if (e) {
1408 e->boundsComputed = false;
1409 Batch *b = e->batch;
1410 if (b) {
1411 if (!e->batch->geometryWasChanged(gn) || !e->batch->isOpaque) {
1412 invalidateBatchAndOverlappingRenderOrders(e->batch);
1413 } else {
1414 b->needsUpload = true;
1415 }
1416 }
1417 }
1418 }
1419
1421 Element *e = shadowNode->element();
1422 if (e) {
1423 bool blended = hasMaterialWithBlending(static_cast<QSGGeometryNode *>(node));
1424 if (e->isMaterialBlended != blended) {
1425 m_rebuild |= Renderer::FullRebuild;
1426 e->isMaterialBlended = blended;
1427 } else if (e->batch) {
1429 invalidateBatchAndOverlappingRenderOrders(e->batch);
1430 } else {
1431 m_rebuild |= Renderer::BuildBatches;
1432 }
1433 }
1434 }
1435
1436 // Mark the shadow tree dirty all the way back to the root...
1437 QSGNode::DirtyState dirtyChain = state & (QSGNode::DirtyNodeAdded
1442 if (dirtyChain != 0) {
1443 dirtyChain = QSGNode::DirtyState(dirtyChain << 16);
1444 Node *sn = shadowNode->parent();
1445 while (sn) {
1446 sn->dirtyState |= dirtyChain;
1447 sn = sn->parent();
1448 }
1449 }
1450
1451 // Delete happens at the very end because it deletes the shadownode.
1453 Node *parent = shadowNode->parent();
1454 if (parent)
1455 parent->remove(shadowNode);
1456 nodeWasRemoved(shadowNode);
1457 Q_ASSERT(m_nodes.value(node) == 0);
1458 }
1459
1461}
1462
1463/*
1464 * Traverses the tree and builds two list of geometry nodes. One for
1465 * the opaque and one for the translucent. These are populated
1466 * in the order they should visually appear in, meaning first
1467 * to the back and last to the front.
1468 *
1469 * We split opaque and translucent as we can perform different
1470 * types of reordering / batching strategies on them, depending
1471 *
1472 * Note: It would be tempting to use the shadow nodes instead of the QSGNodes
1473 * for traversal to avoid hash lookups, but the order of the children
1474 * is important and they are not preserved in the shadow tree, so we must
1475 * use the actual QSGNode tree.
1476 */
1477void Renderer::buildRenderLists(QSGNode *node)
1478{
1479 if (node->isSubtreeBlocked())
1480 return;
1481
1482 Node *shadowNode = m_nodes.value(node);
1483 Q_ASSERT(shadowNode);
1484
1485 if (node->type() == QSGNode::GeometryNodeType) {
1486 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1487
1488 Element *e = shadowNode->element();
1489 Q_ASSERT(e);
1490
1491 bool opaque = gn->inheritedOpacity() > OPAQUE_LIMIT && !(gn->activeMaterial()->flags() & QSGMaterial::Blending);
1492 if (opaque && useDepthBuffer())
1493 m_opaqueRenderList << e;
1494 else
1495 m_alphaRenderList << e;
1496
1497 e->order = ++m_nextRenderOrder;
1498 // Used while rebuilding partial roots.
1499 if (m_partialRebuild)
1500 e->orphaned = false;
1501
1502 } else if (node->type() == QSGNode::ClipNodeType || shadowNode->isBatchRoot) {
1503 Q_ASSERT(m_nodes.contains(node));
1504 BatchRootInfo *info = batchRootInfo(shadowNode);
1505 if (node == m_partialRebuildRoot) {
1506 m_nextRenderOrder = info->firstOrder;
1507 QSGNODE_TRAVERSE(node)
1508 buildRenderLists(child);
1509 m_nextRenderOrder = info->lastOrder + 1;
1510 } else {
1511 int currentOrder = m_nextRenderOrder;
1512 QSGNODE_TRAVERSE(node)
1513 buildRenderLists(child);
1514 int padding = (m_nextRenderOrder - currentOrder) >> 2;
1515 info->firstOrder = currentOrder;
1516 info->availableOrders = padding;
1517 info->lastOrder = m_nextRenderOrder + padding;
1518 m_nextRenderOrder = info->lastOrder;
1519 }
1520 return;
1521 } else if (node->type() == QSGNode::RenderNodeType) {
1522 RenderNodeElement *e = shadowNode->renderNodeElement();
1523 m_alphaRenderList << e;
1524 e->order = ++m_nextRenderOrder;
1525 Q_ASSERT(e);
1526 }
1527
1528 QSGNODE_TRAVERSE(node)
1529 buildRenderLists(child);
1530}
1531
1532void Renderer::tagSubRoots(Node *node)
1533{
1534 BatchRootInfo *i = batchRootInfo(node);
1535 m_taggedRoots << node;
1536 for (QSet<Node *>::const_iterator it = i->subRoots.constBegin();
1537 it != i->subRoots.constEnd(); ++it) {
1538 tagSubRoots(*it);
1539 }
1540}
1541
1542static void qsg_addOrphanedElements(QDataBuffer<Element *> &orphans, const QDataBuffer<Element *> &renderList)
1543{
1544 orphans.reset();
1545 for (int i=0; i<renderList.size(); ++i) {
1546 Element *e = renderList.at(i);
1547 if (e && !e->removed) {
1548 e->orphaned = true;
1549 orphans.add(e);
1550 }
1551 }
1552}
1553
1554static void qsg_addBackOrphanedElements(QDataBuffer<Element *> &orphans, QDataBuffer<Element *> &renderList)
1555{
1556 for (int i=0; i<orphans.size(); ++i) {
1557 Element *e = orphans.at(i);
1558 if (e->orphaned)
1559 renderList.add(e);
1560 }
1561 orphans.reset();
1562}
1563
1564/*
1565 * To rebuild the tagged roots, we start by putting all subroots of tagged
1566 * roots into the list of tagged roots. This is to make the rest of the
1567 * algorithm simpler.
1568 *
1569 * Second, we invalidate all batches which belong to tagged roots, which now
1570 * includes the entire subtree under a given root
1571 *
1572 * Then we call buildRenderLists for all tagged subroots which do not have
1573 * parents which are tagged, aka, we traverse only the topmosts roots.
1574 *
1575 * Then we sort the render lists based on their render order, to restore the
1576 * right order for rendering.
1577 */
1578void Renderer::buildRenderListsForTaggedRoots()
1579{
1580 // Flag any element that is currently in the render lists, but which
1581 // is not in a batch. This happens when we have a partial rebuild
1582 // in one sub tree while we have a BuildBatches change in another
1583 // isolated subtree. So that batch-building takes into account
1584 // these "orphaned" nodes, we flag them now. The ones under tagged
1585 // roots will be cleared again. The remaining ones are added into the
1586 // render lists so that they contain all visual nodes after the
1587 // function completes.
1588 qsg_addOrphanedElements(m_tmpOpaqueElements, m_opaqueRenderList);
1589 qsg_addOrphanedElements(m_tmpAlphaElements, m_alphaRenderList);
1590
1591 // Take a copy now, as we will be adding to this while traversing..
1592 QSet<Node *> roots = m_taggedRoots;
1593 for (QSet<Node *>::const_iterator it = roots.constBegin();
1594 it != roots.constEnd(); ++it) {
1595 tagSubRoots(*it);
1596 }
1597
1598 for (int i=0; i<m_opaqueBatches.size(); ++i) {
1599 Batch *b = m_opaqueBatches.at(i);
1600 if (m_taggedRoots.contains(b->root))
1601 invalidateAndRecycleBatch(b);
1602
1603 }
1604 for (int i=0; i<m_alphaBatches.size(); ++i) {
1605 Batch *b = m_alphaBatches.at(i);
1606 if (m_taggedRoots.contains(b->root))
1607 invalidateAndRecycleBatch(b);
1608 }
1609
1610 m_opaqueRenderList.reset();
1611 m_alphaRenderList.reset();
1612 int maxRenderOrder = m_nextRenderOrder;
1613 m_partialRebuild = true;
1614 // Traverse each root, assigning it
1615 for (QSet<Node *>::const_iterator it = m_taggedRoots.constBegin();
1616 it != m_taggedRoots.constEnd(); ++it) {
1617 Node *root = *it;
1618 BatchRootInfo *i = batchRootInfo(root);
1619 if ((!i->parentRoot || !m_taggedRoots.contains(i->parentRoot))
1620 && !nodeUpdater()->isNodeBlocked(root->sgNode, rootNode())) {
1621 m_nextRenderOrder = i->firstOrder;
1622 m_partialRebuildRoot = root->sgNode;
1623 buildRenderLists(root->sgNode);
1624 }
1625 }
1626 m_partialRebuild = false;
1627 m_partialRebuildRoot = nullptr;
1628 m_taggedRoots.clear();
1629 m_nextRenderOrder = qMax(m_nextRenderOrder, maxRenderOrder);
1630
1631 // Add orphaned elements back into the list and then sort it..
1632 qsg_addBackOrphanedElements(m_tmpOpaqueElements, m_opaqueRenderList);
1633 qsg_addBackOrphanedElements(m_tmpAlphaElements, m_alphaRenderList);
1634
1635 if (m_opaqueRenderList.size())
1636 std::sort(&m_opaqueRenderList.first(), &m_opaqueRenderList.last() + 1, qsg_sort_element_decreasing_order);
1637 if (m_alphaRenderList.size())
1638 std::sort(&m_alphaRenderList.first(), &m_alphaRenderList.last() + 1, qsg_sort_element_increasing_order);
1639
1640}
1641
1642void Renderer::buildRenderListsFromScratch()
1643{
1644 m_opaqueRenderList.reset();
1645 m_alphaRenderList.reset();
1646
1647 for (int i=0; i<m_opaqueBatches.size(); ++i)
1648 invalidateAndRecycleBatch(m_opaqueBatches.at(i));
1649 for (int i=0; i<m_alphaBatches.size(); ++i)
1650 invalidateAndRecycleBatch(m_alphaBatches.at(i));
1651 m_opaqueBatches.reset();
1652 m_alphaBatches.reset();
1653
1654 m_nextRenderOrder = 0;
1655
1656 buildRenderLists(rootNode());
1657}
1658
1659void Renderer::invalidateBatchAndOverlappingRenderOrders(Batch *batch)
1660{
1661 Q_ASSERT(batch);
1662 Q_ASSERT(batch->first);
1663
1664#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1665 if (m_renderOrderRebuildLower < 0 || batch->first->order < m_renderOrderRebuildLower)
1666 m_renderOrderRebuildLower = batch->first->order;
1667 if (m_renderOrderRebuildUpper < 0 || batch->lastOrderInBatch > m_renderOrderRebuildUpper)
1668 m_renderOrderRebuildUpper = batch->lastOrderInBatch;
1669
1670 int first = m_renderOrderRebuildLower;
1671 int last = m_renderOrderRebuildUpper;
1672#else
1673 int first = batch->first->order;
1674 int last = batch->lastOrderInBatch;
1675#endif
1676
1677 batch->invalidate();
1678
1679 for (int i=0; i<m_alphaBatches.size(); ++i) {
1680 Batch *b = m_alphaBatches.at(i);
1681 if (b->first) {
1682 int bf = b->first->order;
1683 int bl = b->lastOrderInBatch;
1684 if (bl > first && bf < last)
1685 b->invalidate();
1686 }
1687 }
1688
1689 m_rebuild |= BuildBatches;
1690}
1691
1692/* Clean up batches by making it a consecutive list of "valid"
1693 * batches and moving all invalidated batches to the batches pool.
1694 */
1695void Renderer::cleanupBatches(QDataBuffer<Batch *> *batches) {
1696 if (batches->size()) {
1697 std::stable_sort(&batches->first(), &batches->last() + 1, qsg_sort_batch_is_valid);
1698 int count = 0;
1699 while (count < batches->size() && batches->at(count)->first)
1700 ++count;
1701 for (int i=count; i<batches->size(); ++i)
1702 invalidateAndRecycleBatch(batches->at(i));
1703 batches->resize(count);
1704 }
1705}
1706
1707void Renderer::prepareOpaqueBatches()
1708{
1709 for (int i=m_opaqueRenderList.size() - 1; i >= 0; --i) {
1710 Element *ei = m_opaqueRenderList.at(i);
1711 if (!ei || ei->batch || ei->node->geometry()->vertexCount() == 0)
1712 continue;
1713 Batch *batch = newBatch();
1714 batch->first = ei;
1715 batch->root = ei->root;
1716 batch->isOpaque = true;
1717 batch->needsUpload = true;
1718 batch->positionAttribute = qsg_positionAttribute(ei->node->geometry());
1719
1720 m_opaqueBatches.add(batch);
1721
1722 ei->batch = batch;
1723 Element *next = ei;
1724
1725 QSGGeometryNode *gni = ei->node;
1726
1727 for (int j = i - 1; j >= 0; --j) {
1728 Element *ej = m_opaqueRenderList.at(j);
1729 if (!ej)
1730 continue;
1731 if (ej->root != ei->root)
1732 break;
1733 if (ej->batch || ej->node->geometry()->vertexCount() == 0)
1734 continue;
1735
1736 QSGGeometryNode *gnj = ej->node;
1737
1738 if (gni->clipList() == gnj->clipList()
1739 && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode()
1740 && (gni->geometry()->drawingMode() != QSGGeometry::DrawLines || gni->geometry()->lineWidth() == gnj->geometry()->lineWidth())
1741 && gni->geometry()->attributes() == gnj->geometry()->attributes()
1742 && gni->inheritedOpacity() == gnj->inheritedOpacity()
1743 && gni->activeMaterial()->type() == gnj->activeMaterial()->type()
1744 && gni->activeMaterial()->viewCount() == gnj->activeMaterial()->viewCount()
1745 && gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) {
1746 ej->batch = batch;
1747 next->nextInBatch = ej;
1748 next = ej;
1749 }
1750 }
1751
1752 batch->lastOrderInBatch = next->order;
1753 }
1754}
1755
1756bool Renderer::checkOverlap(int first, int last, const Rect &bounds)
1757{
1758 for (int i=first; i<=last; ++i) {
1759 Element *e = m_alphaRenderList.at(i);
1760#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1761 if (!e || e->batch)
1762#else
1763 if (!e)
1764#endif
1765 continue;
1766 Q_ASSERT(e->boundsComputed);
1767 if (e->bounds.intersects(bounds))
1768 return true;
1769 }
1770 return false;
1771}
1772
1773/*
1774 *
1775 * To avoid the O(n^2) checkOverlap check in most cases, we have the
1776 * overlapBounds which is the union of all bounding rects to check overlap
1777 * for. We know that if it does not overlap, then none of the individual
1778 * ones will either. For the typical list case, this results in no calls
1779 * to checkOverlap what-so-ever. This also ensures that when all consecutive
1780 * items are matching (such as a table of text), we don't build up an
1781 * overlap bounds and thus do not require full overlap checks.
1782 */
1783
1784void Renderer::prepareAlphaBatches()
1785{
1786 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1787 Element *e = m_alphaRenderList.at(i);
1788 if (!e || e->isRenderNode)
1789 continue;
1790 Q_ASSERT(!e->removed);
1791 e->ensureBoundsValid();
1792 }
1793
1794 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1795 Element *ei = m_alphaRenderList.at(i);
1796 if (!ei || ei->batch)
1797 continue;
1798
1799 if (ei->isRenderNode) {
1800 Batch *rnb = newBatch();
1801 rnb->first = ei;
1802 rnb->root = ei->root;
1803 rnb->isOpaque = false;
1804 rnb->isRenderNode = true;
1805 ei->batch = rnb;
1806 m_alphaBatches.add(rnb);
1807 continue;
1808 }
1809
1810 if (ei->node->geometry()->vertexCount() == 0)
1811 continue;
1812
1813 Batch *batch = newBatch();
1814 batch->first = ei;
1815 batch->root = ei->root;
1816 batch->isOpaque = false;
1817 batch->needsUpload = true;
1818 m_alphaBatches.add(batch);
1819 ei->batch = batch;
1820
1821 QSGGeometryNode *gni = ei->node;
1822 batch->positionAttribute = qsg_positionAttribute(gni->geometry());
1823
1824 Rect overlapBounds;
1825 overlapBounds.set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
1826
1827 Element *next = ei;
1828
1829 for (int j = i + 1; j < m_alphaRenderList.size(); ++j) {
1830 Element *ej = m_alphaRenderList.at(j);
1831 if (!ej)
1832 continue;
1833 if (ej->root != ei->root || ej->isRenderNode)
1834 break;
1835 if (ej->batch) {
1836#if !defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1837 overlapBounds |= ej->bounds;
1838#endif
1839 continue;
1840 }
1841
1842 QSGGeometryNode *gnj = ej->node;
1843 if (gnj->geometry()->vertexCount() == 0)
1844 continue;
1845
1846 if (gni->clipList() == gnj->clipList()
1847 && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode()
1848 && (gni->geometry()->drawingMode() != QSGGeometry::DrawLines
1849 || (gni->geometry()->lineWidth() == gnj->geometry()->lineWidth()
1850 // Must not do overlap checks when the line width is not 1,
1851 // we have no knowledge how such lines are rasterized.
1852 && gni->geometry()->lineWidth() == 1.0f))
1853 && gni->geometry()->attributes() == gnj->geometry()->attributes()
1854 && gni->inheritedOpacity() == gnj->inheritedOpacity()
1855 && gni->activeMaterial()->type() == gnj->activeMaterial()->type()
1856 && gni->activeMaterial()->viewCount() == gnj->activeMaterial()->viewCount()
1857 && gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) {
1858 if (!overlapBounds.intersects(ej->bounds) || !checkOverlap(i+1, j - 1, ej->bounds)) {
1859 ej->batch = batch;
1860 next->nextInBatch = ej;
1861 next = ej;
1862 } else {
1863 /* When we come across a compatible element which hits an overlap, we
1864 * need to stop the batch right away. We cannot add more elements
1865 * to the current batch as they will be rendered before the batch that the
1866 * current 'ej' will be added to.
1867 */
1868 break;
1869 }
1870 } else {
1871 overlapBounds |= ej->bounds;
1872 }
1873 }
1874
1875 batch->lastOrderInBatch = next->order;
1876 }
1877
1878
1879}
1880
1881static inline int qsg_fixIndexCount(int iCount, int drawMode)
1882{
1883 switch (drawMode) {
1885 // Merged triangle strips need to contain degenerate triangles at the beginning and end.
1886 // One could save 2 uploaded ushorts here by ditching the padding for the front of the
1887 // first and the end of the last, but for simplicity, we simply don't care.
1888 // Those extra triangles will be skipped while drawing to preserve the strip's parity
1889 // anyhow.
1890 return iCount + 2;
1892 // For lines we drop the last vertex if the number of vertices is uneven.
1893 return iCount - (iCount % 2);
1895 // For triangles we drop trailing vertices until the result is divisible by 3.
1896 return iCount - (iCount % 3);
1897 default:
1898 return iCount;
1899 }
1900}
1901
1902static inline float calculateElementZOrder(const Element *e, qreal zRange)
1903{
1904 // Clamp the zOrder to within the min and max depth of the viewport.
1905 return std::clamp(1.0f - float(e->order * zRange), VIEWPORT_MIN_DEPTH, VIEWPORT_MAX_DEPTH);
1906}
1907
1908/* These parameters warrant some explanation...
1909 *
1910 * vaOffset: The byte offset into the vertex data to the location of the
1911 * 2D float point vertex attributes.
1912 *
1913 * vertexData: destination where the geometry's vertex data should go
1914 *
1915 * zData: destination of geometries injected Z positioning
1916 *
1917 * indexData: destination of the indices for this element
1918 *
1919 * iBase: The starting index for this element in the batch
1920 */
1921
1922void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, void *iBasePtr, int *indexCount)
1923{
1924 if (Q_UNLIKELY(debug_upload())) qDebug() << " - uploading element:" << e << e->node << (void *) *vertexData << (qintptr) (*zData - *vertexData) << (qintptr) (*indexData - *vertexData);
1925 QSGGeometry *g = e->node->geometry();
1926
1927 const QMatrix4x4 &localx = *e->node->matrix();
1928 const float *localxdata = localx.constData();
1929
1930 const int vCount = g->vertexCount();
1931 const int vSize = g->sizeOfVertex();
1932 memcpy(*vertexData, g->vertexData(), vSize * vCount);
1933
1934 // apply vertex transform..
1935 char *vdata = *vertexData + vaOffset;
1936 if (localx.flags() == QMatrix4x4::Translation) {
1937 for (int i=0; i<vCount; ++i) {
1938 Pt *p = (Pt *) vdata;
1939 p->x += localxdata[12];
1940 p->y += localxdata[13];
1941 vdata += vSize;
1942 }
1943 } else if (localx.flags() > QMatrix4x4::Translation) {
1944 for (int i=0; i<vCount; ++i) {
1945 ((Pt *) vdata)->map(localx);
1946 vdata += vSize;
1947 }
1948 }
1949
1950 if (useDepthBuffer()) {
1951 float *vzorder = (float *) *zData;
1952 float zorder = calculateElementZOrder(e, m_zRange);
1953 for (int i=0; i<vCount; ++i)
1954 vzorder[i] = zorder;
1955 *zData += vCount * sizeof(float);
1956 }
1957
1958 int iCount = g->indexCount();
1959 if (m_uint32IndexForRhi) {
1960 // can only happen when using the rhi
1961 quint32 *iBase = (quint32 *) iBasePtr;
1962 quint32 *indices = (quint32 *) *indexData;
1963 if (iCount == 0) {
1964 iCount = vCount;
1965 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
1966 *indices++ = *iBase;
1967 else
1968 iCount = qsg_fixIndexCount(iCount, g->drawingMode());
1969
1970 for (int i=0; i<iCount; ++i)
1971 indices[i] = *iBase + i;
1972 } else {
1973 // source index data in QSGGeometry is always ushort (we would not merge otherwise)
1974 const quint16 *srcIndices = g->indexDataAsUShort();
1975 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
1976 *indices++ = *iBase + srcIndices[0];
1977 else
1978 iCount = qsg_fixIndexCount(iCount, g->drawingMode());
1979
1980 for (int i=0; i<iCount; ++i)
1981 indices[i] = *iBase + srcIndices[i];
1982 }
1983 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
1984 indices[iCount] = indices[iCount - 1];
1985 iCount += 2;
1986 }
1987 *iBase += vCount;
1988 } else {
1989 // normally batching is only done for ushort index data
1990 quint16 *iBase = (quint16 *) iBasePtr;
1991 quint16 *indices = (quint16 *) *indexData;
1992 if (iCount == 0) {
1993 iCount = vCount;
1994 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
1995 *indices++ = *iBase;
1996 else
1997 iCount = qsg_fixIndexCount(iCount, g->drawingMode());
1998
1999 for (int i=0; i<iCount; ++i)
2000 indices[i] = *iBase + i;
2001 } else {
2002 const quint16 *srcIndices = g->indexDataAsUShort();
2003 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
2004 *indices++ = *iBase + srcIndices[0];
2005 else
2006 iCount = qsg_fixIndexCount(iCount, g->drawingMode());
2007
2008 for (int i=0; i<iCount; ++i)
2009 indices[i] = *iBase + srcIndices[i];
2010 }
2011 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
2012 indices[iCount] = indices[iCount - 1];
2013 iCount += 2;
2014 }
2015 *iBase += vCount;
2016 }
2017
2018 *vertexData += vCount * vSize;
2019 *indexData += iCount * mergedIndexElemSize();
2020 *indexCount += iCount;
2021}
2022
2024{
2025 if (node->type() == QSGNode::TransformNodeType)
2026 return static_cast<QSGTransformNode *>(node->sgNode)->combinedMatrix();
2028 QSGClipNode *c = static_cast<QSGClipNode *>(node->sgNode);
2029 return *c->matrix();
2030}
2031
2032void Renderer::uploadBatch(Batch *b)
2033{
2034 // Early out if nothing has changed in this batch..
2035 if (!b->needsUpload) {
2036 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "already uploaded...";
2037 return;
2038 }
2039
2040 if (!b->first) {
2041 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "is invalid...";
2042 return;
2043 }
2044
2045 if (b->isRenderNode) {
2046 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch: " << b << "is a render node...";
2047 return;
2048 }
2049
2050 // Figure out if we can merge or not, if not, then just render the batch as is..
2051 Q_ASSERT(b->first);
2052 Q_ASSERT(b->first->node);
2053
2054 QSGGeometryNode *gn = b->first->node;
2055 QSGGeometry *g = gn->geometry();
2056 QSGMaterial::Flags flags = gn->activeMaterial()->flags();
2057 bool canMerge = (g->drawingMode() == QSGGeometry::DrawTriangles || g->drawingMode() == QSGGeometry::DrawTriangleStrip ||
2058 g->drawingMode() == QSGGeometry::DrawLines || g->drawingMode() == QSGGeometry::DrawPoints)
2059 && b->positionAttribute >= 0
2060 && (g->indexType() == QSGGeometry::UnsignedShortType && g->indexCount() > 0)
2062 && ((flags & QSGMaterial::RequiresFullMatrixExceptTranslate) == 0 || b->isTranslateOnlyToRoot())
2063 && b->isSafeToBatch();
2064
2065 b->merged = canMerge;
2066
2067 // Figure out how much memory we need...
2068 b->vertexCount = 0;
2069 b->indexCount = 0;
2070 int unmergedIndexSize = 0;
2071 Element *e = b->first;
2072
2073 while (e) {
2074 QSGGeometry *eg = e->node->geometry();
2075 b->vertexCount += eg->vertexCount();
2076 int iCount = eg->indexCount();
2077 if (b->merged) {
2078 if (iCount == 0)
2079 iCount = eg->vertexCount();
2080 iCount = qsg_fixIndexCount(iCount, g->drawingMode());
2081 } else {
2082 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : eg->sizeOfIndex();
2083 unmergedIndexSize += iCount * effectiveIndexSize;
2084 }
2085 b->indexCount += iCount;
2086 e = e->nextInBatch;
2087 }
2088
2089 // Abort if there are no vertices in this batch.. We abort this late as
2090 // this is a broken usecase which we do not care to optimize for...
2091 if (b->vertexCount == 0 || (b->merged && b->indexCount == 0))
2092 return;
2093
2094 /* Allocate memory for this batch. Merged batches are divided into three separate blocks
2095 1. Vertex data for all elements, as they were in the QSGGeometry object, but
2096 with the tranform relative to this batch's root applied. The vertex data
2097 is otherwise unmodified.
2098 2. Z data for all elements, derived from each elements "render order".
2099 This is present for merged data only.
2100 3. Indices for all elements, as they were in the QSGGeometry object, but
2101 adjusted so that each index matches its.
2102 And for TRIANGLE_STRIPs, we need to insert degenerate between each
2103 primitive. These are unsigned shorts for merged and arbitrary for
2104 non-merged.
2105 */
2106 int bufferSize = b->vertexCount * g->sizeOfVertex();
2107 int ibufferSize = 0;
2108 if (b->merged) {
2109 ibufferSize = b->indexCount * mergedIndexElemSize();
2110 if (useDepthBuffer())
2111 bufferSize += b->vertexCount * sizeof(float);
2112 } else {
2113 ibufferSize = unmergedIndexSize;
2114 }
2115
2116 map(&b->ibo, ibufferSize, true);
2117 map(&b->vbo, bufferSize);
2118
2119 if (Q_UNLIKELY(debug_upload())) qDebug() << " - batch" << b << " first:" << b->first << " root:"
2120 << b->root << " merged:" << b->merged << " positionAttribute" << b->positionAttribute
2121 << " vbo:" << b->vbo.buf << ":" << b->vbo.size;
2122
2123 if (b->merged) {
2124 char *vertexData = b->vbo.data;
2125 char *zData = vertexData + b->vertexCount * g->sizeOfVertex();
2126 char *indexData = b->ibo.data;
2127
2128 quint16 iOffset16 = 0;
2129 quint32 iOffset32 = 0;
2130 e = b->first;
2131 uint verticesInSet = 0;
2132 // Start a new set already after 65534 vertices because 0xFFFF may be
2133 // used for an always-on primitive restart with some apis (adapt for
2134 // uint32 indices as appropriate).
2135 const uint verticesInSetLimit = m_uint32IndexForRhi ? 0xfffffffe : 0xfffe;
2136 int indicesInSet = 0;
2137 b->drawSets.reset();
2138 int drawSetIndices = 0;
2139 const char *indexBase = b->ibo.data;
2140 b->drawSets << DrawSet(0, zData - vertexData, drawSetIndices);
2141 while (e) {
2142 verticesInSet += e->node->geometry()->vertexCount();
2143 if (verticesInSet > verticesInSetLimit) {
2144 b->drawSets.last().indexCount = indicesInSet;
2145 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
2146 b->drawSets.last().indices += 1 * mergedIndexElemSize();
2147 b->drawSets.last().indexCount -= 2;
2148 }
2149 drawSetIndices = indexData - indexBase;
2150 b->drawSets << DrawSet(vertexData - b->vbo.data,
2151 zData - b->vbo.data,
2152 drawSetIndices);
2153 iOffset16 = 0;
2154 iOffset32 = 0;
2155 verticesInSet = e->node->geometry()->vertexCount();
2156 indicesInSet = 0;
2157 }
2158 void *iBasePtr = &iOffset16;
2159 if (m_uint32IndexForRhi)
2160 iBasePtr = &iOffset32;
2161 uploadMergedElement(e, b->positionAttribute, &vertexData, &zData, &indexData, iBasePtr, &indicesInSet);
2162 e = e->nextInBatch;
2163 }
2164 b->drawSets.last().indexCount = indicesInSet;
2165 // We skip the very first and very last degenerate triangles since they aren't needed
2166 // and the first one would reverse the vertex ordering of the merged strips.
2167 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
2168 b->drawSets.last().indices += 1 * mergedIndexElemSize();
2169 b->drawSets.last().indexCount -= 2;
2170 }
2171 } else {
2172 char *vboData = b->vbo.data;
2173 char *iboData = b->ibo.data;
2174 Element *e = b->first;
2175 while (e) {
2176 QSGGeometry *g = e->node->geometry();
2177 int vbs = g->vertexCount() * g->sizeOfVertex();
2178 memcpy(vboData, g->vertexData(), vbs);
2179 vboData = vboData + vbs;
2180 const int indexCount = g->indexCount();
2181 if (indexCount) {
2182 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
2183 const int ibs = indexCount * effectiveIndexSize;
2184 if (g->sizeOfIndex() == effectiveIndexSize) {
2185 memcpy(iboData, g->indexData(), ibs);
2186 } else {
2187 if (g->sizeOfIndex() == sizeof(quint16) && effectiveIndexSize == sizeof(quint32)) {
2188 quint16 *src = g->indexDataAsUShort();
2189 quint32 *dst = (quint32 *) iboData;
2190 for (int i = 0; i < indexCount; ++i)
2191 dst[i] = src[i];
2192 } else {
2193 Q_ASSERT_X(false, "uploadBatch (unmerged)", "uint index with ushort effective index - cannot happen");
2194 }
2195 }
2196 iboData += ibs;
2197 }
2198 e = e->nextInBatch;
2199 }
2200 }
2201#ifndef QT_NO_DEBUG_OUTPUT
2202 if (Q_UNLIKELY(debug_upload())) {
2203 const char *vd = b->vbo.data;
2204 qDebug() << " -- Vertex Data, count:" << b->vertexCount << " - " << g->sizeOfVertex() << "bytes/vertex";
2205 for (int i=0; i<b->vertexCount; ++i) {
2206 QDebug dump = qDebug().nospace();
2207 dump << " --- " << i << ": ";
2208 int offset = 0;
2209 for (int a=0; a<g->attributeCount(); ++a) {
2210 const QSGGeometry::Attribute &attr = g->attributes()[a];
2211 dump << attr.position << ":(" << attr.tupleSize << ",";
2212 if (attr.type == QSGGeometry::FloatType) {
2213 dump << "float ";
2214 if (attr.isVertexCoordinate)
2215 dump << "* ";
2216 for (int t=0; t<attr.tupleSize; ++t)
2217 dump << *(const float *)(vd + offset + t * sizeof(float)) << " ";
2218 } else if (attr.type == QSGGeometry::UnsignedByteType) {
2219 dump << "ubyte ";
2220 for (int t=0; t<attr.tupleSize; ++t)
2221 dump << *(const unsigned char *)(vd + offset + t * sizeof(unsigned char)) << " ";
2222 }
2223 dump << ") ";
2224 offset += attr.tupleSize * size_of_type(attr.type);
2225 }
2226 if (b->merged && useDepthBuffer()) {
2227 float zorder = ((float*)(b->vbo.data + b->vertexCount * g->sizeOfVertex()))[i];
2228 dump << " Z:(" << zorder << ")";
2229 }
2230 vd += g->sizeOfVertex();
2231 }
2232
2233 if (!b->drawSets.isEmpty()) {
2234 if (m_uint32IndexForRhi) {
2235 const quint32 *id = (const quint32 *) b->ibo.data;
2236 {
2237 QDebug iDump = qDebug();
2238 iDump << " -- Index Data, count:" << b->indexCount;
2239 for (int i=0; i<b->indexCount; ++i) {
2240 if ((i % 24) == 0)
2241 iDump << Qt::endl << " --- ";
2242 iDump << id[i];
2243 }
2244 }
2245 } else {
2246 const quint16 *id = (const quint16 *) b->ibo.data;
2247 {
2248 QDebug iDump = qDebug();
2249 iDump << " -- Index Data, count:" << b->indexCount;
2250 for (int i=0; i<b->indexCount; ++i) {
2251 if ((i % 24) == 0)
2252 iDump << Qt::endl << " --- ";
2253 iDump << id[i];
2254 }
2255 }
2256 }
2257
2258 for (int i=0; i<b->drawSets.size(); ++i) {
2259 const DrawSet &s = b->drawSets.at(i);
2260 qDebug() << " -- DrawSet: indexCount:" << s.indexCount << " vertices:" << s.vertices << " z:" << s.zorders << " indices:" << s.indices;
2261 }
2262 }
2263 }
2264#endif // QT_NO_DEBUG_OUTPUT
2265
2266 unmap(&b->vbo);
2267 unmap(&b->ibo, true);
2268
2269 if (Q_UNLIKELY(debug_upload())) qDebug() << " --- vertex/index buffers unmapped, batch upload completed...";
2270
2271 b->needsUpload = false;
2272
2273 if (Q_UNLIKELY(debug_render()))
2274 b->uploadedThisFrame = true;
2275}
2276
2277void Renderer::applyClipStateToGraphicsState()
2278{
2279 m_gstate.usesScissor = (m_currentClipState.type & ClipState::ScissorClip);
2280 m_gstate.stencilTest = (m_currentClipState.type & ClipState::StencilClip);
2281}
2282
2283QRhiGraphicsPipeline *Renderer::buildStencilPipeline(const Batch *batch, bool firstStencilClipInBatch)
2284{
2288 blend.colorWrite = {};
2289 ps->setTargetBlends({ blend });
2290 ps->setSampleCount(renderTarget().rt->sampleCount());
2291 ps->setStencilTest(true);
2293 if (firstStencilClipInBatch) {
2295 stencilOp.failOp = QRhiGraphicsPipeline::Keep;
2296 stencilOp.depthFailOp = QRhiGraphicsPipeline::Keep;
2297 stencilOp.passOp = QRhiGraphicsPipeline::Replace;
2298 } else {
2299 stencilOp.compareOp = QRhiGraphicsPipeline::Equal;
2300 stencilOp.failOp = QRhiGraphicsPipeline::Keep;
2301 stencilOp.depthFailOp = QRhiGraphicsPipeline::Keep;
2302 stencilOp.passOp = QRhiGraphicsPipeline::IncrementAndClamp;
2303 }
2304 ps->setStencilFront(stencilOp);
2305 ps->setStencilBack(stencilOp);
2306
2307 ps->setTopology(m_stencilClipCommon.topology);
2308
2309 ps->setMultiViewCount(renderTarget().multiViewCount);
2310
2311 ps->setShaderStages({ QRhiShaderStage(QRhiShaderStage::Vertex, m_stencilClipCommon.vs),
2312 QRhiShaderStage(QRhiShaderStage::Fragment, m_stencilClipCommon.fs) });
2313 ps->setVertexInputLayout(m_stencilClipCommon.inputLayout);
2314 ps->setShaderResourceBindings(batch->stencilClipState.srb); // use something, it just needs to be layout-compatible
2316
2317 if (!ps->create()) {
2318 qWarning("Failed to build stencil clip pipeline");
2319 delete ps;
2320 return nullptr;
2321 }
2322
2323 return ps;
2324}
2325
2326void Renderer::updateClipState(const QSGClipNode *clipList, Batch *batch)
2327{
2328 // Note: No use of the clip-related speparate m_current* vars is allowed
2329 // here. All stored in batch->clipState instead. To collect state during
2330 // the prepare steps, m_currentClipState is used. It should not be used in
2331 // the render steps afterwards.
2332
2333 // The stenciling logic is slightly different from Qt 5's direct OpenGL version
2334 // as we cannot just randomly clear the stencil buffer. We now put all clip
2335 // shapes into the stencil buffer for all batches in the frame. This means
2336 // that the number of total clips in a scene is reduced (since the stencil
2337 // value cannot exceed 255) but we do not need any clears inbetween.
2338
2339 Q_ASSERT(m_rhi);
2340 batch->stencilClipState.updateStencilBuffer = false;
2341 if (clipList == m_currentClipState.clipList || Q_UNLIKELY(debug_noclip())) {
2342 applyClipStateToGraphicsState();
2343 batch->clipState = m_currentClipState;
2344 return;
2345 }
2346
2347 ClipState::ClipType clipType = ClipState::NoClip;
2348 QRect scissorRect;
2349 QVarLengthArray<const QSGClipNode *, 4> stencilClipNodes;
2350 const QSGClipNode *clip = clipList;
2351
2352 batch->stencilClipState.drawCalls.reset();
2353 quint32 totalVSize = 0;
2354 quint32 totalISize = 0;
2355 quint32 totalUSize = 0;
2356 const quint32 StencilClipUbufSize = 64;
2357
2358 while (clip) {
2359 QMatrix4x4 m = m_current_projection_matrix_native_ndc[0]; // never hit for 3D and so multiview
2360 if (clip->matrix())
2361 m *= *clip->matrix();
2362
2363 bool isRectangleWithNoPerspective = clip->isRectangular()
2364 && qFuzzyIsNull(m(3, 0)) && qFuzzyIsNull(m(3, 1));
2365 bool noRotate = qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(1, 0));
2366 bool isRotate90 = qFuzzyIsNull(m(0, 0)) && qFuzzyIsNull(m(1, 1));
2367
2368 if (isRectangleWithNoPerspective && (noRotate || isRotate90)) {
2369 QRectF bbox = clip->clipRect();
2370 qreal invW = 1 / m(3, 3);
2371 qreal fx1, fy1, fx2, fy2;
2372 if (noRotate) {
2373 fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
2374 fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
2375 fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
2376 fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
2377 } else {
2378 Q_ASSERT(isRotate90);
2379 fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
2380 fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
2381 fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
2382 fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
2383 }
2384
2385 if (fx1 > fx2)
2386 qSwap(fx1, fx2);
2387 if (fy1 > fy2)
2388 qSwap(fy1, fy2);
2389
2390 QRect deviceRect = this->deviceRect();
2391
2392 qint32 ix1 = qRound((fx1 + 1) * deviceRect.width() * qreal(0.5));
2393 qint32 iy1 = qRound((fy1 + 1) * deviceRect.height() * qreal(0.5));
2394 qint32 ix2 = qRound((fx2 + 1) * deviceRect.width() * qreal(0.5));
2395 qint32 iy2 = qRound((fy2 + 1) * deviceRect.height() * qreal(0.5));
2396
2397 if (!(clipType & ClipState::ScissorClip)) {
2398 clipType |= ClipState::ScissorClip;
2399 scissorRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2400 } else {
2401 scissorRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2402 }
2403 } else {
2404 clipType |= ClipState::StencilClip;
2405
2406 const QSGGeometry *g = clip->geometry();
2407 Q_ASSERT(g->attributeCount() > 0);
2408
2409 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2410 // the 4 byte alignment may not actually be needed here
2411 totalVSize = aligned(totalVSize, 4u) + vertexByteSize;
2412 if (g->indexCount()) {
2413 const int indexByteSize = g->sizeOfIndex() * g->indexCount();
2414 // so no need to worry about NonFourAlignedEffectiveIndexBufferOffset
2415 totalISize = aligned(totalISize, 4u) + indexByteSize;
2416 }
2417 // ubuf start offsets must be aligned (typically to 256 bytes)
2418 totalUSize = aligned(totalUSize, m_ubufAlignment) + StencilClipUbufSize;
2419
2420 stencilClipNodes.append(clip);
2421 }
2422
2423 clip = clip->clipList();
2424 }
2425
2426 if (clipType & ClipState::StencilClip) {
2427 bool rebuildVBuf = false;
2428 if (!batch->stencilClipState.vbuf) {
2429 batch->stencilClipState.vbuf = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, totalVSize);
2430 rebuildVBuf = true;
2431 } else if (batch->stencilClipState.vbuf->size() < totalVSize) {
2432 batch->stencilClipState.vbuf->setSize(totalVSize);
2433 rebuildVBuf = true;
2434 }
2435 if (rebuildVBuf) {
2436 if (!batch->stencilClipState.vbuf->create()) {
2437 qWarning("Failed to build stencil clip vertex buffer");
2438 delete batch->stencilClipState.vbuf;
2439 batch->stencilClipState.vbuf = nullptr;
2440 return;
2441 }
2442 }
2443
2444 if (totalISize) {
2445 bool rebuildIBuf = false;
2446 if (!batch->stencilClipState.ibuf) {
2447 batch->stencilClipState.ibuf = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::IndexBuffer, totalISize);
2448 rebuildIBuf = true;
2449 } else if (batch->stencilClipState.ibuf->size() < totalISize) {
2450 batch->stencilClipState.ibuf->setSize(totalISize);
2451 rebuildIBuf = true;
2452 }
2453 if (rebuildIBuf) {
2454 if (!batch->stencilClipState.ibuf->create()) {
2455 qWarning("Failed to build stencil clip index buffer");
2456 delete batch->stencilClipState.ibuf;
2457 batch->stencilClipState.ibuf = nullptr;
2458 return;
2459 }
2460 }
2461 }
2462
2463 bool rebuildUBuf = false;
2464 if (!batch->stencilClipState.ubuf) {
2465 batch->stencilClipState.ubuf = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, totalUSize);
2466 rebuildUBuf = true;
2467 } else if (batch->stencilClipState.ubuf->size() < totalUSize) {
2468 batch->stencilClipState.ubuf->setSize(totalUSize);
2469 rebuildUBuf = true;
2470 }
2471 if (rebuildUBuf) {
2472 if (!batch->stencilClipState.ubuf->create()) {
2473 qWarning("Failed to build stencil clip uniform buffer");
2474 delete batch->stencilClipState.ubuf;
2475 batch->stencilClipState.ubuf = nullptr;
2476 return;
2477 }
2478 }
2479
2480 if (!batch->stencilClipState.srb) {
2481 batch->stencilClipState.srb = m_rhi->newShaderResourceBindings();
2483 0, QRhiShaderResourceBinding::VertexStage, batch->stencilClipState.ubuf, StencilClipUbufSize);
2484 batch->stencilClipState.srb->setBindings({ ubufBinding });
2485 if (!batch->stencilClipState.srb->create()) {
2486 qWarning("Failed to build stencil clip srb");
2487 delete batch->stencilClipState.srb;
2488 batch->stencilClipState.srb = nullptr;
2489 return;
2490 }
2491 }
2492
2493 quint32 vOffset = 0;
2494 quint32 iOffset = 0;
2495 quint32 uOffset = 0;
2496 for (const QSGClipNode *clip : stencilClipNodes) {
2497 const QSGGeometry *g = clip->geometry();
2498 const QSGGeometry::Attribute *a = g->attributes();
2499 StencilClipState::StencilDrawCall drawCall;
2500 const bool firstStencilClipInBatch = batch->stencilClipState.drawCalls.isEmpty();
2501
2502 if (firstStencilClipInBatch) {
2503 m_stencilClipCommon.inputLayout.setBindings({ QRhiVertexInputBinding(g->sizeOfVertex()) });
2504 m_stencilClipCommon.inputLayout.setAttributes({ QRhiVertexInputAttribute(0, 0, qsg_vertexInputFormat(*a), 0) });
2505 m_stencilClipCommon.topology = qsg_topology(g->drawingMode());
2506 }
2507#ifndef QT_NO_DEBUG
2508 else {
2509 if (qsg_topology(g->drawingMode()) != m_stencilClipCommon.topology)
2510 qWarning("updateClipState: Clip list entries have different primitive topologies, this is not currently supported.");
2511 if (qsg_vertexInputFormat(*a) != m_stencilClipCommon.inputLayout.cbeginAttributes()->format())
2512 qWarning("updateClipState: Clip list entries have different vertex input layouts, this is must not happen.");
2513 }
2514#endif
2515
2516 drawCall.vbufOffset = aligned(vOffset, 4u);
2517 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2518 vOffset = drawCall.vbufOffset + vertexByteSize;
2519
2520 int indexByteSize = 0;
2521 if (g->indexCount()) {
2522 drawCall.ibufOffset = aligned(iOffset, 4u);
2523 indexByteSize = g->sizeOfIndex() * g->indexCount();
2524 iOffset = drawCall.ibufOffset + indexByteSize;
2525 }
2526
2527 drawCall.ubufOffset = aligned(uOffset, m_ubufAlignment);
2528 uOffset = drawCall.ubufOffset + StencilClipUbufSize;
2529
2530 QMatrix4x4 matrixYUpNDC = m_current_projection_matrix[0];
2531 if (clip->matrix())
2532 matrixYUpNDC *= *clip->matrix();
2533
2534 m_resourceUpdates->updateDynamicBuffer(batch->stencilClipState.ubuf, drawCall.ubufOffset, 64, matrixYUpNDC.constData());
2535 m_resourceUpdates->updateDynamicBuffer(batch->stencilClipState.vbuf, drawCall.vbufOffset, vertexByteSize, g->vertexData());
2536 if (indexByteSize)
2537 m_resourceUpdates->updateDynamicBuffer(batch->stencilClipState.ibuf, drawCall.ibufOffset, indexByteSize, g->indexData());
2538
2539 // stencil ref goes 1, 1, 2, 3, 4, ..., N for the clips in the first batch,
2540 // then N+1, N+1, N+2, N+3, ... for the next batch,
2541 // and so on.
2542 // Note the different stencilOp for the first and the subsequent clips.
2543 drawCall.stencilRef = firstStencilClipInBatch ? m_currentClipState.stencilRef + 1 : m_currentClipState.stencilRef;
2544 m_currentClipState.stencilRef += 1;
2545
2546 drawCall.vertexCount = g->vertexCount();
2547 drawCall.indexCount = g->indexCount();
2548 drawCall.indexFormat = qsg_indexFormat(g);
2549 batch->stencilClipState.drawCalls.add(drawCall);
2550 }
2551
2552 if (!m_stencilClipCommon.vs.isValid())
2553 m_stencilClipCommon.vs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.vert.qsb"));
2554
2555 if (!m_stencilClipCommon.fs.isValid())
2556 m_stencilClipCommon.fs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.frag.qsb"));
2557
2558 if (!m_stencilClipCommon.replacePs)
2559 m_stencilClipCommon.replacePs = buildStencilPipeline(batch, true);
2560
2561 if (!m_stencilClipCommon.incrPs)
2562 m_stencilClipCommon.incrPs = buildStencilPipeline(batch, false);
2563
2564 batch->stencilClipState.updateStencilBuffer = true;
2565 }
2566
2567 m_currentClipState.clipList = clipList;
2568 m_currentClipState.type = clipType;
2569 m_currentClipState.scissor = QRhiScissor(scissorRect.x(), scissorRect.y(),
2570 scissorRect.width(), scissorRect.height());
2571
2572 applyClipStateToGraphicsState();
2573 batch->clipState = m_currentClipState;
2574}
2575
2576void Renderer::enqueueStencilDraw(const Batch *batch)
2577{
2578 // cliptype stencil + updateStencilBuffer==false means the batch uses
2579 // stenciling but relies on the stencil data generated by a previous batch
2580 // (due to the having the same clip node). Do not enqueue draw calls for
2581 // stencil in this case as the stencil buffer is already up-to-date.
2582 if (!batch->stencilClipState.updateStencilBuffer)
2583 return;
2584
2586 const int count = batch->stencilClipState.drawCalls.size();
2587 for (int i = 0; i < count; ++i) {
2588 const StencilClipState::StencilDrawCall &drawCall(batch->stencilClipState.drawCalls.at(i));
2589 QRhiShaderResourceBindings *srb = batch->stencilClipState.srb;
2590 QRhiCommandBuffer::DynamicOffset ubufOffset(0, drawCall.ubufOffset);
2591 if (i == 0) {
2592 cb->setGraphicsPipeline(m_stencilClipCommon.replacePs);
2593 cb->setViewport(m_pstate.viewport);
2594 } else if (i == 1) {
2595 cb->setGraphicsPipeline(m_stencilClipCommon.incrPs);
2596 cb->setViewport(m_pstate.viewport);
2597 }
2598 // else incrPs is already bound
2599 cb->setShaderResources(srb, 1, &ubufOffset);
2600 cb->setStencilRef(drawCall.stencilRef);
2601 const QRhiCommandBuffer::VertexInput vbufBinding(batch->stencilClipState.vbuf, drawCall.vbufOffset);
2602 if (drawCall.indexCount) {
2603 cb->setVertexInput(0, 1, &vbufBinding,
2604 batch->stencilClipState.ibuf, drawCall.ibufOffset, drawCall.indexFormat);
2605 cb->drawIndexed(drawCall.indexCount);
2606 } else {
2607 cb->setVertexInput(0, 1, &vbufBinding);
2608 cb->draw(drawCall.vertexCount);
2609 }
2610 }
2611}
2612
2613void Renderer::setActiveRhiShader(QSGMaterialShader *program, ShaderManager::Shader *shader)
2614{
2615 Q_ASSERT(m_rhi);
2616 m_currentProgram = program;
2617 m_currentShader = shader;
2618 m_currentMaterial = nullptr;
2619}
2620
2628
2629// With QRhi renderBatches() is split to two steps: prepare and render.
2630//
2631// Prepare goes through the batches and elements, and set up a graphics
2632// pipeline, srb, uniform buffer, calculates clipping, based on m_gstate, the
2633// material (shaders), and the batches. This step does not touch the command
2634// buffer or renderpass-related state (m_pstate).
2635//
2636// The render step then starts a renderpass, and goes through all
2637// batches/elements again and records setGraphicsPipeline, drawIndexed, etc. on
2638// the command buffer. The prepare step's accumulated global state like
2639// m_gstate must not be used here. Rather, all data needed for rendering is
2640// available from Batch/Element at this stage. Bookkeeping of state in the
2641// renderpass is done via m_pstate.
2642
2643bool Renderer::ensurePipelineState(Element *e, const ShaderManager::Shader *sms, bool depthPostPass)
2644{
2645 // Note the key's == and qHash implementations: the renderpass descriptor
2646 // and srb are tested for compatibility, not pointer equality.
2647 //
2648 // We do not store the srb pointer itself because the ownership stays with
2649 // the Element and that can go away more often that we would like it
2650 // to. (think scrolling a list view, constantly dropping and creating new
2651 // nodes) Rather, use an opaque blob of a few uints and store and compare
2652 // that. This works because once the pipeline is built, we will always call
2653 // setShaderResources with an explicitly specified srb which is fine even if
2654 // e->srb we used here to bake the pipeline is already gone by that point.
2655 //
2656 // A typical QSGMaterial's serialized srb layout is 8 uints. (uniform buffer
2657 // + texture, 4 fields each) Regardless, using an implicitly shared
2658 // container is essential here. (won't detach so no more allocs and copies
2659 // are done, unless the Element decides to rebake the srb with a different
2660 // layout - but then the detach is exactly what we need)
2661 //
2662 // Same story for the renderpass descriptor: the object can go away but
2663 // that's fine because that has no effect on an already built pipeline, and
2664 // for comparison we only rely on the serialized blob in order decide if the
2665 // render target is compatible with the pipeline.
2666
2667 const GraphicsPipelineStateKey k = GraphicsPipelineStateKey::create(m_gstate, sms, renderTarget().rpDesc, e->srb);
2668
2669 // Note: dynamic state (viewport rect, scissor rect, stencil ref, blend
2670 // constant) is never a part of GraphicsState/QRhiGraphicsPipeline.
2671
2672 // See if there is an existing, matching pipeline state object.
2673 auto it = m_shaderManager->pipelineCache.constFind(k);
2674 if (it != m_shaderManager->pipelineCache.constEnd()) {
2675 if (depthPostPass)
2676 e->depthPostPassPs = *it;
2677 else
2678 e->ps = *it;
2679 return true;
2680 }
2681
2682 // Build a new one. This is potentially expensive.
2684 ps->setShaderStages(sms->stages.cbegin(), sms->stages.cend());
2685 ps->setVertexInputLayout(sms->inputLayout);
2686 ps->setShaderResourceBindings(e->srb);
2688
2689 QRhiGraphicsPipeline::Flags flags;
2690 if (needsBlendConstant(m_gstate.srcColor) || needsBlendConstant(m_gstate.dstColor)
2691 || needsBlendConstant(m_gstate.srcAlpha) || needsBlendConstant(m_gstate.dstAlpha))
2692 {
2694 }
2695 if (m_gstate.usesScissor)
2697 if (m_gstate.stencilTest)
2699
2700 ps->setFlags(flags);
2701 ps->setTopology(qsg_topology(m_gstate.drawMode));
2702 ps->setCullMode(m_gstate.cullMode);
2703 ps->setPolygonMode(m_gstate.polygonMode);
2704 ps->setMultiViewCount(m_gstate.multiViewCount);
2705
2707 blend.colorWrite = m_gstate.colorWrite;
2708 blend.enable = m_gstate.blending;
2709 blend.srcColor = m_gstate.srcColor;
2710 blend.dstColor = m_gstate.dstColor;
2711 blend.srcAlpha = m_gstate.srcAlpha;
2712 blend.dstAlpha = m_gstate.dstAlpha;
2713 blend.opColor = m_gstate.opColor;
2714 blend.opAlpha = m_gstate.opAlpha;
2715 ps->setTargetBlends({ blend });
2716
2717 ps->setDepthTest(m_gstate.depthTest);
2718 ps->setDepthWrite(m_gstate.depthWrite);
2719 ps->setDepthOp(m_gstate.depthFunc);
2720
2721 if (m_gstate.stencilTest) {
2722 ps->setStencilTest(true);
2725 stencilOp.failOp = QRhiGraphicsPipeline::Keep;
2726 stencilOp.depthFailOp = QRhiGraphicsPipeline::Keep;
2727 stencilOp.passOp = QRhiGraphicsPipeline::Keep;
2728 ps->setStencilFront(stencilOp);
2729 ps->setStencilBack(stencilOp);
2730 }
2731
2732 ps->setSampleCount(m_gstate.sampleCount);
2733
2734 ps->setLineWidth(m_gstate.lineWidth);
2735
2736 if (!ps->create()) {
2737 qWarning("Failed to build graphics pipeline state");
2738 delete ps;
2739 return false;
2740 }
2741
2742 m_shaderManager->pipelineCache.insert(k, ps);
2743 if (depthPostPass)
2744 e->depthPostPassPs = ps;
2745 else
2746 e->ps = ps;
2747 return true;
2748}
2749
2751{
2752 QRhiSampler::Filter magFilter;
2753 QRhiSampler::Filter minFilter;
2754 QRhiSampler::Filter mipmapMode;
2757
2758 switch (desc.filtering) {
2759 case QSGTexture::None:
2760 Q_FALLTHROUGH();
2762 magFilter = minFilter = QRhiSampler::Nearest;
2763 break;
2764 case QSGTexture::Linear:
2765 magFilter = minFilter = QRhiSampler::Linear;
2766 break;
2767 default:
2768 Q_UNREACHABLE();
2769 magFilter = minFilter = QRhiSampler::Nearest;
2770 break;
2771 }
2772
2773 switch (desc.mipmapFiltering) {
2774 case QSGTexture::None:
2775 mipmapMode = QRhiSampler::None;
2776 break;
2778 mipmapMode = QRhiSampler::Nearest;
2779 break;
2780 case QSGTexture::Linear:
2781 mipmapMode = QRhiSampler::Linear;
2782 break;
2783 default:
2784 Q_UNREACHABLE();
2785 mipmapMode = QRhiSampler::None;
2786 break;
2787 }
2788
2789 switch (desc.horizontalWrap) {
2790 case QSGTexture::Repeat:
2792 break;
2795 break;
2798 break;
2799 default:
2800 Q_UNREACHABLE();
2802 break;
2803 }
2804
2805 switch (desc.verticalWrap) {
2806 case QSGTexture::Repeat:
2808 break;
2811 break;
2814 break;
2815 default:
2816 Q_UNREACHABLE();
2818 break;
2819 }
2820
2821 return rhi->newSampler(magFilter, minFilter, mipmapMode, u, v);
2822}
2823
2824QRhiTexture *Renderer::dummyTexture()
2825{
2826 if (!m_dummyTexture) {
2827 m_dummyTexture = m_rhi->newTexture(QRhiTexture::RGBA8, QSize(64, 64));
2828 if (m_dummyTexture->create()) {
2829 if (m_resourceUpdates) {
2831 img.fill(0);
2832 m_resourceUpdates->uploadTexture(m_dummyTexture, img);
2833 }
2834 }
2835 }
2836 return m_dummyTexture;
2837}
2838
2841{
2842 dst->blendEnable = src->blending;
2843
2844 // the enum values should match, sanity check it
2852
2853 // For compatibility with any existing code, separateBlendFactors defaults
2854 // to _false_ which means that materials that do not touch srcAlpha and
2855 // dstAlpha will continue to use srcColor and dstColor as the alpha
2856 // blending factors. New code that needs different values for color/alpha,
2857 // can explicitly set separateBlendFactors to true and then set srcAlpha
2858 // and dstAlpha as well.
2859 dst->separateBlendFactors = false;
2860
2863
2866
2867 dst->colorWrite = QSGMaterialShader::GraphicsPipelineState::ColorMask(int(src->colorWrite));
2868
2871}
2872
2875{
2876 dst->blending = src->blendEnable;
2877 dst->srcColor = QRhiGraphicsPipeline::BlendFactor(src->srcColor);
2878 dst->dstColor = QRhiGraphicsPipeline::BlendFactor(src->dstColor);
2879 if (src->separateBlendFactors) {
2880 dst->srcAlpha = QRhiGraphicsPipeline::BlendFactor(src->srcAlpha);
2881 dst->dstAlpha = QRhiGraphicsPipeline::BlendFactor(src->dstAlpha);
2882 } else {
2883 dst->srcAlpha = dst->srcColor;
2884 dst->dstAlpha = dst->dstColor;
2885 }
2886 dst->opColor = QRhiGraphicsPipeline::BlendOp(src->opColor);
2887 dst->opAlpha = QRhiGraphicsPipeline::BlendOp(src->opAlpha);
2888 dst->colorWrite = QRhiGraphicsPipeline::ColorMask(int(src->colorWrite));
2889 dst->cullMode = QRhiGraphicsPipeline::CullMode(src->cullMode);
2890 dst->polygonMode = QRhiGraphicsPipeline::PolygonMode(src->polygonMode);
2891}
2892
2893void Renderer::updateMaterialDynamicData(ShaderManager::Shader *sms,
2894 QSGMaterialShader::RenderState &renderState,
2895 QSGMaterial *material,
2896 const Batch *batch,
2897 Element *e,
2898 int ubufOffset,
2899 int ubufRegionSize)
2900{
2901 m_current_resource_update_batch = m_resourceUpdates;
2902
2903 QSGMaterialShader *shader = sms->materialShader;
2905 QVarLengthArray<QRhiShaderResourceBinding, 8> bindings;
2906
2907 if (pd->ubufBinding >= 0) {
2909 const bool changed = shader->updateUniformData(renderState, material, m_currentMaterial);
2910 m_current_uniform_data = nullptr;
2911
2912 if (changed || !batch->ubufDataValid)
2913 m_resourceUpdates->updateDynamicBuffer(batch->ubuf, ubufOffset, ubufRegionSize, pd->masterUniformData.constData());
2914
2916 pd->ubufStages,
2917 batch->ubuf,
2918 ubufOffset,
2919 ubufRegionSize));
2920 }
2921
2922 for (int binding = 0; binding < QSGMaterialShaderPrivate::MAX_SHADER_RESOURCE_BINDINGS; ++binding) {
2923 const QRhiShaderResourceBinding::StageFlags stages = pd->combinedImageSamplerBindings[binding];
2924 if (!stages)
2925 continue;
2926
2927 QVarLengthArray<QSGTexture *, 4> prevTex = pd->textureBindingTable[binding];
2928 QVarLengthArray<QSGTexture *, 4> nextTex = prevTex;
2929
2930 const int count = pd->combinedImageSamplerCount[binding];
2931 nextTex.resize(count);
2932
2933 shader->updateSampledImage(renderState, binding, nextTex.data(), material,
2934 m_currentMaterial);
2935
2936 if (nextTex.contains(nullptr)) {
2937 qWarning("No QSGTexture provided from updateSampledImage(). This is wrong.");
2938 continue;
2939 }
2940
2941 bool hasDirtySamplerOptions = false;
2942 bool isAnisotropic = false;
2943 for (QSGTexture *t : nextTex) {
2945 hasDirtySamplerOptions |= td->hasDirtySamplerOptions();
2946 isAnisotropic |= t->anisotropyLevel() != QSGTexture::AnisotropyNone;
2948 }
2949
2950 // prevTex may be invalid at this point, avoid dereferencing it
2951 if (nextTex != prevTex || hasDirtySamplerOptions) {
2952
2953 // The QSGTexture, and so the sampler parameters, may have changed.
2954 // The rhiTexture is not relevant here.
2955 pd->textureBindingTable[binding] = nextTex; // does not own
2956 pd->samplerBindingTable[binding].clear();
2957
2958 if (isAnisotropic) // ###
2959 qWarning("QSGTexture anisotropy levels are not currently supported");
2960
2961 QVarLengthArray<QRhiSampler *, 4> samplers;
2962
2963 for (QSGTexture *t : nextTex) {
2965
2966 QRhiSampler *sampler = m_samplers[samplerDesc];
2967
2968 if (!sampler) {
2969 sampler = newSampler(m_rhi, samplerDesc);
2970 if (!sampler->create()) {
2971 qWarning("Failed to build sampler");
2972 delete sampler;
2973 continue;
2974 }
2975 m_samplers[samplerDesc] = sampler;
2976 }
2977 samplers.append(sampler);
2978 }
2979
2980 pd->samplerBindingTable[binding] = samplers; // does not own
2981 }
2982
2983 if (pd->textureBindingTable[binding].size() == pd->samplerBindingTable[binding].size()) {
2984
2985 QVarLengthArray<QRhiShaderResourceBinding::TextureAndSampler, 4> textureSamplers;
2986
2987 for (int i = 0; i < pd->textureBindingTable[binding].size(); ++i) {
2988
2990
2991 // texture may be null if the update above failed for any reason,
2992 // or if the QSGTexture chose to return null intentionally. This is
2993 // valid and we still need to provide something to the shader.
2994 if (!texture)
2995 texture = dummyTexture();
2996
2997 QRhiSampler *sampler = pd->samplerBindingTable[binding].at(i);
2998
2999 textureSamplers.append(
3001 }
3002
3003 if (!textureSamplers.isEmpty())
3005 binding, stages, count, textureSamplers.constData()));
3006 }
3007 }
3008
3009#ifndef QT_NO_DEBUG
3010 if (bindings.isEmpty())
3011 qWarning("No shader resources for material %p, this is odd.", material);
3012#endif
3013
3014 enum class SrbAction {
3015 Unknown,
3016 DoNothing,
3017 UpdateResources,
3018 Rebake
3019 } srbAction = SrbAction::Unknown;
3020
3021 // First, if the Element has no srb created at all, then try to find an existing,
3022 // currently unused srb that is layout-compatible with our binding list.
3023 if (!e->srb) {
3024 // reuse a QVector as our work area, thus possibly reusing the underlying allocation too
3025 QVector<quint32> &layoutDesc(m_shaderManager->srbLayoutDescSerializeWorkspace);
3026 layoutDesc.clear();
3027 QRhiShaderResourceBinding::serializeLayoutDescription(bindings.cbegin(), bindings.cend(), std::back_inserter(layoutDesc));
3028 e->srb = m_shaderManager->srbPool.take(layoutDesc);
3029 if (e->srb) {
3030 // Here we know layout compatibility is satisfied, but do not spend time on full
3031 // comparison. The chance of getting an srb that refers to the same resources
3032 // (buffer, textures) is low in practice. So reuse, but write new resources.
3033 srbAction = SrbAction::UpdateResources;
3034 }
3035 }
3036
3037 // If the Element had an existing srb, investigate:
3038 // - It may be used as-is (when nothing changed in the scene regarding this node compared to the previous frame).
3039 // - Otherwise it may be able to go with a lightweight update (replace resources, binding list layout is the same).
3040 // - If all else fails rebake the full thing, meaning we reuse the memory allocation but will recreate everything underneath.
3041 if (srbAction == SrbAction::Unknown && e->srb) {
3042 if (std::equal(e->srb->cbeginBindings(), e->srb->cendBindings(), bindings.cbegin(), bindings.cend())) {
3043 srbAction = SrbAction::DoNothing;
3044 } else if (std::equal(e->srb->cbeginBindings(), e->srb->cendBindings(), bindings.cbegin(), bindings.cend(),
3045 [](const auto &a, const auto &b) { return a.isLayoutCompatible(b); }))
3046 {
3047 srbAction = SrbAction::UpdateResources;
3048 } else {
3049 srbAction = SrbAction::Rebake;
3050 }
3051 }
3052
3053 // If the Element had no srb associated at all and could not find a layout-compatible
3054 // one from the pool, then create a whole new object.
3055 if (!e->srb) {
3056 e->srb = m_rhi->newShaderResourceBindings();
3057 srbAction = SrbAction::Rebake;
3058 }
3059
3060 Q_ASSERT(srbAction != SrbAction::Unknown && e->srb);
3061
3062 switch (srbAction) {
3063 case SrbAction::DoNothing:
3064 break;
3065 case SrbAction::UpdateResources:
3066 {
3067 e->srb->setBindings(bindings.cbegin(), bindings.cend());
3068 QRhiShaderResourceBindings::UpdateFlags flags;
3069 // Due to the way the binding list is built up above, if we have a uniform buffer
3070 // at binding point 0 (or none at all) then the sampledTexture bindings are added
3071 // with increasing binding points afterwards, so the list is already sorted based
3072 // on the binding points, thus we can save some time by telling the QRhi backend
3073 // not to sort again.
3074 if (pd->ubufBinding <= 0 || bindings.size() <= 1)
3076
3077 e->srb->updateResources(flags);
3078 }
3079 break;
3080 case SrbAction::Rebake:
3081 e->srb->setBindings(bindings.cbegin(), bindings.cend());
3082 if (!e->srb->create())
3083 qWarning("Failed to build srb");
3084 break;
3085 default:
3086 Q_ASSERT_X(false, "updateMaterialDynamicData", "No srb action set, this cannot happen");
3087 }
3088}
3089
3090void Renderer::updateMaterialStaticData(ShaderManager::Shader *sms,
3091 QSGMaterialShader::RenderState &renderState,
3092 QSGMaterial *material,
3093 Batch *batch,
3094 bool *gstateChanged)
3095{
3096 QSGMaterialShader *shader = sms->materialShader;
3097 *gstateChanged = false;
3099 // generate the public mini-state from m_gstate, invoke the material,
3100 // write the changes, if any, back to m_gstate, together with a way to
3101 // roll those back.
3103 rendererToMaterialGraphicsState(&shaderPs, &m_gstate);
3104 const bool changed = shader->updateGraphicsPipelineState(renderState, &shaderPs, material, m_currentMaterial);
3105 if (changed) {
3106 m_gstateStack.push(m_gstate);
3107 materialToRendererGraphicsState(&m_gstate, &shaderPs);
3108 if (needsBlendConstant(m_gstate.srcColor) || needsBlendConstant(m_gstate.dstColor)
3109 || needsBlendConstant(m_gstate.srcAlpha) || needsBlendConstant(m_gstate.dstAlpha))
3110 {
3111 batch->blendConstant = shaderPs.blendConstant;
3112 }
3113 *gstateChanged = true;
3114 }
3115 }
3116}
3117
3118bool Renderer::prepareRenderMergedBatch(Batch *batch, PreparedRenderBatch *renderBatch)
3119{
3120 if (batch->vertexCount == 0 || batch->indexCount == 0)
3121 return false;
3122
3123 Element *e = batch->first;
3124 Q_ASSERT(e);
3125
3126#ifndef QT_NO_DEBUG_OUTPUT
3127 if (Q_UNLIKELY(debug_render())) {
3128 QDebug debug = qDebug();
3129 debug << " -"
3130 << batch
3131 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3132 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3133 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3134 << "[ merged]"
3135 << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
3136 << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
3137 << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
3138 << " root:" << batch->root;
3139 if (batch->drawSets.size() > 1)
3140 debug << "sets:" << batch->drawSets.size();
3141 if (!batch->isOpaque)
3142 debug << "opacity:" << e->node->inheritedOpacity();
3143 batch->uploadedThisFrame = false;
3144 }
3145#endif
3146
3147 QSGGeometryNode *gn = e->node;
3148
3149 // We always have dirty matrix as all batches are at a unique z range.
3150 QSGMaterialShader::RenderState::DirtyStates dirty = QSGMaterialShader::RenderState::DirtyMatrix;
3151 if (batch->root)
3153 else
3156
3157 const int viewCount = projectionMatrixCount();
3159 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3160 m_current_projection_matrix[viewIndex] = projectionMatrix(viewIndex);
3161
3163 for (int viewIndex = 0; viewIndex < projectionMatrixWithNativeNDCCount(); ++viewIndex)
3165
3166 QSGMaterial *material = gn->activeMaterial();
3167 if (m_renderMode != QSGRendererInterface::RenderMode3D)
3168 updateClipState(gn->clipList(), batch);
3169
3170 const QSGGeometry *g = gn->geometry();
3171 const int multiViewCount = renderTarget().multiViewCount;
3172 ShaderManager::Shader *sms = useDepthBuffer() ? m_shaderManager->prepareMaterial(material, g, m_renderMode, multiViewCount)
3173 : m_shaderManager->prepareMaterialNoRewrite(material, g, m_renderMode, multiViewCount);
3174 if (!sms)
3175 return false;
3176
3177 Q_ASSERT(sms->materialShader);
3178 if (m_currentShader != sms)
3179 setActiveRhiShader(sms->materialShader, sms);
3180
3181 m_current_opacity = gn->inheritedOpacity();
3182 if (!qFuzzyCompare(sms->lastOpacity, float(m_current_opacity))) {
3184 sms->lastOpacity = m_current_opacity;
3185 }
3186
3188 const quint32 ubufSize = quint32(pd->masterUniformData.size());
3189 if (pd->ubufBinding >= 0) {
3190 bool ubufRebuild = false;
3191 if (!batch->ubuf) {
3193 ubufRebuild = true;
3194 } else {
3195 if (batch->ubuf->size() < ubufSize) {
3196 batch->ubuf->setSize(ubufSize);
3197 ubufRebuild = true;
3198 }
3199 }
3200 if (ubufRebuild) {
3201 batch->ubufDataValid = false;
3202 if (!batch->ubuf->create()) {
3203 qWarning("Failed to build uniform buffer of size %u bytes", ubufSize);
3204 delete batch->ubuf;
3205 batch->ubuf = nullptr;
3206 return false;
3207 }
3208 }
3209 }
3210
3211 QSGMaterialShader::RenderState renderState = state(QSGMaterialShader::RenderState::DirtyStates(int(dirty)));
3212
3213 bool pendingGStatePop = false;
3214 updateMaterialStaticData(sms, renderState, material, batch, &pendingGStatePop);
3215
3216 updateMaterialDynamicData(sms, renderState, material, batch, e, 0, ubufSize);
3217
3218#ifndef QT_NO_DEBUG
3220 qDebug("QSGMaterial::updateState triggered an error (merged), batch will be skipped:");
3221 Element *ee = e;
3222 while (ee) {
3223 qDebug() << " -" << ee->node;
3224 ee = ee->nextInBatch;
3225 }
3227 qFatal("Aborting: scene graph is invalid...");
3228 }
3229#endif
3230
3231 m_gstate.drawMode = QSGGeometry::DrawingMode(g->drawingMode());
3232 m_gstate.lineWidth = g->lineWidth();
3233
3234 const bool hasPipeline = ensurePipelineState(e, sms);
3235
3236 if (pendingGStatePop)
3237 m_gstate = m_gstateStack.pop();
3238
3239 if (!hasPipeline)
3240 return false;
3241
3242 if (m_renderMode == QSGRendererInterface::RenderMode3D) {
3243 m_gstateStack.push(m_gstate);
3244 setStateForDepthPostPass();
3245 ensurePipelineState(e, sms, true);
3246 m_gstate = m_gstateStack.pop();
3247 }
3248
3249 batch->ubufDataValid = true;
3250
3251 m_currentMaterial = material;
3252
3253 renderBatch->batch = batch;
3254 renderBatch->sms = sms;
3255
3256 return true;
3257}
3258
3259void Renderer::checkLineWidth(QSGGeometry *g)
3260{
3261 if (g->drawingMode() == QSGGeometry::DrawLines || g->drawingMode() == QSGGeometry::DrawLineLoop
3262 || g->drawingMode() == QSGGeometry::DrawLineStrip)
3263 {
3264 if (g->lineWidth() != 1.0f) {
3265 static bool checkedWideLineSupport = false;
3266 if (!checkedWideLineSupport) {
3267 checkedWideLineSupport = true;
3269 qWarning("Line widths other than 1 are not supported by the graphics API");
3270 }
3271 }
3272 } else if (g->drawingMode() == QSGGeometry::DrawPoints) {
3273 if (g->lineWidth() != 1.0f) {
3274 static bool warnedPointSize = false;
3275 if (!warnedPointSize) {
3276 warnedPointSize = true;
3277 qWarning("Point size is not controllable by QSGGeometry. "
3278 "Set gl_PointSize from the vertex shader instead.");
3279 }
3280 }
3281 }
3282}
3283
3284void Renderer::renderMergedBatch(PreparedRenderBatch *renderBatch, bool depthPostPass)
3285{
3286 const Batch *batch = renderBatch->batch;
3287 if (!batch->vbo.buf || !batch->ibo.buf)
3288 return;
3289
3290 Element *e = batch->first;
3291 QSGGeometryNode *gn = e->node;
3292 QSGGeometry *g = gn->geometry();
3293 checkLineWidth(g);
3294
3295 if (batch->clipState.type & ClipState::StencilClip)
3296 enqueueStencilDraw(batch);
3297
3299 setGraphicsPipeline(cb, batch, e, depthPostPass);
3300
3301 for (int i = 0, ie = batch->drawSets.size(); i != ie; ++i) {
3302 const DrawSet &draw = batch->drawSets.at(i);
3303 const QRhiCommandBuffer::VertexInput vbufBindings[] = {
3304 { batch->vbo.buf, quint32(draw.vertices) },
3305 { batch->vbo.buf, quint32(draw.zorders) }
3306 };
3307 cb->setVertexInput(VERTEX_BUFFER_BINDING, useDepthBuffer() ? 2 : 1, vbufBindings,
3308 batch->ibo.buf, draw.indices,
3309 m_uint32IndexForRhi ? QRhiCommandBuffer::IndexUInt32 : QRhiCommandBuffer::IndexUInt16);
3310 cb->drawIndexed(draw.indexCount);
3311 }
3312}
3313
3314bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *renderBatch)
3315{
3316 if (batch->vertexCount == 0)
3317 return false;
3318
3319 Element *e = batch->first;
3320 Q_ASSERT(e);
3321
3322 if (Q_UNLIKELY(debug_render())) {
3323 qDebug() << " -"
3324 << batch
3325 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3326 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3327 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3328 << "[unmerged]"
3329 << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
3330 << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
3331 << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
3332 << " root:" << batch->root;
3333
3334 batch->uploadedThisFrame = false;
3335 }
3336
3337 const int viewCount = projectionMatrixCount();
3339 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3340 m_current_projection_matrix[viewIndex] = projectionMatrix(viewIndex);
3341
3343 for (int viewIndex = 0; viewIndex < projectionMatrixWithNativeNDCCount(); ++viewIndex)
3345
3346 QSGGeometryNode *gn = e->node;
3347 if (m_renderMode != QSGRendererInterface::RenderMode3D)
3348 updateClipState(gn->clipList(), batch);
3349
3350 // We always have dirty matrix as all batches are at a unique z range.
3351 QSGMaterialShader::RenderState::DirtyStates dirty = QSGMaterialShader::RenderState::DirtyMatrix;
3352
3353 // The vertex attributes are assumed to be the same for all elements in the
3354 // unmerged batch since the material (and so the shaders) is the same.
3355 QSGGeometry *g = gn->geometry();
3356 QSGMaterial *material = gn->activeMaterial();
3357 ShaderManager::Shader *sms = m_shaderManager->prepareMaterialNoRewrite(material, g, m_renderMode, renderTarget().multiViewCount);
3358 if (!sms)
3359 return false;
3360
3361 Q_ASSERT(sms->materialShader);
3362 if (m_currentShader != sms)
3363 setActiveRhiShader(sms->materialShader, sms);
3364
3365 m_current_opacity = gn->inheritedOpacity();
3366 if (sms->lastOpacity != m_current_opacity) {
3368 sms->lastOpacity = m_current_opacity;
3369 }
3370
3371 QMatrix4x4 rootMatrix = batch->root ? qsg_matrixForRoot(batch->root) : QMatrix4x4();
3372
3374 const quint32 ubufSize = quint32(pd->masterUniformData.size());
3375 if (pd->ubufBinding >= 0) {
3376 quint32 totalUBufSize = 0;
3377 while (e) {
3378 totalUBufSize += aligned(ubufSize, m_ubufAlignment);
3379 e = e->nextInBatch;
3380 }
3381 bool ubufRebuild = false;
3382 if (!batch->ubuf) {
3383 batch->ubuf = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, totalUBufSize);
3384 ubufRebuild = true;
3385 } else {
3386 if (batch->ubuf->size() < totalUBufSize) {
3387 batch->ubuf->setSize(totalUBufSize);
3388 ubufRebuild = true;
3389 }
3390 }
3391 if (ubufRebuild) {
3392 batch->ubufDataValid = false;
3393 if (!batch->ubuf->create()) {
3394 qWarning("Failed to build uniform buffer of size %u bytes", totalUBufSize);
3395 delete batch->ubuf;
3396 batch->ubuf = nullptr;
3397 return false;
3398 }
3399 }
3400 }
3401
3402 QSGMaterialShader::RenderState renderState = state(QSGMaterialShader::RenderState::DirtyStates(int(dirty)));
3403 bool pendingGStatePop = false;
3404 updateMaterialStaticData(sms, renderState,
3405 material, batch, &pendingGStatePop);
3406
3407 int ubufOffset = 0;
3408 QRhiGraphicsPipeline *ps = nullptr;
3409 QRhiGraphicsPipeline *depthPostPassPs = nullptr;
3410 e = batch->first;
3411 while (e) {
3412 gn = e->node;
3413
3414 m_current_model_view_matrix = rootMatrix * *gn->matrix();
3416
3417 const int viewCount = projectionMatrixCount();
3419 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3420 m_current_projection_matrix[viewIndex] = projectionMatrix(viewIndex);
3421
3423 for (int viewIndex = 0; viewIndex < projectionMatrixWithNativeNDCCount(); ++viewIndex)
3425
3426 if (useDepthBuffer()) {
3427 // this cannot be multiview
3428 m_current_projection_matrix[0](2, 2) = m_zRange;
3430 }
3431
3432 QSGMaterialShader::RenderState renderState = state(QSGMaterialShader::RenderState::DirtyStates(int(dirty)));
3433 updateMaterialDynamicData(sms, renderState, material, batch, e, ubufOffset, ubufSize);
3434
3435#ifndef QT_NO_DEBUG
3437 qDebug("QSGMaterial::updateState() triggered an error (unmerged), batch will be skipped:");
3438 qDebug() << " - offending node is" << e->node;
3440 qFatal("Aborting: scene graph is invalid...");
3441 return false;
3442 }
3443#endif
3444
3445 ubufOffset += aligned(ubufSize, m_ubufAlignment);
3446
3447 const QSGGeometry::DrawingMode prevDrawMode = m_gstate.drawMode;
3448 const float prevLineWidth = m_gstate.lineWidth;
3449 m_gstate.drawMode = QSGGeometry::DrawingMode(g->drawingMode());
3450 m_gstate.lineWidth = g->lineWidth();
3451
3452 // Do not bother even looking up the ps if the topology has not changed
3453 // since everything else is the same for all elements in the batch.
3454 // (except if the material modified blend state)
3455 if (!ps || m_gstate.drawMode != prevDrawMode || m_gstate.lineWidth != prevLineWidth || pendingGStatePop) {
3456 if (!ensurePipelineState(e, sms)) {
3457 if (pendingGStatePop)
3458 m_gstate = m_gstateStack.pop();
3459 return false;
3460 }
3461 ps = e->ps;
3462 if (m_renderMode == QSGRendererInterface::RenderMode3D) {
3463 m_gstateStack.push(m_gstate);
3464 setStateForDepthPostPass();
3465 ensurePipelineState(e, sms, true);
3466 m_gstate = m_gstateStack.pop();
3467 depthPostPassPs = e->depthPostPassPs;
3468 }
3469 } else {
3470 e->ps = ps;
3471 if (m_renderMode == QSGRendererInterface::RenderMode3D)
3472 e->depthPostPassPs = depthPostPassPs;
3473 }
3474
3475 // We don't need to bother with asking each node for its material as they
3476 // are all identical (compare==0) since they are in the same batch.
3477 m_currentMaterial = material;
3478
3479 // We only need to push this on the very first iteration...
3480 dirty &= ~QSGMaterialShader::RenderState::DirtyOpacity;
3481
3482 e = e->nextInBatch;
3483 }
3484
3485 if (pendingGStatePop)
3486 m_gstate = m_gstateStack.pop();
3487
3488 batch->ubufDataValid = true;
3489
3490 renderBatch->batch = batch;
3491 renderBatch->sms = sms;
3492
3493 return true;
3494}
3495
3496void Renderer::renderUnmergedBatch(PreparedRenderBatch *renderBatch, bool depthPostPass)
3497{
3498 const Batch *batch = renderBatch->batch;
3499 if (!batch->vbo.buf)
3500 return;
3501
3502 Element *e = batch->first;
3503
3504 if (batch->clipState.type & ClipState::StencilClip)
3505 enqueueStencilDraw(batch);
3506
3507 quint32 vOffset = 0;
3508 quint32 iOffset = 0;
3510
3511 while (e) {
3512 QSGGeometry *g = e->node->geometry();
3513 checkLineWidth(g);
3514 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
3515
3516 setGraphicsPipeline(cb, batch, e, depthPostPass);
3517
3518 const QRhiCommandBuffer::VertexInput vbufBinding(batch->vbo.buf, vOffset);
3519 if (g->indexCount()) {
3520 if (batch->ibo.buf) {
3521 cb->setVertexInput(VERTEX_BUFFER_BINDING, 1, &vbufBinding,
3522 batch->ibo.buf, iOffset,
3523 effectiveIndexSize == sizeof(quint32) ? QRhiCommandBuffer::IndexUInt32
3524 : QRhiCommandBuffer::IndexUInt16);
3525 cb->drawIndexed(g->indexCount());
3526 }
3527 } else {
3528 cb->setVertexInput(VERTEX_BUFFER_BINDING, 1, &vbufBinding);
3529 cb->draw(g->vertexCount());
3530 }
3531
3532 vOffset += g->sizeOfVertex() * g->vertexCount();
3533 iOffset += g->indexCount() * effectiveIndexSize;
3534
3535 e = e->nextInBatch;
3536 }
3537}
3538
3539void Renderer::setGraphicsPipeline(QRhiCommandBuffer *cb, const Batch *batch, Element *e, bool depthPostPass)
3540{
3541 cb->setGraphicsPipeline(depthPostPass ? e->depthPostPassPs : e->ps);
3542
3543 if (!m_pstate.viewportSet) {
3544 m_pstate.viewportSet = true;
3545 cb->setViewport(m_pstate.viewport);
3546 }
3547 if (batch->clipState.type & ClipState::ScissorClip) {
3548 Q_ASSERT(e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesScissor));
3549 m_pstate.scissorSet = true;
3550 cb->setScissor(batch->clipState.scissor);
3551 } else {
3552 Q_ASSERT(!e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesScissor));
3553 // Regardless of the ps not using scissor, the scissor may need to be
3554 // reset, depending on the backend. So set the viewport again, which in
3555 // turn also sets the scissor on backends where a scissor rect is
3556 // always-on (Vulkan).
3557 if (m_pstate.scissorSet) {
3558 m_pstate.scissorSet = false;
3559 cb->setViewport(m_pstate.viewport);
3560 }
3561 }
3562 if (batch->clipState.type & ClipState::StencilClip) {
3563 Q_ASSERT(e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesStencilRef));
3564 cb->setStencilRef(batch->clipState.stencilRef);
3565 }
3566 if (!depthPostPass && e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesBlendConstants))
3567 cb->setBlendConstants(batch->blendConstant);
3568
3569 cb->setShaderResources(e->srb);
3570}
3571
3572void Renderer::releaseElement(Element *e, bool inDestructor)
3573{
3574 if (e->isRenderNode) {
3575 delete static_cast<RenderNodeElement *>(e);
3576 } else {
3577 if (e->srb) {
3578 if (!inDestructor) {
3579 if (m_shaderManager->srbPool.size() < m_srbPoolThreshold)
3580 m_shaderManager->srbPool.insert(e->srb->serializedLayoutDescription(), e->srb);
3581 else
3582 delete e->srb;
3583 } else {
3584 delete e->srb;
3585 }
3586 e->srb = nullptr;
3587 }
3588 m_elementAllocator.release(e);
3589 }
3590}
3591
3592void Renderer::deleteRemovedElements()
3593{
3594 if (!m_elementsToDelete.size())
3595 return;
3596
3597 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3598 Element **e = m_opaqueRenderList.data() + i;
3599 if (*e && (*e)->removed)
3600 *e = nullptr;
3601 }
3602 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3603 Element **e = m_alphaRenderList.data() + i;
3604 if (*e && (*e)->removed)
3605 *e = nullptr;
3606 }
3607
3608 for (int i=0; i<m_elementsToDelete.size(); ++i)
3609 releaseElement(m_elementsToDelete.at(i));
3610
3611 m_elementsToDelete.reset();
3612}
3613
3615{
3616 // Gracefully handle the lack of a render target - some autotests may rely
3617 // on this in odd cases.
3618 if (!renderTarget().rt)
3619 return;
3620
3621 prepareRenderPass(&m_mainRenderPassContext);
3622 beginRenderPass(&m_mainRenderPassContext);
3623 recordRenderPass(&m_mainRenderPassContext);
3624 endRenderPass(&m_mainRenderPassContext);
3625}
3626
3627// An alternative to render() is to call prepareInline() and renderInline() at
3628// the appropriate times (i.e. outside of a QRhi::beginPass() and then inside,
3629// respectively) These allow rendering within a render pass that is started by
3630// another component. In contrast, render() records a full render pass on its
3631// own.
3632
3634{
3635 prepareRenderPass(&m_mainRenderPassContext);
3636}
3637
3639{
3640 recordRenderPass(&m_mainRenderPassContext);
3641}
3642
3644{
3645 if (ctx->valid)
3646 qWarning("prepareRenderPass() called with an already prepared render pass context");
3647
3648 ctx->valid = true;
3649
3650 if (Q_UNLIKELY(debug_dump())) {
3651 qDebug("\n");
3653 }
3654
3655 ctx->timeRenderLists = 0;
3656 ctx->timePrepareOpaque = 0;
3657 ctx->timePrepareAlpha = 0;
3658 ctx->timeSorting = 0;
3659 ctx->timeUploadOpaque = 0;
3660 ctx->timeUploadAlpha = 0;
3661
3662 if (Q_UNLIKELY(debug_render() || debug_build())) {
3663 QByteArray type("rebuild:");
3664 if (m_rebuild == 0)
3665 type += " none";
3666 if (m_rebuild == FullRebuild)
3667 type += " full";
3668 else {
3669 if (m_rebuild & BuildRenderLists)
3670 type += " renderlists";
3671 else if (m_rebuild & BuildRenderListsForTaggedRoots)
3672 type += " partial";
3673 else if (m_rebuild & BuildBatches)
3674 type += " batches";
3675 }
3676
3677 qDebug() << "Renderer::render()" << this << type;
3678 ctx->timer.start();
3679 }
3680
3681 m_resourceUpdates = m_rhi->nextResourceUpdateBatch();
3682
3683 if (m_rebuild & (BuildRenderLists | BuildRenderListsForTaggedRoots)) {
3684 bool complete = (m_rebuild & BuildRenderLists) != 0;
3685 if (complete)
3686 buildRenderListsFromScratch();
3687 else
3688 buildRenderListsForTaggedRoots();
3689 m_rebuild |= BuildBatches;
3690
3691 if (Q_UNLIKELY(debug_build())) {
3692 qDebug("Opaque render lists %s:", (complete ? "(complete)" : "(partial)"));
3693 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3694 Element *e = m_opaqueRenderList.at(i);
3695 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3696 }
3697 qDebug("Alpha render list %s:", complete ? "(complete)" : "(partial)");
3698 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3699 Element *e = m_alphaRenderList.at(i);
3700 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3701 }
3702 }
3703 }
3704 if (Q_UNLIKELY(debug_render())) ctx->timeRenderLists = ctx->timer.restart();
3705
3706 for (int i=0; i<m_opaqueBatches.size(); ++i)
3707 m_opaqueBatches.at(i)->cleanupRemovedElements();
3708 for (int i=0; i<m_alphaBatches.size(); ++i)
3709 m_alphaBatches.at(i)->cleanupRemovedElements();
3710 deleteRemovedElements();
3711
3712 cleanupBatches(&m_opaqueBatches);
3713 cleanupBatches(&m_alphaBatches);
3714
3715 if (m_rebuild & BuildBatches) {
3716 prepareOpaqueBatches();
3717 if (Q_UNLIKELY(debug_render())) ctx->timePrepareOpaque = ctx->timer.restart();
3718 prepareAlphaBatches();
3719 if (Q_UNLIKELY(debug_render())) ctx->timePrepareAlpha = ctx->timer.restart();
3720
3721 if (Q_UNLIKELY(debug_build())) {
3722 qDebug("Opaque Batches:");
3723 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3724 Batch *b = m_opaqueBatches.at(i);
3725 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3726 for (Element *e = b->first; e; e = e->nextInBatch) {
3727 qDebug() << " - element:" << e << " node:" << e->node << e->order;
3728 }
3729 }
3730 qDebug("Alpha Batches:");
3731 for (int i=0; i<m_alphaBatches.size(); ++i) {
3732 Batch *b = m_alphaBatches.at(i);
3733 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3734 for (Element *e = b->first; e; e = e->nextInBatch) {
3735 qDebug() << " - element:" << e << e->bounds << " node:" << e->node << " order:" << e->order;
3736 }
3737 }
3738 }
3739 } else {
3740 if (Q_UNLIKELY(debug_render())) ctx->timePrepareOpaque = ctx->timePrepareAlpha = ctx->timer.restart();
3741 }
3742
3743
3744 deleteRemovedElements();
3745
3746 if (m_rebuild != 0) {
3747 // Then sort opaque batches so that we're drawing the batches with the highest
3748 // order first, maximizing the benefit of front-to-back z-ordering.
3749 if (m_opaqueBatches.size())
3750 std::sort(&m_opaqueBatches.first(), &m_opaqueBatches.last() + 1, qsg_sort_batch_decreasing_order);
3751
3752 // Sort alpha batches back to front so that they render correctly.
3753 if (m_alphaBatches.size())
3754 std::sort(&m_alphaBatches.first(), &m_alphaBatches.last() + 1, qsg_sort_batch_increasing_order);
3755
3756 m_zRange = m_nextRenderOrder != 0
3757 ? 1.0 / (m_nextRenderOrder)
3758 : 0;
3759 }
3760
3761 if (Q_UNLIKELY(debug_render())) ctx->timeSorting = ctx->timer.restart();
3762
3763 // Set size to 0, nothing is deallocated, they will "grow" again
3764 // as part of uploadBatch.
3765 m_vertexUploadPool.reset();
3766 m_indexUploadPool.reset();
3767
3768 if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Opaque Batches:");
3769 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3770 Batch *b = m_opaqueBatches.at(i);
3771 uploadBatch(b);
3772 }
3773 if (Q_UNLIKELY(debug_render())) ctx->timeUploadOpaque = ctx->timer.restart();
3774
3775 if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Alpha Batches:");
3776 for (int i=0; i<m_alphaBatches.size(); ++i) {
3777 Batch *b = m_alphaBatches.at(i);
3778 uploadBatch(b);
3779 }
3780 if (Q_UNLIKELY(debug_render())) ctx->timeUploadAlpha = ctx->timer.restart();
3781
3782 if (Q_UNLIKELY(debug_render())) {
3783 qDebug().nospace() << "Rendering:" << Qt::endl
3784 << " -> Opaque: " << qsg_countNodesInBatches(m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << Qt::endl
3785 << " -> Alpha: " << qsg_countNodesInBatches(m_alphaBatches) << " nodes in " << m_alphaBatches.size() << " batches...";
3786 }
3787
3789 m_currentMaterial = nullptr;
3790 m_currentShader = nullptr;
3791 m_currentProgram = nullptr;
3792 m_currentClipState.reset();
3793
3794 const QRect viewport = viewportRect();
3795
3796 bool renderOpaque = !debug_noopaque();
3797 bool renderAlpha = !debug_noalpha();
3798
3799 m_pstate.viewport =
3802 m_pstate.clearColor = clearColor();
3803 m_pstate.dsClear = QRhiDepthStencilClearValue(1.0f, 0);
3804 m_pstate.viewportSet = false;
3805 m_pstate.scissorSet = false;
3806
3807 m_gstate.depthTest = useDepthBuffer();
3808 m_gstate.depthWrite = useDepthBuffer();
3810 m_gstate.blending = false;
3811
3818 m_gstate.usesScissor = false;
3819 m_gstate.stencilTest = false;
3820
3821 m_gstate.sampleCount = renderTarget().rt->sampleCount();
3823
3824 ctx->opaqueRenderBatches.clear();
3825 if (Q_LIKELY(renderOpaque)) {
3826 for (int i = 0, ie = m_opaqueBatches.size(); i != ie; ++i) {
3827 Batch *b = m_opaqueBatches.at(i);
3828 PreparedRenderBatch renderBatch;
3829 bool ok;
3830 if (b->merged)
3831 ok = prepareRenderMergedBatch(b, &renderBatch);
3832 else
3833 ok = prepareRenderUnmergedBatch(b, &renderBatch);
3834 if (ok)
3835 ctx->opaqueRenderBatches.append(renderBatch);
3836 }
3837 }
3838
3839 m_gstate.blending = true;
3840 // factors never change, always set for premultiplied alpha based blending
3841
3842 // depth test stays enabled (if useDepthBuffer(), that is) but no need
3843 // to write out depth from the transparent (back-to-front) pass
3844 m_gstate.depthWrite = false;
3845
3846 // special case: the 3D plane mode tests against the depth buffer, but does
3847 // not write (and all batches are alpha because this render mode evaluates
3848 // to useDepthBuffer()==false)
3849 if (m_renderMode == QSGRendererInterface::RenderMode3D) {
3850 Q_ASSERT(m_opaqueBatches.isEmpty());
3851 m_gstate.depthTest = true;
3852 }
3853
3854 ctx->alphaRenderBatches.clear();
3855 if (Q_LIKELY(renderAlpha)) {
3856 for (int i = 0, ie = m_alphaBatches.size(); i != ie; ++i) {
3857 Batch *b = m_alphaBatches.at(i);
3858 PreparedRenderBatch renderBatch;
3859 bool ok;
3860 if (b->merged)
3861 ok = prepareRenderMergedBatch(b, &renderBatch);
3862 else if (b->isRenderNode)
3863 ok = prepareRhiRenderNode(b, &renderBatch);
3864 else
3865 ok = prepareRenderUnmergedBatch(b, &renderBatch);
3866 if (ok)
3867 ctx->alphaRenderBatches.append(renderBatch);
3868 }
3869 }
3870
3871 m_rebuild = 0;
3872
3873#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
3874 m_renderOrderRebuildLower = -1;
3875 m_renderOrderRebuildUpper = -1;
3876#endif
3877
3878 if (m_visualizer->mode() != Visualizer::VisualizeNothing)
3879 m_visualizer->prepareVisualize();
3880
3881 renderTarget().cb->resourceUpdate(m_resourceUpdates);
3882 m_resourceUpdates = nullptr;
3883}
3884
3886{
3887 const QSGRenderTarget &rt(renderTarget());
3888 rt.cb->beginPass(rt.rt, m_pstate.clearColor, m_pstate.dsClear, nullptr,
3889 // we cannot tell if the application will have
3890 // native rendering thrown in to this pass
3891 // (QQuickWindow::beginExternalCommands()), so
3892 // we have no choice but to set the flag always
3893 // (thus triggering using secondary command
3894 // buffers with Vulkan)
3896
3899}
3900
3902{
3903 // prepareRenderPass and recordRenderPass must always be called together.
3904 // They are separate because beginRenderPass and endRenderPass are optional.
3905 //
3906 // The valid call sequence are therefore:
3907 // prepare, begin, record, end
3908 // or
3909 // prepare, record
3910
3911 if (!ctx->valid)
3912 qWarning("recordRenderPass() called without a prepared render pass context");
3913
3914 ctx->valid = false;
3915
3917 cb->debugMarkBegin(QByteArrayLiteral("Qt Quick scene render"));
3918
3919 for (int i = 0, ie = ctx->opaqueRenderBatches.size(); i != ie; ++i) {
3920 if (i == 0)
3921 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick opaque batches"));
3922 PreparedRenderBatch *renderBatch = &ctx->opaqueRenderBatches[i];
3923 if (renderBatch->batch->merged)
3924 renderMergedBatch(renderBatch);
3925 else
3926 renderUnmergedBatch(renderBatch);
3927 }
3928
3929 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
3930 if (i == 0) {
3931 if (m_renderMode == QSGRendererInterface::RenderMode3D)
3932 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D batches"));
3933 else
3934 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick alpha batches"));
3935 }
3936 PreparedRenderBatch *renderBatch = &ctx->alphaRenderBatches[i];
3937 if (renderBatch->batch->merged)
3938 renderMergedBatch(renderBatch);
3939 else if (renderBatch->batch->isRenderNode)
3940 renderRhiRenderNode(renderBatch->batch);
3941 else
3942 renderUnmergedBatch(renderBatch);
3943 }
3944
3945 if (m_renderMode == QSGRendererInterface::RenderMode3D) {
3946 // Depth post-pass to fill up the depth buffer in a way that it
3947 // corresponds to what got rendered to the color buffer in the previous
3948 // (alpha) pass. The previous pass cannot enable depth write due to Z
3949 // fighting. Rather, do it separately in a dedicated color-write-off,
3950 // depth-write-on pass. This enables the 3D content drawn afterwards to
3951 // depth test against the 2D items' rendering.
3952 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
3953 if (i == 0)
3954 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D depth post-pass"));
3955 PreparedRenderBatch *renderBatch = &ctx->alphaRenderBatches[i];
3956 if (renderBatch->batch->merged)
3957 renderMergedBatch(renderBatch, true);
3958 else if (!renderBatch->batch->isRenderNode) // rendernodes are skipped here for now
3959 renderUnmergedBatch(renderBatch, true);
3960 }
3961 }
3962
3963 if (m_currentShader)
3964 setActiveRhiShader(nullptr, nullptr);
3965
3966 cb->debugMarkEnd();
3967
3968 if (Q_UNLIKELY(debug_render())) {
3969 qDebug(" -> times: build: %d, prepare(opaque/alpha): %d/%d, sorting: %d, upload(opaque/alpha): %d/%d, record rendering: %d",
3970 (int) ctx->timeRenderLists,
3971 (int) ctx->timePrepareOpaque, (int) ctx->timePrepareAlpha,
3972 (int) ctx->timeSorting,
3973 (int) ctx->timeUploadOpaque, (int) ctx->timeUploadAlpha,
3974 (int) ctx->timer.elapsed());
3975 }
3976}
3977
3979{
3982
3983 if (m_visualizer->mode() != Visualizer::VisualizeNothing)
3984 m_visualizer->visualize();
3985
3986 renderTarget().cb->endPass();
3987}
3988
3990{
3991 const QMatrix4x4 *projectionMatrix() const override { return m_projectionMatrix; }
3992 QRect scissorRect() const override { return m_scissorRect; }
3993 bool scissorEnabled() const override { return m_scissorEnabled; }
3994 int stencilValue() const override { return m_stencilValue; }
3995 bool stencilEnabled() const override { return m_stencilEnabled; }
3996 const QRegion *clipRegion() const override { return nullptr; }
3997
4003};
4004
4005bool Renderer::prepareRhiRenderNode(Batch *batch, PreparedRenderBatch *renderBatch)
4006{
4007 if (Q_UNLIKELY(debug_render()))
4008 qDebug() << " -" << batch << "rendernode";
4009
4010 Q_ASSERT(batch->first->isRenderNode);
4011 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4012
4013 setActiveRhiShader(nullptr, nullptr);
4014
4016 rd->m_clip_list = nullptr;
4017 if (m_renderMode != QSGRendererInterface::RenderMode3D) {
4018 QSGNode *clip = e->renderNode->parent();
4019 while (clip != rootNode()) {
4020 if (clip->type() == QSGNode::ClipNodeType) {
4021 rd->m_clip_list = static_cast<QSGClipNode *>(clip);
4022 break;
4023 }
4024 clip = clip->parent();
4025 }
4026 updateClipState(rd->m_clip_list, batch);
4027 }
4028
4029 QSGNode *xform = e->renderNode->parent();
4031 QSGNode *root = rootNode();
4032 if (e->root) {
4034 root = e->root->sgNode;
4035 }
4036 while (xform != root) {
4038 matrix = matrix * static_cast<QSGTransformNode *>(xform)->combinedMatrix();
4039 break;
4040 }
4041 xform = xform->parent();
4042 }
4043 rd->m_localMatrix = matrix;
4044 rd->m_matrix = &rd->m_localMatrix;
4045
4046 QSGNode *opacity = e->renderNode->parent();
4047 rd->m_opacity = 1.0;
4048 while (opacity != rootNode()) {
4049 if (opacity->type() == QSGNode::OpacityNodeType) {
4050 rd->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity();
4051 break;
4052 }
4053 opacity = opacity->parent();
4054 }
4055
4056 rd->m_rt = renderTarget();
4057
4058 const int viewCount = projectionMatrixCount();
4059 rd->m_projectionMatrix.resize(viewCount);
4060 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
4061 rd->m_projectionMatrix[viewIndex] = projectionMatrix(viewIndex);
4062
4063 if (useDepthBuffer()) {
4064 // this cannot be multiview
4065 rd->m_projectionMatrix[0](2, 2) = m_zRange;
4066 rd->m_projectionMatrix[0](2, 3) = calculateElementZOrder(e, m_zRange);
4067 }
4068
4069 e->renderNode->prepare();
4070
4071 renderBatch->batch = batch;
4072 renderBatch->sms = nullptr;
4073
4074 return true;
4075}
4076
4077void Renderer::renderRhiRenderNode(const Batch *batch)
4078{
4079 if (batch->clipState.type & ClipState::StencilClip)
4080 enqueueStencilDraw(batch);
4081
4082 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4084
4086 // Expose only the first matrix through the state object, the rest are
4087 // queriable through the QSGRenderNode getters anyway.
4088 state.m_projectionMatrix = &rd->m_projectionMatrix[0];
4089 const std::array<int, 4> scissor = batch->clipState.scissor.scissor();
4090 state.m_scissorRect = QRect(scissor[0], scissor[1], scissor[2], scissor[3]);
4091 state.m_stencilValue = batch->clipState.stencilRef;
4092 state.m_scissorEnabled = batch->clipState.type & ClipState::ScissorClip;
4093 state.m_stencilEnabled = batch->clipState.type & ClipState::StencilClip;
4094
4095 const QSGRenderNode::StateFlags changes = e->renderNode->changedStates();
4096
4098 const bool needsExternal = !e->renderNode->flags().testFlag(QSGRenderNode::NoExternalRendering);
4099 if (needsExternal)
4100 cb->beginExternal();
4101 e->renderNode->render(&state);
4102 if (needsExternal)
4103 cb->endExternal();
4104
4105 rd->m_matrix = nullptr;
4106 rd->m_clip_list = nullptr;
4107
4108 if ((changes & QSGRenderNode::ViewportState)
4109 || (changes & QSGRenderNode::ScissorState))
4110 {
4111 // Reset both flags if either is reported as changed, since with the rhi
4112 // it could be setViewport() that will record the resetting of the scissor.
4113 m_pstate.viewportSet = false;
4114 m_pstate.scissorSet = false;
4115 }
4116
4117 // Do not bother with RenderTargetState. Where applicable, endExternal()
4118 // ensures the correct target is rebound. For others (like Vulkan) it makes
4119 // no sense since render() could not possibly do that on our command buffer
4120 // which is in renderpass recording state.
4121}
4122
4124{
4125 if (mode.isEmpty())
4127 else if (mode == "clip")
4129 else if (mode == "overdraw")
4131 else if (mode == "batches")
4133 else if (mode == "changes")
4135}
4136
4138{
4139 return m_visualizer->mode() == Visualizer::VisualizeOverdraw;
4140}
4141
4142bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
4143{
4144 return a.depthTest == b.depthTest
4145 && a.depthWrite == b.depthWrite
4146 && a.depthFunc == b.depthFunc
4147 && a.blending == b.blending
4148 && a.srcColor == b.srcColor
4149 && a.dstColor == b.dstColor
4150 && a.srcAlpha == b.srcAlpha
4151 && a.dstAlpha == b.dstAlpha
4152 && a.opColor == b.opColor
4153 && a.opAlpha == b.opAlpha
4154 && a.colorWrite == b.colorWrite
4155 && a.cullMode == b.cullMode
4156 && a.usesScissor == b.usesScissor
4157 && a.stencilTest == b.stencilTest
4158 && a.sampleCount == b.sampleCount
4159 && a.drawMode == b.drawMode
4160 && a.lineWidth == b.lineWidth
4161 && a.polygonMode == b.polygonMode
4162 && a.multiViewCount == b.multiViewCount;
4163}
4164
4165bool operator!=(const GraphicsState &a, const GraphicsState &b) noexcept
4166{
4167 return !(a == b);
4168}
4169
4170size_t qHash(const GraphicsState &s, size_t seed) noexcept
4171{
4172 // do not bother with all fields
4173 return seed
4174 + s.depthTest * 1000
4175 + s.depthWrite * 100
4176 + s.depthFunc
4177 + s.blending * 10
4178 + s.srcColor
4179 + s.cullMode
4180 + s.usesScissor
4181 + s.stencilTest
4182 + s.sampleCount
4183 + s.multiViewCount;
4184}
4185
4187{
4188 return a.state == b.state
4189 && a.sms->materialShader == b.sms->materialShader
4190 && a.renderTargetDescription == b.renderTargetDescription
4191 && a.srbLayoutDescription == b.srbLayoutDescription;
4192}
4193
4195{
4196 return !(a == b);
4197}
4198
4199size_t qHash(const GraphicsPipelineStateKey &k, size_t seed) noexcept
4200{
4201 return qHash(k.state, seed)
4202 ^ qHash(k.sms->materialShader)
4203 ^ k.extra.renderTargetDescriptionHash
4204 ^ k.extra.srbLayoutDescriptionHash;
4205}
4206
4207bool operator==(const ShaderKey &a, const ShaderKey &b) noexcept
4208{
4209 return a.type == b.type
4210 && a.renderMode == b.renderMode
4211 && a.multiViewCount == b.multiViewCount;
4212}
4213
4214bool operator!=(const ShaderKey &a, const ShaderKey &b) noexcept
4215{
4216 return !(a == b);
4217}
4218
4219size_t qHash(const ShaderKey &k, size_t seed) noexcept
4220{
4221 return qHash(k.type, seed) ^ int(k.renderMode) ^ k.multiViewCount;
4222}
4223
4225 : m_renderer(renderer),
4226 m_visualizeMode(VisualizeNothing)
4227{
4228}
4229
4233
4234#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded \
4235 | QSGNode::DirtyOpacity \
4236 | QSGNode::DirtyMatrix \
4237 | QSGNode::DirtyNodeRemoved)
4238
4240{
4241 uint childDirty = (parentChanges | n->dirtyState) & QSGNODE_DIRTY_PARENT;
4242 uint selfDirty = n->dirtyState | parentChanges;
4243 if (n->type() == QSGNode::GeometryNodeType && selfDirty != 0)
4244 m_visualizeChangeSet.insert(n, selfDirty);
4246 visualizeChangesPrepare(child, childDirty);
4247 }
4248}
4249
4250} // namespace QSGBatchRenderer
4251
4253
4254#include "moc_qsgbatchrenderer_p.cpp"
Definition lalr.h:136
bool root
Definition lalr.h:175
_Tp data
Definition lalr.h:177
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
void add(const Type &t)
Type & at(qsizetype i)
qsizetype size() const
void shrink(qsizetype size)
Type & last()
void pop_back()
\inmodule QtCore
void clear() noexcept(std::is_nothrow_destructible< Node >::value)
Removes all items from the hash and frees up all memory used by it.
Definition qhash.h:951
\inmodule QtGui
Definition qimage.h:37
@ Format_RGBA8888_Premultiplied
Definition qimage.h:60
The QMatrix4x4 class represents a 4x4 transformation matrix in 3D space.
Definition qmatrix4x4.h:25
double determinant() const
Returns the determinant of this matrix.
void setToIdentity()
Sets this matrix to the identity.
Definition qmatrix4x4.h:316
T findChild(QAnyStringView aName, Qt::FindChildOptions options=Qt::FindChildrenRecursively) const
Returns the child of this object that can be cast into type T and that is called name,...
Definition qobject.h:155
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void setParent(QObject *parent)
Makes the object a child of parent.
Definition qobject.cpp:2195
Q_WEAK_OVERLOAD void setObjectName(const QString &name)
Sets the object's name to name.
Definition qobject.h:127
\inmodule QtCore\reentrant
Definition qrect.h:484
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr int height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:239
constexpr int bottom() const noexcept
Returns the y-coordinate of the rectangle's bottom edge.
Definition qrect.h:182
constexpr int x() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:185
constexpr int width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:236
constexpr int y() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:188
The QRegion class specifies a clip region for a painter.
Definition qregion.h:27
\inmodule QtGui
Definition qrhi.h:846
@ Immutable
Definition qrhi.h:849
@ Dynamic
Definition qrhi.h:851
@ IndexBuffer
Definition qrhi.h:856
@ VertexBuffer
Definition qrhi.h:855
@ UniformBuffer
Definition qrhi.h:857
void setSize(quint32 sz)
Sets the size of the buffer in bytes.
Definition qrhi.h:876
\inmodule QtGui
Definition qrhi.h:1651
void endPass(QRhiResourceUpdateBatch *resourceUpdates=nullptr)
Records ending the current render pass.
Definition qrhi.cpp:9460
void resourceUpdate(QRhiResourceUpdateBatch *resourceUpdates)
Sometimes committing resource updates is necessary or just more convenient without starting a render ...
Definition qrhi.cpp:9384
void beginPass(QRhiRenderTarget *rt, const QColor &colorClearValue, const QRhiDepthStencilClearValue &depthStencilClearValue, QRhiResourceUpdateBatch *resourceUpdates=nullptr, BeginPassFlags flags={})
Records starting a new render pass targeting the render target rt.
Definition qrhi.cpp:9443
QPair< int, quint32 > DynamicOffset
Synonym for QPair<int, quint32>.
Definition qrhi.h:1676
QPair< QRhiBuffer *, quint32 > VertexInput
Synonym for QPair<QRhiBuffer *, quint32>.
Definition qrhi.h:1680
IndexFormat
Specifies the index data type.
Definition qrhi.h:1653
\inmodule QtGui
Definition qrhi.h:44
\inmodule QtGui
Definition qrhi.h:1270
BlendOp
Specifies the blend operation.
Definition qrhi.h:1331
void setStencilFront(const StencilOpState &state)
Sets the stencil test state for front faces.
Definition qrhi.h:1423
void setCullMode(CullMode mode)
Sets the specified face culling mode.
Definition qrhi.h:1393
PolygonMode
Specifies the polygon rasterization mode.
Definition qrhi.h:1379
void setTargetBlends(std::initializer_list< TargetBlend > list)
Sets the list of render target blend settings.
Definition qrhi.h:1398
void setStencilTest(bool enable)
Enables or disables stencil tests based on enable.
Definition qrhi.h:1420
BlendFactor
Specifies the blend factor.
Definition qrhi.h:1309
void setDepthWrite(bool enable)
Controls the writing out of depth data into the depth buffer based on enable.
Definition qrhi.h:1414
void setShaderResourceBindings(QRhiShaderResourceBindings *srb)
Associates with srb describing the resource binding layout and the resources (QRhiBuffer,...
Definition qrhi.h:1462
void setDepthOp(CompareOp op)
Sets the depth comparison function op.
Definition qrhi.h:1417
void setVertexInputLayout(const QRhiVertexInputLayout &layout)
Specifies the vertex input layout.
Definition qrhi.h:1459
CullMode
Specifies the culling mode.
Definition qrhi.h:1290
void setFlags(Flags f)
Sets the flags f.
Definition qrhi.h:1387
void setShaderStages(std::initializer_list< QRhiShaderStage > list)
Sets the list of shader stages.
Definition qrhi.h:1446
void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc)
Associates with the specified QRhiRenderPassDescriptor desc.
Definition qrhi.h:1465
void setSampleCount(int s)
Sets the sample count.
Definition qrhi.h:1435
void setTopology(Topology t)
Sets the primitive topology t.
Definition qrhi.h:1390
void setMultiViewCount(int count)
Sets the view count for multiview rendering.
Definition qrhi.h:1474
Topology
Specifies the primitive topology.
Definition qrhi.h:1280
virtual bool create()=0
Creates the corresponding native graphics resources.
void setDepthTest(bool enable)
Enables or disables depth testing based on enable.
Definition qrhi.h:1411
void setPolygonMode(PolygonMode mode)
Sets the polygon mode.
Definition qrhi.h:1471
void setLineWidth(float width)
Sets the line width.
Definition qrhi.h:1438
void setStencilBack(const StencilOpState &state)
Sets the stencil test state for back faces.
Definition qrhi.h:1426
virtual int sampleCount() const =0
void updateDynamicBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data)
Enqueues updating a region of a QRhiBuffer buf created with the type QRhiBuffer::Dynamic.
Definition qrhi.cpp:8991
void uploadStaticBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data)
Enqueues updating a region of a QRhiBuffer buf created with the type QRhiBuffer::Immutable or QRhiBuf...
Definition qrhi.cpp:9011
void uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc)
Enqueues uploading the image data for one or more mip levels in one or more layers of the texture tex...
Definition qrhi.cpp:9079
\inmodule QtGui
Definition qrhi.h:1030
Filter
Specifies the minification, magnification, or mipmap filtering.
Definition qrhi.h:1032
AddressMode
Specifies the addressing mode.
Definition qrhi.h:1038
@ ClampToEdge
Definition qrhi.h:1040
\inmodule QtGui
Definition qrhi.h:138
\inmodule QtGui
Definition qrhi.h:439
static void serializeLayoutDescription(const QRhiShaderResourceBinding *first, const QRhiShaderResourceBinding *last, Output dst)
Definition qrhi.h:549
static QRhiShaderResourceBinding sampledTextures(int binding, StageFlags stage, int count, const TextureAndSampler *texSamplers)
Definition qrhi.cpp:5686
static QRhiShaderResourceBinding uniformBufferWithDynamicOffset(int binding, StageFlags stage, QRhiBuffer *buf, quint32 size)
Definition qrhi.cpp:5600
static QRhiShaderResourceBinding uniformBuffer(int binding, StageFlags stage, QRhiBuffer *buf)
Definition qrhi.cpp:5526
\inmodule QtGui
Definition qrhi.h:1214
\inmodule QtGui
Definition qrhi.h:379
\inmodule QtGui
Definition qrhi.h:895
virtual bool create()=0
Creates the corresponding native graphics resources.
QSize pixelSize() const
Definition qrhi.h:975
\inmodule QtGui
Definition qrhi.h:232
Format
Specifies the type of the element data.
Definition qrhi.h:234
\inmodule QtGui
Definition qrhi.h:179
\inmodule QtGui
Definition qrhi.h:321
void setBindings(std::initializer_list< QRhiVertexInputBinding > list)
Sets the bindings from the specified list.
Definition qrhi.h:325
void setAttributes(std::initializer_list< QRhiVertexInputAttribute > list)
Sets the attributes from the specified list.
Definition qrhi.h:337
\inmodule QtGui
Definition qrhi.h:85
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1804
QRhiBuffer * newBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
Definition qrhi.cpp:10508
bool isFeatureSupported(QRhi::Feature feature) const
Definition qrhi.cpp:10110
QRhiShaderResourceBindings * newShaderResourceBindings()
Definition qrhi.cpp:10489
QRhiSampler * newSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, QRhiSampler::AddressMode addressU, QRhiSampler::AddressMode addressV, QRhiSampler::AddressMode addressW=QRhiSampler::Repeat)
Definition qrhi.cpp:10665
QRhiGraphicsPipeline * newGraphicsPipeline()
Definition qrhi.cpp:10466
QRhiTexture * newTexture(QRhiTexture::Format format, const QSize &pixelSize, int sampleCount=1, QRhiTexture::Flags flags={})
Definition qrhi.cpp:10562
@ WideLines
Definition qrhi.h:1845
@ NonFourAlignedEffectiveIndexBufferOffset
Definition qrhi.h:1840
QRhiResourceUpdateBatch * nextResourceUpdateBatch()
Definition qrhi.cpp:9252
int ubufAlignment() const
Definition qrhi.cpp:10968
void releaseCachedResources()
Attempts to release resources in the backend's caches.
Definition qrhi.cpp:10175
QRect deviceRect() const
Returns the device rect of the surface being rendered to.
QRect viewportRect() const
Returns the rect of the viewport to render.
QSGRootNode * rootNode() const
Returns the root of the QSGNode scene.
QMatrix4x4 projectionMatrixWithNativeNDC(int index) const
int projectionMatrixWithNativeNDCCount() const
QMatrix4x4 projectionMatrix(int index) const
Returns the projection matrix.
QColor clearColor() const
Returns the color that clears the framebuffer at the beginning of the rendering.
const QSGClipNode * clipList() const
Will be set during rendering to contain the clip of the geometry for that rendering pass.
Definition qsgnode.h:164
const QSGGeometry * geometry() const
Returns this node's geometry.
Definition qsgnode.h:160
void setRendererMatrix(const QMatrix4x4 *m)
Definition qsgnode.h:166
const QMatrix4x4 * matrix() const
Will be set during rendering to contain transformation of the geometry for that rendering pass.
Definition qsgnode.h:163
void beginRenderPass(RenderPassContext *ctx)
bool hasVisualizationModeWithContinuousUpdate() const override
void nodeChanged(QSGNode *node, QSGNode::DirtyState state) override
Updates internal data structures and emits the sceneGraphChanged() signal.
void setVisualizationMode(const QByteArray &mode) override
void recordRenderPass(RenderPassContext *ctx)
void prepareRenderPass(RenderPassContext *ctx)
Renderer(QSGDefaultRenderContext *ctx, QSGRendererInterface::RenderMode renderMode=QSGRendererInterface::RenderMode2D)
void endRenderPass(RenderPassContext *ctx)
Shader * prepareMaterial(QSGMaterial *material, const QSGGeometry *geometry=nullptr, QSGRendererInterface::RenderMode renderMode=QSGRendererInterface::RenderMode2D, int multiViewCount=0)
QMultiHash< QVector< quint32 >, QRhiShaderResourceBindings * > srbPool
QHash< GraphicsPipelineStateKey, QRhiGraphicsPipeline * > pipelineCache
QVector< quint32 > srbLayoutDescSerializeWorkspace
Shader * prepareMaterialNoRewrite(QSGMaterial *material, const QSGGeometry *geometry=nullptr, QSGRendererInterface::RenderMode renderMode=QSGRendererInterface::RenderMode2D, int multiViewCount=0)
void updateStates(QSGNode *n) override
void updateRootTransforms(Node *n)
virtual void visualize()=0
void setMode(VisualizeMode mode)
virtual void visualizeChangesPrepare(Node *n, uint parentChanges=0)
virtual void prepareVisualize()=0
virtual void releaseResources()=0
QHash< Node *, uint > m_visualizeChangeSet
The QSGClipNode class implements the clipping functionality in the scene graph.
Definition qsgnode.h:221
bool isRectangular() const
Returns if this clip node has a rectangular clip.
Definition qsgnode.h:227
QRectF clipRect() const
Returns the clip rect of this node.
Definition qsgnode.h:230
virtual void initializeRhiShader(QSGMaterialShader *shader, QShader::Variant shaderVariant)
The QSGGeometryNode class is used for all rendered content in the scene graph.
Definition qsgnode.h:188
QSGMaterial * activeMaterial() const
Returns the material which should currently be used for geometry node.
Definition qsgnode.cpp:987
The QSGGeometry class provides low-level storage for graphics primitives in the \l{Qt Quick Scene Gra...
Definition qsggeometry.h:15
DrawingMode
Specifies the drawing mode, also called primitive topology.
Definition qsggeometry.h:33
const Attribute * attributes() const
Returns an array with the attributes of this geometry.
int attributeCount() const
Returns the number of attributes in the attrbute set used by this geometry.
int indexType() const
Returns the primitive type used for indices in this geometry object.
int sizeOfVertex() const
Returns the size in bytes of one vertex.
int vertexCount() const
Returns the number of vertices in this geometry object.
QVarLengthArray< QRhiSampler *, 4 > samplerBindingTable[MAX_SHADER_RESOURCE_BINDINGS]
QRhiShaderResourceBinding::StageFlags combinedImageSamplerBindings[MAX_SHADER_RESOURCE_BINDINGS]
int combinedImageSamplerCount[MAX_SHADER_RESOURCE_BINDINGS]
static const int MAX_SHADER_RESOURCE_BINDINGS
static QSGMaterialShaderPrivate * get(QSGMaterialShader *s)
QRhiShaderResourceBinding::StageFlags ubufStages
QVarLengthArray< QSGTexture *, 4 > textureBindingTable[MAX_SHADER_RESOURCE_BINDINGS]
static QShader loadShader(const QString &filename)
Encapsulates the current rendering state during a call to QSGMaterialShader::updateUniformData() and ...
The QSGMaterialShader class represents a graphics API independent shader program.
The QSGMaterial class encapsulates rendering state for a shader program.
Definition qsgmaterial.h:15
virtual QSGMaterialShader * createShader(QSGRendererInterface::RenderMode renderMode) const =0
This function returns a new instance of a the QSGMaterialShader implementation used to render geometr...
int viewCount() const
virtual int compare(const QSGMaterial *other) const
Compares this material to other and returns 0 if they are equal; -1 if this material should sort befo...
virtual QSGMaterialType * type() const =0
This function is called by the scene graph to query an identifier that is unique to the QSGMaterialSh...
void setFlag(Flags flags, bool on=true)
Sets the flags flags on this material if on is true; otherwise clears the attribute.
Flag
\value Blending Set this flag to true if the material requires blending to be enabled during renderin...
Definition qsgmaterial.h:17
@ RequiresFullMatrixExceptTranslate
Definition qsgmaterial.h:20
static void dump(QSGNode *n)
QDataBuffer< qreal > m_opacity_stack
const QSGClipNode * m_current_clip
QDataBuffer< const QMatrix4x4 * > m_combined_matrix_stack
\group qtquick-scenegraph-nodes \title Qt Quick Scene Graph Node classes
Definition qsgnode.h:37
@ DirtyMaterial
Definition qsgnode.h:75
@ DirtyNodeAdded
Definition qsgnode.h:72
@ DirtyForceUpdate
Definition qsgnode.h:78
@ DirtySubtreeBlocked
Definition qsgnode.h:70
@ DirtyNodeRemoved
Definition qsgnode.h:73
@ DirtyOpacity
Definition qsgnode.h:76
@ DirtyGeometry
Definition qsgnode.h:74
@ DirtyMatrix
Definition qsgnode.h:71
virtual bool isSubtreeBlocked() const
Returns whether this node and its subtree is available for use.
Definition qsgnode.cpp:325
@ TransformNodeType
Definition qsgnode.h:42
@ GeometryNodeType
Definition qsgnode.h:41
@ RenderNodeType
Definition qsgnode.h:46
@ ClipNodeType
Definition qsgnode.h:43
@ OpacityNodeType
Definition qsgnode.h:44
QSGNode * parent() const
Returns the parent node of this node.
Definition qsgnode.h:93
NodeType type() const
Returns the type of this node.
Definition qsgnode.h:110
The QSGOpacityNode class is used to change opacity of nodes.
Definition qsgnode.h:276
qreal opacity() const
Returns this opacity node's opacity.
Definition qsgnode.h:282
void setCombinedOpacity(qreal opacity)
Sets the combined opacity of this node to opacity.
Definition qsgnode.cpp:1352
static QSGRenderNodePrivate * get(QSGRenderNode *node)
The QSGRenderNode class represents a set of custom rendering commands targeting the graphics API that...
virtual void prepare()
Called from the frame preparation phase.
QRhiCommandBuffer * cb
QRhiRenderPassDescriptor * rpDesc
QRhiRenderTarget * rt
RenderMode
\value RenderMode2D Normal 2D rendering \value RenderMode2DNoDepthBuffer Normal 2D rendering with dep...
The renderer class is the abstract baseclass used for rendering the QML scene graph.
void setNodeUpdater(QSGNodeUpdater *updater)
Sets the node updater that this renderer uses to update states in the scene graph.
qreal m_current_opacity
QVarLengthArray< QMatrix4x4, 1 > m_current_projection_matrix_native_ndc
void nodeChanged(QSGNode *node, QSGNode::DirtyState state) override
Updates internal data structures and emits the sceneGraphChanged() signal.
const QSGRenderTarget & renderTarget() const
QRhiResourceUpdateBatch * m_current_resource_update_batch
QMatrix4x4 m_current_model_view_matrix
QByteArray * m_current_uniform_data
qreal m_current_determinant
struct QSGRenderer::@701 m_renderPassRecordingCallbacks
QVarLengthArray< QMatrix4x4, 1 > m_current_projection_matrix
QSGNodeUpdater * nodeUpdater() const
Returns the node updater that this renderer uses to update states in the scene graph.
static QSGTexturePrivate * get(QSGTexture *t)
void resetDirtySamplerOptions()
bool hasDirtySamplerOptions() const
\inmodule QtQuick
Definition qsgtexture.h:20
virtual QRhiTexture * rhiTexture() const
The QSGTransformNode class implements transformations in the scene graph.
Definition qsgnode.h:241
const_iterator constBegin() const noexcept
Definition qset.h:139
const_iterator constEnd() const noexcept
Definition qset.h:143
@ BatchableVertexShader
Definition qshader.h:105
@ StandardShader
Definition qshader.h:104
@ VertexStage
Definition qshader.h:84
@ FragmentStage
Definition qshader.h:88
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
TransformationType type() const
Returns the transformation type of this matrix.
constexpr size_type size() const noexcept
const T & at(qsizetype idx) const
void resize(qsizetype sz)
The QWidget class is the base class of all user interface objects.
Definition qwidget.h:99
QRect geometry
the geometry of the widget relative to its parent and excluding the window frame
Definition qwidget.h:106
void create(WId=0, bool initializeWindow=true, bool destroyOldWindow=true)
Creates a new widget window.
Definition qwidget.cpp:1156
EGLContext ctx
QMap< QString, QString > map
[6]
qDeleteAll(list.begin(), list.end())
QSet< QString >::iterator it
else opt state
[0]
short next
Definition keywords.cpp:445
const int ZORDER_BUFFER_BINDING
static void qsg_wipeBuffer(Buffer *buffer)
static void rendererToMaterialGraphicsState(QSGMaterialShader::GraphicsPipelineState *dst, GraphicsState *src)
size_t qHash(const GraphicsState &s, size_t seed) noexcept
static QRhiVertexInputLayout calculateVertexInputLayout(const QSGMaterialShader *s, const QSGGeometry *geometry, bool batchable)
static void qsg_addBackOrphanedElements(QDataBuffer< Element * > &orphans, QDataBuffer< Element * > &renderList)
const float VIEWPORT_MIN_DEPTH
QSGMaterial::Flag QSGMaterial_FullMatrix
bool qsg_sort_batch_decreasing_order(Batch *a, Batch *b)
static void qsg_wipeBatch(Batch *batch)
static QRhiSampler * newSampler(QRhi *rhi, const QSGSamplerDescription &desc)
QRhiVertexInputAttribute::Format qsg_vertexInputFormat(const QSGGeometry::Attribute &a)
QMatrix4x4 qsg_matrixForRoot(Node *node)
bool qsg_sort_batch_increasing_order(Batch *a, Batch *b)
static void qsg_addOrphanedElements(QDataBuffer< Element * > &orphans, const QDataBuffer< Element * > &renderList)
void qsg_setMultiViewFlagsOnMaterial(QSGMaterial *material, int multiViewCount)
const float VIEWPORT_MAX_DEPTH
int qsg_positionAttribute(QSGGeometry *g)
static int size_of_type(int type)
static bool isTranslate(const QMatrix4x4 &m)
static int qsg_countNodesInBatches(const QDataBuffer< Batch * > &batches)
static float calculateElementZOrder(const Element *e, qreal zRange)
static int qsg_fixIndexCount(int iCount, int drawMode)
bool qsg_sort_element_increasing_order(Element *a, Element *b)
static void materialToRendererGraphicsState(GraphicsState *dst, QSGMaterialShader::GraphicsPipelineState *src)
static bool needsBlendConstant(QRhiGraphicsPipeline::BlendFactor f)
const int VERTEX_BUFFER_BINDING
const uint DYNAMIC_VERTEX_INDEX_BUFFER_THRESHOLD
bool hasMaterialWithBlending(QSGGeometryNode *n)
static int qsg_countNodesInBatch(const Batch *batch)
bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
QRhiCommandBuffer::IndexFormat qsg_indexFormat(const QSGGeometry *geometry)
bool operator!=(const GraphicsState &a, const GraphicsState &b) noexcept
bool qsg_sort_element_decreasing_order(Element *a, Element *b)
void qsg_dumpShadowRoots(BatchRootInfo *i, int indent)
static bool isScale(const QMatrix4x4 &m)
static bool is2DSafe(const QMatrix4x4 &m)
QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode)
Int aligned(Int v, Int byteAlign)
bool qsg_sort_batch_is_valid(Batch *a, Batch *b)
Combined button and popup list for selecting options.
QTextStream & hex(QTextStream &stream)
Calls QTextStream::setIntegerBase(16) on stream and returns stream.
QTextStream & dec(QTextStream &stream)
Calls QTextStream::setIntegerBase(10) on stream and returns stream.
@ FindDirectChildrenOnly
@ DirectConnection
QTextStream & endl(QTextStream &stream)
Writes '\n' to the stream and flushes the stream.
#define QByteArrayLiteral(str)
Definition qbytearray.h:52
#define Q_FALLTHROUGH()
#define Q_UNLIKELY(x)
#define Q_LIKELY(x)
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:349
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:327
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
#define qFatal
Definition qlogging.h:168
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
static Q_DECL_CONST_FUNCTION bool qt_is_finite(double d)
Definition qnumeric_p.h:117
#define SLOT(a)
Definition qobjectdefs.h:52
#define SIGNAL(a)
Definition qobjectdefs.h:53
GLboolean GLboolean GLboolean b
GLbitfield stages
GLsizei const GLfloat * v
[13]
GLenum mode
const GLfloat * m
GLuint64 key
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLboolean r
[2]
GLuint sampler
GLenum GLenum GLsizei count
GLfloat GLfloat f
GLenum src
GLenum GLuint buffer
GLenum type
GLenum GLenum dst
GLint GLint bottom
GLenum GLuint GLenum GLsizei const GLchar * buf
GLbitfield flags
GLenum GLuint texture
GLuint program
GLenum GLuint GLintptr offset
GLboolean GLboolean g
GLuint name
GLint first
GLfloat n
GLsizei GLenum const void * indices
GLdouble s
[6]
Definition qopenglext.h:235
const GLubyte * c
GLint void * img
Definition qopenglext.h:233
GLuint GLsizei const GLuint const GLintptr const GLsizeiptr * sizes
GLuint shader
Definition qopenglext.h:665
GLuint GLenum matrix
GLdouble GLdouble t
Definition qopenglext.h:243
GLuint * samplers
GLfloat GLfloat p
[1]
#define DECLARE_DEBUG_VAR(variable)
static Q_CONSTINIT QBasicAtomicInteger< unsigned > seed
Definition qrandom.cpp:196
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
int qt_sg_envInt(const char *name, int defaultValue)
QT_BEGIN_NAMESPACE Q_QUICK_EXPORT bool qsg_test_and_clear_material_failure()
#define QSGNODE_DIRTY_PARENT
#define SHADOWNODE_TRAVERSE(NODE)
#define QSGNODE_TRAVERSE(NODE)
static QString dump(const QByteArray &)
SSL_CTX int void * arg
SSL_CTX int(* cb)(SSL *ssl, unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
QT_BEGIN_NAMESPACE constexpr void qSwap(T &value1, T &value2) noexcept(std::is_nothrow_swappable_v< T >)
Definition qswap.h:20
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
#define Q_UNUSED(x)
unsigned int quint32
Definition qtypes.h:50
unsigned short quint16
Definition qtypes.h:48
int qint32
Definition qtypes.h:49
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned int uint
Definition qtypes.h:34
double qreal
Definition qtypes.h:187
ptrdiff_t qintptr
Definition qtypes.h:166
float vertexData[]
QFuture< QSet< QChar > > set
[10]
Q_CHECK_PTR(a=new int[80])
if(qFloatDistance(a, b)<(1<< 7))
[0]
QRandomGenerator64 rd
[10]
QObject::connect nullptr
view viewport() -> scroll(dx, dy, deviceRect)
myFilter draw(painter, QPoint(0, 0), originalPixmap)
QLayoutItem * child
[0]
myWidget render(this)
QHostInfo info
[0]
QSvgRenderer * renderer
[0]
\variable QRhiGraphicsPipeline::TargetBlend::colorWrite
Definition qrhi.h:1372
bool geometryWasChanged(QSGGeometryNode *gn)
StencilClipState stencilClipState
BatchCompatibility isMaterialCompatible(Element *e) const
static GraphicsPipelineStateKey create(const GraphicsState &state, const ShaderManagerShader *sms, const QRhiRenderPassDescriptor *rpDesc, const QRhiShaderResourceBindings *srb)
QSGGeometry::DrawingMode drawMode
QRhiGraphicsPipeline::BlendFactor dstColor
QRhiGraphicsPipeline::BlendOp opColor
QRhiGraphicsPipeline::CompareOp depthFunc
QRhiGraphicsPipeline::ColorMask colorWrite
QRhiGraphicsPipeline::CullMode cullMode
QRhiGraphicsPipeline::BlendFactor srcAlpha
QRhiGraphicsPipeline::BlendFactor dstAlpha
QRhiGraphicsPipeline::BlendFactor srcColor
QRhiGraphicsPipeline::PolygonMode polygonMode
QRhiGraphicsPipeline::BlendOp opAlpha
QSGNode::NodeType type() const
QSGNode::DirtyState dirtyState
void map(const QMatrix4x4 &mat)
void set(float left, float top, float right, float bottom)
void map(const QMatrix4x4 &m)
const QMatrix4x4 * projectionMatrix() const override
const QRegion * clipRegion() const override
QRhiDepthStencilClearValue dsClear
The QSGGeometry::Attribute describes a single vertex attribute in a QSGGeometry.
Definition qsggeometry.h:58
Describes state changes that the material wants to apply to the currently active graphics pipeline st...
PolygonMode
Specifies the polygon rasterization mode.
The QSGMaterialType class is used as a unique type token in combination with QSGMaterial.
static QSGSamplerDescription fromTexture(QSGTexture *t)