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// Qt-Security score:significant reason:default
6
8
9#include <qmath.h>
10
11#include <QtCore/QElapsedTimer>
12#include <QtCore/QtNumeric>
13
14#include <QtGui/QGuiApplication>
15
16#include <private/qnumeric_p.h>
18
20
21#include <QtQuick/private/qsgnode_p.h>
22
23#include <algorithm>
24
26
27int qt_sg_envInt(const char *name, int defaultValue);
28
30{
31
32#define DECLARE_DEBUG_VAR(variable)
33 static bool debug_ ## variable()
34 { static bool value = qgetenv("QSG_RENDERER_DEBUG").contains(QT_STRINGIFY(variable)); return value; }
42DECLARE_DEBUG_VAR(noalpha)
43DECLARE_DEBUG_VAR(noopaque)
45#undef DECLARE_DEBUG_VAR
46
47#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling())
48#define SHADOWNODE_TRAVERSE(NODE) for (Node *child = NODE->firstChild(); child; child = child->sibling())
49
50static inline int size_of_type(int type)
51{
52 static int sizes[] = {
53 sizeof(char),
54 sizeof(unsigned char),
55 sizeof(short),
56 sizeof(unsigned short),
57 sizeof(int),
58 sizeof(unsigned int),
59 sizeof(float),
60 2,
61 3,
62 4,
63 sizeof(double)
64 };
65 Q_ASSERT(type >= QSGGeometry::ByteType && type <= QSGGeometry::DoubleType);
66 return sizes[type - QSGGeometry::ByteType];
67}
68
73
75
76static bool isTranslate(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Translation; }
77static bool isScale(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Scale; }
78static bool is2DSafe(const QMatrix4x4 &m) { return m.flags() < QMatrix4x4::Rotation; }
79
80const float OPAQUE_LIMIT = 0.999f;
81
85
86const float VIEWPORT_MIN_DEPTH = 0.0f;
87const float VIEWPORT_MAX_DEPTH = 1.0f;
88
89const quint32 DEFAULT_BUFFER_POOL_SIZE_LIMIT = 2 * 1024 * 1024; // 2 MB for m_vboPool and m_iboPool each
90
93
94template <class Int>
95inline Int aligned(Int v, Int byteAlign)
96{
97 return (v + byteAlign - 1) & ~(byteAlign - 1);
98}
99
101{
102 switch (a.type) {
103 case QSGGeometry::FloatType:
104 if (a.tupleSize == 4)
105 return QRhiVertexInputAttribute::Float4;
106 if (a.tupleSize == 3)
107 return QRhiVertexInputAttribute::Float3;
108 if (a.tupleSize == 2)
109 return QRhiVertexInputAttribute::Float2;
110 if (a.tupleSize == 1)
111 return QRhiVertexInputAttribute::Float;
112 break;
113 case QSGGeometry::UnsignedByteType:
114 if (a.tupleSize == 4)
115 return QRhiVertexInputAttribute::UNormByte4;
116 if (a.tupleSize == 2)
117 return QRhiVertexInputAttribute::UNormByte2;
118 if (a.tupleSize == 1)
119 return QRhiVertexInputAttribute::UNormByte;
120 break;
121 default:
122 break;
123 }
124 qWarning("Unsupported attribute type 0x%x with %d components", a.type, a.tupleSize);
125 Q_UNREACHABLE_RETURN(QRhiVertexInputAttribute::Float);
126}
127
128static QRhiVertexInputLayout calculateVertexInputLayout(const QSGMaterialShader *s, const QSGGeometry *geometry, bool batchable)
129{
130 Q_ASSERT(geometry);
131 const QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
132 if (!sd->vertexShader) {
133 qWarning("No vertex shader in QSGMaterialShader %p", s);
134 return QRhiVertexInputLayout();
135 }
136
137 const int attrCount = geometry->attributeCount();
138 QVarLengthArray<QRhiVertexInputAttribute, 8> inputAttributes;
139 inputAttributes.reserve(attrCount + 1);
140 quint32 offset = 0;
141 for (int i = 0; i < attrCount; ++i) {
142 const QSGGeometry::Attribute &a = geometry->attributes()[i];
143 if (!sd->vertexShader->vertexInputLocations.contains(a.position)) {
144 qWarning("Vertex input %d is present in material but not in shader. This is wrong.",
145 a.position);
146 }
147 inputAttributes.append(QRhiVertexInputAttribute(VERTEX_BUFFER_BINDING, a.position, qsg_vertexInputFormat(a), offset));
148 offset += a.tupleSize * size_of_type(a.type);
149 }
150 if (batchable) {
151 inputAttributes.append(QRhiVertexInputAttribute(ZORDER_BUFFER_BINDING, sd->vertexShader->qt_order_attrib_location,
152 QRhiVertexInputAttribute::Float, 0));
153 }
154
155 Q_ASSERT(VERTEX_BUFFER_BINDING == 0 && ZORDER_BUFFER_BINDING == 1); // not very flexible
156 QVarLengthArray<QRhiVertexInputBinding, 2> inputBindings;
157 inputBindings.append(QRhiVertexInputBinding(geometry->sizeOfVertex()));
158 if (batchable)
159 inputBindings.append(QRhiVertexInputBinding(sizeof(float)));
160
161 QRhiVertexInputLayout inputLayout;
162 inputLayout.setBindings(inputBindings.cbegin(), inputBindings.cend());
163 inputLayout.setAttributes(inputAttributes.cbegin(), inputAttributes.cend());
164
165 return inputLayout;
166}
167
168QRhiCommandBuffer::IndexFormat qsg_indexFormat(const QSGGeometry *geometry)
169{
170 switch (geometry->indexType()) {
171 case QSGGeometry::UnsignedShortType:
172 return QRhiCommandBuffer::IndexUInt16;
173 break;
174 case QSGGeometry::UnsignedIntType:
175 return QRhiCommandBuffer::IndexUInt32;
176 break;
177 default:
178 Q_UNREACHABLE_RETURN(QRhiCommandBuffer::IndexUInt16);
179 }
180}
181
182QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode, QRhi *rhi)
183{
184 QRhiGraphicsPipeline::Topology topology = QRhiGraphicsPipeline::Triangles;
185 switch (geomDrawMode) {
186 case QSGGeometry::DrawPoints:
187 topology = QRhiGraphicsPipeline::Points;
188 break;
189 case QSGGeometry::DrawLines:
190 topology = QRhiGraphicsPipeline::Lines;
191 break;
192 case QSGGeometry::DrawLineStrip:
193 topology = QRhiGraphicsPipeline::LineStrip;
194 break;
195 case QSGGeometry::DrawTriangles:
196 topology = QRhiGraphicsPipeline::Triangles;
197 break;
198 case QSGGeometry::DrawTriangleStrip:
199 topology = QRhiGraphicsPipeline::TriangleStrip;
200 break;
201 case QSGGeometry::DrawTriangleFan:
202 {
203 static bool triangleFanSupported = false;
204 static bool triangleFanSupportChecked = false;
205 if (!triangleFanSupportChecked) {
206 triangleFanSupportChecked = true;
207 triangleFanSupported = rhi->isFeatureSupported(QRhi::TriangleFanTopology);
208 }
209 if (triangleFanSupported) {
210 topology = QRhiGraphicsPipeline::TriangleFan;
211 break;
212 }
213 Q_FALLTHROUGH();
214 }
215 default:
216 qWarning("Primitive topology 0x%x not supported", geomDrawMode);
217 break;
218 }
219 return topology;
220}
221
222void qsg_setMultiViewFlagsOnMaterial(QSGMaterial *material, int multiViewCount)
223{
224 material->setFlag(QSGMaterial::MultiView2, multiViewCount == 2);
225 material->setFlag(QSGMaterial::MultiView3, multiViewCount == 3);
226 material->setFlag(QSGMaterial::MultiView4, multiViewCount == 4);
227}
228
229ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material,
230 const QSGGeometry *geometry,
231 QSGRendererInterface::RenderMode renderMode,
232 int multiViewCount)
233{
234 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
235
236 QSGMaterialType *type = material->type();
237 ShaderKey key = { type, renderMode, multiViewCount };
238 Shader *shader = rewrittenShaders.value(key, nullptr);
239 if (shader)
240 return shader;
241
242 shader = new Shader;
243 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
244 context->initializeRhiShader(s, QShader::BatchableVertexShader);
245 shader->materialShader = s;
246 shader->inputLayout = calculateVertexInputLayout(s, geometry, true);
247 QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
248 shader->stages = {
249 { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage), QShader::BatchableVertexShader },
250 { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
251 };
252
253 shader->lastOpacity = 0;
254
255 rewrittenShaders[key] = shader;
256 return shader;
257}
258
259ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material,
260 const QSGGeometry *geometry,
261 QSGRendererInterface::RenderMode renderMode,
262 int multiViewCount)
263{
264 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
265
266 QSGMaterialType *type = material->type();
267 ShaderKey key = { type, renderMode, multiViewCount };
268 Shader *shader = stockShaders.value(key, nullptr);
269 if (shader)
270 return shader;
271
272 shader = new Shader;
273 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
274 context->initializeRhiShader(s, QShader::StandardShader);
275 shader->materialShader = s;
276 shader->inputLayout = calculateVertexInputLayout(s, geometry, false);
277 QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
278 shader->stages = {
279 { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage) },
280 { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
281 };
282
283 shader->lastOpacity = 0;
284
285 stockShaders[key] = shader;
286
287 return shader;
288}
289
290void ShaderManager::invalidated()
291{
292 qDeleteAll(stockShaders);
293 stockShaders.clear();
294 qDeleteAll(rewrittenShaders);
295 rewrittenShaders.clear();
296
297 qDeleteAll(pipelineCache);
298 pipelineCache.clear();
299
300 qDeleteAll(srbPool);
301 srbPool.clear();
302}
303
305{
306 for (ShaderManager::Shader *sms : std::as_const(stockShaders)) {
307 QSGMaterialShader *s = sms->materialShader;
308 if (s) {
309 QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
310 sd->clearCachedRendererData();
311 }
312 }
313 for (ShaderManager::Shader *sms : std::as_const(rewrittenShaders)) {
314 QSGMaterialShader *s = sms->materialShader;
315 if (s) {
316 QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
317 sd->clearCachedRendererData();
318 }
319 }
320}
321
323{
324 static int extraIndent = 0;
325 ++extraIndent;
326
327 QByteArray ind(indent + extraIndent + 10, ' ');
328
329 if (!i) {
330 qDebug("%s - no info", ind.constData());
331 } else {
332 qDebug() << ind.constData() << "- parent:" << i->parentRoot << "orders" << i->firstOrder << "->" << i->lastOrder << ", avail:" << i->availableOrders;
333 for (QSet<Node *>::const_iterator it = i->subRoots.constBegin();
334 it != i->subRoots.constEnd(); ++it) {
335 qDebug() << ind.constData() << "-" << *it;
336 qsg_dumpShadowRoots((*it)->rootInfo(), indent);
337 }
338 }
339
340 --extraIndent;
341}
342
344{
345#ifndef QT_NO_DEBUG_OUTPUT
346 static int indent = 0;
347 ++indent;
348
349 QByteArray ind(indent, ' ');
350
351 if (n->type() == QSGNode::ClipNodeType || n->isBatchRoot) {
352 qDebug() << ind.constData() << "[X]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
354 } else {
355 QDebug d = qDebug();
356 d << ind.constData() << "[ ]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
357 if (n->type() == QSGNode::GeometryNodeType)
358 d << "order" << Qt::dec << n->element()->order;
359 }
360
363
364 --indent;
365#else
366 Q_UNUSED(n);
367#endif
368}
369
370Updater::Updater(Renderer *r)
371 : renderer(r)
372 , m_roots(32)
373 , m_rootMatrices(8)
374{
375 m_roots.add(0);
376 m_combined_matrix_stack.add(&m_identityMatrix);
377 m_rootMatrices.add(m_identityMatrix);
378}
379
380void Updater::updateStates(QSGNode *n)
381{
382 m_current_clip = nullptr;
383
384 m_added = 0;
385 m_transformChange = 0;
386 m_opacityChange = 0;
387
388 Node *sn = renderer->m_nodes.value(n, 0);
389 Q_ASSERT(sn);
390
391 if (Q_UNLIKELY(debug_roots()))
393
394 if (Q_UNLIKELY(debug_build())) {
395 qDebug("Updater::updateStates()");
396 if (sn->dirtyState & (QSGNode::DirtyNodeAdded << 16))
397 qDebug(" - nodes have been added");
398 if (sn->dirtyState & (QSGNode::DirtyMatrix << 16))
399 qDebug(" - transforms have changed");
400 if (sn->dirtyState & (QSGNode::DirtyOpacity << 16))
401 qDebug(" - opacity has changed");
402 if (uint(sn->dirtyState) & uint(QSGNode::DirtyForceUpdate << 16))
403 qDebug(" - forceupdate");
404 }
405
406 if (Q_UNLIKELY(renderer->m_visualizer->mode() == Visualizer::VisualizeChanges))
407 renderer->m_visualizer->visualizeChangesPrepare(sn);
408
409 visitNode(sn);
410}
411
413{
414 if (m_added == 0 && n->dirtyState == 0 && m_force_update == 0 && m_transformChange == 0 && m_opacityChange == 0)
415 return;
416
417 int count = m_added;
418 if (n->dirtyState & QSGNode::DirtyNodeAdded)
419 ++m_added;
420
421 int force = m_force_update;
422 if (n->dirtyState & QSGNode::DirtyForceUpdate)
423 ++m_force_update;
424
425 switch (n->type()) {
426 case QSGNode::OpacityNodeType:
428 break;
429 case QSGNode::TransformNodeType:
431 break;
432 case QSGNode::GeometryNodeType:
434 break;
435 case QSGNode::ClipNodeType:
437 break;
438 case QSGNode::RenderNodeType:
439 if (m_added)
440 n->renderNodeElement()->root = m_roots.last();
441 Q_FALLTHROUGH(); // to visit children
442 default:
444 break;
445 }
446
447 m_added = count;
448 m_force_update = force;
449 n->dirtyState = {};
450}
451
453{
455
456 QSGClipNode *cn = static_cast<QSGClipNode *>(n->sgNode);
457
458 if (m_roots.last() && m_added > 0)
459 renderer->registerBatchRoot(n, m_roots.last());
460
461 cn->setRendererClipList(m_current_clip);
462 m_current_clip = cn;
463 m_roots << n;
464 m_rootMatrices.add(m_rootMatrices.last() * *m_combined_matrix_stack.last());
465 extra->matrix = m_rootMatrices.last();
466 cn->setRendererMatrix(&extra->matrix);
467 m_combined_matrix_stack << &m_identityMatrix;
468
470
471 m_current_clip = cn->clipList();
472 m_rootMatrices.pop_back();
473 m_combined_matrix_stack.pop_back();
474 m_roots.pop_back();
475}
476
478{
479 QSGOpacityNode *on = static_cast<QSGOpacityNode *>(n->sgNode);
480
481 qreal combined = m_opacity_stack.last() * on->opacity();
482 on->setCombinedOpacity(combined);
483 m_opacity_stack.add(combined);
484
485 if (m_added == 0 && n->dirtyState & QSGNode::DirtyOpacity) {
486 bool was = n->isOpaque;
487 bool is = on->opacity() > OPAQUE_LIMIT;
488 if (was != is) {
489 renderer->m_rebuild = Renderer::FullRebuild;
490 n->isOpaque = is;
491 }
492 ++m_opacityChange;
494 --m_opacityChange;
495 } else {
496 if (m_added > 0)
497 n->isOpaque = on->opacity() > OPAQUE_LIMIT;
499 }
500
501 m_opacity_stack.pop_back();
502}
503
505{
506 bool popMatrixStack = false;
507 bool popRootStack = false;
508 bool dirty = n->dirtyState & QSGNode::DirtyMatrix;
509
510 QSGTransformNode *tn = static_cast<QSGTransformNode *>(n->sgNode);
511
512 if (n->isBatchRoot) {
513 if (m_added > 0 && m_roots.last())
514 renderer->registerBatchRoot(n, m_roots.last());
515 tn->setCombinedMatrix(m_rootMatrices.last() * *m_combined_matrix_stack.last() * tn->matrix());
516
517 // The only change in this subtree is ourselves and we are a batch root, so
518 // only update subroots and return, saving tons of child-processing (flickable-panning)
519
520 if (!n->becameBatchRoot && m_added == 0 && m_force_update == 0 && m_opacityChange == 0 && dirty && (n->dirtyState & ~QSGNode::DirtyMatrix) == 0) {
521 BatchRootInfo *info = renderer->batchRootInfo(n);
522 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
523 it != info->subRoots.constEnd(); ++it) {
524 updateRootTransforms(*it, n, tn->combinedMatrix());
525 }
526 return;
527 }
528
529 n->becameBatchRoot = false;
530
531 m_combined_matrix_stack.add(&m_identityMatrix);
532 m_roots.add(n);
533 m_rootMatrices.add(tn->combinedMatrix());
534
535 popMatrixStack = true;
536 popRootStack = true;
537 } else if (!tn->matrix().isIdentity()) {
538 tn->setCombinedMatrix(*m_combined_matrix_stack.last() * tn->matrix());
539 m_combined_matrix_stack.add(&tn->combinedMatrix());
540 popMatrixStack = true;
541 } else {
542 tn->setCombinedMatrix(*m_combined_matrix_stack.last());
543 }
544
545 if (dirty)
546 ++m_transformChange;
547
549
550 if (dirty)
551 --m_transformChange;
552 if (popMatrixStack)
553 m_combined_matrix_stack.pop_back();
554 if (popRootStack) {
555 m_roots.pop_back();
556 m_rootMatrices.pop_back();
557 }
558}
559
561{
562 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode);
563
564 gn->setRendererMatrix(m_combined_matrix_stack.last());
565 gn->setRendererClipList(m_current_clip);
566 gn->setInheritedOpacity(m_opacity_stack.last());
567
568 if (m_added) {
569 Element *e = n->element();
570 e->root = m_roots.last();
571 e->translateOnlyToRoot = isTranslate(*gn->matrix());
572
573 if (e->root) {
574 BatchRootInfo *info = renderer->batchRootInfo(e->root);
575 while (info != nullptr) {
576 info->availableOrders--;
577 if (info->availableOrders < 0) {
578 renderer->m_rebuild |= Renderer::BuildRenderLists;
579 } else {
580 renderer->m_rebuild |= Renderer::BuildRenderListsForTaggedRoots;
581 renderer->m_taggedRoots << e->root;
582 }
583 if (info->parentRoot != nullptr)
584 info = renderer->batchRootInfo(info->parentRoot);
585 else
586 info = nullptr;
587 }
588 } else {
589 renderer->m_rebuild |= Renderer::FullRebuild;
590 }
591 } else {
592 if (m_transformChange) {
593 Element *e = n->element();
594 e->translateOnlyToRoot = isTranslate(*gn->matrix());
595 }
596 if (m_opacityChange) {
597 Element *e = n->element();
598 if (e->batch)
599 renderer->invalidateBatchAndOverlappingRenderOrders(e->batch);
600 }
601 }
602
604}
605
606void Updater::updateRootTransforms(Node *node, Node *root, const QMatrix4x4 &combined)
607{
608 BatchRootInfo *info = renderer->batchRootInfo(node);
609 QMatrix4x4 m;
610 Node *n = node;
611
612 while (n != root) {
613 if (n->type() == QSGNode::TransformNodeType)
614 m = static_cast<QSGTransformNode *>(n->sgNode)->matrix() * m;
615 n = n->parent();
616 }
617
618 m = combined * m;
619
620 if (node->type() == QSGNode::ClipNodeType) {
621 static_cast<ClipBatchRootInfo *>(info)->matrix = m;
622 } else {
623 Q_ASSERT(node->type() == QSGNode::TransformNodeType);
624 static_cast<QSGTransformNode *>(node->sgNode)->setCombinedMatrix(m);
625 }
626
627 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
628 it != info->subRoots.constEnd(); ++it) {
629 updateRootTransforms(*it, node, m);
630 }
631}
632
633int qsg_positionAttribute(QSGGeometry *g)
634{
635 int vaOffset = 0;
636 for (int a=0; a<g->attributeCount(); ++a) {
637 const QSGGeometry::Attribute &attr = g->attributes()[a];
638 if (attr.isVertexCoordinate && attr.tupleSize == 2 && attr.type == QSGGeometry::FloatType) {
639 return vaOffset;
640 }
641 vaOffset += attr.tupleSize * size_of_type(attr.type);
642 }
643 return -1;
644}
645
646
647void Rect::map(const QMatrix4x4 &matrix)
648{
649 const float *m = matrix.constData();
650 if (isScale(matrix)) {
651 tl.x = tl.x * m[0] + m[12];
652 tl.y = tl.y * m[5] + m[13];
653 br.x = br.x * m[0] + m[12];
654 br.y = br.y * m[5] + m[13];
655 if (tl.x > br.x)
656 qSwap(tl.x, br.x);
657 if (tl.y > br.y)
658 qSwap(tl.y, br.y);
659 } else {
660 Pt mtl = tl;
661 Pt mtr = { br.x, tl.y };
662 Pt mbl = { tl.x, br.y };
663 Pt mbr = br;
664
665 mtl.map(matrix);
666 mtr.map(matrix);
667 mbl.map(matrix);
668 mbr.map(matrix);
669
670 set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
671 (*this) |= mtl;
672 (*this) |= mtr;
673 (*this) |= mbl;
674 (*this) |= mbr;
675 }
676}
677
679{
680 Q_ASSERT(!boundsComputed);
681 boundsComputed = true;
682
683 QSGGeometry *g = node->geometry();
684 int offset = qsg_positionAttribute(g);
685 if (offset == -1) {
686 // No position attribute means overlaps with everything..
687 bounds.set(-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX);
688 return;
689 }
690
691 bounds.set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
692 char *vd = (char *) g->vertexData() + offset;
693 for (int i=0; i<g->vertexCount(); ++i) {
694 bounds |= *(Pt *) vd;
695 vd += g->sizeOfVertex();
696 }
697 bounds.map(*node->matrix());
698
699 if (!qt_is_finite(bounds.tl.x) || bounds.tl.x == FLT_MAX)
700 bounds.tl.x = -FLT_MAX;
701 if (!qt_is_finite(bounds.tl.y) || bounds.tl.y == FLT_MAX)
702 bounds.tl.y = -FLT_MAX;
703 if (!qt_is_finite(bounds.br.x) || bounds.br.x == -FLT_MAX)
704 bounds.br.x = FLT_MAX;
705 if (!qt_is_finite(bounds.br.y) || bounds.br.y == -FLT_MAX)
706 bounds.br.y = FLT_MAX;
707
708 Q_ASSERT(bounds.tl.x <= bounds.br.x);
709 Q_ASSERT(bounds.tl.y <= bounds.br.y);
710
711 boundsOutsideFloatRange = bounds.isOutsideFloatRange();
712}
713
715{
716 Element *n = first;
717 // Skip to the first node other than e which has not been removed
718 while (n && (n == e || n->removed))
719 n = n->nextInBatch;
720
721 // Only 'e' in this batch, so a material change doesn't change anything as long as
722 // its blending is still in sync with this batch...
723 if (!n)
724 return BatchIsCompatible;
725
726 QSGMaterial *m = e->node->activeMaterial();
727 QSGMaterial *nm = n->node->activeMaterial();
728 return (nm->type() == m->type() && nm->viewCount() == m->viewCount() && nm->compare(m) == 0)
731}
732
733/*
734 * Marks this batch as dirty or in the case where the geometry node has
735 * changed to be incompatible with this batch, return false so that
736 * the caller can mark the entire sg for a full rebuild...
737 */
738bool Batch::geometryWasChanged(QSGGeometryNode *gn)
739{
740 Element *e = first;
741 Q_ASSERT_X(e, "Batch::geometryWasChanged", "Batch is expected to 'valid' at this time");
742 // 'gn' is the first node in the batch, compare against the next one.
743 while (e && (e->node == gn || e->removed))
744 e = e->nextInBatch;
745 if (!e || e->node->geometry()->attributes() == gn->geometry()->attributes()) {
746 needsUpload = true;
747 return true;
748 } else {
749 return false;
750 }
751}
752
754{
755 if (!needsPurge)
756 return;
757
758 // remove from front of batch..
759 while (first && first->removed) {
761 }
762
763 // Then continue and remove other nodes further out in the batch..
764 if (first) {
765 Element *e = first;
766 while (e->nextInBatch) {
767 if (e->nextInBatch->removed)
769 else
770 e = e->nextInBatch;
771
772 }
773 }
774
775 needsPurge = false;
776}
777
778/*
779 * Iterates through all geometry nodes in this batch and unsets their batch,
780 * thus forcing them to be rebuilt
781 */
783{
785 Element *e = first;
786 first = nullptr;
787 root = nullptr;
788 while (e) {
789 e->batch = nullptr;
791 e->nextInBatch = nullptr;
792 e = n;
793 }
794}
795
797 bool only = true;
798 Element *e = first;
799 while (e && only) {
800 only &= e->translateOnlyToRoot;
801 e = e->nextInBatch;
802 }
803 return only;
804}
805
806/*
807 * Iterates through all the nodes in the batch and returns true if the
808 * nodes are all safe to batch. There are two separate criteria:
809 *
810 * - The matrix is such that the z component of the result is of no
811 * consequence.
812 *
813 * - The bounds are inside the stable floating point range. This applies
814 * to desktop only where we in this case can trigger a fallback to
815 * unmerged in which case we pass the geometry straight through and
816 * just apply the matrix.
817 *
818 * NOTE: This also means a slight performance impact for geometries which
819 * are defined to be outside the stable floating point range and still
820 * use single precision float, but given that this implicitly fixes
821 * huge lists and tables, it is worth it.
822 */
823bool Batch::isSafeToBatch() const {
824 Element *e = first;
825 while (e) {
826 if (e->boundsOutsideFloatRange)
827 return false;
828 if (!is2DSafe(*e->node->matrix()))
829 return false;
830 e = e->nextInBatch;
831 }
832 return true;
833}
834
835static int qsg_countNodesInBatch(const Batch *batch)
836{
837 int sum = 0;
838 Element *e = batch->first;
839 while (e) {
840 ++sum;
841 e = e->nextInBatch;
842 }
843 return sum;
844}
845
846static int qsg_countNodesInBatches(const QDataBuffer<Batch *> &batches)
847{
848 int sum = 0;
849 for (int i=0; i<batches.size(); ++i) {
850 sum += qsg_countNodesInBatch(batches.at(i));
851 }
852 return sum;
853}
854
857 , m_context(ctx)
862 , m_partialRebuild(false)
863 , m_partialRebuildRoot(nullptr)
864 , m_forceNoDepthBuffer(false)
865 , m_opaqueBatches(16)
866 , m_alphaBatches(16)
867 , m_batchPool(16)
871 , m_vboPool(16)
872 , m_iboPool(16)
873 , m_vboPoolCost(0)
874 , m_iboPoolCost(0)
876 , m_zRange(0)
877#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
880#endif
882 , m_currentMaterial(nullptr)
883 , m_currentShader(nullptr)
884 , m_vertexUploadPool(256)
886{
887 m_rhi = m_context->rhi();
888 Q_ASSERT(m_rhi); // no more direct OpenGL code path in Qt 6
889
891
893 if (qEnvironmentVariableIntValue("QSG_RHI_UINT32_INDEX"))
894 m_uint32IndexForRhi = true;
895
896 bool ok = false;
897 int padding = qEnvironmentVariableIntValue("QSG_BATCHRENDERER_MINIMUM_ORDER_PADDING", &ok);
898 if (ok)
900
901 m_visualizer = new RhiVisualizer(this);
902
903 setNodeUpdater(new Updater(this));
904
905 // The shader manager is shared between renderers (think for example Item
906 // layers that create a new Renderer each) with the same rendercontext (and
907 // so same QRhi).
909 if (!m_shaderManager) {
911 m_shaderManager->setObjectName(QStringLiteral("__qt_ShaderManager"));
914 }
915
916 m_batchNodeThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_NODE_THRESHOLD", 64);
917 m_batchVertexThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_VERTEX_THRESHOLD", 1024);
918 m_srbPoolThreshold = qt_sg_envInt("QSG_RENDERER_SRB_POOL_THRESHOLD", 1024);
919 m_bufferPoolSizeLimit = qt_sg_envInt("QSG_RENDERER_BUFFER_POOL_LIMIT", DEFAULT_BUFFER_POOL_SIZE_LIMIT);
920
922 qDebug("Batch thresholds: nodes: %d vertices: %d srb pool: %d buffer pool: %d",
924 }
925}
926
927static void qsg_wipeBuffer(Buffer *buffer)
928{
929 delete buffer->buf;
930
931 // The free here is ok because we're in one of two situations.
932 // 1. We're using the upload pool in which case unmap will have set the
933 // data pointer to 0 and calling free on 0 is ok.
934 // 2. We're using dedicated buffers because of visualization or IBO workaround
935 // and the data something we malloced and must be freed.
936 free(buffer->data);
937}
938
939static void qsg_wipeBatch(Batch *batch)
940{
941 qsg_wipeBuffer(&batch->vbo);
942 qsg_wipeBuffer(&batch->ibo);
943 batch->stencilClipState.reset();
944 delete batch;
945}
946
948{
949 if (m_rhi) {
950 // Clean up batches and buffers
951 for (int i = 0; i < m_opaqueBatches.size(); ++i)
953 for (int i = 0; i < m_alphaBatches.size(); ++i)
955 for (int i = 0; i < m_batchPool.size(); ++i)
957 for (int i = 0; i < m_vboPool.size(); ++i)
958 delete m_vboPool.at(i);
959 for (int i = 0; i < m_iboPool.size(); ++i)
960 delete m_iboPool.at(i);
961 delete m_uniformBuffer;
962 }
963
964 for (Node *n : std::as_const(m_nodes)) {
965 if (n->type() == QSGNode::GeometryNodeType) {
966 Element *e = n->element();
967 if (!e->removed)
969 } else if (n->type() == QSGNode::ClipNodeType) {
970 delete n->clipInfo();
971 } else if (n->type() == QSGNode::RenderNodeType) {
973 if (!e->removed)
975 }
976
978 }
979
980 // Remaining elements...
981 for (int i=0; i<m_elementsToDelete.size(); ++i)
983
985
986 delete m_visualizer;
987}
988
990{
991 // If this is from the dtor, then the shader manager and its already
992 // prepared shaders will stay around for other renderers -> the cached data
993 // in the rhi shaders have to be purged as it may refer to samplers we
994 // are going to destroy.
996
999 delete m_dummyTexture;
1001}
1002
1004{
1006
1008
1009 m_samplers.clear();
1010 m_dummyTexture = nullptr;
1011
1013
1018
1019 // m_uniformBuffer stays, the elements' srbs refer to it
1022
1023 for (int i = 0; i < m_vboPool.size(); ++i)
1024 delete m_vboPool.at(i);
1025 m_vboPool.reset();
1026 m_vboPoolCost = 0;
1027
1028 for (int i = 0; i < m_iboPool.size(); ++i)
1029 delete m_iboPool.at(i);
1030 m_iboPool.reset();
1031 m_iboPoolCost = 0;
1032}
1033
1035{
1036 if (b->vbo.buf != nullptr && m_vboPoolCost + b->vbo.buf->size() <= quint32(m_bufferPoolSizeLimit)) {
1037 m_vboPool.add(b->vbo.buf);
1038 m_vboPoolCost += b->vbo.buf->size();
1039 } else {
1040 delete b->vbo.buf;
1041 }
1042 if (b->ibo.buf != nullptr && m_iboPoolCost + b->ibo.buf->size() <= quint32(m_bufferPoolSizeLimit)) {
1043 m_iboPool.add(b->ibo.buf);
1044 m_iboPoolCost += b->ibo.buf->size();
1045 } else {
1046 delete b->ibo.buf;
1047 }
1048 b->vbo.buf = nullptr;
1049 b->ibo.buf = nullptr;
1050 b->invalidate();
1051 for (int i=0; i<m_batchPool.size(); ++i)
1052 if (b == m_batchPool.at(i))
1053 return;
1054 m_batchPool.add(b);
1055}
1056
1058{
1060 // Common case, use a shared memory pool for uploading vertex data to avoid
1061 // excessive reevaluation
1063 if (byteSize > quint32(pool.size()))
1065 buffer->data = pool.data();
1066 } else if (buffer->size != byteSize) {
1067 free(buffer->data);
1068 buffer->data = (char *) malloc(byteSize);
1070 }
1071 buffer->size = byteSize;
1072}
1073
1075{
1076 // Batches are pooled and reused which means the QRhiBuffer will be
1077 // still valid in a recycled Batch. We only hit the newBuffer() path
1078 // when there are no buffers to recycle.
1080 if (!buffer->buf && bufferPool->isEmpty()) {
1083 buffer->size);
1084 if (!buffer->buf->create()) {
1085 qWarning("Failed to build vertex/index buffer of size %u", buffer->size);
1086 delete buffer->buf;
1087 buffer->buf = nullptr;
1088 }
1089 } else {
1090 if (!buffer->buf) {
1093 for (qsizetype i = 0; i < bufferPool->size(); ++i) {
1095 if (!buffer->buf
1097 || (testBuffer->size() >= expectedSize && testBuffer->size() < buffer->buf->size())
1098 || (testBuffer->size() < expectedSize && testBuffer->size() > buffer->buf->size())) {
1101 if (buffer->buf->size() == expectedSize)
1102 break;
1103 }
1104 }
1105
1110 }
1111 if (isIndexBuf)
1113 else
1116 }
1117
1118 bool needsRebuild = false;
1119 if (buffer->buf->size() < buffer->size) {
1121 needsRebuild = true;
1122 }
1123 if (buffer->buf->type() != QRhiBuffer::Dynamic
1125 {
1128 needsRebuild = true;
1129 }
1130 if (needsRebuild) {
1131 if (!buffer->buf->create()) {
1132 qWarning("Failed to (re)build vertex/index buffer of size %u", buffer->size);
1133 delete buffer->buf;
1134 buffer->buf = nullptr;
1135 }
1136 }
1137 }
1138 if (buffer->buf) {
1139 if (buffer->buf->type() != QRhiBuffer::Dynamic) {
1142 } else {
1145 else
1147 }
1148 }
1150 buffer->data = nullptr;
1151}
1152
1154{
1156 if (!info) {
1157 if (node->type() == QSGNode::ClipNodeType)
1158 info = new ClipBatchRootInfo;
1159 else {
1161 info = new BatchRootInfo;
1162 }
1163 node->data = info;
1164 }
1165 return info;
1166}
1167
1169{
1171 if (!childInfo->parentRoot)
1172 return;
1174
1177 childInfo->parentRoot = nullptr;
1178}
1179
1181{
1186}
1187
1189{
1191 if (subInfo->parentRoot == root)
1192 return false;
1193 if (subInfo->parentRoot) {
1196 }
1200 return true;
1201}
1202
1204{
1205 if (node->type() == QSGNode::ClipNodeType || node->isBatchRoot) {
1206 // When we reach a batchroot, we only need to update it. Its subtree
1207 // is relative to that root, so no need to recurse further.
1209 return;
1210 } else if (node->type() == QSGNode::GeometryNodeType) {
1211 // Only need to change the root as nodeChanged anyway flags a full update.
1212 Element *e = node->element();
1213 if (e) {
1214 e->root = root;
1215 e->boundsComputed = false;
1216 }
1217 } else if (node->type() == QSGNode::RenderNodeType) {
1219 if (e)
1220 e->root = root;
1221 }
1222
1225}
1226
1228{
1229 if (node->type() == QSGNode::GeometryNodeType) {
1230 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node->sgNode);
1232 Element *e = node->element();
1233 if (e) {
1234 e->boundsComputed = false;
1235 if (e->batch) {
1236 if (!e->batch->isOpaque) {
1238 } else if (e->batch->merged) {
1239 e->batch->needsUpload = true;
1240 }
1241 }
1242 }
1243 }
1244
1247}
1248
1250{
1252 if (node->isSubtreeBlocked())
1253 return;
1254
1256 snode->sgNode = node;
1258 if (shadowParent)
1260
1261 if (node->type() == QSGNode::GeometryNodeType) {
1263 snode->element()->setNode(static_cast<QSGGeometryNode *>(node));
1264
1265 } else if (node->type() == QSGNode::ClipNodeType) {
1268
1269 } else if (node->type() == QSGNode::RenderNodeType) {
1270 QSGRenderNode *rn = static_cast<QSGRenderNode *>(node);
1272 snode->data = e;
1276 m_forceNoDepthBuffer = true;
1278 }
1279
1282}
1283
1285{
1286 // Prefix traversal as removeBatchRootFromParent below removes nodes
1287 // in a bottom-up manner. Note that we *cannot* use SHADOWNODE_TRAVERSE
1288 // here, because we delete 'child' (when recursed, down below), so we'd
1289 // have a use-after-free.
1290 {
1291 Node *child = node->firstChild();
1292 while (child) {
1293 // Remove (and delete) child
1294 node->remove(child);
1296 child = node->firstChild();
1297 }
1298 }
1299
1300 if (node->type() == QSGNode::GeometryNodeType) {
1301 Element *e = node->element();
1302 if (e) {
1303 e->removed = true;
1305 e->node = nullptr;
1306 if (e->root) {
1309 }
1310 if (e->batch) {
1311 e->batch->needsUpload = true;
1312 e->batch->needsPurge = true;
1313 }
1314
1315 }
1316
1317 } else if (node->type() == QSGNode::ClipNodeType) {
1319 delete node->clipInfo();
1322
1323 } else if (node->isBatchRoot) {
1325 delete node->rootInfo();
1328
1329 } else if (node->type() == QSGNode::RenderNodeType) {
1331 if (e) {
1332 e->removed = true;
1335 m_forceNoDepthBuffer = false;
1336 // Must have a full rebuild given useDepthBuffer() now returns
1337 // a different value than before, meaning there can once again
1338 // be an opaque pass.
1340 }
1341
1342 if (e->batch != nullptr)
1343 e->batch->needsPurge = true;
1344 }
1345 }
1346
1348
1350}
1351
1353{
1354 if (Q_UNLIKELY(debug_change())) qDebug(" - new batch root");
1356 node->isBatchRoot = true;
1357 node->becameBatchRoot = true;
1358
1359 Node *p = node->parent();
1360 while (p) {
1361 if (p->type() == QSGNode::ClipNodeType || p->isBatchRoot) {
1363 break;
1364 }
1365 p = p->parent();
1366 }
1367
1370}
1371
1372
1374{
1375#ifndef QT_NO_DEBUG_OUTPUT
1376 if (Q_UNLIKELY(debug_change())) {
1377 QDebug debug = qDebug();
1378 debug << "dirty:";
1380 debug << "Geometry";
1382 debug << "Material";
1383 if (state & QSGNode::DirtyMatrix)
1384 debug << "Matrix";
1386 debug << "Added";
1388 debug << "Removed";
1389 if (state & QSGNode::DirtyOpacity)
1390 debug << "Opacity";
1392 debug << "SubtreeBlocked";
1394 debug << "ForceUpdate";
1395
1396 // when removed, some parts of the node could already have been destroyed
1397 // so don't debug it out.
1399 debug << (void *) node << node->type();
1400 else
1401 debug << node;
1402 }
1403#endif
1404 // As this function calls nodeChanged recursively, we do it at the top
1405 // to avoid that any of the others are processed twice.
1407 Node *sn = m_nodes.value(node);
1408
1409#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1410 // Force a batch rebuild if this includes an opacity change. Batches
1411 // may otherwise end up in the wrong order when their first element is
1412 // removed (QTBUG-85965). Not needed without this define, since then a
1413 // batch never skips over overlapping elements of other batches.
1414 if (state & QSGNode::DirtyOpacity)
1416#endif
1417
1418 bool blocked = node->isSubtreeBlocked();
1419 if (blocked && sn) {
1421 Q_ASSERT(m_nodes.value(node) == 0);
1422 } else if (!blocked && !sn) {
1424 }
1425 return;
1426 }
1427
1428 if (state & QSGNode::DirtyNodeAdded) {
1431 return;
1432 }
1433 if (node == rootNode())
1434 nodeWasAdded(node, nullptr);
1435 else
1437 }
1438
1439 // Mark this node dirty in the shadow tree.
1441
1442 // Blocked subtrees won't have shadow nodes, so we can safely abort
1443 // here..
1444 if (!shadowNode) {
1446 return;
1447 }
1448
1450
1455 } else {
1456 int vertices = 0;
1460 }
1461 }
1462 }
1463
1465 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1467
1468 if (e) {
1473 }
1474
1475 e->boundsComputed = false;
1476 Batch *b = e->batch;
1477 if (b) {
1478 if (!e->batch->geometryWasChanged(gn) || !e->batch->isOpaque) {
1480 } else {
1481 b->needsUpload = true;
1482 }
1483 }
1484 }
1485 }
1486
1489 if (e) {
1490 bool blended = hasMaterialWithBlending(static_cast<QSGGeometryNode *>(node));
1491 if (e->isMaterialBlended != blended) {
1494 } else if (e->batch) {
1497 } else {
1499 }
1500 }
1501 }
1502
1503 // Mark the shadow tree dirty all the way back to the root...
1509 if (dirtyChain != 0) {
1511 Node *sn = shadowNode->parent();
1512 while (sn) {
1514 sn = sn->parent();
1515 }
1516 }
1517
1518 // Delete happens at the very end because it deletes the shadownode.
1521 if (parent)
1524 Q_ASSERT(m_nodes.value(node) == 0);
1525 }
1526
1528}
1529
1530/*
1531 * Traverses the tree and builds two list of geometry nodes. One for
1532 * the opaque and one for the translucent. These are populated
1533 * in the order they should visually appear in, meaning first
1534 * to the back and last to the front.
1535 *
1536 * We split opaque and translucent as we can perform different
1537 * types of reordering / batching strategies on them, depending
1538 *
1539 * Note: It would be tempting to use the shadow nodes instead of the QSGNodes
1540 * for traversal to avoid hash lookups, but the order of the children
1541 * is important and they are not preserved in the shadow tree, so we must
1542 * use the actual QSGNode tree.
1543 */
1545{
1546 if (node->isSubtreeBlocked())
1547 return;
1548
1551
1552 if (node->type() == QSGNode::GeometryNodeType) {
1553 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1554
1556 Q_ASSERT(e);
1557
1559 if (opaque && useDepthBuffer())
1561 else
1563
1565 // Used while rebuilding partial roots.
1566 if (m_partialRebuild)
1567 e->orphaned = false;
1568
1569 } else if (node->type() == QSGNode::ClipNodeType || shadowNode->isBatchRoot) {
1572 if (node == m_partialRebuildRoot) {
1577 } else {
1586 }
1587 return;
1588 } else if (node->type() == QSGNode::RenderNodeType) {
1592 Q_ASSERT(e);
1593 }
1594
1597}
1598
1600{
1604 it != i->subRoots.constEnd(); ++it) {
1605 tagSubRoots(*it);
1606 }
1607}
1608
1609static void qsg_addOrphanedElements(QDataBuffer<Element *> &orphans, const QDataBuffer<Element *> &renderList)
1610{
1611 orphans.reset();
1612 for (int i=0; i<renderList.size(); ++i) {
1613 Element *e = renderList.at(i);
1614 if (e && !e->removed) {
1615 e->orphaned = true;
1616 orphans.add(e);
1617 }
1618 }
1619}
1620
1621static void qsg_addBackOrphanedElements(QDataBuffer<Element *> &orphans, QDataBuffer<Element *> &renderList)
1622{
1623 for (int i=0; i<orphans.size(); ++i) {
1624 Element *e = orphans.at(i);
1625 if (e->orphaned)
1626 renderList.add(e);
1627 }
1628 orphans.reset();
1629}
1630
1631/*
1632 * To rebuild the tagged roots, we start by putting all subroots of tagged
1633 * roots into the list of tagged roots. This is to make the rest of the
1634 * algorithm simpler.
1635 *
1636 * Second, we invalidate all batches which belong to tagged roots, which now
1637 * includes the entire subtree under a given root
1638 *
1639 * Then we call buildRenderLists for all tagged subroots which do not have
1640 * parents which are tagged, aka, we traverse only the topmosts roots.
1641 *
1642 * Then we sort the render lists based on their render order, to restore the
1643 * right order for rendering.
1644 */
1646{
1647 // Flag any element that is currently in the render lists, but which
1648 // is not in a batch. This happens when we have a partial rebuild
1649 // in one sub tree while we have a BuildBatches change in another
1650 // isolated subtree. So that batch-building takes into account
1651 // these "orphaned" nodes, we flag them now. The ones under tagged
1652 // roots will be cleared again. The remaining ones are added into the
1653 // render lists so that they contain all visual nodes after the
1654 // function completes.
1657
1658 // Take a copy now, as we will be adding to this while traversing..
1661 it != roots.constEnd(); ++it) {
1662 tagSubRoots(*it);
1663 }
1664
1665 for (int i=0; i<m_opaqueBatches.size(); ++i) {
1669
1670 }
1671 for (int i=0; i<m_alphaBatches.size(); ++i) {
1675 }
1676
1680 m_partialRebuild = true;
1681 // Traverse each root, assigning it
1683 it != m_taggedRoots.constEnd(); ++it) {
1684 Node *root = *it;
1691 }
1692 }
1693 m_partialRebuild = false;
1694 m_partialRebuildRoot = nullptr;
1697
1698 // Add orphaned elements back into the list and then sort it..
1701
1704 if (m_alphaRenderList.size())
1706
1707}
1708
1710{
1713
1714 for (int i=0; i<m_opaqueBatches.size(); ++i)
1716 for (int i=0; i<m_alphaBatches.size(); ++i)
1720
1722
1724}
1725
1727{
1728 Q_ASSERT(batch);
1730
1731#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1736
1739#else
1740 int first = batch->first->order;
1742#endif
1743
1744 batch->invalidate();
1745
1746 for (int i=0; i<m_alphaBatches.size(); ++i) {
1748 if (b->first) {
1749 int bf = b->first->order;
1750 int bl = b->lastOrderInBatch;
1751 if (bl > first && bf < last)
1752 b->invalidate();
1753 }
1754 }
1755
1757}
1758
1759/* Clean up batches by making it a consecutive list of "valid"
1760 * batches and moving all invalidated batches to the batches pool.
1761 */
1763 qsizetype n = batches->size();
1764 if (n == 0)
1765 return;
1766
1768 for (qsizetype i = 0; i < n; ++i) {
1769 Batch *b = batches->at(i);
1770 if (b->first)
1771 (*batches).data()[writeIndex++] = b;
1772 else
1774 }
1776}
1777
1779{
1780 for (int i=m_opaqueRenderList.size() - 1; i >= 0; --i) {
1782 if (!ei || ei->batch || ei->node->geometry()->vertexCount() == 0)
1783 continue;
1784 Batch *batch = newBatch();
1785 batch->first = ei;
1786 batch->root = ei->root;
1787 batch->isOpaque = true;
1788 batch->needsUpload = true;
1790
1792
1793 ei->batch = batch;
1794 Element *next = ei;
1795
1797
1798 for (int j = i - 1; j >= 0; --j) {
1800 if (!ej)
1801 continue;
1802 if (ej->root != ei->root)
1803 break;
1804 if (ej->batch || ej->node->geometry()->vertexCount() == 0)
1805 continue;
1806
1808
1809 const QSGGeometry *gniGeometry = gni->geometry();
1811 const QSGGeometry *gnjGeometry = gnj->geometry();
1813 if (gni->clipList() == gnj->clipList()
1821 && gniMaterial->type() == gnjMaterial->type()
1825 {
1826 ej->batch = batch;
1827 next->nextInBatch = ej;
1828 next = ej;
1829 }
1830 }
1831
1833 }
1834}
1835
1836bool Renderer::checkOverlap(int first, int last, const Rect &bounds)
1837{
1838 for (int i=first; i<=last; ++i) {
1840#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1841 if (!e || e->batch)
1842#else
1843 if (!e)
1844#endif
1845 continue;
1847 if (e->bounds.intersects(bounds))
1848 return true;
1849 }
1850 return false;
1851}
1852
1853/*
1854 *
1855 * To avoid the O(n^2) checkOverlap check in most cases, we have the
1856 * overlapBounds which is the union of all bounding rects to check overlap
1857 * for. We know that if it does not overlap, then none of the individual
1858 * ones will either. For the typical list case, this results in no calls
1859 * to checkOverlap what-so-ever. This also ensures that when all consecutive
1860 * items are matching (such as a table of text), we don't build up an
1861 * overlap bounds and thus do not require full overlap checks.
1862 */
1863
1865{
1866 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1868 if (!e || e->isRenderNode)
1869 continue;
1870 Q_ASSERT(!e->removed);
1872 }
1873
1874 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1876 if (!ei || ei->batch)
1877 continue;
1878
1879 if (ei->isRenderNode) {
1880 Batch *rnb = newBatch();
1881 rnb->first = ei;
1882 rnb->root = ei->root;
1883 rnb->isOpaque = false;
1884 rnb->isRenderNode = true;
1885 ei->batch = rnb;
1887 continue;
1888 }
1889
1890 if (ei->node->geometry()->vertexCount() == 0)
1891 continue;
1892
1893 Batch *batch = newBatch();
1894 batch->first = ei;
1895 batch->root = ei->root;
1896 batch->isOpaque = false;
1897 batch->needsUpload = true;
1899 ei->batch = batch;
1900
1903
1906
1907 Element *next = ei;
1908
1909 for (int j = i + 1; j < m_alphaRenderList.size(); ++j) {
1911 if (!ej)
1912 continue;
1913 if (ej->root != ei->root || ej->isRenderNode)
1914 break;
1915 if (ej->batch) {
1916#if !defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1918#endif
1919 continue;
1920 }
1921
1923 if (gnj->geometry()->vertexCount() == 0)
1924 continue;
1925
1926 const QSGGeometry *gniGeometry = gni->geometry();
1928 const QSGGeometry *gnjGeometry = gnj->geometry();
1930 if (gni->clipList() == gnj->clipList()
1934 // Must not do overlap checks when the line width is not 1,
1935 // we have no knowledge how such lines are rasterized.
1936 && gniGeometry->lineWidth() == 1.0f))
1940 && gniMaterial->type() == gnjMaterial->type()
1944 {
1945 if (!overlapBounds.intersects(ej->bounds) || !checkOverlap(i+1, j - 1, ej->bounds)) {
1946 ej->batch = batch;
1947 next->nextInBatch = ej;
1948 next = ej;
1949 } else {
1950 /* When we come across a compatible element which hits an overlap, we
1951 * need to stop the batch right away. We cannot add more elements
1952 * to the current batch as they will be rendered before the batch that the
1953 * current 'ej' will be added to.
1954 */
1955 break;
1956 }
1957 } else {
1959 }
1960 }
1961
1963 }
1964
1965
1966}
1967
1968static inline int qsg_fixIndexCount(int iCount, int drawMode)
1969{
1970 switch (drawMode) {
1971 case QSGGeometry::DrawTriangleStrip:
1972 // Merged triangle strips need to contain degenerate triangles at the beginning and end.
1973 // One could save 2 uploaded ushorts here by ditching the padding for the front of the
1974 // first and the end of the last, but for simplicity, we simply don't care.
1975 // Those extra triangles will be skipped while drawing to preserve the strip's parity
1976 // anyhow.
1977 return iCount + 2;
1978 case QSGGeometry::DrawLines:
1979 // For lines we drop the last vertex if the number of vertices is uneven.
1980 return iCount - (iCount % 2);
1981 case QSGGeometry::DrawTriangles:
1982 // For triangles we drop trailing vertices until the result is divisible by 3.
1983 return iCount - (iCount % 3);
1984 default:
1985 return iCount;
1986 }
1987}
1988
1989static inline float calculateElementZOrder(const Element *e, qreal zRange)
1990{
1991 // Clamp the zOrder to within the min and max depth of the viewport.
1992 return std::clamp(1.0f - float(e->order * zRange), VIEWPORT_MIN_DEPTH, VIEWPORT_MAX_DEPTH);
1993}
1994
1995/* These parameters warrant some explanation...
1996 *
1997 * vaOffset: The byte offset into the vertex data to the location of the
1998 * 2D float point vertex attributes.
1999 *
2000 * vertexData: destination where the geometry's vertex data should go
2001 *
2002 * zData: destination of geometries injected Z positioning
2003 *
2004 * indexData: destination of the indices for this element
2005 *
2006 * iBase: The starting index for this element in the batch
2007 */
2008
2009void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, void *iBasePtr, int *indexCount)
2010{
2011 if (Q_UNLIKELY(debug_upload())) qDebug() << " - uploading element:" << e << e->node << (void *) *vertexData << (qintptr) (*zData - *vertexData) << (qintptr) (*indexData - *vertexData);
2012 QSGGeometry *g = e->node->geometry();
2013
2014 const QMatrix4x4 &localx = *e->node->matrix();
2015 const float *localxdata = localx.constData();
2016
2017 const int vCount = g->vertexCount();
2018 const int vSize = g->sizeOfVertex();
2020
2021 // apply vertex transform..
2022 char *vdata = *vertexData + vaOffset;
2023 if (localx.flags() == QMatrix4x4::Translation) {
2024 for (int i=0; i<vCount; ++i) {
2025 Pt *p = (Pt *) vdata;
2026 p->x += localxdata[12];
2027 p->y += localxdata[13];
2028 vdata += vSize;
2029 }
2030 } else if (localx.flags() > QMatrix4x4::Translation) {
2031 for (int i=0; i<vCount; ++i) {
2032 ((Pt *) vdata)->map(localx);
2033 vdata += vSize;
2034 }
2035 }
2036
2037 if (useDepthBuffer()) {
2038 float *vzorder = (float *) *zData;
2040 for (int i=0; i<vCount; ++i)
2041 vzorder[i] = zorder;
2042 *zData += vCount * sizeof(float);
2043 }
2044
2045 int iCount = g->indexCount();
2046 if (m_uint32IndexForRhi) {
2047 // can only happen when using the rhi
2050 if (iCount == 0) {
2051 iCount = vCount;
2053 *indices++ = *iBase;
2054 else
2056
2057 for (int i=0; i<iCount; ++i)
2058 indices[i] = *iBase + i;
2059 } else {
2060 // source index data in QSGGeometry is always ushort (we would not merge otherwise)
2063 *indices++ = *iBase + srcIndices[0];
2064 else
2066
2067 for (int i=0; i<iCount; ++i)
2068 indices[i] = *iBase + srcIndices[i];
2069 }
2071 indices[iCount] = indices[iCount - 1];
2072 iCount += 2;
2073 }
2074 *iBase += vCount;
2075 } else {
2076 // normally batching is only done for ushort index data
2079 if (iCount == 0) {
2080 iCount = vCount;
2082 *indices++ = *iBase;
2083 else
2085
2086 for (int i=0; i<iCount; ++i)
2087 indices[i] = *iBase + i;
2088 } else {
2091 *indices++ = *iBase + srcIndices[0];
2092 else
2094
2095 for (int i=0; i<iCount; ++i)
2096 indices[i] = *iBase + srcIndices[i];
2097 }
2099 indices[iCount] = indices[iCount - 1];
2100 iCount += 2;
2101 }
2102 *iBase += vCount;
2103 }
2104
2105 *vertexData += vCount * vSize;
2107 *indexCount += iCount;
2108}
2109
2111{
2112 if (node->type() == QSGNode::TransformNodeType)
2113 return static_cast<QSGTransformNode *>(node->sgNode)->combinedMatrix();
2114 Q_ASSERT(node->type() == QSGNode::ClipNodeType);
2115 QSGClipNode *c = static_cast<QSGClipNode *>(node->sgNode);
2116 return *c->matrix();
2117}
2118
2120{
2121 // Early out if nothing has changed in this batch..
2122 if (!b->needsUpload) {
2123 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "already uploaded...";
2124 return;
2125 }
2126
2127 if (!b->first) {
2128 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "is invalid...";
2129 return;
2130 }
2131
2132 if (b->isRenderNode) {
2133 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch: " << b << "is a render node...";
2134 return;
2135 }
2136
2137 // Figure out if we can merge or not, if not, then just render the batch as is..
2138 Q_ASSERT(b->first);
2139 Q_ASSERT(b->first->node);
2140
2142 QSGGeometry *g = gn->geometry();
2146 && b->positionAttribute >= 0
2150 && b->isSafeToBatch();
2151
2152 b->merged = canMerge;
2153
2154 // Figure out how much memory we need...
2155 b->vertexCount = 0;
2156 b->indexCount = 0;
2157 int unmergedIndexSize = 0;
2158 Element *e = b->first;
2159
2160 // Merged batches always do indexed draw calls. Non-indexed geometry gets
2161 // indices generated automatically, when merged.
2162 while (e) {
2163 QSGGeometry *eg = e->node->geometry();
2164 b->vertexCount += eg->vertexCount();
2165 int iCount = eg->indexCount();
2166 if (b->merged) {
2167 if (iCount == 0)
2168 iCount = eg->vertexCount();
2170 } else {
2171 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : eg->sizeOfIndex();
2173 }
2174 b->indexCount += iCount;
2175 e = e->nextInBatch;
2176 }
2177
2178 // Abort if there are no vertices in this batch.. We abort this late as
2179 // this is a broken usecase which we do not care to optimize for...
2180 if (b->vertexCount == 0 || (b->merged && b->indexCount == 0))
2181 return;
2182
2183 /* Allocate memory for this batch. Merged batches are divided into three separate blocks
2184 1. Vertex data for all elements, as they were in the QSGGeometry object, but
2185 with the tranform relative to this batch's root applied. The vertex data
2186 is otherwise unmodified.
2187 2. Z data for all elements, derived from each elements "render order".
2188 This is present for merged data only.
2189 3. Indices for all elements, as they were in the QSGGeometry object, but
2190 adjusted so that each index matches its.
2191 And for TRIANGLE_STRIPs, we need to insert degenerate between each
2192 primitive. These are unsigned shorts for merged and arbitrary for
2193 non-merged.
2194 */
2196 int ibufferSize = 0;
2197 if (b->merged) {
2199 if (useDepthBuffer())
2200 bufferSize += b->vertexCount * sizeof(float);
2201 } else {
2203 }
2204
2205 map(&b->ibo, ibufferSize, true);
2206 map(&b->vbo, bufferSize);
2207
2208 if (Q_UNLIKELY(debug_upload())) qDebug() << " - batch" << b << " first:" << b->first << " root:"
2209 << b->root << " merged:" << b->merged << " positionAttribute" << b->positionAttribute
2210 << " vbo:" << b->vbo.buf << ":" << b->vbo.size;
2211
2212 if (b->merged) {
2213 char *vertexData = b->vbo.data;
2214 char *zData = vertexData + b->vertexCount * g->sizeOfVertex();
2215 char *indexData = b->ibo.data;
2216
2217 quint16 iOffset16 = 0;
2218 quint32 iOffset32 = 0;
2219 e = b->first;
2220 uint verticesInSet = 0;
2221 // Start a new set already after 65534 vertices because 0xFFFF may be
2222 // used for an always-on primitive restart with some apis (adapt for
2223 // uint32 indices as appropriate).
2224 const uint verticesInSetLimit = m_uint32IndexForRhi ? 0xfffffffe : 0xfffe;
2225 int indicesInSet = 0;
2226 b->drawSets.reset();
2227 int drawSetIndices = 0;
2228 const char *indexBase = b->ibo.data;
2230 while (e) {
2236 b->drawSets.last().indexCount -= 2;
2237 }
2240 zData - b->vbo.data,
2242 iOffset16 = 0;
2243 iOffset32 = 0;
2245 indicesInSet = 0;
2246 }
2247 void *iBasePtr = &iOffset16;
2251 e = e->nextInBatch;
2252 }
2254 // We skip the very first and very last degenerate triangles since they aren't needed
2255 // and the first one would reverse the vertex ordering of the merged strips.
2258 b->drawSets.last().indexCount -= 2;
2259 }
2260 } else {
2261 char *vboData = b->vbo.data;
2262 char *iboData = b->ibo.data;
2263 Element *e = b->first;
2264 while (e) {
2265 QSGGeometry *g = e->node->geometry();
2266 int vbs = g->vertexCount() * g->sizeOfVertex();
2268 vboData = vboData + vbs;
2269 const int indexCount = g->indexCount();
2270 if (indexCount) {
2271 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
2272 const int ibs = indexCount * effectiveIndexSize;
2273 if (g->sizeOfIndex() == effectiveIndexSize) {
2275 } else {
2276 if (g->sizeOfIndex() == sizeof(quint16) && effectiveIndexSize == sizeof(quint32)) {
2278 quint32 *dst = (quint32 *) iboData;
2279 for (int i = 0; i < indexCount; ++i)
2280 dst[i] = src[i];
2281 } else {
2282 Q_ASSERT_X(false, "uploadBatch (unmerged)", "uint index with ushort effective index - cannot happen");
2283 }
2284 }
2285 iboData += ibs;
2286 }
2287 e = e->nextInBatch;
2288 }
2289 }
2290#ifndef QT_NO_DEBUG_OUTPUT
2291 if (Q_UNLIKELY(debug_upload())) {
2292 const char *vd = b->vbo.data;
2293 qDebug() << " -- Vertex Data, count:" << b->vertexCount << " - " << g->sizeOfVertex() << "bytes/vertex";
2294 for (int i=0; i<b->vertexCount; ++i) {
2295 QDebug dump = qDebug().nospace();
2296 dump << " --- " << i << ": ";
2297 int offset = 0;
2298 for (int a=0; a<g->attributeCount(); ++a) {
2299 const QSGGeometry::Attribute &attr = g->attributes()[a];
2300 dump << attr.position << ":(" << attr.tupleSize << ",";
2301 if (attr.type == QSGGeometry::FloatType) {
2302 dump << "float ";
2304 dump << "* ";
2305 for (int t=0; t<attr.tupleSize; ++t)
2306 dump << *(const float *)(vd + offset + t * sizeof(float)) << " ";
2307 } else if (attr.type == QSGGeometry::UnsignedByteType) {
2308 dump << "ubyte ";
2309 for (int t=0; t<attr.tupleSize; ++t)
2310 dump << *(const unsigned char *)(vd + offset + t * sizeof(unsigned char)) << " ";
2311 }
2312 dump << ") ";
2314 }
2315 if (b->merged && useDepthBuffer()) {
2316 float zorder = ((float*)(b->vbo.data + b->vertexCount * g->sizeOfVertex()))[i];
2317 dump << " Z:(" << zorder << ")";
2318 }
2319 vd += g->sizeOfVertex();
2320 }
2321
2322 if (!b->drawSets.isEmpty()) {
2323 if (m_uint32IndexForRhi) {
2324 const quint32 *id = (const quint32 *) b->ibo.data;
2325 {
2326 QDebug iDump = qDebug();
2327 iDump << " -- Index Data, count:" << b->indexCount;
2328 for (int i=0; i<b->indexCount; ++i) {
2329 if ((i % 24) == 0)
2330 iDump << Qt::endl << " --- ";
2331 iDump << id[i];
2332 }
2333 }
2334 } else {
2335 const quint16 *id = (const quint16 *) b->ibo.data;
2336 {
2337 QDebug iDump = qDebug();
2338 iDump << " -- Index Data, count:" << b->indexCount;
2339 for (int i=0; i<b->indexCount; ++i) {
2340 if ((i % 24) == 0)
2341 iDump << Qt::endl << " --- ";
2342 iDump << id[i];
2343 }
2344 }
2345 }
2346
2347 for (int i=0; i<b->drawSets.size(); ++i) {
2348 const DrawSet &s = b->drawSets.at(i);
2349 qDebug() << " -- DrawSet: indexCount:" << s.indexCount << " vertices:" << s.vertices << " z:" << s.zorders << " indices:" << s.indices;
2350 }
2351 }
2352 }
2353#endif // QT_NO_DEBUG_OUTPUT
2354
2355 unmap(&b->vbo);
2356 unmap(&b->ibo, true);
2357
2359 qDebug() << " --- vertex/index buffers unmapped, batch upload completed... vbo pool size" << m_vboPoolCost << "ibo pool size" << m_iboPoolCost;
2360
2361 b->needsUpload = false;
2362
2363 if (Q_UNLIKELY(debug_render()))
2364 b->uploadedThisFrame = true;
2365}
2366
2368{
2371}
2372
2374{
2378 blend.colorWrite = {};
2379 ps->setTargetBlends({ blend });
2381 ps->setStencilTest(true);
2388 } else {
2393 }
2396
2398
2400
2404 ps->setShaderResourceBindings(batch->stencilClipState.srb); // use something, it just needs to be layout-compatible
2406
2407 if (!ps->create()) {
2408 qWarning("Failed to build stencil clip pipeline");
2409 delete ps;
2410 return nullptr;
2411 }
2412
2413 return ps;
2414}
2415
2417{
2418 // Note: No use of the clip-related speparate m_current* vars is allowed
2419 // here. All stored in batch->clipState instead. To collect state during
2420 // the prepare steps, m_currentClipState is used. It should not be used in
2421 // the render steps afterwards.
2422
2423 // The stenciling logic is slightly different from Qt 5's direct OpenGL version
2424 // as we cannot just randomly clear the stencil buffer. We now put all clip
2425 // shapes into the stencil buffer for all batches in the frame. This means
2426 // that the number of total clips in a scene is reduced (since the stencil
2427 // value cannot exceed 255) but we do not need any clears inbetween.
2428
2429 Q_ASSERT(m_rhi);
2434 return;
2435 }
2436
2440 const QSGClipNode *clip = clipList;
2441
2443 quint32 totalVSize = 0;
2444 quint32 totalISize = 0;
2445 quint32 totalUSize = 0;
2446 const quint32 StencilClipUbufSize = 64;
2447
2448 while (clip) {
2449 QMatrix4x4 m = m_current_projection_matrix_native_ndc[0]; // never hit for 3D and so multiview
2450 if (clip->matrix())
2451 m *= *clip->matrix();
2452
2454 && qFuzzyIsNull(m(3, 0)) && qFuzzyIsNull(m(3, 1));
2455 bool noRotate = qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(1, 0));
2456 bool isRotate90 = qFuzzyIsNull(m(0, 0)) && qFuzzyIsNull(m(1, 1));
2457
2459 QRectF bbox = clip->clipRect();
2460 qreal invW = 1 / m(3, 3);
2461 qreal fx1, fy1, fx2, fy2;
2462 if (noRotate) {
2463 fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
2464 fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
2465 fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
2466 fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
2467 } else {
2469 fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
2470 fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
2471 fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
2472 fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
2473 }
2474
2475 if (fx1 > fx2)
2476 qSwap(fx1, fx2);
2477 if (fy1 > fy2)
2478 qSwap(fy1, fy2);
2479
2480 QRect deviceRect = this->deviceRect();
2481
2482 qint32 ix1 = qRound((fx1 + 1) * deviceRect.width() * qreal(0.5));
2483 qint32 iy1 = qRound((fy1 + 1) * deviceRect.height() * qreal(0.5));
2484 qint32 ix2 = qRound((fx2 + 1) * deviceRect.width() * qreal(0.5));
2485 qint32 iy2 = qRound((fy2 + 1) * deviceRect.height() * qreal(0.5));
2486
2487 if (!(clipType & ClipState::ScissorClip)) {
2489 scissorRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2490 } else {
2491 scissorRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2492 }
2493 } else {
2495
2496 const QSGGeometry *g = clip->geometry();
2497 Q_ASSERT(g->attributeCount() > 0);
2498
2499 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2500 // the 4 byte alignment may not actually be needed here
2502 if (g->indexCount()) {
2503 const int indexByteSize = g->sizeOfIndex() * g->indexCount();
2504 // so no need to worry about NonFourAlignedEffectiveIndexBufferOffset
2506 }
2507 // ubuf start offsets must be aligned (typically to 256 bytes)
2509
2511 }
2512
2513 clip = clip->clipList();
2514 }
2515
2517 bool rebuildVBuf = false;
2518 if (!batch->stencilClipState.vbuf) {
2520 rebuildVBuf = true;
2521 } else if (batch->stencilClipState.vbuf->size() < totalVSize) {
2523 rebuildVBuf = true;
2524 }
2525 if (rebuildVBuf) {
2526 if (!batch->stencilClipState.vbuf->create()) {
2527 qWarning("Failed to build stencil clip vertex buffer");
2528 delete batch->stencilClipState.vbuf;
2529 batch->stencilClipState.vbuf = nullptr;
2530 return;
2531 }
2532 }
2533
2534 if (totalISize) {
2535 bool rebuildIBuf = false;
2536 if (!batch->stencilClipState.ibuf) {
2538 rebuildIBuf = true;
2539 } else if (batch->stencilClipState.ibuf->size() < totalISize) {
2541 rebuildIBuf = true;
2542 }
2543 if (rebuildIBuf) {
2544 if (!batch->stencilClipState.ibuf->create()) {
2545 qWarning("Failed to build stencil clip index buffer");
2546 delete batch->stencilClipState.ibuf;
2547 batch->stencilClipState.ibuf = nullptr;
2548 return;
2549 }
2550 }
2551 }
2552
2553 bool rebuildUBuf = false;
2554 if (!batch->stencilClipState.ubuf) {
2556 rebuildUBuf = true;
2557 } else if (batch->stencilClipState.ubuf->size() < totalUSize) {
2559 rebuildUBuf = true;
2560 }
2561 if (rebuildUBuf) {
2562 if (!batch->stencilClipState.ubuf->create()) {
2563 qWarning("Failed to build stencil clip uniform buffer");
2564 delete batch->stencilClipState.ubuf;
2565 batch->stencilClipState.ubuf = nullptr;
2566 return;
2567 }
2568 }
2569
2570 if (!batch->stencilClipState.srb) {
2575 if (!batch->stencilClipState.srb->create()) {
2576 qWarning("Failed to build stencil clip srb");
2577 delete batch->stencilClipState.srb;
2578 batch->stencilClipState.srb = nullptr;
2579 return;
2580 }
2581 }
2582
2583 quint32 vOffset = 0;
2584 quint32 iOffset = 0;
2585 quint32 uOffset = 0;
2586 for (const QSGClipNode *clip : stencilClipNodes) {
2587 const QSGGeometry *g = clip->geometry();
2588 const QSGGeometry::Attribute *a = g->attributes();
2591
2596 }
2597#ifndef QT_NO_DEBUG
2598 else {
2600 qWarning("updateClipState: Clip list entries have different primitive topologies, this is not currently supported.");
2602 qWarning("updateClipState: Clip list entries have different vertex input layouts, this is must not happen.");
2603 }
2604#endif
2605
2607 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2609
2610 int indexByteSize = 0;
2611 if (g->indexCount()) {
2615 }
2616
2619
2621 if (clip->matrix())
2622 matrixYUpNDC *= *clip->matrix();
2623
2626 if (indexByteSize)
2628
2629 // stencil ref goes 1, 1, 2, 3, 4, ..., N for the clips in the first batch,
2630 // then N+1, N+1, N+2, N+3, ... for the next batch,
2631 // and so on.
2632 // Note the different stencilOp for the first and the subsequent clips.
2635
2640 }
2641
2643 m_stencilClipCommon.vs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.vert.qsb"));
2644
2646 m_stencilClipCommon.fs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.frag.qsb"));
2647
2650
2653
2655 }
2656
2661
2664}
2665
2667{
2668 // cliptype stencil + updateStencilBuffer==false means the batch uses
2669 // stenciling but relies on the stencil data generated by a previous batch
2670 // (due to the having the same clip node). Do not enqueue draw calls for
2671 // stencil in this case as the stencil buffer is already up-to-date.
2673 return;
2674
2676 const int count = batch->stencilClipState.drawCalls.size();
2677 for (int i = 0; i < count; ++i) {
2681 if (i == 0) {
2684 } else if (i == 1) {
2687 }
2688 // else incrPs is already bound
2692 if (drawCall.indexCount) {
2696 } else {
2697 cb->setVertexInput(0, 1, &vbufBinding);
2699 }
2700 }
2701}
2702
2704{
2705 Q_ASSERT(m_rhi);
2708 m_currentMaterial = nullptr;
2709}
2710
2711static inline bool needsBlendConstant(QRhiGraphicsPipeline::BlendFactor f)
2712{
2713 return f == QRhiGraphicsPipeline::ConstantColor
2714 || f == QRhiGraphicsPipeline::OneMinusConstantColor
2715 || f == QRhiGraphicsPipeline::ConstantAlpha
2716 || f == QRhiGraphicsPipeline::OneMinusConstantAlpha;
2717}
2718
2719// With QRhi renderBatches() is split to two steps: prepare and render.
2720//
2721// Prepare goes through the batches and elements, and set up a graphics
2722// pipeline, srb, uniform buffer, calculates clipping, based on m_gstate, the
2723// material (shaders), and the batches. This step does not touch the command
2724// buffer or renderpass-related state (m_pstate).
2725//
2726// The render step then starts a renderpass, and goes through all
2727// batches/elements again and records setGraphicsPipeline, drawIndexed, etc. on
2728// the command buffer. The prepare step's accumulated global state like
2729// m_gstate must not be used here. Rather, all data needed for rendering is
2730// available from Batch/Element at this stage. Bookkeeping of state in the
2731// renderpass is done via m_pstate.
2732
2734{
2735 // Note the key's == and qHash implementations: the renderpass descriptor
2736 // and srb are tested for compatibility, not pointer equality.
2737 //
2738 // We do not store the srb pointer itself because the ownership stays with
2739 // the Element and that can go away more often that we would like it
2740 // to. (think scrolling a list view, constantly dropping and creating new
2741 // nodes) Rather, use an opaque blob of a few uints and store and compare
2742 // that. This works because once the pipeline is built, we will always call
2743 // setShaderResources with an explicitly specified srb which is fine even if
2744 // e->srb we used here to bake the pipeline is already gone by that point.
2745 //
2746 // A typical QSGMaterial's serialized srb layout is 8 uints. (uniform buffer
2747 // + texture, 4 fields each) Regardless, using an implicitly shared
2748 // container is essential here. (won't detach so no more allocs and copies
2749 // are done, unless the Element decides to rebake the srb with a different
2750 // layout - but then the detach is exactly what we need)
2751 //
2752 // Same story for the renderpass descriptor: the object can go away but
2753 // that's fine because that has no effect on an already built pipeline, and
2754 // for comparison we only rely on the serialized blob in order decide if the
2755 // render target is compatible with the pipeline.
2756
2758
2759 // Note: dynamic state (viewport rect, scissor rect, stencil ref, blend
2760 // constant) is never a part of GraphicsState/QRhiGraphicsPipeline.
2761
2762 // See if there is an existing, matching pipeline state object.
2765 if (depthPostPass)
2766 e->depthPostPassPs = *it;
2767 else
2768 e->ps = *it;
2769 return true;
2770 }
2771
2772 // Build a new one. This is potentially expensive.
2778
2782 {
2784 }
2789
2790 ps->setFlags(flags);
2796
2806 ps->setTargetBlends({ blend });
2807
2811
2812 if (m_gstate.stencilTest) {
2813 ps->setStencilTest(true);
2821 }
2822
2824
2826
2827 if (!ps->create()) {
2828 qWarning("Failed to build graphics pipeline state");
2829 delete ps;
2830 return false;
2831 }
2832
2834 if (depthPostPass)
2835 e->depthPostPassPs = ps;
2836 else
2837 e->ps = ps;
2838 return true;
2839}
2840
2841static QRhiSampler *newSampler(QRhi *rhi, const QSGSamplerDescription &desc)
2842{
2843 QRhiSampler::Filter magFilter;
2844 QRhiSampler::Filter minFilter;
2845 QRhiSampler::Filter mipmapMode;
2846 QRhiSampler::AddressMode u;
2847 QRhiSampler::AddressMode v;
2848
2849 switch (desc.filtering) {
2850 case QSGTexture::None:
2851 Q_FALLTHROUGH();
2852 case QSGTexture::Nearest:
2853 magFilter = minFilter = QRhiSampler::Nearest;
2854 break;
2855 case QSGTexture::Linear:
2856 magFilter = minFilter = QRhiSampler::Linear;
2857 break;
2858 default:
2859 Q_UNREACHABLE();
2860 magFilter = minFilter = QRhiSampler::Nearest;
2861 break;
2862 }
2863
2864 switch (desc.mipmapFiltering) {
2865 case QSGTexture::None:
2866 mipmapMode = QRhiSampler::None;
2867 break;
2868 case QSGTexture::Nearest:
2869 mipmapMode = QRhiSampler::Nearest;
2870 break;
2871 case QSGTexture::Linear:
2872 mipmapMode = QRhiSampler::Linear;
2873 break;
2874 default:
2875 Q_UNREACHABLE();
2876 mipmapMode = QRhiSampler::None;
2877 break;
2878 }
2879
2880 switch (desc.horizontalWrap) {
2881 case QSGTexture::Repeat:
2882 u = QRhiSampler::Repeat;
2883 break;
2884 case QSGTexture::ClampToEdge:
2885 u = QRhiSampler::ClampToEdge;
2886 break;
2887 case QSGTexture::MirroredRepeat:
2888 u = QRhiSampler::Mirror;
2889 break;
2890 default:
2891 Q_UNREACHABLE();
2892 u = QRhiSampler::ClampToEdge;
2893 break;
2894 }
2895
2896 switch (desc.verticalWrap) {
2897 case QSGTexture::Repeat:
2898 v = QRhiSampler::Repeat;
2899 break;
2900 case QSGTexture::ClampToEdge:
2901 v = QRhiSampler::ClampToEdge;
2902 break;
2903 case QSGTexture::MirroredRepeat:
2904 v = QRhiSampler::Mirror;
2905 break;
2906 default:
2907 Q_UNREACHABLE();
2908 v = QRhiSampler::ClampToEdge;
2909 break;
2910 }
2911
2912 return rhi->newSampler(magFilter, minFilter, mipmapMode, u, v);
2913}
2914
2916{
2917 if (!m_dummyTexture) {
2919 if (m_dummyTexture->create()) {
2920 if (m_resourceUpdates) {
2922 img.fill(0);
2924 }
2925 }
2926 }
2927 return m_dummyTexture;
2928}
2929
2930static void rendererToMaterialGraphicsState(QSGMaterialShader::GraphicsPipelineState *dst,
2931 GraphicsState *src)
2932{
2933 dst->blendEnable = src->blending;
2934
2935 // the enum values should match, sanity check it
2936 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::OneMinusSrc1Alpha) == int(QRhiGraphicsPipeline::OneMinusSrc1Alpha));
2937 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::BlendOp::Max) == int(QRhiGraphicsPipeline::Max));
2938 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::A) == int(QRhiGraphicsPipeline::A));
2939 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::CullBack) == int(QRhiGraphicsPipeline::Back));
2940 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::Line) == int(QRhiGraphicsPipeline::Line));
2941 dst->srcColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcColor);
2942 dst->dstColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstColor);
2943
2944 // For compatibility with any existing code, separateBlendFactors defaults
2945 // to _false_ which means that materials that do not touch srcAlpha and
2946 // dstAlpha will continue to use srcColor and dstColor as the alpha
2947 // blending factors. New code that needs different values for color/alpha,
2948 // can explicitly set separateBlendFactors to true and then set srcAlpha
2949 // and dstAlpha as well.
2950 dst->separateBlendFactors = false;
2951
2952 dst->srcAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcAlpha);
2953 dst->dstAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstAlpha);
2954
2955 dst->opColor = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opColor);
2956 dst->opAlpha = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opAlpha);
2957
2958 dst->colorWrite = QSGMaterialShader::GraphicsPipelineState::ColorMask(int(src->colorWrite));
2959
2960 dst->cullMode = QSGMaterialShader::GraphicsPipelineState::CullMode(src->cullMode);
2961 dst->polygonMode = QSGMaterialShader::GraphicsPipelineState::PolygonMode(src->polygonMode);
2962}
2963
2965 QSGMaterialShader::GraphicsPipelineState *src)
2966{
2967 dst->blending = src->blendEnable;
2968 dst->srcColor = QRhiGraphicsPipeline::BlendFactor(src->srcColor);
2969 dst->dstColor = QRhiGraphicsPipeline::BlendFactor(src->dstColor);
2970 if (src->separateBlendFactors) {
2971 dst->srcAlpha = QRhiGraphicsPipeline::BlendFactor(src->srcAlpha);
2972 dst->dstAlpha = QRhiGraphicsPipeline::BlendFactor(src->dstAlpha);
2973 } else {
2974 dst->srcAlpha = dst->srcColor;
2975 dst->dstAlpha = dst->dstColor;
2976 }
2977 dst->opColor = QRhiGraphicsPipeline::BlendOp(src->opColor);
2978 dst->opAlpha = QRhiGraphicsPipeline::BlendOp(src->opAlpha);
2979 dst->colorWrite = QRhiGraphicsPipeline::ColorMask(int(src->colorWrite));
2980 dst->cullMode = QRhiGraphicsPipeline::CullMode(src->cullMode);
2981 dst->polygonMode = QRhiGraphicsPipeline::PolygonMode(src->polygonMode);
2982}
2983
2985{
2987 return false;
2988
2991 if (end > quint32(m_uniformData.size()))
2993
2994 if (!m_uniformBuffer || m_uniformBuffer->size() < end) {
2996 if (m_uniformBuffer)
2998 else
3000 if (!m_uniformBuffer->create()) {
3001 qWarning("Failed to build uniform buffer of size %u bytes", bufferSize);
3002 // The batches prepared so far in this frame refer to the buffer as
3003 // well, prepareRenderPass() drops them. Retried in the next frame.
3004 delete m_uniformBuffer;
3005 m_uniformBuffer = nullptr;
3006 m_uniformBufferFailed = true;
3007 return false;
3008 }
3009 }
3010
3011 *offset = start;
3013 return true;
3014}
3015
3016// Not to be called between prepareRenderPass() and recordRenderPass(), the
3017// contents is lost.
3019{
3021 return;
3022
3024 if (!m_uniformBuffer->create()) {
3025 // recreated on the next allocateUniformData()
3026 delete m_uniformBuffer;
3027 m_uniformBuffer = nullptr;
3028 }
3029}
3030
3034 Element *e)
3035{
3037
3041
3042 if (pd->ubufBinding >= 0) {
3045 return false;
3046
3048 // The return value ('changed') is no longer used, and did not have much value to
3049 // begin with due to mostly being true (due to the matrix being dirty).
3051 m_current_uniform_data = nullptr;
3052
3054
3056 pd->ubufStages,
3058 size));
3059 }
3060
3063 if (!stages)
3064 continue;
3065
3068
3071
3074
3075 if (nextTex.contains(nullptr)) {
3076 qWarning("No QSGTexture provided from updateSampledImage(). This is wrong.");
3077 continue;
3078 }
3079
3080 bool hasDirtySamplerOptions = false;
3081 bool isAnisotropic = false;
3082 for (QSGTexture *t : nextTex) {
3087 }
3088
3089 // prevTex may be invalid at this point, avoid dereferencing it
3091
3092 // The QSGTexture, and so the sampler parameters, may have changed.
3093 // The rhiTexture is not relevant here.
3094 pd->textureBindingTable[binding] = nextTex; // does not own
3096
3097 if (isAnisotropic) // ###
3098 qWarning("QSGTexture anisotropy levels are not currently supported");
3099
3101
3102 for (QSGTexture *t : nextTex) {
3104
3106
3107 if (!sampler) {
3109 if (!sampler->create()) {
3110 qWarning("Failed to build sampler");
3111 delete sampler;
3112 continue;
3113 }
3115 }
3117 }
3118
3119 pd->samplerBindingTable[binding] = samplers; // does not own
3120 }
3121
3123
3125
3126 for (int i = 0; i < pd->textureBindingTable[binding].size(); ++i) {
3127
3129
3130 // texture may be null if the update above failed for any reason,
3131 // or if the QSGTexture chose to return null intentionally. This is
3132 // valid and we still need to provide something to the shader.
3133 if (!texture)
3135
3137
3140 }
3141
3142 if (!textureSamplers.isEmpty())
3145 }
3146 }
3147
3148#ifndef QT_NO_DEBUG
3149 if (bindings.isEmpty())
3150 qWarning("No shader resources for material %p, this is odd.", material);
3151#endif
3152
3153 enum class SrbAction {
3154 Unknown,
3155 DoNothing,
3157 Rebake
3159
3160 // First, if the Element has no srb created at all, then try to find an existing,
3161 // currently unused srb that is layout-compatible with our binding list.
3162 if (!e->srb) {
3163 // reuse a QVector as our work area, thus possibly reusing the underlying allocation too
3165 layoutDesc.clear();
3168 if (e->srb) {
3169 // Here we know layout compatibility is satisfied, but do not spend time on full
3170 // comparison. The chance of getting an srb that refers to the same resources
3171 // (buffer, textures) is low in practice. So reuse, but write new resources.
3173 }
3174 }
3175
3176 // If the Element had an existing srb, investigate:
3177 // - It may be used as-is (when nothing changed in the scene regarding this node compared to the previous frame).
3178 // - Otherwise it may be able to go with a lightweight update (replace resources, binding list layout is the same).
3179 // - If all else fails rebake the full thing, meaning we reuse the memory allocation but will recreate everything underneath.
3180 if (srbAction == SrbAction::Unknown && e->srb) {
3184 [](const auto &a, const auto &b) { return a.isLayoutCompatible(b); }))
3185 {
3187 } else {
3189 }
3190 }
3191
3192 // If the Element had no srb associated at all and could not find a layout-compatible
3193 // one from the pool, then create a whole new object.
3194 if (!e->srb) {
3197 }
3198
3200
3201 switch (srbAction) {
3202 case SrbAction::DoNothing:
3203 break;
3205 {
3208 // Due to the way the binding list is built up above, if we have a uniform buffer
3209 // at binding point 0 (or none at all) then the sampledTexture bindings are added
3210 // with increasing binding points afterwards, so the list is already sorted based
3211 // on the binding points, thus we can save some time by telling the QRhi backend
3212 // not to sort again.
3213 if (pd->ubufBinding <= 0 || bindings.size() <= 1)
3215
3217 }
3218 break;
3219 case SrbAction::Rebake:
3221 if (!e->srb->create())
3222 qWarning("Failed to build srb");
3223 break;
3224 default:
3225 Q_ASSERT_X(false, "updateMaterialDynamicData", "No srb action set, this cannot happen");
3226 }
3227
3228 return true;
3229}
3230
3234 Batch *batch,
3235 bool *gstateChanged)
3236{
3238 *gstateChanged = false;
3240 // generate the public mini-state from m_gstate, invoke the material,
3241 // write the changes, if any, back to m_gstate, together with a way to
3242 // roll those back.
3246 if (changed) {
3251 {
3253 }
3254 *gstateChanged = true;
3255 }
3256 }
3257}
3258
3260{
3261 if (batch->vertexCount == 0 || batch->indexCount == 0)
3262 return false;
3263
3264 Element *e = batch->first;
3265 Q_ASSERT(e);
3266
3267#ifndef QT_NO_DEBUG_OUTPUT
3268 if (Q_UNLIKELY(debug_render())) {
3269 QDebug debug = qDebug();
3270 debug << " -"
3271 << batch
3272 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3273 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3274 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3275 << "[ merged]"
3276 << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
3277 << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
3278 << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
3279 << " root:" << batch->root
3280 << " mutability group: " << e->mutabilityGroup;
3281 if (batch->drawSets.size() > 1)
3282 debug << "sets:" << batch->drawSets.size();
3283 if (!batch->isOpaque)
3284 debug << "opacity:" << e->node->inheritedOpacity();
3285 batch->uploadedThisFrame = false;
3286 }
3287#endif
3288
3290
3291 // We always have dirty matrix as all batches are at a unique z range.
3293 if (batch->root)
3295 else
3298
3299 const int viewCount = projectionMatrixCount();
3301 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3303
3307
3311
3312 const QSGGeometry *g = gn->geometry();
3316 if (!sms)
3317 return false;
3318
3320 if (m_currentShader != sms)
3322
3327 }
3328
3330
3331 bool pendingGStatePop = false;
3333
3335 if (pendingGStatePop)
3337 return false;
3338 }
3339
3342
3343 const bool hasPipeline = ensurePipelineState(e, sms);
3344
3345 if (pendingGStatePop)
3347
3348 if (!hasPipeline)
3349 return false;
3350
3354 ensurePipelineState(e, sms, true);
3356 }
3357
3359
3361 renderBatch->sms = sms;
3362
3363 return true;
3364}
3365
3367{
3370 {
3371 if (g->lineWidth() != 1.0f) {
3372 static bool checkedWideLineSupport = false;
3376 qWarning("Line widths other than 1 are not supported by the graphics API");
3377 }
3378 }
3379 } else if (g->drawingMode() == QSGGeometry::DrawPoints) {
3380 if (g->lineWidth() != 1.0f) {
3381 static bool warnedPointSize = false;
3382 if (!warnedPointSize) {
3383 warnedPointSize = true;
3384 qWarning("Point size is not controllable by QSGGeometry. "
3385 "Set gl_PointSize from the vertex shader instead.");
3386 }
3387 }
3388 }
3389}
3390
3392{
3393 const Batch *batch = renderBatch->batch;
3394 if (!batch->vbo.buf || !batch->ibo.buf)
3395 return;
3396
3397 Element *e = batch->first;
3399 QSGGeometry *g = gn->geometry();
3401
3404
3407
3408 for (int i = 0, ie = batch->drawSets.size(); i != ie; ++i) {
3409 const DrawSet &draw = batch->drawSets.at(i);
3413 };
3418 }
3419}
3420
3422{
3423 if (batch->vertexCount == 0)
3424 return false;
3425
3426 Element *e = batch->first;
3427 Q_ASSERT(e);
3428
3429 if (Q_UNLIKELY(debug_render())) {
3430 qDebug() << " -"
3431 << batch
3432 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3433 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3434 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3435 << "[unmerged]"
3436 << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
3437 << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
3438 << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
3439 << " root:" << batch->root
3440 << " mutability group: " << e->mutabilityGroup;
3441
3442
3443 batch->uploadedThisFrame = false;
3444 }
3445
3446 const int viewCount = projectionMatrixCount();
3448 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3450
3454
3458
3459 // We always have dirty matrix as all batches are at a unique z range.
3461
3462 // The vertex attributes are assumed to be the same for all elements in the
3463 // unmerged batch since the material (and so the shaders) is the same.
3464 QSGGeometry *g = gn->geometry();
3467 if (!sms)
3468 return false;
3469
3471 if (m_currentShader != sms)
3473
3478 }
3479
3481
3483 bool pendingGStatePop = false;
3486
3487 QRhiGraphicsPipeline *ps = nullptr;
3489 e = batch->first;
3490
3491 while (e) {
3492 gn = e->node;
3493
3496
3497 const int viewCount = projectionMatrixCount();
3499 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3501
3505
3506 if (useDepthBuffer()) {
3507 // this cannot be multiview
3510 }
3511
3514 if (pendingGStatePop)
3516 return false;
3517 }
3518
3520 const float prevLineWidth = m_gstate.lineWidth;
3523
3524 // Do not bother even looking up the ps if the topology has not changed
3525 // since everything else is the same for all elements in the batch.
3526 // (except if the material modified blend state)
3528 if (!ensurePipelineState(e, sms)) {
3529 if (pendingGStatePop)
3531 return false;
3532 }
3533 ps = e->ps;
3537 ensurePipelineState(e, sms, true);
3540 }
3541 } else {
3542 e->ps = ps;
3545 }
3546
3547 // We don't need to bother with asking each node for its material as they
3548 // are all identical (compare==0) since they are in the same batch.
3550
3551 // We only need to push this on the very first iteration...
3553
3554 e = e->nextInBatch;
3555 }
3556
3557 if (pendingGStatePop)
3559
3561 renderBatch->sms = sms;
3562
3563 return true;
3564}
3565
3567{
3568 const Batch *batch = renderBatch->batch;
3569 if (!batch->vbo.buf)
3570 return;
3571
3572 Element *e = batch->first;
3573
3576
3577 quint32 vOffset = 0;
3578 quint32 iOffset = 0;
3580
3581 while (e) {
3582 QSGGeometry *g = e->node->geometry();
3584 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
3585
3587
3589 if (g->indexCount()) {
3590 if (batch->ibo.buf) {
3592 batch->ibo.buf, iOffset,
3596 }
3597 } else {
3599 cb->draw(g->vertexCount());
3600 }
3601
3602 vOffset += g->sizeOfVertex() * g->vertexCount();
3604
3605 e = e->nextInBatch;
3606 }
3607}
3608
3610{
3611 if (!m_pstate.viewportSet) {
3612 m_pstate.viewportSet = true;
3614 }
3616 m_pstate.scissorSet = true;
3618 } else {
3619 // Regardless of the ps not using scissor, the scissor may need to be
3620 // reset, depending on the backend. So set the viewport again, which in
3621 // turn also sets the scissor on backends where a scissor rect is
3622 // always-on (Vulkan).
3623 if (m_pstate.scissorSet) {
3624 m_pstate.scissorSet = false;
3626 }
3627 }
3628}
3629
3630
3632 const ShaderManager::Shader *sms, bool depthPostPass)
3633{
3635
3637
3641 }
3644
3646 if (ubufBinding >= 0) {
3649 } else {
3651 }
3652}
3653
3655{
3656 if (e->isRenderNode) {
3657 delete static_cast<RenderNodeElement *>(e);
3658 } else {
3659 if (e->srb) {
3660 if (!inDestructor) {
3663 else
3664 delete e->srb;
3665 } else {
3666 delete e->srb;
3667 }
3668 e->srb = nullptr;
3669 }
3671 }
3672}
3673
3675{
3676 if (!m_elementsToDelete.size())
3677 return;
3678
3679 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3681 if (*e && (*e)->removed)
3682 *e = nullptr;
3683 }
3684 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3686 if (*e && (*e)->removed)
3687 *e = nullptr;
3688 }
3689
3690 for (int i=0; i<m_elementsToDelete.size(); ++i)
3692
3694}
3695
3697{
3698 // Gracefully handle the lack of a render target - some autotests may rely
3699 // on this in odd cases.
3700 if (!renderTarget().rt)
3701 return;
3702
3707}
3708
3709// An alternative to render() is to call prepareInline() and renderInline() at
3710// the appropriate times (i.e. outside of a QRhi::beginPass() and then inside,
3711// respectively) These allow rendering within a render pass that is started by
3712// another component. In contrast, render() records a full render pass on its
3713// own.
3714
3719
3724
3726{
3727 if (ctx->valid)
3728 qWarning("prepareRenderPass() called with an already prepared render pass context");
3729
3730 ctx->valid = true;
3731
3732 if (Q_UNLIKELY(debug_dump())) {
3733 qDebug("\n");
3735 }
3736
3737 ctx->timeRenderLists = 0;
3739 ctx->timePrepareAlpha = 0;
3740 ctx->timeSorting = 0;
3741 ctx->timeUploadOpaque = 0;
3742 ctx->timeUploadAlpha = 0;
3743
3744 if (Q_UNLIKELY(debug_render() || debug_build())) {
3745 QByteArray type("rebuild:");
3746 if (m_rebuild == 0)
3747 type += " none";
3748 if (m_rebuild == FullRebuild)
3749 type += " full";
3750 else {
3752 type += " renderlists";
3754 type += " partial";
3755 else if (m_rebuild & BuildBatches)
3756 type += " batches";
3757 }
3758
3759 qDebug() << "Renderer::render()" << this << type;
3760 ctx->timer.start();
3761 }
3762
3764
3766 bool complete = (m_rebuild & BuildRenderLists) != 0;
3767 if (complete)
3769 else
3772
3773 if (Q_UNLIKELY(debug_build())) {
3774 qDebug("Opaque render lists %s:", (complete ? "(complete)" : "(partial)"));
3775 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3777 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3778 }
3779 qDebug("Alpha render list %s:", complete ? "(complete)" : "(partial)");
3780 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3782 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3783 }
3784 }
3785 }
3787
3788 for (int i=0; i<m_opaqueBatches.size(); ++i)
3790 for (int i=0; i<m_alphaBatches.size(); ++i)
3793
3796
3797 if (m_rebuild & BuildBatches) {
3802
3803 if (Q_UNLIKELY(debug_build())) {
3804 qDebug("Opaque Batches:");
3805 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3807 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3808 for (Element *e = b->first; e; e = e->nextInBatch) {
3809 qDebug() << " - element:" << e << " node:" << e->node << e->order;
3810 }
3811 }
3812 qDebug("Alpha Batches:");
3813 for (int i=0; i<m_alphaBatches.size(); ++i) {
3815 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3816 for (Element *e = b->first; e; e = e->nextInBatch) {
3817 qDebug() << " - element:" << e << e->bounds << " node:" << e->node << " order:" << e->order;
3818 }
3819 }
3820 }
3821 } else {
3823 }
3824
3825
3827
3828 if (m_rebuild != 0) {
3829 // Then sort opaque batches so that we're drawing the batches with the highest
3830 // order first, maximizing the benefit of front-to-back z-ordering.
3831 if (m_opaqueBatches.size())
3833
3834 // Sort alpha batches back to front so that they render correctly.
3835 if (m_alphaBatches.size())
3837
3839 ? 1.0 / (m_nextRenderOrder)
3840 : 0;
3841 }
3842
3844
3845 // Set size to 0, nothing is deallocated, they will "grow" again
3846 // as part of uploadBatch.
3849
3850 if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Opaque Batches:");
3851 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3853 uploadBatch(b);
3854 }
3856
3857 if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Alpha Batches:");
3858 for (int i=0; i<m_alphaBatches.size(); ++i) {
3860 uploadBatch(b);
3861 }
3863
3864 if (Q_UNLIKELY(debug_render())) {
3865 qDebug().nospace() << "Rendering:" << Qt::endl
3866 << " -> Opaque: " << qsg_countNodesInBatches(m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << Qt::endl
3867 << " -> Alpha: " << qsg_countNodesInBatches(m_alphaBatches) << " nodes in " << m_alphaBatches.size() << " batches...";
3868 }
3869
3871 m_currentMaterial = nullptr;
3872 m_currentShader = nullptr;
3873 m_currentProgram = nullptr;
3875
3876 const QRect viewport = viewportRect();
3877
3878 bool renderOpaque = !debug_noopaque();
3879 bool renderAlpha = !debug_noalpha();
3880
3886 m_pstate.viewportSet = false;
3887 m_pstate.scissorSet = false;
3888
3892 m_gstate.blending = false;
3893
3900 m_gstate.usesScissor = false;
3901 m_gstate.stencilTest = false;
3902
3905
3906 // Give back memory when the scene has become much smaller: if the peak
3907 // usage in the last UNIFORM_BUFFER_SHRINK_FRAMES frames is less than a
3908 // quarter of the uniform buffer size, shrink to twice the peak. Done
3909 // before anything refers to the buffer in this frame.
3913 if (m_uniformBuffer && m_uniformBuffer->size() > 2 * size)
3915 if (quint32(m_uniformData.size()) > 2 * size) {
3918 }
3921 }
3922
3924 m_uniformBufferFailed = false;
3925
3927 if (Q_LIKELY(renderOpaque)) {
3928 for (int i = 0, ie = m_opaqueBatches.size(); i != ie; ++i) {
3931 bool ok;
3932 if (b->merged)
3934 else
3936 if (ok)
3938 }
3939 }
3940
3941 m_gstate.blending = true;
3942 // factors never change, always set for premultiplied alpha based blending
3943
3944 // depth test stays enabled (if useDepthBuffer(), that is) but no need
3945 // to write out depth from the transparent (back-to-front) pass
3946 m_gstate.depthWrite = false;
3947
3948 // special case: the 3D plane mode tests against the depth buffer, but does
3949 // not write (and all batches are alpha because this render mode evaluates
3950 // to useDepthBuffer()==false)
3953 m_gstate.depthTest = true;
3954 }
3955
3957 if (Q_LIKELY(renderAlpha)) {
3958 for (int i = 0, ie = m_alphaBatches.size(); i != ie; ++i) {
3961 bool ok;
3962 if (b->merged)
3964 else if (b->isRenderNode)
3966 else
3968 if (ok)
3970 }
3971 }
3972
3973 m_rebuild = 0;
3974
3975#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
3978#endif
3979
3982
3984 // the batches prepared before the failure refer to the uniform buffer too
3987 } else if (m_uniformDataSize) {
3988 // All regions read by this frame's draws are written here, every
3989 // frame, so the direct update of the current frame slot is sufficient.
3991 }
3992
3994 m_resourceUpdates = nullptr;
3995}
3996
3998{
4001 // we cannot tell if the application will have
4002 // native rendering thrown in to this pass
4003 // (QQuickWindow::beginExternalCommands()), so
4004 // we have no choice but to set the flag always
4005 // (thus triggering using secondary command
4006 // buffers with Vulkan)
4008 // We do not use GPU compute at all at the moment, this means we can
4009 // get a small performance gain with OpenGL by declaring this.
4011
4014}
4015
4017{
4018 // prepareRenderPass and recordRenderPass must always be called together.
4019 // They are separate because beginRenderPass and endRenderPass are optional.
4020 //
4021 // The valid call sequence are therefore:
4022 // prepare, begin, record, end
4023 // or
4024 // prepare, record
4025
4026 if (!ctx->valid)
4027 qWarning("recordRenderPass() called without a prepared render pass context");
4028
4029 ctx->valid = false;
4030
4032 cb->debugMarkBegin(QByteArrayLiteral("Qt Quick scene render"));
4033
4034 for (int i = 0, ie = ctx->opaqueRenderBatches.size(); i != ie; ++i) {
4035 if (i == 0)
4036 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick opaque batches"));
4038 if (renderBatch->batch->merged)
4040 else
4042 }
4043
4044 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
4045 if (i == 0) {
4047 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D batches"));
4048 else
4049 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick alpha batches"));
4050 }
4052 if (renderBatch->batch->merged)
4054 else if (renderBatch->batch->isRenderNode)
4056 else
4058 }
4059
4061 // Depth post-pass to fill up the depth buffer in a way that it
4062 // corresponds to what got rendered to the color buffer in the previous
4063 // (alpha) pass. The previous pass cannot enable depth write due to Z
4064 // fighting. Rather, do it separately in a dedicated color-write-off,
4065 // depth-write-on pass. This enables the 3D content drawn afterwards to
4066 // depth test against the 2D items' rendering.
4067 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
4068 if (i == 0)
4069 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D depth post-pass"));
4071 if (renderBatch->batch->merged)
4073 else if (!renderBatch->batch->isRenderNode) // rendernodes are skipped here for now
4075 }
4076 }
4077
4078 if (m_currentShader)
4079 setActiveRhiShader(nullptr, nullptr);
4080
4081 cb->debugMarkEnd();
4082
4083 if (Q_UNLIKELY(debug_render())) {
4084 qDebug(" -> times: build: %d, prepare(opaque/alpha): %d/%d, sorting: %d, upload(opaque/alpha): %d/%d, record rendering: %d",
4085 (int) ctx->timeRenderLists,
4087 (int) ctx->timeSorting,
4088 (int) ctx->timeUploadOpaque, (int) ctx->timeUploadAlpha,
4089 (int) ctx->timer.elapsed());
4090 }
4091}
4092
4103
4105{
4106 const QMatrix4x4 *projectionMatrix() const override { return m_projectionMatrix; }
4107 QRect scissorRect() const override { return m_scissorRect; }
4109 int stencilValue() const override { return m_stencilValue; }
4111 const QRegion *clipRegion() const override { return nullptr; }
4112
4118};
4119
4121{
4122 if (Q_UNLIKELY(debug_render()))
4123 qDebug() << " -" << batch << "rendernode";
4124
4125 const int viewCount = projectionMatrixCount();
4127 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
4132
4134 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4135
4136 setActiveRhiShader(nullptr, nullptr);
4137
4139 rd->m_clip_list = nullptr;
4142 while (clip != rootNode()) {
4143 if (clip->type() == QSGNode::ClipNodeType) {
4144 rd->m_clip_list = static_cast<QSGClipNode *>(clip);
4145 break;
4146 }
4147 clip = clip->parent();
4148 }
4150 }
4151
4154 QSGNode *root = rootNode();
4155 if (e->root) {
4157 root = e->root->sgNode;
4158 }
4159 while (xform != root) {
4160 if (xform->type() == QSGNode::TransformNodeType) {
4161 matrix = matrix * static_cast<QSGTransformNode *>(xform)->combinedMatrix();
4162 break;
4163 }
4164 xform = xform->parent();
4165 }
4168
4170 rd->m_opacity = 1.0;
4171 while (opacity != rootNode()) {
4172 if (opacity->type() == QSGNode::OpacityNodeType) {
4173 rd->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity();
4174 break;
4175 }
4176 opacity = opacity->parent();
4177 }
4178
4179 rd->m_rt = renderTarget();
4180
4182 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
4184
4185 if (useDepthBuffer()) {
4186 // this cannot be multiview
4187 rd->m_projectionMatrix[0](2, 2) = m_zRange;
4189 }
4190
4191 e->renderNode->prepare();
4192
4194 renderBatch->sms = nullptr;
4195
4196 return true;
4197}
4198
4200{
4203
4204 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4206
4208 // Expose only the first matrix through the state object, the rest are
4209 // queriable through the QSGRenderNode getters anyway.
4211 const std::array<int, 4> scissor = batch->clipState.scissor.scissor();
4216
4218
4222 if (needsExternal)
4223 cb->beginExternal();
4225 if (needsExternal)
4226 cb->endExternal();
4227
4228 rd->m_matrix = nullptr;
4229 rd->m_clip_list = nullptr;
4230
4233 {
4234 // Reset both flags if either is reported as changed, since with the rhi
4235 // it could be setViewport() that will record the resetting of the scissor.
4236 m_pstate.viewportSet = false;
4237 m_pstate.scissorSet = false;
4238 }
4239
4240 // Do not bother with RenderTargetState. Where applicable, endExternal()
4241 // ensures the correct target is rebound. For others (like Vulkan) it makes
4242 // no sense since render() could not possibly do that on our command buffer
4243 // which is in renderpass recording state.
4244}
4245
4247{
4248 if (mode.isEmpty())
4250 else if (mode == "clip")
4252 else if (mode == "overdraw")
4254 else if (mode == "batches")
4256 else if (mode == "changes")
4258}
4259
4264
4265bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
4266{
4267 return a.depthTest == b.depthTest
4268 && a.depthWrite == b.depthWrite
4269 && a.depthFunc == b.depthFunc
4270 && a.blending == b.blending
4271 && a.srcColor == b.srcColor
4272 && a.dstColor == b.dstColor
4273 && a.srcAlpha == b.srcAlpha
4274 && a.dstAlpha == b.dstAlpha
4275 && a.opColor == b.opColor
4276 && a.opAlpha == b.opAlpha
4277 && a.colorWrite == b.colorWrite
4278 && a.cullMode == b.cullMode
4282 && a.drawMode == b.drawMode
4283 && a.lineWidth == b.lineWidth
4284 && a.polygonMode == b.polygonMode
4286}
4287
4288bool operator!=(const GraphicsState &a, const GraphicsState &b) noexcept
4289{
4290 return !(a == b);
4291}
4292
4293size_t qHash(const GraphicsState &s, size_t seed) noexcept
4294{
4295 // do not bother with all fields
4296 return seed
4297 + s.depthTest * 1000
4298 + s.depthWrite * 100
4299 + s.depthFunc
4300 + s.blending * 10
4301 + s.srcColor
4302 + s.cullMode
4303 + s.usesScissor
4304 + s.stencilTest
4305 + s.sampleCount
4306 + s.multiViewCount;
4307}
4308
4310{
4311 return a.state == b.state
4312 && a.sms->materialShader == b.sms->materialShader
4313 && a.renderTargetDescription == b.renderTargetDescription
4314 && a.srbLayoutDescription == b.srbLayoutDescription;
4315}
4316
4318{
4319 return !(a == b);
4320}
4321
4322size_t qHash(const GraphicsPipelineStateKey &k, size_t seed) noexcept
4323{
4324 return qHash(k.state, seed)
4325 ^ qHash(k.sms->materialShader)
4326 ^ k.extra.renderTargetDescriptionHash
4327 ^ k.extra.srbLayoutDescriptionHash;
4328}
4329
4330bool operator==(const ShaderKey &a, const ShaderKey &b) noexcept
4331{
4332 return a.type == b.type
4333 && a.renderMode == b.renderMode
4335}
4336
4337bool operator!=(const ShaderKey &a, const ShaderKey &b) noexcept
4338{
4339 return !(a == b);
4340}
4341
4342size_t qHash(const ShaderKey &k, size_t seed) noexcept
4343{
4344 return qHash(k.type, seed) ^ int(k.renderMode) ^ k.multiViewCount;
4345}
4346
4347Visualizer::Visualizer(Renderer *renderer)
4348 : m_renderer(renderer),
4350{
4351}
4352
4354{
4355}
4356
4357#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded
4358 | QSGNode::DirtyOpacity
4359 | QSGNode::DirtyMatrix
4360 | QSGNode::DirtyNodeRemoved)
4361
4362void Visualizer::visualizeChangesPrepare(Node *n, uint parentChanges)
4363{
4364 uint childDirty = (parentChanges | n->dirtyState) & QSGNODE_DIRTY_PARENT;
4365 uint selfDirty = n->dirtyState | parentChanges;
4366 if (n->type() == QSGNode::GeometryNodeType && selfDirty != 0)
4367 m_visualizeChangeSet.insert(n, selfDirty);
4369 visualizeChangesPrepare(child, childDirty);
4370 }
4371}
4372
4373} // namespace QSGBatchRenderer
4374
4375QT_END_NAMESPACE
4376
4377#include "moc_qsgbatchrenderer_p.cpp"
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:326
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)
QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode, QRhi *rhi)
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)
const quint32 MIN_UNIFORM_BUFFER_SIZE
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)
const int UNIFORM_BUFFER_SHRINK_FRAMES
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)
size_t qHash(const GraphicsPipelineStateKey &k, size_t seed) noexcept
Int aligned(Int v, Int byteAlign)
const quint32 DEFAULT_BUFFER_POOL_SIZE_LIMIT
Q_CORE_EXPORT QDebug operator<<(QDebug debug, QDir::Filters filters)
Definition qdir.cpp:2620
#define DECLARE_DEBUG_VAR(variable)
#define QSGNODE_DIRTY_PARENT
QT_BEGIN_NAMESPACE int qt_sg_envInt(const char *name, int defaultValue)
#define SHADOWNODE_TRAVERSE(NODE)
#define QSGNODE_TRAVERSE(NODE)
bool geometryWasChanged(QSGGeometryNode *gn)
BatchCompatibility isMaterialCompatible(Element *e) const
BatchRootInfo * rootInfo() const
ClipBatchRootInfo * clipInfo() const
RenderNodeElement * renderNodeElement() const
void operator|=(const Pt &pt)
void set(float left, float top, float right, float bottom)
void map(const QMatrix4x4 &m)
const QMatrix4x4 * projectionMatrix() const override
const QRegion * clipRegion() const override