Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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>
17
19
20#include <algorithm>
21
23
24#ifndef QT_NO_DEBUG
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; }
43DECLARE_DEBUG_VAR(noalpha)
44DECLARE_DEBUG_VAR(noopaque)
46#undef DECLARE_DEBUG_VAR
47
48#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling())
49#define SHADOWNODE_TRAVERSE(NODE) for (Node *child = NODE->firstChild(); child; child = child->sibling())
50
51static inline int size_of_type(int type)
52{
53 static int sizes[] = {
54 sizeof(char),
55 sizeof(unsigned char),
56 sizeof(short),
57 sizeof(unsigned short),
58 sizeof(int),
59 sizeof(unsigned int),
60 sizeof(float),
61 2,
62 3,
63 4,
64 sizeof(double)
65 };
66 Q_ASSERT(type >= QSGGeometry::ByteType && type <= QSGGeometry::DoubleType);
67 return sizes[type - QSGGeometry::ByteType];
68}
69
72bool qsg_sort_batch_is_valid(Batch *a, Batch *b) { return a->first && !b->first; }
75
77
78static bool isTranslate(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Translation; }
79static bool isScale(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Scale; }
80static bool is2DSafe(const QMatrix4x4 &m) { return m.flags() < QMatrix4x4::Rotation; }
81
82const float OPAQUE_LIMIT = 0.999f;
83
87
88const float VIEWPORT_MIN_DEPTH = 0.0f;
89const float VIEWPORT_MAX_DEPTH = 1.0f;
90
91const quint32 DEFAULT_BUFFER_POOL_SIZE_LIMIT = 2 * 1024 * 1024; // 2 MB for m_vboPool and m_iboPool each
92
93template <class Int>
94inline Int aligned(Int v, Int byteAlign)
95{
96 return (v + byteAlign - 1) & ~(byteAlign - 1);
97}
98
99QRhiVertexInputAttribute::Format qsg_vertexInputFormat(const QSGGeometry::Attribute &a)
100{
101 switch (a.type) {
102 case QSGGeometry::FloatType:
103 if (a.tupleSize == 4)
104 return QRhiVertexInputAttribute::Float4;
105 if (a.tupleSize == 3)
106 return QRhiVertexInputAttribute::Float3;
107 if (a.tupleSize == 2)
108 return QRhiVertexInputAttribute::Float2;
109 if (a.tupleSize == 1)
110 return QRhiVertexInputAttribute::Float;
111 break;
112 case QSGGeometry::UnsignedByteType:
113 if (a.tupleSize == 4)
114 return QRhiVertexInputAttribute::UNormByte4;
115 if (a.tupleSize == 2)
116 return QRhiVertexInputAttribute::UNormByte2;
117 if (a.tupleSize == 1)
118 return QRhiVertexInputAttribute::UNormByte;
119 break;
120 default:
121 break;
122 }
123 qWarning("Unsupported attribute type 0x%x with %d components", a.type, a.tupleSize);
124 Q_UNREACHABLE_RETURN(QRhiVertexInputAttribute::Float);
125}
126
127static QRhiVertexInputLayout calculateVertexInputLayout(const QSGMaterialShader *s, const QSGGeometry *geometry, bool batchable)
128{
129 Q_ASSERT(geometry);
130 const QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
131 if (!sd->vertexShader) {
132 qWarning("No vertex shader in QSGMaterialShader %p", s);
133 return QRhiVertexInputLayout();
134 }
135
136 const int attrCount = geometry->attributeCount();
137 QVarLengthArray<QRhiVertexInputAttribute, 8> inputAttributes;
138 inputAttributes.reserve(attrCount + 1);
139 quint32 offset = 0;
140 for (int i = 0; i < attrCount; ++i) {
141 const QSGGeometry::Attribute &a = geometry->attributes()[i];
142 if (!sd->vertexShader->vertexInputLocations.contains(a.position)) {
143 qWarning("Vertex input %d is present in material but not in shader. This is wrong.",
144 a.position);
145 }
146 inputAttributes.append(QRhiVertexInputAttribute(VERTEX_BUFFER_BINDING, a.position, qsg_vertexInputFormat(a), offset));
147 offset += a.tupleSize * size_of_type(a.type);
148 }
149 if (batchable) {
150 inputAttributes.append(QRhiVertexInputAttribute(ZORDER_BUFFER_BINDING, sd->vertexShader->qt_order_attrib_location,
151 QRhiVertexInputAttribute::Float, 0));
152 }
153
154 Q_ASSERT(VERTEX_BUFFER_BINDING == 0 && ZORDER_BUFFER_BINDING == 1); // not very flexible
155 QVarLengthArray<QRhiVertexInputBinding, 2> inputBindings;
156 inputBindings.append(QRhiVertexInputBinding(geometry->sizeOfVertex()));
157 if (batchable)
158 inputBindings.append(QRhiVertexInputBinding(sizeof(float)));
159
160 QRhiVertexInputLayout inputLayout;
161 inputLayout.setBindings(inputBindings.cbegin(), inputBindings.cend());
162 inputLayout.setAttributes(inputAttributes.cbegin(), inputAttributes.cend());
163
164 return inputLayout;
165}
166
167QRhiCommandBuffer::IndexFormat qsg_indexFormat(const QSGGeometry *geometry)
168{
169 switch (geometry->indexType()) {
170 case QSGGeometry::UnsignedShortType:
171 return QRhiCommandBuffer::IndexUInt16;
172 break;
173 case QSGGeometry::UnsignedIntType:
174 return QRhiCommandBuffer::IndexUInt32;
175 break;
176 default:
177 Q_UNREACHABLE_RETURN(QRhiCommandBuffer::IndexUInt16);
178 }
179}
180
182{
183 QRhiGraphicsPipeline::Topology topology = QRhiGraphicsPipeline::Triangles;
184 switch (geomDrawMode) {
185 case QSGGeometry::DrawPoints:
186 topology = QRhiGraphicsPipeline::Points;
187 break;
188 case QSGGeometry::DrawLines:
189 topology = QRhiGraphicsPipeline::Lines;
190 break;
191 case QSGGeometry::DrawLineStrip:
192 topology = QRhiGraphicsPipeline::LineStrip;
193 break;
194 case QSGGeometry::DrawTriangles:
195 topology = QRhiGraphicsPipeline::Triangles;
196 break;
197 case QSGGeometry::DrawTriangleStrip:
198 topology = QRhiGraphicsPipeline::TriangleStrip;
199 break;
200 default:
201 qWarning("Primitive topology 0x%x not supported", geomDrawMode);
202 break;
203 }
204 return topology;
205}
206
207void qsg_setMultiViewFlagsOnMaterial(QSGMaterial *material, int multiViewCount)
208{
209 material->setFlag(QSGMaterial::MultiView2, multiViewCount == 2);
210 material->setFlag(QSGMaterial::MultiView3, multiViewCount == 3);
211 material->setFlag(QSGMaterial::MultiView4, multiViewCount == 4);
212}
213
214ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material,
215 const QSGGeometry *geometry,
216 QSGRendererInterface::RenderMode renderMode,
217 int multiViewCount)
218{
219 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
220
221 QSGMaterialType *type = material->type();
222 ShaderKey key = { type, renderMode, multiViewCount };
223 Shader *shader = rewrittenShaders.value(key, nullptr);
224 if (shader)
225 return shader;
226
227 shader = new Shader;
228 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
229 context->initializeRhiShader(s, QShader::BatchableVertexShader);
230 shader->materialShader = s;
231 shader->inputLayout = calculateVertexInputLayout(s, geometry, true);
232 QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
233 shader->stages = {
234 { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage), QShader::BatchableVertexShader },
235 { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
236 };
237
238 shader->lastOpacity = 0;
239
240 rewrittenShaders[key] = shader;
241 return shader;
242}
243
244ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material,
245 const QSGGeometry *geometry,
246 QSGRendererInterface::RenderMode renderMode,
247 int multiViewCount)
248{
249 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
250
251 QSGMaterialType *type = material->type();
252 ShaderKey key = { type, renderMode, multiViewCount };
253 Shader *shader = stockShaders.value(key, nullptr);
254 if (shader)
255 return shader;
256
257 shader = new Shader;
258 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
259 context->initializeRhiShader(s, QShader::StandardShader);
260 shader->materialShader = s;
261 shader->inputLayout = calculateVertexInputLayout(s, geometry, false);
262 QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
263 shader->stages = {
264 { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage) },
265 { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
266 };
267
268 shader->lastOpacity = 0;
269
270 stockShaders[key] = shader;
271
272 return shader;
273}
274
275void ShaderManager::invalidated()
276{
277 qDeleteAll(stockShaders);
278 stockShaders.clear();
279 qDeleteAll(rewrittenShaders);
280 rewrittenShaders.clear();
281
282 qDeleteAll(pipelineCache);
283 pipelineCache.clear();
284
285 qDeleteAll(srbPool);
286 srbPool.clear();
287}
288
290{
291 for (ShaderManager::Shader *sms : std::as_const(stockShaders)) {
292 QSGMaterialShader *s = sms->materialShader;
293 if (s) {
294 QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
295 sd->clearCachedRendererData();
296 }
297 }
298 for (ShaderManager::Shader *sms : std::as_const(rewrittenShaders)) {
299 QSGMaterialShader *s = sms->materialShader;
300 if (s) {
301 QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
302 sd->clearCachedRendererData();
303 }
304 }
305}
306
308{
309 static int extraIndent = 0;
310 ++extraIndent;
311
312 QByteArray ind(indent + extraIndent + 10, ' ');
313
314 if (!i) {
315 qDebug("%s - no info", ind.constData());
316 } else {
317 qDebug() << ind.constData() << "- parent:" << i->parentRoot << "orders" << i->firstOrder << "->" << i->lastOrder << ", avail:" << i->availableOrders;
318 for (QSet<Node *>::const_iterator it = i->subRoots.constBegin();
319 it != i->subRoots.constEnd(); ++it) {
320 qDebug() << ind.constData() << "-" << *it;
321 qsg_dumpShadowRoots((*it)->rootInfo(), indent);
322 }
323 }
324
325 --extraIndent;
326}
327
329{
330#ifndef QT_NO_DEBUG_OUTPUT
331 static int indent = 0;
332 ++indent;
333
334 QByteArray ind(indent, ' ');
335
336 if (n->type() == QSGNode::ClipNodeType || n->isBatchRoot) {
337 qDebug() << ind.constData() << "[X]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
339 } else {
340 QDebug d = qDebug();
341 d << ind.constData() << "[ ]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
342 if (n->type() == QSGNode::GeometryNodeType)
343 d << "order" << Qt::dec << n->element()->order;
344 }
345
348
349 --indent;
350#else
351 Q_UNUSED(n);
352#endif
353}
354
355Updater::Updater(Renderer *r)
356 : renderer(r)
357 , m_roots(32)
358 , m_rootMatrices(8)
359{
360 m_roots.add(0);
361 m_combined_matrix_stack.add(&m_identityMatrix);
362 m_rootMatrices.add(m_identityMatrix);
363}
364
365void Updater::updateStates(QSGNode *n)
366{
367 m_current_clip = nullptr;
368
369 m_added = 0;
370 m_transformChange = 0;
371 m_opacityChange = 0;
372
373 Node *sn = renderer->m_nodes.value(n, 0);
374 Q_ASSERT(sn);
375
376 if (Q_UNLIKELY(debug_roots()))
378
379 if (Q_UNLIKELY(debug_build())) {
380 qDebug("Updater::updateStates()");
381 if (sn->dirtyState & (QSGNode::DirtyNodeAdded << 16))
382 qDebug(" - nodes have been added");
383 if (sn->dirtyState & (QSGNode::DirtyMatrix << 16))
384 qDebug(" - transforms have changed");
385 if (sn->dirtyState & (QSGNode::DirtyOpacity << 16))
386 qDebug(" - opacity has changed");
387 if (uint(sn->dirtyState) & uint(QSGNode::DirtyForceUpdate << 16))
388 qDebug(" - forceupdate");
389 }
390
391 if (Q_UNLIKELY(renderer->m_visualizer->mode() == Visualizer::VisualizeChanges))
392 renderer->m_visualizer->visualizeChangesPrepare(sn);
393
394 visitNode(sn);
395}
396
398{
399 if (m_added == 0 && n->dirtyState == 0 && m_force_update == 0 && m_transformChange == 0 && m_opacityChange == 0)
400 return;
401
402 int count = m_added;
403 if (n->dirtyState & QSGNode::DirtyNodeAdded)
404 ++m_added;
405
406 int force = m_force_update;
407 if (n->dirtyState & QSGNode::DirtyForceUpdate)
408 ++m_force_update;
409
410 switch (n->type()) {
411 case QSGNode::OpacityNodeType:
413 break;
414 case QSGNode::TransformNodeType:
416 break;
417 case QSGNode::GeometryNodeType:
419 break;
420 case QSGNode::ClipNodeType:
422 break;
423 case QSGNode::RenderNodeType:
424 if (m_added)
425 n->renderNodeElement()->root = m_roots.last();
426 Q_FALLTHROUGH(); // to visit children
427 default:
429 break;
430 }
431
432 m_added = count;
433 m_force_update = force;
434 n->dirtyState = {};
435}
436
438{
440
441 QSGClipNode *cn = static_cast<QSGClipNode *>(n->sgNode);
442
443 if (m_roots.last() && m_added > 0)
444 renderer->registerBatchRoot(n, m_roots.last());
445
446 cn->setRendererClipList(m_current_clip);
447 m_current_clip = cn;
448 m_roots << n;
449 m_rootMatrices.add(m_rootMatrices.last() * *m_combined_matrix_stack.last());
450 extra->matrix = m_rootMatrices.last();
451 cn->setRendererMatrix(&extra->matrix);
452 m_combined_matrix_stack << &m_identityMatrix;
453
455
456 m_current_clip = cn->clipList();
457 m_rootMatrices.pop_back();
458 m_combined_matrix_stack.pop_back();
459 m_roots.pop_back();
460}
461
463{
464 QSGOpacityNode *on = static_cast<QSGOpacityNode *>(n->sgNode);
465
466 qreal combined = m_opacity_stack.last() * on->opacity();
467 on->setCombinedOpacity(combined);
468 m_opacity_stack.add(combined);
469
470 if (m_added == 0 && n->dirtyState & QSGNode::DirtyOpacity) {
471 bool was = n->isOpaque;
472 bool is = on->opacity() > OPAQUE_LIMIT;
473 if (was != is) {
474 renderer->m_rebuild = Renderer::FullRebuild;
475 n->isOpaque = is;
476 }
477 ++m_opacityChange;
479 --m_opacityChange;
480 } else {
481 if (m_added > 0)
482 n->isOpaque = on->opacity() > OPAQUE_LIMIT;
484 }
485
486 m_opacity_stack.pop_back();
487}
488
490{
491 bool popMatrixStack = false;
492 bool popRootStack = false;
493 bool dirty = n->dirtyState & QSGNode::DirtyMatrix;
494
495 QSGTransformNode *tn = static_cast<QSGTransformNode *>(n->sgNode);
496
497 if (n->isBatchRoot) {
498 if (m_added > 0 && m_roots.last())
499 renderer->registerBatchRoot(n, m_roots.last());
500 tn->setCombinedMatrix(m_rootMatrices.last() * *m_combined_matrix_stack.last() * tn->matrix());
501
502 // The only change in this subtree is ourselves and we are a batch root, so
503 // only update subroots and return, saving tons of child-processing (flickable-panning)
504
505 if (!n->becameBatchRoot && m_added == 0 && m_force_update == 0 && m_opacityChange == 0 && dirty && (n->dirtyState & ~QSGNode::DirtyMatrix) == 0) {
506 BatchRootInfo *info = renderer->batchRootInfo(n);
507 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
508 it != info->subRoots.constEnd(); ++it) {
509 updateRootTransforms(*it, n, tn->combinedMatrix());
510 }
511 return;
512 }
513
514 n->becameBatchRoot = false;
515
516 m_combined_matrix_stack.add(&m_identityMatrix);
517 m_roots.add(n);
518 m_rootMatrices.add(tn->combinedMatrix());
519
520 popMatrixStack = true;
521 popRootStack = true;
522 } else if (!tn->matrix().isIdentity()) {
523 tn->setCombinedMatrix(*m_combined_matrix_stack.last() * tn->matrix());
524 m_combined_matrix_stack.add(&tn->combinedMatrix());
525 popMatrixStack = true;
526 } else {
527 tn->setCombinedMatrix(*m_combined_matrix_stack.last());
528 }
529
530 if (dirty)
531 ++m_transformChange;
532
534
535 if (dirty)
536 --m_transformChange;
537 if (popMatrixStack)
538 m_combined_matrix_stack.pop_back();
539 if (popRootStack) {
540 m_roots.pop_back();
541 m_rootMatrices.pop_back();
542 }
543}
544
546{
547 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode);
548
549 gn->setRendererMatrix(m_combined_matrix_stack.last());
550 gn->setRendererClipList(m_current_clip);
551 gn->setInheritedOpacity(m_opacity_stack.last());
552
553 if (m_added) {
554 Element *e = n->element();
555 e->root = m_roots.last();
556 e->translateOnlyToRoot = isTranslate(*gn->matrix());
557
558 if (e->root) {
559 BatchRootInfo *info = renderer->batchRootInfo(e->root);
560 while (info != nullptr) {
561 info->availableOrders--;
562 if (info->availableOrders < 0) {
563 renderer->m_rebuild |= Renderer::BuildRenderLists;
564 } else {
565 renderer->m_rebuild |= Renderer::BuildRenderListsForTaggedRoots;
566 renderer->m_taggedRoots << e->root;
567 }
568 if (info->parentRoot != nullptr)
569 info = renderer->batchRootInfo(info->parentRoot);
570 else
571 info = nullptr;
572 }
573 } else {
574 renderer->m_rebuild |= Renderer::FullRebuild;
575 }
576 } else {
577 if (m_transformChange) {
578 Element *e = n->element();
579 e->translateOnlyToRoot = isTranslate(*gn->matrix());
580 }
581 if (m_opacityChange) {
582 Element *e = n->element();
583 if (e->batch)
584 renderer->invalidateBatchAndOverlappingRenderOrders(e->batch);
585 }
586 }
587
589}
590
591void Updater::updateRootTransforms(Node *node, Node *root, const QMatrix4x4 &combined)
592{
593 BatchRootInfo *info = renderer->batchRootInfo(node);
594 QMatrix4x4 m;
595 Node *n = node;
596
597 while (n != root) {
598 if (n->type() == QSGNode::TransformNodeType)
599 m = static_cast<QSGTransformNode *>(n->sgNode)->matrix() * m;
600 n = n->parent();
601 }
602
603 m = combined * m;
604
605 if (node->type() == QSGNode::ClipNodeType) {
606 static_cast<ClipBatchRootInfo *>(info)->matrix = m;
607 } else {
608 Q_ASSERT(node->type() == QSGNode::TransformNodeType);
609 static_cast<QSGTransformNode *>(node->sgNode)->setCombinedMatrix(m);
610 }
611
612 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
613 it != info->subRoots.constEnd(); ++it) {
614 updateRootTransforms(*it, node, m);
615 }
616}
617
618int qsg_positionAttribute(QSGGeometry *g)
619{
620 int vaOffset = 0;
621 for (int a=0; a<g->attributeCount(); ++a) {
622 const QSGGeometry::Attribute &attr = g->attributes()[a];
623 if (attr.isVertexCoordinate && attr.tupleSize == 2 && attr.type == QSGGeometry::FloatType) {
624 return vaOffset;
625 }
626 vaOffset += attr.tupleSize * size_of_type(attr.type);
627 }
628 return -1;
629}
630
631
632void Rect::map(const QMatrix4x4 &matrix)
633{
634 const float *m = matrix.constData();
635 if (isScale(matrix)) {
636 tl.x = tl.x * m[0] + m[12];
637 tl.y = tl.y * m[5] + m[13];
638 br.x = br.x * m[0] + m[12];
639 br.y = br.y * m[5] + m[13];
640 if (tl.x > br.x)
641 qSwap(tl.x, br.x);
642 if (tl.y > br.y)
643 qSwap(tl.y, br.y);
644 } else {
645 Pt mtl = tl;
646 Pt mtr = { br.x, tl.y };
647 Pt mbl = { tl.x, br.y };
648 Pt mbr = br;
649
650 mtl.map(matrix);
651 mtr.map(matrix);
652 mbl.map(matrix);
653 mbr.map(matrix);
654
655 set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
656 (*this) |= mtl;
657 (*this) |= mtr;
658 (*this) |= mbl;
659 (*this) |= mbr;
660 }
661}
662
664{
665 Q_ASSERT(!boundsComputed);
666 boundsComputed = true;
667
668 QSGGeometry *g = node->geometry();
669 int offset = qsg_positionAttribute(g);
670 if (offset == -1) {
671 // No position attribute means overlaps with everything..
672 bounds.set(-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX);
673 return;
674 }
675
676 bounds.set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
677 char *vd = (char *) g->vertexData() + offset;
678 for (int i=0; i<g->vertexCount(); ++i) {
679 bounds |= *(Pt *) vd;
680 vd += g->sizeOfVertex();
681 }
682 bounds.map(*node->matrix());
683
684 if (!qt_is_finite(bounds.tl.x) || bounds.tl.x == FLT_MAX)
685 bounds.tl.x = -FLT_MAX;
686 if (!qt_is_finite(bounds.tl.y) || bounds.tl.y == FLT_MAX)
687 bounds.tl.y = -FLT_MAX;
688 if (!qt_is_finite(bounds.br.x) || bounds.br.x == -FLT_MAX)
689 bounds.br.x = FLT_MAX;
690 if (!qt_is_finite(bounds.br.y) || bounds.br.y == -FLT_MAX)
691 bounds.br.y = FLT_MAX;
692
693 Q_ASSERT(bounds.tl.x <= bounds.br.x);
694 Q_ASSERT(bounds.tl.y <= bounds.br.y);
695
696 boundsOutsideFloatRange = bounds.isOutsideFloatRange();
697}
698
700{
701 Element *n = first;
702 // Skip to the first node other than e which has not been removed
703 while (n && (n == e || n->removed))
704 n = n->nextInBatch;
705
706 // Only 'e' in this batch, so a material change doesn't change anything as long as
707 // its blending is still in sync with this batch...
708 if (!n)
709 return BatchIsCompatible;
710
711 QSGMaterial *m = e->node->activeMaterial();
712 QSGMaterial *nm = n->node->activeMaterial();
713 return (nm->type() == m->type() && nm->viewCount() == m->viewCount() && nm->compare(m) == 0)
716}
717
718/*
719 * Marks this batch as dirty or in the case where the geometry node has
720 * changed to be incompatible with this batch, return false so that
721 * the caller can mark the entire sg for a full rebuild...
722 */
723bool Batch::geometryWasChanged(QSGGeometryNode *gn)
724{
725 Element *e = first;
726 Q_ASSERT_X(e, "Batch::geometryWasChanged", "Batch is expected to 'valid' at this time");
727 // 'gn' is the first node in the batch, compare against the next one.
728 while (e && (e->node == gn || e->removed))
729 e = e->nextInBatch;
730 if (!e || e->node->geometry()->attributes() == gn->geometry()->attributes()) {
731 needsUpload = true;
732 return true;
733 } else {
734 return false;
735 }
736}
737
739{
740 if (!needsPurge)
741 return;
742
743 // remove from front of batch..
744 while (first && first->removed) {
746 }
747
748 // Then continue and remove other nodes further out in the batch..
749 if (first) {
750 Element *e = first;
751 while (e->nextInBatch) {
752 if (e->nextInBatch->removed)
754 else
755 e = e->nextInBatch;
756
757 }
758 }
759
760 needsPurge = false;
761}
762
763/*
764 * Iterates through all geometry nodes in this batch and unsets their batch,
765 * thus forcing them to be rebuilt
766 */
768{
770 Element *e = first;
771 first = nullptr;
772 root = nullptr;
773 while (e) {
774 e->batch = nullptr;
776 e->nextInBatch = nullptr;
777 e = n;
778 }
779}
780
782 bool only = true;
783 Element *e = first;
784 while (e && only) {
785 only &= e->translateOnlyToRoot;
786 e = e->nextInBatch;
787 }
788 return only;
789}
790
791/*
792 * Iterates through all the nodes in the batch and returns true if the
793 * nodes are all safe to batch. There are two separate criteria:
794 *
795 * - The matrix is such that the z component of the result is of no
796 * consequence.
797 *
798 * - The bounds are inside the stable floating point range. This applies
799 * to desktop only where we in this case can trigger a fallback to
800 * unmerged in which case we pass the geometry straight through and
801 * just apply the matrix.
802 *
803 * NOTE: This also means a slight performance impact for geometries which
804 * are defined to be outside the stable floating point range and still
805 * use single precision float, but given that this implicitly fixes
806 * huge lists and tables, it is worth it.
807 */
808bool Batch::isSafeToBatch() const {
809 Element *e = first;
810 while (e) {
811 if (e->boundsOutsideFloatRange)
812 return false;
813 if (!is2DSafe(*e->node->matrix()))
814 return false;
815 e = e->nextInBatch;
816 }
817 return true;
818}
819
820static int qsg_countNodesInBatch(const Batch *batch)
821{
822 int sum = 0;
823 Element *e = batch->first;
824 while (e) {
825 ++sum;
826 e = e->nextInBatch;
827 }
828 return sum;
829}
830
831static int qsg_countNodesInBatches(const QDataBuffer<Batch *> &batches)
832{
833 int sum = 0;
834 for (int i=0; i<batches.size(); ++i) {
835 sum += qsg_countNodesInBatch(batches.at(i));
836 }
837 return sum;
838}
839
842 , m_context(ctx)
847 , m_partialRebuild(false)
848 , m_partialRebuildRoot(nullptr)
849 , m_forceNoDepthBuffer(false)
850 , m_opaqueBatches(16)
851 , m_alphaBatches(16)
852 , m_batchPool(16)
856 , m_vboPool(16)
857 , m_iboPool(16)
858 , m_vboPoolCost(0)
859 , m_iboPoolCost(0)
861 , m_zRange(0)
862#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
865#endif
866 , m_currentMaterial(nullptr)
867 , m_currentShader(nullptr)
868 , m_vertexUploadPool(256)
870{
871 m_rhi = m_context->rhi();
872 Q_ASSERT(m_rhi); // no more direct OpenGL code path in Qt 6
873
875
877 if (qEnvironmentVariableIntValue("QSG_RHI_UINT32_INDEX"))
878 m_uint32IndexForRhi = true;
879
880 m_visualizer = new RhiVisualizer(this);
881
882 setNodeUpdater(new Updater(this));
883
884 // The shader manager is shared between renderers (think for example Item
885 // layers that create a new Renderer each) with the same rendercontext (and
886 // so same QRhi).
888 if (!m_shaderManager) {
890 m_shaderManager->setObjectName(QStringLiteral("__qt_ShaderManager"));
893 }
894
895 m_batchNodeThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_NODE_THRESHOLD", 64);
896 m_batchVertexThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_VERTEX_THRESHOLD", 1024);
897 m_srbPoolThreshold = qt_sg_envInt("QSG_RENDERER_SRB_POOL_THRESHOLD", 1024);
898 m_bufferPoolSizeLimit = qt_sg_envInt("QSG_RENDERER_BUFFER_POOL_LIMIT", DEFAULT_BUFFER_POOL_SIZE_LIMIT);
899
901 qDebug("Batch thresholds: nodes: %d vertices: %d srb pool: %d buffer pool: %d",
903 }
904}
905
906static void qsg_wipeBuffer(Buffer *buffer)
907{
908 delete buffer->buf;
909
910 // The free here is ok because we're in one of two situations.
911 // 1. We're using the upload pool in which case unmap will have set the
912 // data pointer to 0 and calling free on 0 is ok.
913 // 2. We're using dedicated buffers because of visualization or IBO workaround
914 // and the data something we malloced and must be freed.
915 free(buffer->data);
916}
917
918static void qsg_wipeBatch(Batch *batch)
919{
920 qsg_wipeBuffer(&batch->vbo);
921 qsg_wipeBuffer(&batch->ibo);
922 delete batch->ubuf;
923 batch->stencilClipState.reset();
924 delete batch;
925}
926
928{
929 if (m_rhi) {
930 // Clean up batches and buffers
931 for (int i = 0; i < m_opaqueBatches.size(); ++i)
933 for (int i = 0; i < m_alphaBatches.size(); ++i)
935 for (int i = 0; i < m_batchPool.size(); ++i)
937 for (int i = 0; i < m_vboPool.size(); ++i)
938 delete m_vboPool.at(i);
939 for (int i = 0; i < m_iboPool.size(); ++i)
940 delete m_iboPool.at(i);
941 }
942
943 for (Node *n : std::as_const(m_nodes)) {
944 if (n->type() == QSGNode::GeometryNodeType) {
945 Element *e = n->element();
946 if (!e->removed)
948 } else if (n->type() == QSGNode::ClipNodeType) {
949 delete n->clipInfo();
950 } else if (n->type() == QSGNode::RenderNodeType) {
952 if (!e->removed)
954 }
955
957 }
958
959 // Remaining elements...
960 for (int i=0; i<m_elementsToDelete.size(); ++i)
962
964
965 delete m_visualizer;
966}
967
969{
970 // If this is from the dtor, then the shader manager and its already
971 // prepared shaders will stay around for other renderers -> the cached data
972 // in the rhi shaders have to be purged as it may refer to samplers we
973 // are going to destroy.
975
978 delete m_dummyTexture;
980}
981
983{
985
987
989 m_dummyTexture = nullptr;
990
992
997
998 for (int i = 0; i < m_vboPool.size(); ++i)
999 delete m_vboPool.at(i);
1000 m_vboPool.reset();
1001 m_vboPoolCost = 0;
1002
1003 for (int i = 0; i < m_iboPool.size(); ++i)
1004 delete m_iboPool.at(i);
1005 m_iboPool.reset();
1006 m_iboPoolCost = 0;
1007}
1008
1010{
1011 if (b->vbo.buf != nullptr && m_vboPoolCost + b->vbo.buf->size() <= quint32(m_bufferPoolSizeLimit)) {
1012 m_vboPool.add(b->vbo.buf);
1013 m_vboPoolCost += b->vbo.buf->size();
1014 } else {
1015 delete b->vbo.buf;
1016 }
1017 if (b->ibo.buf != nullptr && m_iboPoolCost + b->ibo.buf->size() <= quint32(m_bufferPoolSizeLimit)) {
1018 m_iboPool.add(b->ibo.buf);
1019 m_iboPoolCost += b->ibo.buf->size();
1020 } else {
1021 delete b->ibo.buf;
1022 }
1023 b->vbo.buf = nullptr;
1024 b->ibo.buf = nullptr;
1025 b->invalidate();
1026 for (int i=0; i<m_batchPool.size(); ++i)
1027 if (b == m_batchPool.at(i))
1028 return;
1029 m_batchPool.add(b);
1030}
1031
1033{
1035 // Common case, use a shared memory pool for uploading vertex data to avoid
1036 // excessive reevaluation
1038 if (byteSize > quint32(pool.size()))
1040 buffer->data = pool.data();
1041 } else if (buffer->size != byteSize) {
1042 free(buffer->data);
1043 buffer->data = (char *) malloc(byteSize);
1045 }
1046 buffer->size = byteSize;
1047}
1048
1050{
1051 // Batches are pooled and reused which means the QRhiBuffer will be
1052 // still valid in a recycled Batch. We only hit the newBuffer() path
1053 // when there are no buffers to recycle.
1055 if (!buffer->buf && bufferPool->isEmpty()) {
1058 buffer->size);
1059 if (!buffer->buf->create()) {
1060 qWarning("Failed to build vertex/index buffer of size %u", buffer->size);
1061 delete buffer->buf;
1062 buffer->buf = nullptr;
1063 }
1064 } else {
1065 if (!buffer->buf) {
1068 for (qsizetype i = 0; i < bufferPool->size(); ++i) {
1070 if (!buffer->buf
1071 || (testBuffer->size() >= expectedSize && testBuffer->size() < buffer->buf->size())
1072 || (testBuffer->size() < expectedSize && testBuffer->size() > buffer->buf->size())) {
1075 if (buffer->buf->size() == expectedSize)
1076 break;
1077 }
1078 }
1079
1084 }
1085 if (isIndexBuf)
1087 else
1090 }
1091
1092 bool needsRebuild = false;
1093 if (buffer->buf->size() < buffer->size) {
1095 needsRebuild = true;
1096 }
1097 if (buffer->buf->type() != QRhiBuffer::Dynamic
1099 {
1102 needsRebuild = true;
1103 }
1104 if (needsRebuild) {
1105 if (!buffer->buf->create()) {
1106 qWarning("Failed to (re)build vertex/index buffer of size %u", buffer->size);
1107 delete buffer->buf;
1108 buffer->buf = nullptr;
1109 }
1110 }
1111 }
1112 if (buffer->buf) {
1113 if (buffer->buf->type() != QRhiBuffer::Dynamic) {
1116 } else {
1119 else
1121 }
1122 }
1124 buffer->data = nullptr;
1125}
1126
1128{
1130 if (!info) {
1131 if (node->type() == QSGNode::ClipNodeType)
1132 info = new ClipBatchRootInfo;
1133 else {
1135 info = new BatchRootInfo;
1136 }
1137 node->data = info;
1138 }
1139 return info;
1140}
1141
1143{
1145 if (!childInfo->parentRoot)
1146 return;
1148
1151 childInfo->parentRoot = nullptr;
1152}
1153
1155{
1160}
1161
1163{
1165 if (subInfo->parentRoot == root)
1166 return false;
1167 if (subInfo->parentRoot) {
1170 }
1174 return true;
1175}
1176
1178{
1179 if (node->type() == QSGNode::ClipNodeType || node->isBatchRoot) {
1180 // When we reach a batchroot, we only need to update it. Its subtree
1181 // is relative to that root, so no need to recurse further.
1183 return;
1184 } else if (node->type() == QSGNode::GeometryNodeType) {
1185 // Only need to change the root as nodeChanged anyway flags a full update.
1186 Element *e = node->element();
1187 if (e) {
1188 e->root = root;
1189 e->boundsComputed = false;
1190 }
1191 } else if (node->type() == QSGNode::RenderNodeType) {
1193 if (e)
1194 e->root = root;
1195 }
1196
1199}
1200
1202{
1203 if (node->type() == QSGNode::GeometryNodeType) {
1204 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node->sgNode);
1206 Element *e = node->element();
1207 if (e) {
1208 e->boundsComputed = false;
1209 if (e->batch) {
1210 if (!e->batch->isOpaque) {
1212 } else if (e->batch->merged) {
1213 e->batch->needsUpload = true;
1214 }
1215 }
1216 }
1217 }
1218
1221}
1222
1224{
1226 if (node->isSubtreeBlocked())
1227 return;
1228
1230 snode->sgNode = node;
1232 if (shadowParent)
1234
1235 if (node->type() == QSGNode::GeometryNodeType) {
1237 snode->element()->setNode(static_cast<QSGGeometryNode *>(node));
1238
1239 } else if (node->type() == QSGNode::ClipNodeType) {
1242
1243 } else if (node->type() == QSGNode::RenderNodeType) {
1244 QSGRenderNode *rn = static_cast<QSGRenderNode *>(node);
1246 snode->data = e;
1250 m_forceNoDepthBuffer = true;
1252 }
1253
1256}
1257
1259{
1260 // Prefix traversal as removeBatchRootFromParent below removes nodes
1261 // in a bottom-up manner. Note that we *cannot* use SHADOWNODE_TRAVERSE
1262 // here, because we delete 'child' (when recursed, down below), so we'd
1263 // have a use-after-free.
1264 {
1265 Node *child = node->firstChild();
1266 while (child) {
1267 // Remove (and delete) child
1268 node->remove(child);
1270 child = node->firstChild();
1271 }
1272 }
1273
1274 if (node->type() == QSGNode::GeometryNodeType) {
1275 Element *e = node->element();
1276 if (e) {
1277 e->removed = true;
1279 e->node = nullptr;
1280 if (e->root) {
1283 }
1284 if (e->batch) {
1285 e->batch->needsUpload = true;
1286 e->batch->needsPurge = true;
1287 }
1288
1289 }
1290
1291 } else if (node->type() == QSGNode::ClipNodeType) {
1293 delete node->clipInfo();
1296
1297 } else if (node->isBatchRoot) {
1299 delete node->rootInfo();
1302
1303 } else if (node->type() == QSGNode::RenderNodeType) {
1305 if (e) {
1306 e->removed = true;
1309 m_forceNoDepthBuffer = false;
1310 // Must have a full rebuild given useDepthBuffer() now returns
1311 // a different value than before, meaning there can once again
1312 // be an opaque pass.
1314 }
1315
1316 if (e->batch != nullptr)
1317 e->batch->needsPurge = true;
1318 }
1319 }
1320
1322
1324}
1325
1327{
1328 if (Q_UNLIKELY(debug_change())) qDebug(" - new batch root");
1330 node->isBatchRoot = true;
1331 node->becameBatchRoot = true;
1332
1333 Node *p = node->parent();
1334 while (p) {
1335 if (p->type() == QSGNode::ClipNodeType || p->isBatchRoot) {
1337 break;
1338 }
1339 p = p->parent();
1340 }
1341
1344}
1345
1346
1348{
1349#ifndef QT_NO_DEBUG_OUTPUT
1350 if (Q_UNLIKELY(debug_change())) {
1351 QDebug debug = qDebug();
1352 debug << "dirty:";
1354 debug << "Geometry";
1356 debug << "Material";
1357 if (state & QSGNode::DirtyMatrix)
1358 debug << "Matrix";
1360 debug << "Added";
1362 debug << "Removed";
1363 if (state & QSGNode::DirtyOpacity)
1364 debug << "Opacity";
1366 debug << "SubtreeBlocked";
1368 debug << "ForceUpdate";
1369
1370 // when removed, some parts of the node could already have been destroyed
1371 // so don't debug it out.
1373 debug << (void *) node << node->type();
1374 else
1375 debug << node;
1376 }
1377#endif
1378 // As this function calls nodeChanged recursively, we do it at the top
1379 // to avoid that any of the others are processed twice.
1381 Node *sn = m_nodes.value(node);
1382
1383 // Force a batch rebuild if this includes an opacity change
1384 if (state & QSGNode::DirtyOpacity)
1386
1387 bool blocked = node->isSubtreeBlocked();
1388 if (blocked && sn) {
1390 Q_ASSERT(m_nodes.value(node) == 0);
1391 } else if (!blocked && !sn) {
1393 }
1394 return;
1395 }
1396
1397 if (state & QSGNode::DirtyNodeAdded) {
1400 return;
1401 }
1402 if (node == rootNode())
1403 nodeWasAdded(node, nullptr);
1404 else
1406 }
1407
1408 // Mark this node dirty in the shadow tree.
1410
1411 // Blocked subtrees won't have shadow nodes, so we can safely abort
1412 // here..
1413 if (!shadowNode) {
1415 return;
1416 }
1417
1419
1424 } else {
1425 int vertices = 0;
1429 }
1430 }
1431 }
1432
1434 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1436 if (e) {
1437 e->boundsComputed = false;
1438 Batch *b = e->batch;
1439 if (b) {
1440 if (!e->batch->geometryWasChanged(gn) || !e->batch->isOpaque) {
1442 } else {
1443 b->needsUpload = true;
1444 }
1445 }
1446 }
1447 }
1448
1451 if (e) {
1452 bool blended = hasMaterialWithBlending(static_cast<QSGGeometryNode *>(node));
1453 if (e->isMaterialBlended != blended) {
1456 } else if (e->batch) {
1459 } else {
1461 }
1462 }
1463 }
1464
1465 // Mark the shadow tree dirty all the way back to the root...
1471 if (dirtyChain != 0) {
1473 Node *sn = shadowNode->parent();
1474 while (sn) {
1476 sn = sn->parent();
1477 }
1478 }
1479
1480 // Delete happens at the very end because it deletes the shadownode.
1483 if (parent)
1486 Q_ASSERT(m_nodes.value(node) == 0);
1487 }
1488
1490}
1491
1492/*
1493 * Traverses the tree and builds two list of geometry nodes. One for
1494 * the opaque and one for the translucent. These are populated
1495 * in the order they should visually appear in, meaning first
1496 * to the back and last to the front.
1497 *
1498 * We split opaque and translucent as we can perform different
1499 * types of reordering / batching strategies on them, depending
1500 *
1501 * Note: It would be tempting to use the shadow nodes instead of the QSGNodes
1502 * for traversal to avoid hash lookups, but the order of the children
1503 * is important and they are not preserved in the shadow tree, so we must
1504 * use the actual QSGNode tree.
1505 */
1507{
1508 if (node->isSubtreeBlocked())
1509 return;
1510
1513
1514 if (node->type() == QSGNode::GeometryNodeType) {
1515 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1516
1518 Q_ASSERT(e);
1519
1521 if (opaque && useDepthBuffer())
1523 else
1525
1527 // Used while rebuilding partial roots.
1528 if (m_partialRebuild)
1529 e->orphaned = false;
1530
1531 } else if (node->type() == QSGNode::ClipNodeType || shadowNode->isBatchRoot) {
1534 if (node == m_partialRebuildRoot) {
1539 } else {
1543 int padding = (m_nextRenderOrder - currentOrder) >> 2;
1548 }
1549 return;
1550 } else if (node->type() == QSGNode::RenderNodeType) {
1554 Q_ASSERT(e);
1555 }
1556
1559}
1560
1562{
1566 it != i->subRoots.constEnd(); ++it) {
1567 tagSubRoots(*it);
1568 }
1569}
1570
1571static void qsg_addOrphanedElements(QDataBuffer<Element *> &orphans, const QDataBuffer<Element *> &renderList)
1572{
1573 orphans.reset();
1574 for (int i=0; i<renderList.size(); ++i) {
1575 Element *e = renderList.at(i);
1576 if (e && !e->removed) {
1577 e->orphaned = true;
1578 orphans.add(e);
1579 }
1580 }
1581}
1582
1583static void qsg_addBackOrphanedElements(QDataBuffer<Element *> &orphans, QDataBuffer<Element *> &renderList)
1584{
1585 for (int i=0; i<orphans.size(); ++i) {
1586 Element *e = orphans.at(i);
1587 if (e->orphaned)
1588 renderList.add(e);
1589 }
1590 orphans.reset();
1591}
1592
1593/*
1594 * To rebuild the tagged roots, we start by putting all subroots of tagged
1595 * roots into the list of tagged roots. This is to make the rest of the
1596 * algorithm simpler.
1597 *
1598 * Second, we invalidate all batches which belong to tagged roots, which now
1599 * includes the entire subtree under a given root
1600 *
1601 * Then we call buildRenderLists for all tagged subroots which do not have
1602 * parents which are tagged, aka, we traverse only the topmosts roots.
1603 *
1604 * Then we sort the render lists based on their render order, to restore the
1605 * right order for rendering.
1606 */
1608{
1609 // Flag any element that is currently in the render lists, but which
1610 // is not in a batch. This happens when we have a partial rebuild
1611 // in one sub tree while we have a BuildBatches change in another
1612 // isolated subtree. So that batch-building takes into account
1613 // these "orphaned" nodes, we flag them now. The ones under tagged
1614 // roots will be cleared again. The remaining ones are added into the
1615 // render lists so that they contain all visual nodes after the
1616 // function completes.
1619
1620 // Take a copy now, as we will be adding to this while traversing..
1623 it != roots.constEnd(); ++it) {
1624 tagSubRoots(*it);
1625 }
1626
1627 for (int i=0; i<m_opaqueBatches.size(); ++i) {
1631
1632 }
1633 for (int i=0; i<m_alphaBatches.size(); ++i) {
1637 }
1638
1642 m_partialRebuild = true;
1643 // Traverse each root, assigning it
1645 it != m_taggedRoots.constEnd(); ++it) {
1646 Node *root = *it;
1653 }
1654 }
1655 m_partialRebuild = false;
1656 m_partialRebuildRoot = nullptr;
1659
1660 // Add orphaned elements back into the list and then sort it..
1663
1666 if (m_alphaRenderList.size())
1668
1669}
1670
1672{
1675
1676 for (int i=0; i<m_opaqueBatches.size(); ++i)
1678 for (int i=0; i<m_alphaBatches.size(); ++i)
1682
1684
1686}
1687
1689{
1690 Q_ASSERT(batch);
1692
1693#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1698
1701#else
1702 int first = batch->first->order;
1704#endif
1705
1706 batch->invalidate();
1707
1708 for (int i=0; i<m_alphaBatches.size(); ++i) {
1710 if (b->first) {
1711 int bf = b->first->order;
1712 int bl = b->lastOrderInBatch;
1713 if (bl > first && bf < last)
1714 b->invalidate();
1715 }
1716 }
1717
1719}
1720
1721/* Clean up batches by making it a consecutive list of "valid"
1722 * batches and moving all invalidated batches to the batches pool.
1723 */
1725 if (batches->size()) {
1727 int count = 0;
1728 while (count < batches->size() && batches->at(count)->first)
1729 ++count;
1730 for (int i=count; i<batches->size(); ++i)
1733 }
1734}
1735
1737{
1738 for (int i=m_opaqueRenderList.size() - 1; i >= 0; --i) {
1740 if (!ei || ei->batch || ei->node->geometry()->vertexCount() == 0)
1741 continue;
1742 Batch *batch = newBatch();
1743 batch->first = ei;
1744 batch->root = ei->root;
1745 batch->isOpaque = true;
1746 batch->needsUpload = true;
1748
1750
1751 ei->batch = batch;
1752 Element *next = ei;
1753
1755
1756 for (int j = i - 1; j >= 0; --j) {
1758 if (!ej)
1759 continue;
1760 if (ej->root != ei->root)
1761 break;
1762 if (ej->batch || ej->node->geometry()->vertexCount() == 0)
1763 continue;
1764
1766
1767 const QSGGeometry *gniGeometry = gni->geometry();
1769 const QSGGeometry *gnjGeometry = gnj->geometry();
1771 if (gni->clipList() == gnj->clipList()
1777 && gniMaterial->type() == gnjMaterial->type()
1780 {
1781 ej->batch = batch;
1782 next->nextInBatch = ej;
1783 next = ej;
1784 }
1785 }
1786
1788 }
1789}
1790
1791bool Renderer::checkOverlap(int first, int last, const Rect &bounds)
1792{
1793 for (int i=first; i<=last; ++i) {
1795#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1796 if (!e || e->batch)
1797#else
1798 if (!e)
1799#endif
1800 continue;
1802 if (e->bounds.intersects(bounds))
1803 return true;
1804 }
1805 return false;
1806}
1807
1808/*
1809 *
1810 * To avoid the O(n^2) checkOverlap check in most cases, we have the
1811 * overlapBounds which is the union of all bounding rects to check overlap
1812 * for. We know that if it does not overlap, then none of the individual
1813 * ones will either. For the typical list case, this results in no calls
1814 * to checkOverlap what-so-ever. This also ensures that when all consecutive
1815 * items are matching (such as a table of text), we don't build up an
1816 * overlap bounds and thus do not require full overlap checks.
1817 */
1818
1820{
1821 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1823 if (!e || e->isRenderNode)
1824 continue;
1825 Q_ASSERT(!e->removed);
1827 }
1828
1829 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1831 if (!ei || ei->batch)
1832 continue;
1833
1834 if (ei->isRenderNode) {
1835 Batch *rnb = newBatch();
1836 rnb->first = ei;
1837 rnb->root = ei->root;
1838 rnb->isOpaque = false;
1839 rnb->isRenderNode = true;
1840 ei->batch = rnb;
1842 continue;
1843 }
1844
1845 if (ei->node->geometry()->vertexCount() == 0)
1846 continue;
1847
1848 Batch *batch = newBatch();
1849 batch->first = ei;
1850 batch->root = ei->root;
1851 batch->isOpaque = false;
1852 batch->needsUpload = true;
1854 ei->batch = batch;
1855
1858
1861
1862 Element *next = ei;
1863
1864 for (int j = i + 1; j < m_alphaRenderList.size(); ++j) {
1866 if (!ej)
1867 continue;
1868 if (ej->root != ei->root || ej->isRenderNode)
1869 break;
1870 if (ej->batch) {
1871#if !defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1873#endif
1874 continue;
1875 }
1876
1878 if (gnj->geometry()->vertexCount() == 0)
1879 continue;
1880
1881 const QSGGeometry *gniGeometry = gni->geometry();
1883 const QSGGeometry *gnjGeometry = gnj->geometry();
1885 if (gni->clipList() == gnj->clipList()
1889 // Must not do overlap checks when the line width is not 1,
1890 // we have no knowledge how such lines are rasterized.
1891 && gniGeometry->lineWidth() == 1.0f))
1895 && gniMaterial->type() == gnjMaterial->type()
1898 {
1899 if (!overlapBounds.intersects(ej->bounds) || !checkOverlap(i+1, j - 1, ej->bounds)) {
1900 ej->batch = batch;
1901 next->nextInBatch = ej;
1902 next = ej;
1903 } else {
1904 /* When we come across a compatible element which hits an overlap, we
1905 * need to stop the batch right away. We cannot add more elements
1906 * to the current batch as they will be rendered before the batch that the
1907 * current 'ej' will be added to.
1908 */
1909 break;
1910 }
1911 } else {
1913 }
1914 }
1915
1917 }
1918
1919
1920}
1921
1922static inline int qsg_fixIndexCount(int iCount, int drawMode)
1923{
1924 switch (drawMode) {
1925 case QSGGeometry::DrawTriangleStrip:
1926 // Merged triangle strips need to contain degenerate triangles at the beginning and end.
1927 // One could save 2 uploaded ushorts here by ditching the padding for the front of the
1928 // first and the end of the last, but for simplicity, we simply don't care.
1929 // Those extra triangles will be skipped while drawing to preserve the strip's parity
1930 // anyhow.
1931 return iCount + 2;
1932 case QSGGeometry::DrawLines:
1933 // For lines we drop the last vertex if the number of vertices is uneven.
1934 return iCount - (iCount % 2);
1935 case QSGGeometry::DrawTriangles:
1936 // For triangles we drop trailing vertices until the result is divisible by 3.
1937 return iCount - (iCount % 3);
1938 default:
1939 return iCount;
1940 }
1941}
1942
1943static inline float calculateElementZOrder(const Element *e, qreal zRange)
1944{
1945 // Clamp the zOrder to within the min and max depth of the viewport.
1946 return std::clamp(1.0f - float(e->order * zRange), VIEWPORT_MIN_DEPTH, VIEWPORT_MAX_DEPTH);
1947}
1948
1949/* These parameters warrant some explanation...
1950 *
1951 * vaOffset: The byte offset into the vertex data to the location of the
1952 * 2D float point vertex attributes.
1953 *
1954 * vertexData: destination where the geometry's vertex data should go
1955 *
1956 * zData: destination of geometries injected Z positioning
1957 *
1958 * indexData: destination of the indices for this element
1959 *
1960 * iBase: The starting index for this element in the batch
1961 */
1962
1963void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, void *iBasePtr, int *indexCount)
1964{
1965 if (Q_UNLIKELY(debug_upload())) qDebug() << " - uploading element:" << e << e->node << (void *) *vertexData << (qintptr) (*zData - *vertexData) << (qintptr) (*indexData - *vertexData);
1966 QSGGeometry *g = e->node->geometry();
1967
1968 const QMatrix4x4 &localx = *e->node->matrix();
1969 const float *localxdata = localx.constData();
1970
1971 const int vCount = g->vertexCount();
1972 const int vSize = g->sizeOfVertex();
1974
1975 // apply vertex transform..
1976 char *vdata = *vertexData + vaOffset;
1977 if (localx.flags() == QMatrix4x4::Translation) {
1978 for (int i=0; i<vCount; ++i) {
1979 Pt *p = (Pt *) vdata;
1980 p->x += localxdata[12];
1981 p->y += localxdata[13];
1982 vdata += vSize;
1983 }
1984 } else if (localx.flags() > QMatrix4x4::Translation) {
1985 for (int i=0; i<vCount; ++i) {
1986 ((Pt *) vdata)->map(localx);
1987 vdata += vSize;
1988 }
1989 }
1990
1991 if (useDepthBuffer()) {
1992 float *vzorder = (float *) *zData;
1994 for (int i=0; i<vCount; ++i)
1995 vzorder[i] = zorder;
1996 *zData += vCount * sizeof(float);
1997 }
1998
1999 int iCount = g->indexCount();
2000 if (m_uint32IndexForRhi) {
2001 // can only happen when using the rhi
2004 if (iCount == 0) {
2005 iCount = vCount;
2007 *indices++ = *iBase;
2008 else
2010
2011 for (int i=0; i<iCount; ++i)
2012 indices[i] = *iBase + i;
2013 } else {
2014 // source index data in QSGGeometry is always ushort (we would not merge otherwise)
2017 *indices++ = *iBase + srcIndices[0];
2018 else
2020
2021 for (int i=0; i<iCount; ++i)
2022 indices[i] = *iBase + srcIndices[i];
2023 }
2025 indices[iCount] = indices[iCount - 1];
2026 iCount += 2;
2027 }
2028 *iBase += vCount;
2029 } else {
2030 // normally batching is only done for ushort index data
2033 if (iCount == 0) {
2034 iCount = vCount;
2036 *indices++ = *iBase;
2037 else
2039
2040 for (int i=0; i<iCount; ++i)
2041 indices[i] = *iBase + i;
2042 } else {
2045 *indices++ = *iBase + srcIndices[0];
2046 else
2048
2049 for (int i=0; i<iCount; ++i)
2050 indices[i] = *iBase + srcIndices[i];
2051 }
2053 indices[iCount] = indices[iCount - 1];
2054 iCount += 2;
2055 }
2056 *iBase += vCount;
2057 }
2058
2059 *vertexData += vCount * vSize;
2061 *indexCount += iCount;
2062}
2063
2065{
2066 if (node->type() == QSGNode::TransformNodeType)
2067 return static_cast<QSGTransformNode *>(node->sgNode)->combinedMatrix();
2068 Q_ASSERT(node->type() == QSGNode::ClipNodeType);
2069 QSGClipNode *c = static_cast<QSGClipNode *>(node->sgNode);
2070 return *c->matrix();
2071}
2072
2074{
2075 // Early out if nothing has changed in this batch..
2076 if (!b->needsUpload) {
2077 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "already uploaded...";
2078 return;
2079 }
2080
2081 if (!b->first) {
2082 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "is invalid...";
2083 return;
2084 }
2085
2086 if (b->isRenderNode) {
2087 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch: " << b << "is a render node...";
2088 return;
2089 }
2090
2091 // Figure out if we can merge or not, if not, then just render the batch as is..
2092 Q_ASSERT(b->first);
2093 Q_ASSERT(b->first->node);
2094
2096 QSGGeometry *g = gn->geometry();
2100 && b->positionAttribute >= 0
2104 && b->isSafeToBatch();
2105
2106 b->merged = canMerge;
2107
2108 // Figure out how much memory we need...
2109 b->vertexCount = 0;
2110 b->indexCount = 0;
2111 int unmergedIndexSize = 0;
2112 Element *e = b->first;
2113
2114 // Merged batches always do indexed draw calls. Non-indexed geometry gets
2115 // indices generated automatically, when merged.
2116 while (e) {
2117 QSGGeometry *eg = e->node->geometry();
2118 b->vertexCount += eg->vertexCount();
2119 int iCount = eg->indexCount();
2120 if (b->merged) {
2121 if (iCount == 0)
2122 iCount = eg->vertexCount();
2124 } else {
2125 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : eg->sizeOfIndex();
2127 }
2128 b->indexCount += iCount;
2129 e = e->nextInBatch;
2130 }
2131
2132 // Abort if there are no vertices in this batch.. We abort this late as
2133 // this is a broken usecase which we do not care to optimize for...
2134 if (b->vertexCount == 0 || (b->merged && b->indexCount == 0))
2135 return;
2136
2137 /* Allocate memory for this batch. Merged batches are divided into three separate blocks
2138 1. Vertex data for all elements, as they were in the QSGGeometry object, but
2139 with the tranform relative to this batch's root applied. The vertex data
2140 is otherwise unmodified.
2141 2. Z data for all elements, derived from each elements "render order".
2142 This is present for merged data only.
2143 3. Indices for all elements, as they were in the QSGGeometry object, but
2144 adjusted so that each index matches its.
2145 And for TRIANGLE_STRIPs, we need to insert degenerate between each
2146 primitive. These are unsigned shorts for merged and arbitrary for
2147 non-merged.
2148 */
2150 int ibufferSize = 0;
2151 if (b->merged) {
2153 if (useDepthBuffer())
2154 bufferSize += b->vertexCount * sizeof(float);
2155 } else {
2157 }
2158
2159 map(&b->ibo, ibufferSize, true);
2160 map(&b->vbo, bufferSize);
2161
2162 if (Q_UNLIKELY(debug_upload())) qDebug() << " - batch" << b << " first:" << b->first << " root:"
2163 << b->root << " merged:" << b->merged << " positionAttribute" << b->positionAttribute
2164 << " vbo:" << b->vbo.buf << ":" << b->vbo.size;
2165
2166 if (b->merged) {
2167 char *vertexData = b->vbo.data;
2168 char *zData = vertexData + b->vertexCount * g->sizeOfVertex();
2169 char *indexData = b->ibo.data;
2170
2171 quint16 iOffset16 = 0;
2172 quint32 iOffset32 = 0;
2173 e = b->first;
2174 uint verticesInSet = 0;
2175 // Start a new set already after 65534 vertices because 0xFFFF may be
2176 // used for an always-on primitive restart with some apis (adapt for
2177 // uint32 indices as appropriate).
2178 const uint verticesInSetLimit = m_uint32IndexForRhi ? 0xfffffffe : 0xfffe;
2179 int indicesInSet = 0;
2180 b->drawSets.reset();
2181 int drawSetIndices = 0;
2182 const char *indexBase = b->ibo.data;
2184 while (e) {
2190 b->drawSets.last().indexCount -= 2;
2191 }
2194 zData - b->vbo.data,
2196 iOffset16 = 0;
2197 iOffset32 = 0;
2199 indicesInSet = 0;
2200 }
2201 void *iBasePtr = &iOffset16;
2205 e = e->nextInBatch;
2206 }
2208 // We skip the very first and very last degenerate triangles since they aren't needed
2209 // and the first one would reverse the vertex ordering of the merged strips.
2212 b->drawSets.last().indexCount -= 2;
2213 }
2214 } else {
2215 char *vboData = b->vbo.data;
2216 char *iboData = b->ibo.data;
2217 Element *e = b->first;
2218 while (e) {
2219 QSGGeometry *g = e->node->geometry();
2220 int vbs = g->vertexCount() * g->sizeOfVertex();
2222 vboData = vboData + vbs;
2223 const int indexCount = g->indexCount();
2224 if (indexCount) {
2225 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
2226 const int ibs = indexCount * effectiveIndexSize;
2227 if (g->sizeOfIndex() == effectiveIndexSize) {
2229 } else {
2230 if (g->sizeOfIndex() == sizeof(quint16) && effectiveIndexSize == sizeof(quint32)) {
2232 quint32 *dst = (quint32 *) iboData;
2233 for (int i = 0; i < indexCount; ++i)
2234 dst[i] = src[i];
2235 } else {
2236 Q_ASSERT_X(false, "uploadBatch (unmerged)", "uint index with ushort effective index - cannot happen");
2237 }
2238 }
2239 iboData += ibs;
2240 }
2241 e = e->nextInBatch;
2242 }
2243 }
2244#ifndef QT_NO_DEBUG_OUTPUT
2245 if (Q_UNLIKELY(debug_upload())) {
2246 const char *vd = b->vbo.data;
2247 qDebug() << " -- Vertex Data, count:" << b->vertexCount << " - " << g->sizeOfVertex() << "bytes/vertex";
2248 for (int i=0; i<b->vertexCount; ++i) {
2249 QDebug dump = qDebug().nospace();
2250 dump << " --- " << i << ": ";
2251 int offset = 0;
2252 for (int a=0; a<g->attributeCount(); ++a) {
2253 const QSGGeometry::Attribute &attr = g->attributes()[a];
2254 dump << attr.position << ":(" << attr.tupleSize << ",";
2255 if (attr.type == QSGGeometry::FloatType) {
2256 dump << "float ";
2258 dump << "* ";
2259 for (int t=0; t<attr.tupleSize; ++t)
2260 dump << *(const float *)(vd + offset + t * sizeof(float)) << " ";
2261 } else if (attr.type == QSGGeometry::UnsignedByteType) {
2262 dump << "ubyte ";
2263 for (int t=0; t<attr.tupleSize; ++t)
2264 dump << *(const unsigned char *)(vd + offset + t * sizeof(unsigned char)) << " ";
2265 }
2266 dump << ") ";
2268 }
2269 if (b->merged && useDepthBuffer()) {
2270 float zorder = ((float*)(b->vbo.data + b->vertexCount * g->sizeOfVertex()))[i];
2271 dump << " Z:(" << zorder << ")";
2272 }
2273 vd += g->sizeOfVertex();
2274 }
2275
2276 if (!b->drawSets.isEmpty()) {
2277 if (m_uint32IndexForRhi) {
2278 const quint32 *id = (const quint32 *) b->ibo.data;
2279 {
2280 QDebug iDump = qDebug();
2281 iDump << " -- Index Data, count:" << b->indexCount;
2282 for (int i=0; i<b->indexCount; ++i) {
2283 if ((i % 24) == 0)
2284 iDump << Qt::endl << " --- ";
2285 iDump << id[i];
2286 }
2287 }
2288 } else {
2289 const quint16 *id = (const quint16 *) b->ibo.data;
2290 {
2291 QDebug iDump = qDebug();
2292 iDump << " -- Index Data, count:" << b->indexCount;
2293 for (int i=0; i<b->indexCount; ++i) {
2294 if ((i % 24) == 0)
2295 iDump << Qt::endl << " --- ";
2296 iDump << id[i];
2297 }
2298 }
2299 }
2300
2301 for (int i=0; i<b->drawSets.size(); ++i) {
2302 const DrawSet &s = b->drawSets.at(i);
2303 qDebug() << " -- DrawSet: indexCount:" << s.indexCount << " vertices:" << s.vertices << " z:" << s.zorders << " indices:" << s.indices;
2304 }
2305 }
2306 }
2307#endif // QT_NO_DEBUG_OUTPUT
2308
2309 unmap(&b->vbo);
2310 unmap(&b->ibo, true);
2311
2313 qDebug() << " --- vertex/index buffers unmapped, batch upload completed... vbo pool size" << m_vboPoolCost << "ibo pool size" << m_iboPoolCost;
2314
2315 b->needsUpload = false;
2316
2317 if (Q_UNLIKELY(debug_render()))
2318 b->uploadedThisFrame = true;
2319}
2320
2322{
2325}
2326
2328{
2332 blend.colorWrite = {};
2333 ps->setTargetBlends({ blend });
2335 ps->setStencilTest(true);
2342 } else {
2347 }
2350
2352
2354
2358 ps->setShaderResourceBindings(batch->stencilClipState.srb); // use something, it just needs to be layout-compatible
2360
2361 if (!ps->create()) {
2362 qWarning("Failed to build stencil clip pipeline");
2363 delete ps;
2364 return nullptr;
2365 }
2366
2367 return ps;
2368}
2369
2371{
2372 // Note: No use of the clip-related speparate m_current* vars is allowed
2373 // here. All stored in batch->clipState instead. To collect state during
2374 // the prepare steps, m_currentClipState is used. It should not be used in
2375 // the render steps afterwards.
2376
2377 // The stenciling logic is slightly different from Qt 5's direct OpenGL version
2378 // as we cannot just randomly clear the stencil buffer. We now put all clip
2379 // shapes into the stencil buffer for all batches in the frame. This means
2380 // that the number of total clips in a scene is reduced (since the stencil
2381 // value cannot exceed 255) but we do not need any clears inbetween.
2382
2383 Q_ASSERT(m_rhi);
2388 return;
2389 }
2390
2394 const QSGClipNode *clip = clipList;
2395
2397 quint32 totalVSize = 0;
2398 quint32 totalISize = 0;
2399 quint32 totalUSize = 0;
2400 const quint32 StencilClipUbufSize = 64;
2401
2402 while (clip) {
2403 QMatrix4x4 m = m_current_projection_matrix_native_ndc[0]; // never hit for 3D and so multiview
2404 if (clip->matrix())
2405 m *= *clip->matrix();
2406
2408 && qFuzzyIsNull(m(3, 0)) && qFuzzyIsNull(m(3, 1));
2409 bool noRotate = qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(1, 0));
2410 bool isRotate90 = qFuzzyIsNull(m(0, 0)) && qFuzzyIsNull(m(1, 1));
2411
2413 QRectF bbox = clip->clipRect();
2414 qreal invW = 1 / m(3, 3);
2415 qreal fx1, fy1, fx2, fy2;
2416 if (noRotate) {
2417 fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
2418 fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
2419 fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
2420 fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
2421 } else {
2423 fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
2424 fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
2425 fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
2426 fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
2427 }
2428
2429 if (fx1 > fx2)
2430 qSwap(fx1, fx2);
2431 if (fy1 > fy2)
2432 qSwap(fy1, fy2);
2433
2434 QRect deviceRect = this->deviceRect();
2435
2436 qint32 ix1 = qRound((fx1 + 1) * deviceRect.width() * qreal(0.5));
2437 qint32 iy1 = qRound((fy1 + 1) * deviceRect.height() * qreal(0.5));
2438 qint32 ix2 = qRound((fx2 + 1) * deviceRect.width() * qreal(0.5));
2439 qint32 iy2 = qRound((fy2 + 1) * deviceRect.height() * qreal(0.5));
2440
2441 if (!(clipType & ClipState::ScissorClip)) {
2443 scissorRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2444 } else {
2445 scissorRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2446 }
2447 } else {
2449
2450 const QSGGeometry *g = clip->geometry();
2451 Q_ASSERT(g->attributeCount() > 0);
2452
2453 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2454 // the 4 byte alignment may not actually be needed here
2456 if (g->indexCount()) {
2457 const int indexByteSize = g->sizeOfIndex() * g->indexCount();
2458 // so no need to worry about NonFourAlignedEffectiveIndexBufferOffset
2460 }
2461 // ubuf start offsets must be aligned (typically to 256 bytes)
2463
2465 }
2466
2467 clip = clip->clipList();
2468 }
2469
2471 bool rebuildVBuf = false;
2472 if (!batch->stencilClipState.vbuf) {
2474 rebuildVBuf = true;
2475 } else if (batch->stencilClipState.vbuf->size() < totalVSize) {
2477 rebuildVBuf = true;
2478 }
2479 if (rebuildVBuf) {
2480 if (!batch->stencilClipState.vbuf->create()) {
2481 qWarning("Failed to build stencil clip vertex buffer");
2482 delete batch->stencilClipState.vbuf;
2483 batch->stencilClipState.vbuf = nullptr;
2484 return;
2485 }
2486 }
2487
2488 if (totalISize) {
2489 bool rebuildIBuf = false;
2490 if (!batch->stencilClipState.ibuf) {
2492 rebuildIBuf = true;
2493 } else if (batch->stencilClipState.ibuf->size() < totalISize) {
2495 rebuildIBuf = true;
2496 }
2497 if (rebuildIBuf) {
2498 if (!batch->stencilClipState.ibuf->create()) {
2499 qWarning("Failed to build stencil clip index buffer");
2500 delete batch->stencilClipState.ibuf;
2501 batch->stencilClipState.ibuf = nullptr;
2502 return;
2503 }
2504 }
2505 }
2506
2507 bool rebuildUBuf = false;
2508 if (!batch->stencilClipState.ubuf) {
2510 rebuildUBuf = true;
2511 } else if (batch->stencilClipState.ubuf->size() < totalUSize) {
2513 rebuildUBuf = true;
2514 }
2515 if (rebuildUBuf) {
2516 if (!batch->stencilClipState.ubuf->create()) {
2517 qWarning("Failed to build stencil clip uniform buffer");
2518 delete batch->stencilClipState.ubuf;
2519 batch->stencilClipState.ubuf = nullptr;
2520 return;
2521 }
2522 }
2523
2524 if (!batch->stencilClipState.srb) {
2529 if (!batch->stencilClipState.srb->create()) {
2530 qWarning("Failed to build stencil clip srb");
2531 delete batch->stencilClipState.srb;
2532 batch->stencilClipState.srb = nullptr;
2533 return;
2534 }
2535 }
2536
2537 quint32 vOffset = 0;
2538 quint32 iOffset = 0;
2539 quint32 uOffset = 0;
2540 for (const QSGClipNode *clip : stencilClipNodes) {
2541 const QSGGeometry *g = clip->geometry();
2542 const QSGGeometry::Attribute *a = g->attributes();
2545
2550 }
2551#ifndef QT_NO_DEBUG
2552 else {
2554 qWarning("updateClipState: Clip list entries have different primitive topologies, this is not currently supported.");
2556 qWarning("updateClipState: Clip list entries have different vertex input layouts, this is must not happen.");
2557 }
2558#endif
2559
2561 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2563
2564 int indexByteSize = 0;
2565 if (g->indexCount()) {
2569 }
2570
2573
2575 if (clip->matrix())
2576 matrixYUpNDC *= *clip->matrix();
2577
2580 if (indexByteSize)
2582
2583 // stencil ref goes 1, 1, 2, 3, 4, ..., N for the clips in the first batch,
2584 // then N+1, N+1, N+2, N+3, ... for the next batch,
2585 // and so on.
2586 // Note the different stencilOp for the first and the subsequent clips.
2589
2594 }
2595
2597 m_stencilClipCommon.vs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.vert.qsb"));
2598
2600 m_stencilClipCommon.fs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.frag.qsb"));
2601
2604
2607
2609 }
2610
2615
2618}
2619
2621{
2622 // cliptype stencil + updateStencilBuffer==false means the batch uses
2623 // stenciling but relies on the stencil data generated by a previous batch
2624 // (due to the having the same clip node). Do not enqueue draw calls for
2625 // stencil in this case as the stencil buffer is already up-to-date.
2627 return;
2628
2630 const int count = batch->stencilClipState.drawCalls.size();
2631 for (int i = 0; i < count; ++i) {
2635 if (i == 0) {
2638 } else if (i == 1) {
2641 }
2642 // else incrPs is already bound
2646 if (drawCall.indexCount) {
2650 } else {
2651 cb->setVertexInput(0, 1, &vbufBinding);
2653 }
2654 }
2655}
2656
2658{
2659 Q_ASSERT(m_rhi);
2662 m_currentMaterial = nullptr;
2663}
2664
2665static inline bool needsBlendConstant(QRhiGraphicsPipeline::BlendFactor f)
2666{
2667 return f == QRhiGraphicsPipeline::ConstantColor
2668 || f == QRhiGraphicsPipeline::OneMinusConstantColor
2669 || f == QRhiGraphicsPipeline::ConstantAlpha
2670 || f == QRhiGraphicsPipeline::OneMinusConstantAlpha;
2671}
2672
2673// With QRhi renderBatches() is split to two steps: prepare and render.
2674//
2675// Prepare goes through the batches and elements, and set up a graphics
2676// pipeline, srb, uniform buffer, calculates clipping, based on m_gstate, the
2677// material (shaders), and the batches. This step does not touch the command
2678// buffer or renderpass-related state (m_pstate).
2679//
2680// The render step then starts a renderpass, and goes through all
2681// batches/elements again and records setGraphicsPipeline, drawIndexed, etc. on
2682// the command buffer. The prepare step's accumulated global state like
2683// m_gstate must not be used here. Rather, all data needed for rendering is
2684// available from Batch/Element at this stage. Bookkeeping of state in the
2685// renderpass is done via m_pstate.
2686
2688{
2689 // Note the key's == and qHash implementations: the renderpass descriptor
2690 // and srb are tested for compatibility, not pointer equality.
2691 //
2692 // We do not store the srb pointer itself because the ownership stays with
2693 // the Element and that can go away more often that we would like it
2694 // to. (think scrolling a list view, constantly dropping and creating new
2695 // nodes) Rather, use an opaque blob of a few uints and store and compare
2696 // that. This works because once the pipeline is built, we will always call
2697 // setShaderResources with an explicitly specified srb which is fine even if
2698 // e->srb we used here to bake the pipeline is already gone by that point.
2699 //
2700 // A typical QSGMaterial's serialized srb layout is 8 uints. (uniform buffer
2701 // + texture, 4 fields each) Regardless, using an implicitly shared
2702 // container is essential here. (won't detach so no more allocs and copies
2703 // are done, unless the Element decides to rebake the srb with a different
2704 // layout - but then the detach is exactly what we need)
2705 //
2706 // Same story for the renderpass descriptor: the object can go away but
2707 // that's fine because that has no effect on an already built pipeline, and
2708 // for comparison we only rely on the serialized blob in order decide if the
2709 // render target is compatible with the pipeline.
2710
2712
2713 // Note: dynamic state (viewport rect, scissor rect, stencil ref, blend
2714 // constant) is never a part of GraphicsState/QRhiGraphicsPipeline.
2715
2716 // See if there is an existing, matching pipeline state object.
2719 if (depthPostPass)
2720 e->depthPostPassPs = *it;
2721 else
2722 e->ps = *it;
2723 return true;
2724 }
2725
2726 // Build a new one. This is potentially expensive.
2732
2736 {
2738 }
2743
2744 ps->setFlags(flags);
2749
2759 ps->setTargetBlends({ blend });
2760
2764
2765 if (m_gstate.stencilTest) {
2766 ps->setStencilTest(true);
2774 }
2775
2777
2779
2780 if (!ps->create()) {
2781 qWarning("Failed to build graphics pipeline state");
2782 delete ps;
2783 return false;
2784 }
2785
2787 if (depthPostPass)
2788 e->depthPostPassPs = ps;
2789 else
2790 e->ps = ps;
2791 return true;
2792}
2793
2794static QRhiSampler *newSampler(QRhi *rhi, const QSGSamplerDescription &desc)
2795{
2796 QRhiSampler::Filter magFilter;
2797 QRhiSampler::Filter minFilter;
2798 QRhiSampler::Filter mipmapMode;
2799 QRhiSampler::AddressMode u;
2800 QRhiSampler::AddressMode v;
2801
2802 switch (desc.filtering) {
2803 case QSGTexture::None:
2804 Q_FALLTHROUGH();
2805 case QSGTexture::Nearest:
2806 magFilter = minFilter = QRhiSampler::Nearest;
2807 break;
2808 case QSGTexture::Linear:
2809 magFilter = minFilter = QRhiSampler::Linear;
2810 break;
2811 default:
2812 Q_UNREACHABLE();
2813 magFilter = minFilter = QRhiSampler::Nearest;
2814 break;
2815 }
2816
2817 switch (desc.mipmapFiltering) {
2818 case QSGTexture::None:
2819 mipmapMode = QRhiSampler::None;
2820 break;
2821 case QSGTexture::Nearest:
2822 mipmapMode = QRhiSampler::Nearest;
2823 break;
2824 case QSGTexture::Linear:
2825 mipmapMode = QRhiSampler::Linear;
2826 break;
2827 default:
2828 Q_UNREACHABLE();
2829 mipmapMode = QRhiSampler::None;
2830 break;
2831 }
2832
2833 switch (desc.horizontalWrap) {
2834 case QSGTexture::Repeat:
2835 u = QRhiSampler::Repeat;
2836 break;
2837 case QSGTexture::ClampToEdge:
2838 u = QRhiSampler::ClampToEdge;
2839 break;
2840 case QSGTexture::MirroredRepeat:
2841 u = QRhiSampler::Mirror;
2842 break;
2843 default:
2844 Q_UNREACHABLE();
2845 u = QRhiSampler::ClampToEdge;
2846 break;
2847 }
2848
2849 switch (desc.verticalWrap) {
2850 case QSGTexture::Repeat:
2851 v = QRhiSampler::Repeat;
2852 break;
2853 case QSGTexture::ClampToEdge:
2854 v = QRhiSampler::ClampToEdge;
2855 break;
2856 case QSGTexture::MirroredRepeat:
2857 v = QRhiSampler::Mirror;
2858 break;
2859 default:
2860 Q_UNREACHABLE();
2861 v = QRhiSampler::ClampToEdge;
2862 break;
2863 }
2864
2865 return rhi->newSampler(magFilter, minFilter, mipmapMode, u, v);
2866}
2867
2869{
2870 if (!m_dummyTexture) {
2872 if (m_dummyTexture->create()) {
2873 if (m_resourceUpdates) {
2875 img.fill(0);
2877 }
2878 }
2879 }
2880 return m_dummyTexture;
2881}
2882
2883static void rendererToMaterialGraphicsState(QSGMaterialShader::GraphicsPipelineState *dst,
2884 GraphicsState *src)
2885{
2886 dst->blendEnable = src->blending;
2887
2888 // the enum values should match, sanity check it
2889 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::OneMinusSrc1Alpha) == int(QRhiGraphicsPipeline::OneMinusSrc1Alpha));
2890 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::BlendOp::Max) == int(QRhiGraphicsPipeline::Max));
2891 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::A) == int(QRhiGraphicsPipeline::A));
2892 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::CullBack) == int(QRhiGraphicsPipeline::Back));
2893 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::Line) == int(QRhiGraphicsPipeline::Line));
2894 dst->srcColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcColor);
2895 dst->dstColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstColor);
2896
2897 // For compatibility with any existing code, separateBlendFactors defaults
2898 // to _false_ which means that materials that do not touch srcAlpha and
2899 // dstAlpha will continue to use srcColor and dstColor as the alpha
2900 // blending factors. New code that needs different values for color/alpha,
2901 // can explicitly set separateBlendFactors to true and then set srcAlpha
2902 // and dstAlpha as well.
2903 dst->separateBlendFactors = false;
2904
2905 dst->srcAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcAlpha);
2906 dst->dstAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstAlpha);
2907
2908 dst->opColor = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opColor);
2909 dst->opAlpha = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opAlpha);
2910
2911 dst->colorWrite = QSGMaterialShader::GraphicsPipelineState::ColorMask(int(src->colorWrite));
2912
2913 dst->cullMode = QSGMaterialShader::GraphicsPipelineState::CullMode(src->cullMode);
2914 dst->polygonMode = QSGMaterialShader::GraphicsPipelineState::PolygonMode(src->polygonMode);
2915}
2916
2918 QSGMaterialShader::GraphicsPipelineState *src)
2919{
2920 dst->blending = src->blendEnable;
2921 dst->srcColor = QRhiGraphicsPipeline::BlendFactor(src->srcColor);
2922 dst->dstColor = QRhiGraphicsPipeline::BlendFactor(src->dstColor);
2923 if (src->separateBlendFactors) {
2924 dst->srcAlpha = QRhiGraphicsPipeline::BlendFactor(src->srcAlpha);
2925 dst->dstAlpha = QRhiGraphicsPipeline::BlendFactor(src->dstAlpha);
2926 } else {
2927 dst->srcAlpha = dst->srcColor;
2928 dst->dstAlpha = dst->dstColor;
2929 }
2930 dst->opColor = QRhiGraphicsPipeline::BlendOp(src->opColor);
2931 dst->opAlpha = QRhiGraphicsPipeline::BlendOp(src->opAlpha);
2932 dst->colorWrite = QRhiGraphicsPipeline::ColorMask(int(src->colorWrite));
2933 dst->cullMode = QRhiGraphicsPipeline::CullMode(src->cullMode);
2934 dst->polygonMode = QRhiGraphicsPipeline::PolygonMode(src->polygonMode);
2935}
2936
2940 const Batch *batch,
2941 Element *e,
2942 int ubufOffset,
2943 int ubufRegionSize,
2944 char *directUpdatePtr)
2945{
2947
2951
2952 if (pd->ubufBinding >= 0) {
2955 m_current_uniform_data = nullptr;
2956
2957 if (changed || !batch->ubufDataValid) {
2958 if (directUpdatePtr)
2960 else
2962 }
2963
2965 pd->ubufStages,
2966 batch->ubuf,
2967 ubufOffset,
2969 }
2970
2973 if (!stages)
2974 continue;
2975
2978
2981
2984
2985 if (nextTex.contains(nullptr)) {
2986 qWarning("No QSGTexture provided from updateSampledImage(). This is wrong.");
2987 continue;
2988 }
2989
2990 bool hasDirtySamplerOptions = false;
2991 bool isAnisotropic = false;
2992 for (QSGTexture *t : nextTex) {
2997 }
2998
2999 // prevTex may be invalid at this point, avoid dereferencing it
3001
3002 // The QSGTexture, and so the sampler parameters, may have changed.
3003 // The rhiTexture is not relevant here.
3004 pd->textureBindingTable[binding] = nextTex; // does not own
3006
3007 if (isAnisotropic) // ###
3008 qWarning("QSGTexture anisotropy levels are not currently supported");
3009
3011
3012 for (QSGTexture *t : nextTex) {
3014
3016
3017 if (!sampler) {
3019 if (!sampler->create()) {
3020 qWarning("Failed to build sampler");
3021 delete sampler;
3022 continue;
3023 }
3025 }
3027 }
3028
3029 pd->samplerBindingTable[binding] = samplers; // does not own
3030 }
3031
3033
3035
3036 for (int i = 0; i < pd->textureBindingTable[binding].size(); ++i) {
3037
3039
3040 // texture may be null if the update above failed for any reason,
3041 // or if the QSGTexture chose to return null intentionally. This is
3042 // valid and we still need to provide something to the shader.
3043 if (!texture)
3045
3047
3050 }
3051
3052 if (!textureSamplers.isEmpty())
3055 }
3056 }
3057
3058#ifndef QT_NO_DEBUG
3059 if (bindings.isEmpty())
3060 qWarning("No shader resources for material %p, this is odd.", material);
3061#endif
3062
3063 enum class SrbAction {
3064 Unknown,
3065 DoNothing,
3067 Rebake
3069
3070 // First, if the Element has no srb created at all, then try to find an existing,
3071 // currently unused srb that is layout-compatible with our binding list.
3072 if (!e->srb) {
3073 // reuse a QVector as our work area, thus possibly reusing the underlying allocation too
3075 layoutDesc.clear();
3078 if (e->srb) {
3079 // Here we know layout compatibility is satisfied, but do not spend time on full
3080 // comparison. The chance of getting an srb that refers to the same resources
3081 // (buffer, textures) is low in practice. So reuse, but write new resources.
3083 }
3084 }
3085
3086 // If the Element had an existing srb, investigate:
3087 // - It may be used as-is (when nothing changed in the scene regarding this node compared to the previous frame).
3088 // - Otherwise it may be able to go with a lightweight update (replace resources, binding list layout is the same).
3089 // - If all else fails rebake the full thing, meaning we reuse the memory allocation but will recreate everything underneath.
3090 if (srbAction == SrbAction::Unknown && e->srb) {
3094 [](const auto &a, const auto &b) { return a.isLayoutCompatible(b); }))
3095 {
3097 } else {
3099 }
3100 }
3101
3102 // If the Element had no srb associated at all and could not find a layout-compatible
3103 // one from the pool, then create a whole new object.
3104 if (!e->srb) {
3107 }
3108
3110
3111 switch (srbAction) {
3112 case SrbAction::DoNothing:
3113 break;
3115 {
3118 // Due to the way the binding list is built up above, if we have a uniform buffer
3119 // at binding point 0 (or none at all) then the sampledTexture bindings are added
3120 // with increasing binding points afterwards, so the list is already sorted based
3121 // on the binding points, thus we can save some time by telling the QRhi backend
3122 // not to sort again.
3123 if (pd->ubufBinding <= 0 || bindings.size() <= 1)
3125
3127 }
3128 break;
3129 case SrbAction::Rebake:
3131 if (!e->srb->create())
3132 qWarning("Failed to build srb");
3133 break;
3134 default:
3135 Q_ASSERT_X(false, "updateMaterialDynamicData", "No srb action set, this cannot happen");
3136 }
3137}
3138
3142 Batch *batch,
3143 bool *gstateChanged)
3144{
3146 *gstateChanged = false;
3148 // generate the public mini-state from m_gstate, invoke the material,
3149 // write the changes, if any, back to m_gstate, together with a way to
3150 // roll those back.
3154 if (changed) {
3159 {
3161 }
3162 *gstateChanged = true;
3163 }
3164 }
3165}
3166
3168{
3169 if (batch->vertexCount == 0 || batch->indexCount == 0)
3170 return false;
3171
3172 Element *e = batch->first;
3173 Q_ASSERT(e);
3174
3175#ifndef QT_NO_DEBUG_OUTPUT
3176 if (Q_UNLIKELY(debug_render())) {
3177 QDebug debug = qDebug();
3178 debug << " -"
3179 << batch
3180 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3181 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3182 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3183 << "[ merged]"
3184 << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
3185 << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
3186 << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
3187 << " root:" << batch->root;
3188 if (batch->drawSets.size() > 1)
3189 debug << "sets:" << batch->drawSets.size();
3190 if (!batch->isOpaque)
3191 debug << "opacity:" << e->node->inheritedOpacity();
3192 batch->uploadedThisFrame = false;
3193 }
3194#endif
3195
3197
3198 // We always have dirty matrix as all batches are at a unique z range.
3200 if (batch->root)
3202 else
3205
3206 const int viewCount = projectionMatrixCount();
3208 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3210
3214
3218
3219 const QSGGeometry *g = gn->geometry();
3223 if (!sms)
3224 return false;
3225
3227 if (m_currentShader != sms)
3229
3234 }
3235
3238 if (pd->ubufBinding >= 0) {
3239 bool ubufRebuild = false;
3240 if (!batch->ubuf) {
3242 ubufRebuild = true;
3243 } else {
3244 if (batch->ubuf->size() < ubufSize) {
3246 ubufRebuild = true;
3247 }
3248 }
3249 if (ubufRebuild) {
3250 batch->ubufDataValid = false;
3251 if (!batch->ubuf->create()) {
3252 qWarning("Failed to build uniform buffer of size %u bytes", ubufSize);
3253 delete batch->ubuf;
3254 batch->ubuf = nullptr;
3255 return false;
3256 }
3257 }
3258 }
3259
3261
3262 bool pendingGStatePop = false;
3264
3265 char *directUpdatePtr = nullptr;
3266 if (batch->ubuf->nativeBuffer().slotCount == 0)
3268
3270
3271 if (directUpdatePtr)
3273
3274#ifndef QT_NO_DEBUG
3276 qDebug("QSGMaterial::updateState triggered an error (merged), batch will be skipped:");
3277 Element *ee = e;
3278 while (ee) {
3279 qDebug() << " -" << ee->node;
3280 ee = ee->nextInBatch;
3281 }
3283 qFatal("Aborting: scene graph is invalid...");
3284 }
3285#endif
3286
3289
3290 const bool hasPipeline = ensurePipelineState(e, sms);
3291
3292 if (pendingGStatePop)
3294
3295 if (!hasPipeline)
3296 return false;
3297
3301 ensurePipelineState(e, sms, true);
3303 }
3304
3305 batch->ubufDataValid = true;
3306
3308
3310 renderBatch->sms = sms;
3311
3312 return true;
3313}
3314
3316{
3319 {
3320 if (g->lineWidth() != 1.0f) {
3321 static bool checkedWideLineSupport = false;
3325 qWarning("Line widths other than 1 are not supported by the graphics API");
3326 }
3327 }
3328 } else if (g->drawingMode() == QSGGeometry::DrawPoints) {
3329 if (g->lineWidth() != 1.0f) {
3330 static bool warnedPointSize = false;
3331 if (!warnedPointSize) {
3332 warnedPointSize = true;
3333 qWarning("Point size is not controllable by QSGGeometry. "
3334 "Set gl_PointSize from the vertex shader instead.");
3335 }
3336 }
3337 }
3338}
3339
3341{
3342 const Batch *batch = renderBatch->batch;
3343 if (!batch->vbo.buf || !batch->ibo.buf)
3344 return;
3345
3346 Element *e = batch->first;
3348 QSGGeometry *g = gn->geometry();
3350
3353
3356
3357 for (int i = 0, ie = batch->drawSets.size(); i != ie; ++i) {
3358 const DrawSet &draw = batch->drawSets.at(i);
3362 };
3367 }
3368}
3369
3371{
3372 if (batch->vertexCount == 0)
3373 return false;
3374
3375 Element *e = batch->first;
3376 Q_ASSERT(e);
3377
3378 if (Q_UNLIKELY(debug_render())) {
3379 qDebug() << " -"
3380 << batch
3381 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3382 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3383 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3384 << "[unmerged]"
3385 << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
3386 << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
3387 << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
3388 << " root:" << batch->root;
3389
3390 batch->uploadedThisFrame = false;
3391 }
3392
3393 const int viewCount = projectionMatrixCount();
3395 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3397
3401
3405
3406 // We always have dirty matrix as all batches are at a unique z range.
3408
3409 // The vertex attributes are assumed to be the same for all elements in the
3410 // unmerged batch since the material (and so the shaders) is the same.
3411 QSGGeometry *g = gn->geometry();
3414 if (!sms)
3415 return false;
3416
3418 if (m_currentShader != sms)
3420
3425 }
3426
3428
3431 if (pd->ubufBinding >= 0) {
3433 while (e) {
3435 e = e->nextInBatch;
3436 }
3437 bool ubufRebuild = false;
3438 if (!batch->ubuf) {
3440 ubufRebuild = true;
3441 } else {
3442 if (batch->ubuf->size() < totalUBufSize) {
3444 ubufRebuild = true;
3445 }
3446 }
3447 if (ubufRebuild) {
3448 batch->ubufDataValid = false;
3449 if (!batch->ubuf->create()) {
3450 qWarning("Failed to build uniform buffer of size %u bytes", totalUBufSize);
3451 delete batch->ubuf;
3452 batch->ubuf = nullptr;
3453 return false;
3454 }
3455 }
3456 }
3457
3459 bool pendingGStatePop = false;
3462
3463 int ubufOffset = 0;
3464 QRhiGraphicsPipeline *ps = nullptr;
3466 e = batch->first;
3467
3468 char *directUpdatePtr = nullptr;
3469 if (batch->ubuf->nativeBuffer().slotCount == 0)
3471
3472 while (e) {
3473 gn = e->node;
3474
3477
3478 const int viewCount = projectionMatrixCount();
3480 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3482
3486
3487 if (useDepthBuffer()) {
3488 // this cannot be multiview
3491 }
3492
3495
3496#ifndef QT_NO_DEBUG
3498 qDebug("QSGMaterial::updateState() triggered an error (unmerged), batch will be skipped:");
3499 qDebug() << " - offending node is" << e->node;
3501 qFatal("Aborting: scene graph is invalid...");
3502 return false;
3503 }
3504#endif
3505
3507
3509 const float prevLineWidth = m_gstate.lineWidth;
3512
3513 // Do not bother even looking up the ps if the topology has not changed
3514 // since everything else is the same for all elements in the batch.
3515 // (except if the material modified blend state)
3517 if (!ensurePipelineState(e, sms)) {
3518 if (pendingGStatePop)
3520 return false;
3521 }
3522 ps = e->ps;
3526 ensurePipelineState(e, sms, true);
3529 }
3530 } else {
3531 e->ps = ps;
3534 }
3535
3536 // We don't need to bother with asking each node for its material as they
3537 // are all identical (compare==0) since they are in the same batch.
3539
3540 // We only need to push this on the very first iteration...
3542
3543 e = e->nextInBatch;
3544 }
3545
3546 if (directUpdatePtr)
3548
3549 if (pendingGStatePop)
3551
3552 batch->ubufDataValid = true;
3553
3555 renderBatch->sms = sms;
3556
3557 return true;
3558}
3559
3561{
3562 const Batch *batch = renderBatch->batch;
3563 if (!batch->vbo.buf)
3564 return;
3565
3566 Element *e = batch->first;
3567
3570
3571 quint32 vOffset = 0;
3572 quint32 iOffset = 0;
3574
3575 while (e) {
3576 QSGGeometry *g = e->node->geometry();
3578 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
3579
3581
3583 if (g->indexCount()) {
3584 if (batch->ibo.buf) {
3586 batch->ibo.buf, iOffset,
3590 }
3591 } else {
3593 cb->draw(g->vertexCount());
3594 }
3595
3596 vOffset += g->sizeOfVertex() * g->vertexCount();
3598
3599 e = e->nextInBatch;
3600 }
3601}
3602
3604{
3606
3607 if (!m_pstate.viewportSet) {
3608 m_pstate.viewportSet = true;
3610 }
3613 m_pstate.scissorSet = true;
3615 } else {
3617 // Regardless of the ps not using scissor, the scissor may need to be
3618 // reset, depending on the backend. So set the viewport again, which in
3619 // turn also sets the scissor on backends where a scissor rect is
3620 // always-on (Vulkan).
3621 if (m_pstate.scissorSet) {
3622 m_pstate.scissorSet = false;
3624 }
3625 }
3629 }
3632
3634}
3635
3637{
3638 if (e->isRenderNode) {
3639 delete static_cast<RenderNodeElement *>(e);
3640 } else {
3641 if (e->srb) {
3642 if (!inDestructor) {
3645 else
3646 delete e->srb;
3647 } else {
3648 delete e->srb;
3649 }
3650 e->srb = nullptr;
3651 }
3653 }
3654}
3655
3657{
3658 if (!m_elementsToDelete.size())
3659 return;
3660
3661 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3663 if (*e && (*e)->removed)
3664 *e = nullptr;
3665 }
3666 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3668 if (*e && (*e)->removed)
3669 *e = nullptr;
3670 }
3671
3672 for (int i=0; i<m_elementsToDelete.size(); ++i)
3674
3676}
3677
3679{
3680 // Gracefully handle the lack of a render target - some autotests may rely
3681 // on this in odd cases.
3682 if (!renderTarget().rt)
3683 return;
3684
3689}
3690
3691// An alternative to render() is to call prepareInline() and renderInline() at
3692// the appropriate times (i.e. outside of a QRhi::beginPass() and then inside,
3693// respectively) These allow rendering within a render pass that is started by
3694// another component. In contrast, render() records a full render pass on its
3695// own.
3696
3701
3706
3708{
3709 if (ctx->valid)
3710 qWarning("prepareRenderPass() called with an already prepared render pass context");
3711
3712 ctx->valid = true;
3713
3714 if (Q_UNLIKELY(debug_dump())) {
3715 qDebug("\n");
3717 }
3718
3719 ctx->timeRenderLists = 0;
3721 ctx->timePrepareAlpha = 0;
3722 ctx->timeSorting = 0;
3723 ctx->timeUploadOpaque = 0;
3724 ctx->timeUploadAlpha = 0;
3725
3726 if (Q_UNLIKELY(debug_render() || debug_build())) {
3727 QByteArray type("rebuild:");
3728 if (m_rebuild == 0)
3729 type += " none";
3730 if (m_rebuild == FullRebuild)
3731 type += " full";
3732 else {
3734 type += " renderlists";
3736 type += " partial";
3737 else if (m_rebuild & BuildBatches)
3738 type += " batches";
3739 }
3740
3741 qDebug() << "Renderer::render()" << this << type;
3742 ctx->timer.start();
3743 }
3744
3746
3748 bool complete = (m_rebuild & BuildRenderLists) != 0;
3749 if (complete)
3751 else
3754
3755 if (Q_UNLIKELY(debug_build())) {
3756 qDebug("Opaque render lists %s:", (complete ? "(complete)" : "(partial)"));
3757 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3759 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3760 }
3761 qDebug("Alpha render list %s:", complete ? "(complete)" : "(partial)");
3762 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3764 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3765 }
3766 }
3767 }
3769
3770 for (int i=0; i<m_opaqueBatches.size(); ++i)
3772 for (int i=0; i<m_alphaBatches.size(); ++i)
3775
3778
3779 if (m_rebuild & BuildBatches) {
3784
3785 if (Q_UNLIKELY(debug_build())) {
3786 qDebug("Opaque Batches:");
3787 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3789 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3790 for (Element *e = b->first; e; e = e->nextInBatch) {
3791 qDebug() << " - element:" << e << " node:" << e->node << e->order;
3792 }
3793 }
3794 qDebug("Alpha Batches:");
3795 for (int i=0; i<m_alphaBatches.size(); ++i) {
3797 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3798 for (Element *e = b->first; e; e = e->nextInBatch) {
3799 qDebug() << " - element:" << e << e->bounds << " node:" << e->node << " order:" << e->order;
3800 }
3801 }
3802 }
3803 } else {
3805 }
3806
3807
3809
3810 if (m_rebuild != 0) {
3811 // Then sort opaque batches so that we're drawing the batches with the highest
3812 // order first, maximizing the benefit of front-to-back z-ordering.
3813 if (m_opaqueBatches.size())
3815
3816 // Sort alpha batches back to front so that they render correctly.
3817 if (m_alphaBatches.size())
3819
3821 ? 1.0 / (m_nextRenderOrder)
3822 : 0;
3823 }
3824
3826
3827 // Set size to 0, nothing is deallocated, they will "grow" again
3828 // as part of uploadBatch.
3831
3832 if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Opaque Batches:");
3833 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3835 uploadBatch(b);
3836 }
3838
3839 if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Alpha Batches:");
3840 for (int i=0; i<m_alphaBatches.size(); ++i) {
3842 uploadBatch(b);
3843 }
3845
3846 if (Q_UNLIKELY(debug_render())) {
3847 qDebug().nospace() << "Rendering:" << Qt::endl
3848 << " -> Opaque: " << qsg_countNodesInBatches(m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << Qt::endl
3849 << " -> Alpha: " << qsg_countNodesInBatches(m_alphaBatches) << " nodes in " << m_alphaBatches.size() << " batches...";
3850 }
3851
3853 m_currentMaterial = nullptr;
3854 m_currentShader = nullptr;
3855 m_currentProgram = nullptr;
3857
3858 const QRect viewport = viewportRect();
3859
3860 bool renderOpaque = !debug_noopaque();
3861 bool renderAlpha = !debug_noalpha();
3862
3868 m_pstate.viewportSet = false;
3869 m_pstate.scissorSet = false;
3870
3874 m_gstate.blending = false;
3875
3882 m_gstate.usesScissor = false;
3883 m_gstate.stencilTest = false;
3884
3887
3889 if (Q_LIKELY(renderOpaque)) {
3890 for (int i = 0, ie = m_opaqueBatches.size(); i != ie; ++i) {
3893 bool ok;
3894 if (b->merged)
3896 else
3898 if (ok)
3900 }
3901 }
3902
3903 m_gstate.blending = true;
3904 // factors never change, always set for premultiplied alpha based blending
3905
3906 // depth test stays enabled (if useDepthBuffer(), that is) but no need
3907 // to write out depth from the transparent (back-to-front) pass
3908 m_gstate.depthWrite = false;
3909
3910 // special case: the 3D plane mode tests against the depth buffer, but does
3911 // not write (and all batches are alpha because this render mode evaluates
3912 // to useDepthBuffer()==false)
3915 m_gstate.depthTest = true;
3916 }
3917
3919 if (Q_LIKELY(renderAlpha)) {
3920 for (int i = 0, ie = m_alphaBatches.size(); i != ie; ++i) {
3923 bool ok;
3924 if (b->merged)
3926 else if (b->isRenderNode)
3928 else
3930 if (ok)
3932 }
3933 }
3934
3935 m_rebuild = 0;
3936
3937#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
3940#endif
3941
3944
3946 m_resourceUpdates = nullptr;
3947}
3948
3950{
3953 // we cannot tell if the application will have
3954 // native rendering thrown in to this pass
3955 // (QQuickWindow::beginExternalCommands()), so
3956 // we have no choice but to set the flag always
3957 // (thus triggering using secondary command
3958 // buffers with Vulkan)
3960 // We do not use GPU compute at all at the moment, this means we can
3961 // get a small performance gain with OpenGL by declaring this.
3963
3966}
3967
3969{
3970 // prepareRenderPass and recordRenderPass must always be called together.
3971 // They are separate because beginRenderPass and endRenderPass are optional.
3972 //
3973 // The valid call sequence are therefore:
3974 // prepare, begin, record, end
3975 // or
3976 // prepare, record
3977
3978 if (!ctx->valid)
3979 qWarning("recordRenderPass() called without a prepared render pass context");
3980
3981 ctx->valid = false;
3982
3984 cb->debugMarkBegin(QByteArrayLiteral("Qt Quick scene render"));
3985
3986 for (int i = 0, ie = ctx->opaqueRenderBatches.size(); i != ie; ++i) {
3987 if (i == 0)
3988 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick opaque batches"));
3990 if (renderBatch->batch->merged)
3992 else
3994 }
3995
3996 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
3997 if (i == 0) {
3999 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D batches"));
4000 else
4001 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick alpha batches"));
4002 }
4004 if (renderBatch->batch->merged)
4006 else if (renderBatch->batch->isRenderNode)
4008 else
4010 }
4011
4013 // Depth post-pass to fill up the depth buffer in a way that it
4014 // corresponds to what got rendered to the color buffer in the previous
4015 // (alpha) pass. The previous pass cannot enable depth write due to Z
4016 // fighting. Rather, do it separately in a dedicated color-write-off,
4017 // depth-write-on pass. This enables the 3D content drawn afterwards to
4018 // depth test against the 2D items' rendering.
4019 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
4020 if (i == 0)
4021 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D depth post-pass"));
4023 if (renderBatch->batch->merged)
4025 else if (!renderBatch->batch->isRenderNode) // rendernodes are skipped here for now
4027 }
4028 }
4029
4030 if (m_currentShader)
4031 setActiveRhiShader(nullptr, nullptr);
4032
4033 cb->debugMarkEnd();
4034
4035 if (Q_UNLIKELY(debug_render())) {
4036 qDebug(" -> times: build: %d, prepare(opaque/alpha): %d/%d, sorting: %d, upload(opaque/alpha): %d/%d, record rendering: %d",
4037 (int) ctx->timeRenderLists,
4039 (int) ctx->timeSorting,
4040 (int) ctx->timeUploadOpaque, (int) ctx->timeUploadAlpha,
4041 (int) ctx->timer.elapsed());
4042 }
4043}
4044
4055
4057{
4058 const QMatrix4x4 *projectionMatrix() const override { return m_projectionMatrix; }
4059 QRect scissorRect() const override { return m_scissorRect; }
4061 int stencilValue() const override { return m_stencilValue; }
4063 const QRegion *clipRegion() const override { return nullptr; }
4064
4070};
4071
4073{
4074 if (Q_UNLIKELY(debug_render()))
4075 qDebug() << " -" << batch << "rendernode";
4076
4078 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4079
4080 setActiveRhiShader(nullptr, nullptr);
4081
4083 rd->m_clip_list = nullptr;
4086 while (clip != rootNode()) {
4087 if (clip->type() == QSGNode::ClipNodeType) {
4088 rd->m_clip_list = static_cast<QSGClipNode *>(clip);
4089 break;
4090 }
4091 clip = clip->parent();
4092 }
4094 }
4095
4098 QSGNode *root = rootNode();
4099 if (e->root) {
4101 root = e->root->sgNode;
4102 }
4103 while (xform != root) {
4104 if (xform->type() == QSGNode::TransformNodeType) {
4105 matrix = matrix * static_cast<QSGTransformNode *>(xform)->combinedMatrix();
4106 break;
4107 }
4108 xform = xform->parent();
4109 }
4112
4114 rd->m_opacity = 1.0;
4115 while (opacity != rootNode()) {
4116 if (opacity->type() == QSGNode::OpacityNodeType) {
4117 rd->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity();
4118 break;
4119 }
4120 opacity = opacity->parent();
4121 }
4122
4123 rd->m_rt = renderTarget();
4124
4125 const int viewCount = projectionMatrixCount();
4127 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
4129
4130 if (useDepthBuffer()) {
4131 // this cannot be multiview
4132 rd->m_projectionMatrix[0](2, 2) = m_zRange;
4134 }
4135
4136 e->renderNode->prepare();
4137
4139 renderBatch->sms = nullptr;
4140
4141 return true;
4142}
4143
4145{
4148
4149 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4151
4153 // Expose only the first matrix through the state object, the rest are
4154 // queriable through the QSGRenderNode getters anyway.
4156 const std::array<int, 4> scissor = batch->clipState.scissor.scissor();
4161
4163
4166 if (needsExternal)
4167 cb->beginExternal();
4169 if (needsExternal)
4170 cb->endExternal();
4171
4172 rd->m_matrix = nullptr;
4173 rd->m_clip_list = nullptr;
4174
4177 {
4178 // Reset both flags if either is reported as changed, since with the rhi
4179 // it could be setViewport() that will record the resetting of the scissor.
4180 m_pstate.viewportSet = false;
4181 m_pstate.scissorSet = false;
4182 }
4183
4184 // Do not bother with RenderTargetState. Where applicable, endExternal()
4185 // ensures the correct target is rebound. For others (like Vulkan) it makes
4186 // no sense since render() could not possibly do that on our command buffer
4187 // which is in renderpass recording state.
4188}
4189
4191{
4192 if (mode.isEmpty())
4194 else if (mode == "clip")
4196 else if (mode == "overdraw")
4198 else if (mode == "batches")
4200 else if (mode == "changes")
4202}
4203
4208
4209bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
4210{
4211 return a.depthTest == b.depthTest
4212 && a.depthWrite == b.depthWrite
4213 && a.depthFunc == b.depthFunc
4214 && a.blending == b.blending
4215 && a.srcColor == b.srcColor
4216 && a.dstColor == b.dstColor
4217 && a.srcAlpha == b.srcAlpha
4218 && a.dstAlpha == b.dstAlpha
4219 && a.opColor == b.opColor
4220 && a.opAlpha == b.opAlpha
4221 && a.colorWrite == b.colorWrite
4222 && a.cullMode == b.cullMode
4226 && a.drawMode == b.drawMode
4227 && a.lineWidth == b.lineWidth
4228 && a.polygonMode == b.polygonMode
4230}
4231
4232bool operator!=(const GraphicsState &a, const GraphicsState &b) noexcept
4233{
4234 return !(a == b);
4235}
4236
4237size_t qHash(const GraphicsState &s, size_t seed) noexcept
4238{
4239 // do not bother with all fields
4240 return seed
4241 + s.depthTest * 1000
4242 + s.depthWrite * 100
4243 + s.depthFunc
4244 + s.blending * 10
4245 + s.srcColor
4246 + s.cullMode
4247 + s.usesScissor
4248 + s.stencilTest
4249 + s.sampleCount
4250 + s.multiViewCount;
4251}
4252
4254{
4255 return a.state == b.state
4256 && a.sms->materialShader == b.sms->materialShader
4257 && a.renderTargetDescription == b.renderTargetDescription
4258 && a.srbLayoutDescription == b.srbLayoutDescription;
4259}
4260
4262{
4263 return !(a == b);
4264}
4265
4266size_t qHash(const GraphicsPipelineStateKey &k, size_t seed) noexcept
4267{
4268 return qHash(k.state, seed)
4269 ^ qHash(k.sms->materialShader)
4270 ^ k.extra.renderTargetDescriptionHash
4271 ^ k.extra.srbLayoutDescriptionHash;
4272}
4273
4274bool operator==(const ShaderKey &a, const ShaderKey &b) noexcept
4275{
4276 return a.type == b.type
4277 && a.renderMode == b.renderMode
4279}
4280
4281bool operator!=(const ShaderKey &a, const ShaderKey &b) noexcept
4282{
4283 return !(a == b);
4284}
4285
4286size_t qHash(const ShaderKey &k, size_t seed) noexcept
4287{
4288 return qHash(k.type, seed) ^ int(k.renderMode) ^ k.multiViewCount;
4289}
4290
4291Visualizer::Visualizer(Renderer *renderer)
4292 : m_renderer(renderer),
4294{
4295}
4296
4298{
4299}
4300
4301#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded
4302 | QSGNode::DirtyOpacity
4303 | QSGNode::DirtyMatrix
4304 | QSGNode::DirtyNodeRemoved)
4305
4306void Visualizer::visualizeChangesPrepare(Node *n, uint parentChanges)
4307{
4308 uint childDirty = (parentChanges | n->dirtyState) & QSGNODE_DIRTY_PARENT;
4309 uint selfDirty = n->dirtyState | parentChanges;
4310 if (n->type() == QSGNode::GeometryNodeType && selfDirty != 0)
4311 m_visualizeChangeSet.insert(n, selfDirty);
4313 visualizeChangesPrepare(child, childDirty);
4314 }
4315}
4316
4317} // namespace QSGBatchRenderer
4318
4319QT_END_NAMESPACE
4320
4321#include "moc_qsgbatchrenderer_p.cpp"
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:322
void updateRootTransforms(Node *n, Node *root, const QMatrix4x4 &combined)
void updateStates(QSGNode *n) override
void updateRootTransforms(Node *n)
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)
bool operator!=(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) noexcept
const float VIEWPORT_MIN_DEPTH
QSGMaterial::Flag QSGMaterial_FullMatrix
bool qsg_sort_batch_decreasing_order(Batch *a, Batch *b)
bool operator==(const ShaderKey &a, const ShaderKey &b) noexcept
bool operator==(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) noexcept
bool operator!=(const ShaderKey &a, const ShaderKey &b) noexcept
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
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)
size_t qHash(const ShaderKey &k, size_t seed) noexcept
void qsg_dumpShadowRoots(BatchRootInfo *i, int indent)
void qsg_dumpShadowRoots(Node *n)
static bool isScale(const QMatrix4x4 &m)
static bool is2DSafe(const QMatrix4x4 &m)
QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode)
size_t qHash(const GraphicsPipelineStateKey &k, size_t seed) noexcept
Int aligned(Int v, Int byteAlign)
const quint32 DEFAULT_BUFFER_POOL_SIZE_LIMIT
bool qsg_sort_batch_is_valid(Batch *a, Batch *b)
Q_CORE_EXPORT QDebug operator<<(QDebug debug, QDir::Filters filters)
Definition qdir.cpp:2462
#define DECLARE_DEBUG_VAR(variable)
QDebug Q_QUICK_EXPORT operator<<(QDebug debug, const QQuickWindow *item)
#define QSGNODE_DIRTY_PARENT
int qt_sg_envInt(const char *name, int defaultValue)
QT_BEGIN_NAMESPACE Q_QUICK_EXPORT bool qsg_test_and_clear_material_failure()
#define SHADOWNODE_TRAVERSE(NODE)
#define QSGNODE_TRAVERSE(NODE)
bool geometryWasChanged(QSGGeometryNode *gn)
BatchCompatibility isMaterialCompatible(Element *e) const
BatchRootInfo * rootInfo() const
ClipBatchRootInfo * clipInfo() const
void operator|=(const Pt &pt)
void map(const QMatrix4x4 &m)
const QMatrix4x4 * projectionMatrix() const override
const QRegion * clipRegion() const override