Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsgbatchrenderer.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
3// Copyright (C) 2016 Robin Burchell <robin.burchell@viroteck.net>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5
7
8#include <qmath.h>
9
10#include <QtCore/QElapsedTimer>
11#include <QtCore/QtNumeric>
12
13#include <QtGui/QGuiApplication>
14
15#include <private/qnumeric_p.h>
17
19
20#include <algorithm>
21
23
24int qt_sg_envInt(const char *name, int defaultValue);
25
27{
28
29#define DECLARE_DEBUG_VAR(variable)
30 static bool debug_ ## variable()
31 { static bool value = qgetenv("QSG_RENDERER_DEBUG").contains(QT_STRINGIFY(variable)); return value; }
39DECLARE_DEBUG_VAR(noalpha)
40DECLARE_DEBUG_VAR(noopaque)
42#undef DECLARE_DEBUG_VAR
43
44#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling())
45#define SHADOWNODE_TRAVERSE(NODE) for (Node *child = NODE->firstChild(); child; child = child->sibling())
46
47static inline int size_of_type(int type)
48{
49 static int sizes[] = {
50 sizeof(char),
51 sizeof(unsigned char),
52 sizeof(short),
53 sizeof(unsigned short),
54 sizeof(int),
55 sizeof(unsigned int),
56 sizeof(float),
57 2,
58 3,
59 4,
60 sizeof(double)
61 };
62 Q_ASSERT(type >= QSGGeometry::ByteType && type <= QSGGeometry::DoubleType);
63 return sizes[type - QSGGeometry::ByteType];
64}
65
70
72
73static bool isTranslate(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Translation; }
74static bool isScale(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Scale; }
75static bool is2DSafe(const QMatrix4x4 &m) { return m.flags() < QMatrix4x4::Rotation; }
76
77const float OPAQUE_LIMIT = 0.999f;
78
82
83const float VIEWPORT_MIN_DEPTH = 0.0f;
84const float VIEWPORT_MAX_DEPTH = 1.0f;
85
86const quint32 DEFAULT_BUFFER_POOL_SIZE_LIMIT = 2 * 1024 * 1024; // 2 MB for m_vboPool and m_iboPool each
87
88template <class Int>
89inline Int aligned(Int v, Int byteAlign)
90{
91 return (v + byteAlign - 1) & ~(byteAlign - 1);
92}
93
94QRhiVertexInputAttribute::Format qsg_vertexInputFormat(const QSGGeometry::Attribute &a)
95{
96 switch (a.type) {
97 case QSGGeometry::FloatType:
98 if (a.tupleSize == 4)
99 return QRhiVertexInputAttribute::Float4;
100 if (a.tupleSize == 3)
101 return QRhiVertexInputAttribute::Float3;
102 if (a.tupleSize == 2)
103 return QRhiVertexInputAttribute::Float2;
104 if (a.tupleSize == 1)
105 return QRhiVertexInputAttribute::Float;
106 break;
107 case QSGGeometry::UnsignedByteType:
108 if (a.tupleSize == 4)
109 return QRhiVertexInputAttribute::UNormByte4;
110 if (a.tupleSize == 2)
111 return QRhiVertexInputAttribute::UNormByte2;
112 if (a.tupleSize == 1)
113 return QRhiVertexInputAttribute::UNormByte;
114 break;
115 default:
116 break;
117 }
118 qWarning("Unsupported attribute type 0x%x with %d components", a.type, a.tupleSize);
119 Q_UNREACHABLE_RETURN(QRhiVertexInputAttribute::Float);
120}
121
122static QRhiVertexInputLayout calculateVertexInputLayout(const QSGMaterialShader *s, const QSGGeometry *geometry, bool batchable)
123{
124 Q_ASSERT(geometry);
125 const QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
126 if (!sd->vertexShader) {
127 qWarning("No vertex shader in QSGMaterialShader %p", s);
128 return QRhiVertexInputLayout();
129 }
130
131 const int attrCount = geometry->attributeCount();
132 QVarLengthArray<QRhiVertexInputAttribute, 8> inputAttributes;
133 inputAttributes.reserve(attrCount + 1);
134 quint32 offset = 0;
135 for (int i = 0; i < attrCount; ++i) {
136 const QSGGeometry::Attribute &a = geometry->attributes()[i];
137 if (!sd->vertexShader->vertexInputLocations.contains(a.position)) {
138 qWarning("Vertex input %d is present in material but not in shader. This is wrong.",
139 a.position);
140 }
141 inputAttributes.append(QRhiVertexInputAttribute(VERTEX_BUFFER_BINDING, a.position, qsg_vertexInputFormat(a), offset));
142 offset += a.tupleSize * size_of_type(a.type);
143 }
144 if (batchable) {
145 inputAttributes.append(QRhiVertexInputAttribute(ZORDER_BUFFER_BINDING, sd->vertexShader->qt_order_attrib_location,
146 QRhiVertexInputAttribute::Float, 0));
147 }
148
149 Q_ASSERT(VERTEX_BUFFER_BINDING == 0 && ZORDER_BUFFER_BINDING == 1); // not very flexible
150 QVarLengthArray<QRhiVertexInputBinding, 2> inputBindings;
151 inputBindings.append(QRhiVertexInputBinding(geometry->sizeOfVertex()));
152 if (batchable)
153 inputBindings.append(QRhiVertexInputBinding(sizeof(float)));
154
155 QRhiVertexInputLayout inputLayout;
156 inputLayout.setBindings(inputBindings.cbegin(), inputBindings.cend());
157 inputLayout.setAttributes(inputAttributes.cbegin(), inputAttributes.cend());
158
159 return inputLayout;
160}
161
162QRhiCommandBuffer::IndexFormat qsg_indexFormat(const QSGGeometry *geometry)
163{
164 switch (geometry->indexType()) {
165 case QSGGeometry::UnsignedShortType:
166 return QRhiCommandBuffer::IndexUInt16;
167 break;
168 case QSGGeometry::UnsignedIntType:
169 return QRhiCommandBuffer::IndexUInt32;
170 break;
171 default:
172 Q_UNREACHABLE_RETURN(QRhiCommandBuffer::IndexUInt16);
173 }
174}
175
176QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode, QRhi *rhi)
177{
178 QRhiGraphicsPipeline::Topology topology = QRhiGraphicsPipeline::Triangles;
179 switch (geomDrawMode) {
180 case QSGGeometry::DrawPoints:
181 topology = QRhiGraphicsPipeline::Points;
182 break;
183 case QSGGeometry::DrawLines:
184 topology = QRhiGraphicsPipeline::Lines;
185 break;
186 case QSGGeometry::DrawLineStrip:
187 topology = QRhiGraphicsPipeline::LineStrip;
188 break;
189 case QSGGeometry::DrawTriangles:
190 topology = QRhiGraphicsPipeline::Triangles;
191 break;
192 case QSGGeometry::DrawTriangleStrip:
193 topology = QRhiGraphicsPipeline::TriangleStrip;
194 break;
195 case QSGGeometry::DrawTriangleFan:
196 {
197 static bool triangleFanSupported = false;
198 static bool triangleFanSupportChecked = false;
199 if (!triangleFanSupportChecked) {
200 triangleFanSupportChecked = true;
201 triangleFanSupported = rhi->isFeatureSupported(QRhi::TriangleFanTopology);
202 }
203 if (triangleFanSupported) {
204 topology = QRhiGraphicsPipeline::TriangleFan;
205 break;
206 }
207 Q_FALLTHROUGH();
208 }
209 default:
210 qWarning("Primitive topology 0x%x not supported", geomDrawMode);
211 break;
212 }
213 return topology;
214}
215
216void qsg_setMultiViewFlagsOnMaterial(QSGMaterial *material, int multiViewCount)
217{
218 material->setFlag(QSGMaterial::MultiView2, multiViewCount == 2);
219 material->setFlag(QSGMaterial::MultiView3, multiViewCount == 3);
220 material->setFlag(QSGMaterial::MultiView4, multiViewCount == 4);
221}
222
223ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material,
224 const QSGGeometry *geometry,
225 QSGRendererInterface::RenderMode renderMode,
226 int multiViewCount)
227{
228 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
229
230 QSGMaterialType *type = material->type();
231 ShaderKey key = { type, renderMode, multiViewCount };
232 Shader *shader = rewrittenShaders.value(key, nullptr);
233 if (shader)
234 return shader;
235
236 shader = new Shader;
237 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
238 context->initializeRhiShader(s, QShader::BatchableVertexShader);
239 shader->materialShader = s;
240 shader->inputLayout = calculateVertexInputLayout(s, geometry, true);
241 QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
242 shader->stages = {
243 { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage), QShader::BatchableVertexShader },
244 { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
245 };
246
247 shader->lastOpacity = 0;
248
249 rewrittenShaders[key] = shader;
250 return shader;
251}
252
253ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material,
254 const QSGGeometry *geometry,
255 QSGRendererInterface::RenderMode renderMode,
256 int multiViewCount)
257{
258 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
259
260 QSGMaterialType *type = material->type();
261 ShaderKey key = { type, renderMode, multiViewCount };
262 Shader *shader = stockShaders.value(key, nullptr);
263 if (shader)
264 return shader;
265
266 shader = new Shader;
267 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
268 context->initializeRhiShader(s, QShader::StandardShader);
269 shader->materialShader = s;
270 shader->inputLayout = calculateVertexInputLayout(s, geometry, false);
271 QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
272 shader->stages = {
273 { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage) },
274 { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
275 };
276
277 shader->lastOpacity = 0;
278
279 stockShaders[key] = shader;
280
281 return shader;
282}
283
284void ShaderManager::invalidated()
285{
286 qDeleteAll(stockShaders);
287 stockShaders.clear();
288 qDeleteAll(rewrittenShaders);
289 rewrittenShaders.clear();
290
291 qDeleteAll(pipelineCache);
292 pipelineCache.clear();
293
294 qDeleteAll(srbPool);
295 srbPool.clear();
296}
297
299{
300 for (ShaderManager::Shader *sms : std::as_const(stockShaders)) {
301 QSGMaterialShader *s = sms->materialShader;
302 if (s) {
303 QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
304 sd->clearCachedRendererData();
305 }
306 }
307 for (ShaderManager::Shader *sms : std::as_const(rewrittenShaders)) {
308 QSGMaterialShader *s = sms->materialShader;
309 if (s) {
310 QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
311 sd->clearCachedRendererData();
312 }
313 }
314}
315
317{
318 static int extraIndent = 0;
319 ++extraIndent;
320
321 QByteArray ind(indent + extraIndent + 10, ' ');
322
323 if (!i) {
324 qDebug("%s - no info", ind.constData());
325 } else {
326 qDebug() << ind.constData() << "- parent:" << i->parentRoot << "orders" << i->firstOrder << "->" << i->lastOrder << ", avail:" << i->availableOrders;
327 for (QSet<Node *>::const_iterator it = i->subRoots.constBegin();
328 it != i->subRoots.constEnd(); ++it) {
329 qDebug() << ind.constData() << "-" << *it;
330 qsg_dumpShadowRoots((*it)->rootInfo(), indent);
331 }
332 }
333
334 --extraIndent;
335}
336
338{
339#ifndef QT_NO_DEBUG_OUTPUT
340 static int indent = 0;
341 ++indent;
342
343 QByteArray ind(indent, ' ');
344
345 if (n->type() == QSGNode::ClipNodeType || n->isBatchRoot) {
346 qDebug() << ind.constData() << "[X]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
348 } else {
349 QDebug d = qDebug();
350 d << ind.constData() << "[ ]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
351 if (n->type() == QSGNode::GeometryNodeType)
352 d << "order" << Qt::dec << n->element()->order;
353 }
354
357
358 --indent;
359#else
360 Q_UNUSED(n);
361#endif
362}
363
364Updater::Updater(Renderer *r)
365 : renderer(r)
366 , m_roots(32)
367 , m_rootMatrices(8)
368{
369 m_roots.add(0);
370 m_combined_matrix_stack.add(&m_identityMatrix);
371 m_rootMatrices.add(m_identityMatrix);
372}
373
374void Updater::updateStates(QSGNode *n)
375{
376 m_current_clip = nullptr;
377
378 m_added = 0;
379 m_transformChange = 0;
380 m_opacityChange = 0;
381
382 Node *sn = renderer->m_nodes.value(n, 0);
383 Q_ASSERT(sn);
384
385 if (Q_UNLIKELY(debug_roots()))
387
388 if (Q_UNLIKELY(debug_build())) {
389 qDebug("Updater::updateStates()");
390 if (sn->dirtyState & (QSGNode::DirtyNodeAdded << 16))
391 qDebug(" - nodes have been added");
392 if (sn->dirtyState & (QSGNode::DirtyMatrix << 16))
393 qDebug(" - transforms have changed");
394 if (sn->dirtyState & (QSGNode::DirtyOpacity << 16))
395 qDebug(" - opacity has changed");
396 if (uint(sn->dirtyState) & uint(QSGNode::DirtyForceUpdate << 16))
397 qDebug(" - forceupdate");
398 }
399
400 if (Q_UNLIKELY(renderer->m_visualizer->mode() == Visualizer::VisualizeChanges))
401 renderer->m_visualizer->visualizeChangesPrepare(sn);
402
403 visitNode(sn);
404}
405
407{
408 if (m_added == 0 && n->dirtyState == 0 && m_force_update == 0 && m_transformChange == 0 && m_opacityChange == 0)
409 return;
410
411 int count = m_added;
412 if (n->dirtyState & QSGNode::DirtyNodeAdded)
413 ++m_added;
414
415 int force = m_force_update;
416 if (n->dirtyState & QSGNode::DirtyForceUpdate)
417 ++m_force_update;
418
419 switch (n->type()) {
420 case QSGNode::OpacityNodeType:
422 break;
423 case QSGNode::TransformNodeType:
425 break;
426 case QSGNode::GeometryNodeType:
428 break;
429 case QSGNode::ClipNodeType:
431 break;
432 case QSGNode::RenderNodeType:
433 if (m_added)
434 n->renderNodeElement()->root = m_roots.last();
435 Q_FALLTHROUGH(); // to visit children
436 default:
438 break;
439 }
440
441 m_added = count;
442 m_force_update = force;
443 n->dirtyState = {};
444}
445
447{
449
450 QSGClipNode *cn = static_cast<QSGClipNode *>(n->sgNode);
451
452 if (m_roots.last() && m_added > 0)
453 renderer->registerBatchRoot(n, m_roots.last());
454
455 cn->setRendererClipList(m_current_clip);
456 m_current_clip = cn;
457 m_roots << n;
458 m_rootMatrices.add(m_rootMatrices.last() * *m_combined_matrix_stack.last());
459 extra->matrix = m_rootMatrices.last();
460 cn->setRendererMatrix(&extra->matrix);
461 m_combined_matrix_stack << &m_identityMatrix;
462
464
465 m_current_clip = cn->clipList();
466 m_rootMatrices.pop_back();
467 m_combined_matrix_stack.pop_back();
468 m_roots.pop_back();
469}
470
472{
473 QSGOpacityNode *on = static_cast<QSGOpacityNode *>(n->sgNode);
474
475 qreal combined = m_opacity_stack.last() * on->opacity();
476 on->setCombinedOpacity(combined);
477 m_opacity_stack.add(combined);
478
479 if (m_added == 0 && n->dirtyState & QSGNode::DirtyOpacity) {
480 bool was = n->isOpaque;
481 bool is = on->opacity() > OPAQUE_LIMIT;
482 if (was != is) {
483 renderer->m_rebuild = Renderer::FullRebuild;
484 n->isOpaque = is;
485 }
486 ++m_opacityChange;
488 --m_opacityChange;
489 } else {
490 if (m_added > 0)
491 n->isOpaque = on->opacity() > OPAQUE_LIMIT;
493 }
494
495 m_opacity_stack.pop_back();
496}
497
499{
500 bool popMatrixStack = false;
501 bool popRootStack = false;
502 bool dirty = n->dirtyState & QSGNode::DirtyMatrix;
503
504 QSGTransformNode *tn = static_cast<QSGTransformNode *>(n->sgNode);
505
506 if (n->isBatchRoot) {
507 if (m_added > 0 && m_roots.last())
508 renderer->registerBatchRoot(n, m_roots.last());
509 tn->setCombinedMatrix(m_rootMatrices.last() * *m_combined_matrix_stack.last() * tn->matrix());
510
511 // The only change in this subtree is ourselves and we are a batch root, so
512 // only update subroots and return, saving tons of child-processing (flickable-panning)
513
514 if (!n->becameBatchRoot && m_added == 0 && m_force_update == 0 && m_opacityChange == 0 && dirty && (n->dirtyState & ~QSGNode::DirtyMatrix) == 0) {
515 BatchRootInfo *info = renderer->batchRootInfo(n);
516 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
517 it != info->subRoots.constEnd(); ++it) {
518 updateRootTransforms(*it, n, tn->combinedMatrix());
519 }
520 return;
521 }
522
523 n->becameBatchRoot = false;
524
525 m_combined_matrix_stack.add(&m_identityMatrix);
526 m_roots.add(n);
527 m_rootMatrices.add(tn->combinedMatrix());
528
529 popMatrixStack = true;
530 popRootStack = true;
531 } else if (!tn->matrix().isIdentity()) {
532 tn->setCombinedMatrix(*m_combined_matrix_stack.last() * tn->matrix());
533 m_combined_matrix_stack.add(&tn->combinedMatrix());
534 popMatrixStack = true;
535 } else {
536 tn->setCombinedMatrix(*m_combined_matrix_stack.last());
537 }
538
539 if (dirty)
540 ++m_transformChange;
541
543
544 if (dirty)
545 --m_transformChange;
546 if (popMatrixStack)
547 m_combined_matrix_stack.pop_back();
548 if (popRootStack) {
549 m_roots.pop_back();
550 m_rootMatrices.pop_back();
551 }
552}
553
555{
556 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode);
557
558 gn->setRendererMatrix(m_combined_matrix_stack.last());
559 gn->setRendererClipList(m_current_clip);
560 gn->setInheritedOpacity(m_opacity_stack.last());
561
562 if (m_added) {
563 Element *e = n->element();
564 e->root = m_roots.last();
565 e->translateOnlyToRoot = isTranslate(*gn->matrix());
566
567 if (e->root) {
568 BatchRootInfo *info = renderer->batchRootInfo(e->root);
569 while (info != nullptr) {
570 info->availableOrders--;
571 if (info->availableOrders < 0) {
572 renderer->m_rebuild |= Renderer::BuildRenderLists;
573 } else {
574 renderer->m_rebuild |= Renderer::BuildRenderListsForTaggedRoots;
575 renderer->m_taggedRoots << e->root;
576 }
577 if (info->parentRoot != nullptr)
578 info = renderer->batchRootInfo(info->parentRoot);
579 else
580 info = nullptr;
581 }
582 } else {
583 renderer->m_rebuild |= Renderer::FullRebuild;
584 }
585 } else {
586 if (m_transformChange) {
587 Element *e = n->element();
588 e->translateOnlyToRoot = isTranslate(*gn->matrix());
589 }
590 if (m_opacityChange) {
591 Element *e = n->element();
592 if (e->batch)
593 renderer->invalidateBatchAndOverlappingRenderOrders(e->batch);
594 }
595 }
596
598}
599
600void Updater::updateRootTransforms(Node *node, Node *root, const QMatrix4x4 &combined)
601{
602 BatchRootInfo *info = renderer->batchRootInfo(node);
603 QMatrix4x4 m;
604 Node *n = node;
605
606 while (n != root) {
607 if (n->type() == QSGNode::TransformNodeType)
608 m = static_cast<QSGTransformNode *>(n->sgNode)->matrix() * m;
609 n = n->parent();
610 }
611
612 m = combined * m;
613
614 if (node->type() == QSGNode::ClipNodeType) {
615 static_cast<ClipBatchRootInfo *>(info)->matrix = m;
616 } else {
617 Q_ASSERT(node->type() == QSGNode::TransformNodeType);
618 static_cast<QSGTransformNode *>(node->sgNode)->setCombinedMatrix(m);
619 }
620
621 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
622 it != info->subRoots.constEnd(); ++it) {
623 updateRootTransforms(*it, node, m);
624 }
625}
626
627int qsg_positionAttribute(QSGGeometry *g)
628{
629 int vaOffset = 0;
630 for (int a=0; a<g->attributeCount(); ++a) {
631 const QSGGeometry::Attribute &attr = g->attributes()[a];
632 if (attr.isVertexCoordinate && attr.tupleSize == 2 && attr.type == QSGGeometry::FloatType) {
633 return vaOffset;
634 }
635 vaOffset += attr.tupleSize * size_of_type(attr.type);
636 }
637 return -1;
638}
639
640
641void Rect::map(const QMatrix4x4 &matrix)
642{
643 const float *m = matrix.constData();
644 if (isScale(matrix)) {
645 tl.x = tl.x * m[0] + m[12];
646 tl.y = tl.y * m[5] + m[13];
647 br.x = br.x * m[0] + m[12];
648 br.y = br.y * m[5] + m[13];
649 if (tl.x > br.x)
650 qSwap(tl.x, br.x);
651 if (tl.y > br.y)
652 qSwap(tl.y, br.y);
653 } else {
654 Pt mtl = tl;
655 Pt mtr = { br.x, tl.y };
656 Pt mbl = { tl.x, br.y };
657 Pt mbr = br;
658
659 mtl.map(matrix);
660 mtr.map(matrix);
661 mbl.map(matrix);
662 mbr.map(matrix);
663
664 set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
665 (*this) |= mtl;
666 (*this) |= mtr;
667 (*this) |= mbl;
668 (*this) |= mbr;
669 }
670}
671
673{
674 Q_ASSERT(!boundsComputed);
675 boundsComputed = true;
676
677 QSGGeometry *g = node->geometry();
678 int offset = qsg_positionAttribute(g);
679 if (offset == -1) {
680 // No position attribute means overlaps with everything..
681 bounds.set(-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX);
682 return;
683 }
684
685 bounds.set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
686 char *vd = (char *) g->vertexData() + offset;
687 for (int i=0; i<g->vertexCount(); ++i) {
688 bounds |= *(Pt *) vd;
689 vd += g->sizeOfVertex();
690 }
691 bounds.map(*node->matrix());
692
693 if (!qt_is_finite(bounds.tl.x) || bounds.tl.x == FLT_MAX)
694 bounds.tl.x = -FLT_MAX;
695 if (!qt_is_finite(bounds.tl.y) || bounds.tl.y == FLT_MAX)
696 bounds.tl.y = -FLT_MAX;
697 if (!qt_is_finite(bounds.br.x) || bounds.br.x == -FLT_MAX)
698 bounds.br.x = FLT_MAX;
699 if (!qt_is_finite(bounds.br.y) || bounds.br.y == -FLT_MAX)
700 bounds.br.y = FLT_MAX;
701
702 Q_ASSERT(bounds.tl.x <= bounds.br.x);
703 Q_ASSERT(bounds.tl.y <= bounds.br.y);
704
705 boundsOutsideFloatRange = bounds.isOutsideFloatRange();
706}
707
709{
710 Element *n = first;
711 // Skip to the first node other than e which has not been removed
712 while (n && (n == e || n->removed))
713 n = n->nextInBatch;
714
715 // Only 'e' in this batch, so a material change doesn't change anything as long as
716 // its blending is still in sync with this batch...
717 if (!n)
718 return BatchIsCompatible;
719
720 QSGMaterial *m = e->node->activeMaterial();
721 QSGMaterial *nm = n->node->activeMaterial();
722 return (nm->type() == m->type() && nm->viewCount() == m->viewCount() && nm->compare(m) == 0)
725}
726
727/*
728 * Marks this batch as dirty or in the case where the geometry node has
729 * changed to be incompatible with this batch, return false so that
730 * the caller can mark the entire sg for a full rebuild...
731 */
732bool Batch::geometryWasChanged(QSGGeometryNode *gn)
733{
734 Element *e = first;
735 Q_ASSERT_X(e, "Batch::geometryWasChanged", "Batch is expected to 'valid' at this time");
736 // 'gn' is the first node in the batch, compare against the next one.
737 while (e && (e->node == gn || e->removed))
738 e = e->nextInBatch;
739 if (!e || e->node->geometry()->attributes() == gn->geometry()->attributes()) {
740 needsUpload = true;
741 return true;
742 } else {
743 return false;
744 }
745}
746
748{
749 if (!needsPurge)
750 return;
751
752 // remove from front of batch..
753 while (first && first->removed) {
755 }
756
757 // Then continue and remove other nodes further out in the batch..
758 if (first) {
759 Element *e = first;
760 while (e->nextInBatch) {
761 if (e->nextInBatch->removed)
763 else
764 e = e->nextInBatch;
765
766 }
767 }
768
769 needsPurge = false;
770}
771
772/*
773 * Iterates through all geometry nodes in this batch and unsets their batch,
774 * thus forcing them to be rebuilt
775 */
777{
779 Element *e = first;
780 first = nullptr;
781 root = nullptr;
782 while (e) {
783 e->batch = nullptr;
785 e->nextInBatch = nullptr;
786 e = n;
787 }
788}
789
791 bool only = true;
792 Element *e = first;
793 while (e && only) {
794 only &= e->translateOnlyToRoot;
795 e = e->nextInBatch;
796 }
797 return only;
798}
799
800/*
801 * Iterates through all the nodes in the batch and returns true if the
802 * nodes are all safe to batch. There are two separate criteria:
803 *
804 * - The matrix is such that the z component of the result is of no
805 * consequence.
806 *
807 * - The bounds are inside the stable floating point range. This applies
808 * to desktop only where we in this case can trigger a fallback to
809 * unmerged in which case we pass the geometry straight through and
810 * just apply the matrix.
811 *
812 * NOTE: This also means a slight performance impact for geometries which
813 * are defined to be outside the stable floating point range and still
814 * use single precision float, but given that this implicitly fixes
815 * huge lists and tables, it is worth it.
816 */
817bool Batch::isSafeToBatch() const {
818 Element *e = first;
819 while (e) {
820 if (e->boundsOutsideFloatRange)
821 return false;
822 if (!is2DSafe(*e->node->matrix()))
823 return false;
824 e = e->nextInBatch;
825 }
826 return true;
827}
828
829static int qsg_countNodesInBatch(const Batch *batch)
830{
831 int sum = 0;
832 Element *e = batch->first;
833 while (e) {
834 ++sum;
835 e = e->nextInBatch;
836 }
837 return sum;
838}
839
840static int qsg_countNodesInBatches(const QDataBuffer<Batch *> &batches)
841{
842 int sum = 0;
843 for (int i=0; i<batches.size(); ++i) {
844 sum += qsg_countNodesInBatch(batches.at(i));
845 }
846 return sum;
847}
848
851 , m_context(ctx)
856 , m_partialRebuild(false)
857 , m_partialRebuildRoot(nullptr)
858 , m_forceNoDepthBuffer(false)
859 , m_opaqueBatches(16)
860 , m_alphaBatches(16)
861 , m_batchPool(16)
865 , m_vboPool(16)
866 , m_iboPool(16)
867 , m_vboPoolCost(0)
868 , m_iboPoolCost(0)
870 , m_zRange(0)
871#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
874#endif
876 , m_currentMaterial(nullptr)
877 , m_currentShader(nullptr)
878 , m_vertexUploadPool(256)
880{
881 m_rhi = m_context->rhi();
882 Q_ASSERT(m_rhi); // no more direct OpenGL code path in Qt 6
883
885
887 if (qEnvironmentVariableIntValue("QSG_RHI_UINT32_INDEX"))
888 m_uint32IndexForRhi = true;
889
890 bool ok = false;
891 int padding = qEnvironmentVariableIntValue("QSG_BATCHRENDERER_MINIMUM_ORDER_PADDING", &ok);
892 if (ok)
894
895 m_visualizer = new RhiVisualizer(this);
896
897 setNodeUpdater(new Updater(this));
898
899 // The shader manager is shared between renderers (think for example Item
900 // layers that create a new Renderer each) with the same rendercontext (and
901 // so same QRhi).
903 if (!m_shaderManager) {
905 m_shaderManager->setObjectName(QStringLiteral("__qt_ShaderManager"));
908 }
909
910 m_batchNodeThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_NODE_THRESHOLD", 64);
911 m_batchVertexThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_VERTEX_THRESHOLD", 1024);
912 m_srbPoolThreshold = qt_sg_envInt("QSG_RENDERER_SRB_POOL_THRESHOLD", 1024);
913 m_bufferPoolSizeLimit = qt_sg_envInt("QSG_RENDERER_BUFFER_POOL_LIMIT", DEFAULT_BUFFER_POOL_SIZE_LIMIT);
914
916 qDebug("Batch thresholds: nodes: %d vertices: %d srb pool: %d buffer pool: %d",
918 }
919}
920
921static void qsg_wipeBuffer(Buffer *buffer)
922{
923 delete buffer->buf;
924
925 // The free here is ok because we're in one of two situations.
926 // 1. We're using the upload pool in which case unmap will have set the
927 // data pointer to 0 and calling free on 0 is ok.
928 // 2. We're using dedicated buffers because of visualization or IBO workaround
929 // and the data something we malloced and must be freed.
930 free(buffer->data);
931}
932
933static void qsg_wipeBatch(Batch *batch)
934{
935 qsg_wipeBuffer(&batch->vbo);
936 qsg_wipeBuffer(&batch->ibo);
937 delete batch->ubuf;
938 batch->stencilClipState.reset();
939 delete batch;
940}
941
943{
944 if (m_rhi) {
945 // Clean up batches and buffers
946 for (int i = 0; i < m_opaqueBatches.size(); ++i)
948 for (int i = 0; i < m_alphaBatches.size(); ++i)
950 for (int i = 0; i < m_batchPool.size(); ++i)
952 for (int i = 0; i < m_vboPool.size(); ++i)
953 delete m_vboPool.at(i);
954 for (int i = 0; i < m_iboPool.size(); ++i)
955 delete m_iboPool.at(i);
956 }
957
958 for (Node *n : std::as_const(m_nodes)) {
959 if (n->type() == QSGNode::GeometryNodeType) {
960 Element *e = n->element();
961 if (!e->removed)
963 } else if (n->type() == QSGNode::ClipNodeType) {
964 delete n->clipInfo();
965 } else if (n->type() == QSGNode::RenderNodeType) {
967 if (!e->removed)
969 }
970
972 }
973
974 // Remaining elements...
975 for (int i=0; i<m_elementsToDelete.size(); ++i)
977
979
980 delete m_visualizer;
981}
982
984{
985 // If this is from the dtor, then the shader manager and its already
986 // prepared shaders will stay around for other renderers -> the cached data
987 // in the rhi shaders have to be purged as it may refer to samplers we
988 // are going to destroy.
990
993 delete m_dummyTexture;
995}
996
998{
1000
1002
1003 m_samplers.clear();
1004 m_dummyTexture = nullptr;
1005
1007
1012
1013 for (int i = 0; i < m_vboPool.size(); ++i)
1014 delete m_vboPool.at(i);
1015 m_vboPool.reset();
1016 m_vboPoolCost = 0;
1017
1018 for (int i = 0; i < m_iboPool.size(); ++i)
1019 delete m_iboPool.at(i);
1020 m_iboPool.reset();
1021 m_iboPoolCost = 0;
1022}
1023
1025{
1026 if (b->vbo.buf != nullptr && m_vboPoolCost + b->vbo.buf->size() <= quint32(m_bufferPoolSizeLimit)) {
1027 m_vboPool.add(b->vbo.buf);
1028 m_vboPoolCost += b->vbo.buf->size();
1029 } else {
1030 delete b->vbo.buf;
1031 }
1032 if (b->ibo.buf != nullptr && m_iboPoolCost + b->ibo.buf->size() <= quint32(m_bufferPoolSizeLimit)) {
1033 m_iboPool.add(b->ibo.buf);
1034 m_iboPoolCost += b->ibo.buf->size();
1035 } else {
1036 delete b->ibo.buf;
1037 }
1038 b->vbo.buf = nullptr;
1039 b->ibo.buf = nullptr;
1040 b->invalidate();
1041 for (int i=0; i<m_batchPool.size(); ++i)
1042 if (b == m_batchPool.at(i))
1043 return;
1044 m_batchPool.add(b);
1045}
1046
1048{
1050 // Common case, use a shared memory pool for uploading vertex data to avoid
1051 // excessive reevaluation
1053 if (byteSize > quint32(pool.size()))
1055 buffer->data = pool.data();
1056 } else if (buffer->size != byteSize) {
1057 free(buffer->data);
1058 buffer->data = (char *) malloc(byteSize);
1060 }
1061 buffer->size = byteSize;
1062}
1063
1065{
1066 // Batches are pooled and reused which means the QRhiBuffer will be
1067 // still valid in a recycled Batch. We only hit the newBuffer() path
1068 // when there are no buffers to recycle.
1070 if (!buffer->buf && bufferPool->isEmpty()) {
1073 buffer->size);
1074 if (!buffer->buf->create()) {
1075 qWarning("Failed to build vertex/index buffer of size %u", buffer->size);
1076 delete buffer->buf;
1077 buffer->buf = nullptr;
1078 }
1079 } else {
1080 if (!buffer->buf) {
1083 for (qsizetype i = 0; i < bufferPool->size(); ++i) {
1085 if (!buffer->buf
1086 || (testBuffer->size() >= expectedSize && testBuffer->size() < buffer->buf->size())
1087 || (testBuffer->size() < expectedSize && testBuffer->size() > buffer->buf->size())) {
1090 if (buffer->buf->size() == expectedSize)
1091 break;
1092 }
1093 }
1094
1099 }
1100 if (isIndexBuf)
1102 else
1105 }
1106
1107 bool needsRebuild = false;
1108 if (buffer->buf->size() < buffer->size) {
1110 needsRebuild = true;
1111 }
1112 if (buffer->buf->type() != QRhiBuffer::Dynamic
1114 {
1117 needsRebuild = true;
1118 }
1119 if (needsRebuild) {
1120 if (!buffer->buf->create()) {
1121 qWarning("Failed to (re)build vertex/index buffer of size %u", buffer->size);
1122 delete buffer->buf;
1123 buffer->buf = nullptr;
1124 }
1125 }
1126 }
1127 if (buffer->buf) {
1128 if (buffer->buf->type() != QRhiBuffer::Dynamic) {
1131 } else {
1134 else
1136 }
1137 }
1139 buffer->data = nullptr;
1140}
1141
1143{
1145 if (!info) {
1146 if (node->type() == QSGNode::ClipNodeType)
1147 info = new ClipBatchRootInfo;
1148 else {
1150 info = new BatchRootInfo;
1151 }
1152 node->data = info;
1153 }
1154 return info;
1155}
1156
1158{
1160 if (!childInfo->parentRoot)
1161 return;
1163
1166 childInfo->parentRoot = nullptr;
1167}
1168
1170{
1175}
1176
1178{
1180 if (subInfo->parentRoot == root)
1181 return false;
1182 if (subInfo->parentRoot) {
1185 }
1189 return true;
1190}
1191
1193{
1194 if (node->type() == QSGNode::ClipNodeType || node->isBatchRoot) {
1195 // When we reach a batchroot, we only need to update it. Its subtree
1196 // is relative to that root, so no need to recurse further.
1198 return;
1199 } else if (node->type() == QSGNode::GeometryNodeType) {
1200 // Only need to change the root as nodeChanged anyway flags a full update.
1201 Element *e = node->element();
1202 if (e) {
1203 e->root = root;
1204 e->boundsComputed = false;
1205 }
1206 } else if (node->type() == QSGNode::RenderNodeType) {
1208 if (e)
1209 e->root = root;
1210 }
1211
1214}
1215
1217{
1218 if (node->type() == QSGNode::GeometryNodeType) {
1219 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node->sgNode);
1221 Element *e = node->element();
1222 if (e) {
1223 e->boundsComputed = false;
1224 if (e->batch) {
1225 if (!e->batch->isOpaque) {
1227 } else if (e->batch->merged) {
1228 e->batch->needsUpload = true;
1229 }
1230 }
1231 }
1232 }
1233
1236}
1237
1239{
1241 if (node->isSubtreeBlocked())
1242 return;
1243
1245 snode->sgNode = node;
1247 if (shadowParent)
1249
1250 if (node->type() == QSGNode::GeometryNodeType) {
1252 snode->element()->setNode(static_cast<QSGGeometryNode *>(node));
1253
1254 } else if (node->type() == QSGNode::ClipNodeType) {
1257
1258 } else if (node->type() == QSGNode::RenderNodeType) {
1259 QSGRenderNode *rn = static_cast<QSGRenderNode *>(node);
1261 snode->data = e;
1265 m_forceNoDepthBuffer = true;
1267 }
1268
1271}
1272
1274{
1275 // Prefix traversal as removeBatchRootFromParent below removes nodes
1276 // in a bottom-up manner. Note that we *cannot* use SHADOWNODE_TRAVERSE
1277 // here, because we delete 'child' (when recursed, down below), so we'd
1278 // have a use-after-free.
1279 {
1280 Node *child = node->firstChild();
1281 while (child) {
1282 // Remove (and delete) child
1283 node->remove(child);
1285 child = node->firstChild();
1286 }
1287 }
1288
1289 if (node->type() == QSGNode::GeometryNodeType) {
1290 Element *e = node->element();
1291 if (e) {
1292 e->removed = true;
1294 e->node = nullptr;
1295 if (e->root) {
1298 }
1299 if (e->batch) {
1300 e->batch->needsUpload = true;
1301 e->batch->needsPurge = true;
1302 }
1303
1304 }
1305
1306 } else if (node->type() == QSGNode::ClipNodeType) {
1308 delete node->clipInfo();
1311
1312 } else if (node->isBatchRoot) {
1314 delete node->rootInfo();
1317
1318 } else if (node->type() == QSGNode::RenderNodeType) {
1320 if (e) {
1321 e->removed = true;
1324 m_forceNoDepthBuffer = false;
1325 // Must have a full rebuild given useDepthBuffer() now returns
1326 // a different value than before, meaning there can once again
1327 // be an opaque pass.
1329 }
1330
1331 if (e->batch != nullptr)
1332 e->batch->needsPurge = true;
1333 }
1334 }
1335
1337
1339}
1340
1342{
1343 if (Q_UNLIKELY(debug_change())) qDebug(" - new batch root");
1345 node->isBatchRoot = true;
1346 node->becameBatchRoot = true;
1347
1348 Node *p = node->parent();
1349 while (p) {
1350 if (p->type() == QSGNode::ClipNodeType || p->isBatchRoot) {
1352 break;
1353 }
1354 p = p->parent();
1355 }
1356
1359}
1360
1361
1363{
1364#ifndef QT_NO_DEBUG_OUTPUT
1365 if (Q_UNLIKELY(debug_change())) {
1366 QDebug debug = qDebug();
1367 debug << "dirty:";
1369 debug << "Geometry";
1371 debug << "Material";
1372 if (state & QSGNode::DirtyMatrix)
1373 debug << "Matrix";
1375 debug << "Added";
1377 debug << "Removed";
1378 if (state & QSGNode::DirtyOpacity)
1379 debug << "Opacity";
1381 debug << "SubtreeBlocked";
1383 debug << "ForceUpdate";
1384
1385 // when removed, some parts of the node could already have been destroyed
1386 // so don't debug it out.
1388 debug << (void *) node << node->type();
1389 else
1390 debug << node;
1391 }
1392#endif
1393 // As this function calls nodeChanged recursively, we do it at the top
1394 // to avoid that any of the others are processed twice.
1396 Node *sn = m_nodes.value(node);
1397
1398 // Force a batch rebuild if this includes an opacity change
1399 if (state & QSGNode::DirtyOpacity)
1401
1402 bool blocked = node->isSubtreeBlocked();
1403 if (blocked && sn) {
1405 Q_ASSERT(m_nodes.value(node) == 0);
1406 } else if (!blocked && !sn) {
1408 }
1409 return;
1410 }
1411
1412 if (state & QSGNode::DirtyNodeAdded) {
1415 return;
1416 }
1417 if (node == rootNode())
1418 nodeWasAdded(node, nullptr);
1419 else
1421 }
1422
1423 // Mark this node dirty in the shadow tree.
1425
1426 // Blocked subtrees won't have shadow nodes, so we can safely abort
1427 // here..
1428 if (!shadowNode) {
1430 return;
1431 }
1432
1434
1439 } else {
1440 int vertices = 0;
1444 }
1445 }
1446 }
1447
1449 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1451 if (e) {
1452 e->boundsComputed = false;
1453 Batch *b = e->batch;
1454 if (b) {
1455 if (!e->batch->geometryWasChanged(gn) || !e->batch->isOpaque) {
1457 } else {
1458 b->needsUpload = true;
1459 }
1460 }
1461 }
1462 }
1463
1466 if (e) {
1467 bool blended = hasMaterialWithBlending(static_cast<QSGGeometryNode *>(node));
1468 if (e->isMaterialBlended != blended) {
1471 } else if (e->batch) {
1474 } else {
1476 }
1477 }
1478 }
1479
1480 // Mark the shadow tree dirty all the way back to the root...
1486 if (dirtyChain != 0) {
1488 Node *sn = shadowNode->parent();
1489 while (sn) {
1491 sn = sn->parent();
1492 }
1493 }
1494
1495 // Delete happens at the very end because it deletes the shadownode.
1498 if (parent)
1501 Q_ASSERT(m_nodes.value(node) == 0);
1502 }
1503
1505}
1506
1507/*
1508 * Traverses the tree and builds two list of geometry nodes. One for
1509 * the opaque and one for the translucent. These are populated
1510 * in the order they should visually appear in, meaning first
1511 * to the back and last to the front.
1512 *
1513 * We split opaque and translucent as we can perform different
1514 * types of reordering / batching strategies on them, depending
1515 *
1516 * Note: It would be tempting to use the shadow nodes instead of the QSGNodes
1517 * for traversal to avoid hash lookups, but the order of the children
1518 * is important and they are not preserved in the shadow tree, so we must
1519 * use the actual QSGNode tree.
1520 */
1522{
1523 if (node->isSubtreeBlocked())
1524 return;
1525
1528
1529 if (node->type() == QSGNode::GeometryNodeType) {
1530 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1531
1533 Q_ASSERT(e);
1534
1536 if (opaque && useDepthBuffer())
1538 else
1540
1542 // Used while rebuilding partial roots.
1543 if (m_partialRebuild)
1544 e->orphaned = false;
1545
1546 } else if (node->type() == QSGNode::ClipNodeType || shadowNode->isBatchRoot) {
1549 if (node == m_partialRebuildRoot) {
1554 } else {
1563 }
1564 return;
1565 } else if (node->type() == QSGNode::RenderNodeType) {
1569 Q_ASSERT(e);
1570 }
1571
1574}
1575
1577{
1581 it != i->subRoots.constEnd(); ++it) {
1582 tagSubRoots(*it);
1583 }
1584}
1585
1586static void qsg_addOrphanedElements(QDataBuffer<Element *> &orphans, const QDataBuffer<Element *> &renderList)
1587{
1588 orphans.reset();
1589 for (int i=0; i<renderList.size(); ++i) {
1590 Element *e = renderList.at(i);
1591 if (e && !e->removed) {
1592 e->orphaned = true;
1593 orphans.add(e);
1594 }
1595 }
1596}
1597
1598static void qsg_addBackOrphanedElements(QDataBuffer<Element *> &orphans, QDataBuffer<Element *> &renderList)
1599{
1600 for (int i=0; i<orphans.size(); ++i) {
1601 Element *e = orphans.at(i);
1602 if (e->orphaned)
1603 renderList.add(e);
1604 }
1605 orphans.reset();
1606}
1607
1608/*
1609 * To rebuild the tagged roots, we start by putting all subroots of tagged
1610 * roots into the list of tagged roots. This is to make the rest of the
1611 * algorithm simpler.
1612 *
1613 * Second, we invalidate all batches which belong to tagged roots, which now
1614 * includes the entire subtree under a given root
1615 *
1616 * Then we call buildRenderLists for all tagged subroots which do not have
1617 * parents which are tagged, aka, we traverse only the topmosts roots.
1618 *
1619 * Then we sort the render lists based on their render order, to restore the
1620 * right order for rendering.
1621 */
1623{
1624 // Flag any element that is currently in the render lists, but which
1625 // is not in a batch. This happens when we have a partial rebuild
1626 // in one sub tree while we have a BuildBatches change in another
1627 // isolated subtree. So that batch-building takes into account
1628 // these "orphaned" nodes, we flag them now. The ones under tagged
1629 // roots will be cleared again. The remaining ones are added into the
1630 // render lists so that they contain all visual nodes after the
1631 // function completes.
1634
1635 // Take a copy now, as we will be adding to this while traversing..
1638 it != roots.constEnd(); ++it) {
1639 tagSubRoots(*it);
1640 }
1641
1642 for (int i=0; i<m_opaqueBatches.size(); ++i) {
1646
1647 }
1648 for (int i=0; i<m_alphaBatches.size(); ++i) {
1652 }
1653
1657 m_partialRebuild = true;
1658 // Traverse each root, assigning it
1660 it != m_taggedRoots.constEnd(); ++it) {
1661 Node *root = *it;
1668 }
1669 }
1670 m_partialRebuild = false;
1671 m_partialRebuildRoot = nullptr;
1674
1675 // Add orphaned elements back into the list and then sort it..
1678
1681 if (m_alphaRenderList.size())
1683
1684}
1685
1687{
1690
1691 for (int i=0; i<m_opaqueBatches.size(); ++i)
1693 for (int i=0; i<m_alphaBatches.size(); ++i)
1697
1699
1701}
1702
1704{
1705 Q_ASSERT(batch);
1707
1708#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1713
1716#else
1717 int first = batch->first->order;
1719#endif
1720
1721 batch->invalidate();
1722
1723 for (int i=0; i<m_alphaBatches.size(); ++i) {
1725 if (b->first) {
1726 int bf = b->first->order;
1727 int bl = b->lastOrderInBatch;
1728 if (bl > first && bf < last)
1729 b->invalidate();
1730 }
1731 }
1732
1734}
1735
1736/* Clean up batches by making it a consecutive list of "valid"
1737 * batches and moving all invalidated batches to the batches pool.
1738 */
1740 qsizetype n = batches->size();
1741 if (n == 0)
1742 return;
1743
1745 for (qsizetype i = 0; i < n; ++i) {
1746 Batch *b = batches->at(i);
1747 if (b->first)
1748 (*batches).data()[writeIndex++] = b;
1749 else
1751 }
1753}
1754
1756{
1757 for (int i=m_opaqueRenderList.size() - 1; i >= 0; --i) {
1759 if (!ei || ei->batch || ei->node->geometry()->vertexCount() == 0)
1760 continue;
1761 Batch *batch = newBatch();
1762 batch->first = ei;
1763 batch->root = ei->root;
1764 batch->isOpaque = true;
1765 batch->needsUpload = true;
1767
1769
1770 ei->batch = batch;
1771 Element *next = ei;
1772
1774
1775 for (int j = i - 1; j >= 0; --j) {
1777 if (!ej)
1778 continue;
1779 if (ej->root != ei->root)
1780 break;
1781 if (ej->batch || ej->node->geometry()->vertexCount() == 0)
1782 continue;
1783
1785
1786 const QSGGeometry *gniGeometry = gni->geometry();
1788 const QSGGeometry *gnjGeometry = gnj->geometry();
1790 if (gni->clipList() == gnj->clipList()
1798 && gniMaterial->type() == gnjMaterial->type()
1801 {
1802 ej->batch = batch;
1803 next->nextInBatch = ej;
1804 next = ej;
1805 }
1806 }
1807
1809 }
1810}
1811
1812bool Renderer::checkOverlap(int first, int last, const Rect &bounds)
1813{
1814 for (int i=first; i<=last; ++i) {
1816#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1817 if (!e || e->batch)
1818#else
1819 if (!e)
1820#endif
1821 continue;
1823 if (e->bounds.intersects(bounds))
1824 return true;
1825 }
1826 return false;
1827}
1828
1829/*
1830 *
1831 * To avoid the O(n^2) checkOverlap check in most cases, we have the
1832 * overlapBounds which is the union of all bounding rects to check overlap
1833 * for. We know that if it does not overlap, then none of the individual
1834 * ones will either. For the typical list case, this results in no calls
1835 * to checkOverlap what-so-ever. This also ensures that when all consecutive
1836 * items are matching (such as a table of text), we don't build up an
1837 * overlap bounds and thus do not require full overlap checks.
1838 */
1839
1841{
1842 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1844 if (!e || e->isRenderNode)
1845 continue;
1846 Q_ASSERT(!e->removed);
1848 }
1849
1850 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1852 if (!ei || ei->batch)
1853 continue;
1854
1855 if (ei->isRenderNode) {
1856 Batch *rnb = newBatch();
1857 rnb->first = ei;
1858 rnb->root = ei->root;
1859 rnb->isOpaque = false;
1860 rnb->isRenderNode = true;
1861 ei->batch = rnb;
1863 continue;
1864 }
1865
1866 if (ei->node->geometry()->vertexCount() == 0)
1867 continue;
1868
1869 Batch *batch = newBatch();
1870 batch->first = ei;
1871 batch->root = ei->root;
1872 batch->isOpaque = false;
1873 batch->needsUpload = true;
1875 ei->batch = batch;
1876
1879
1882
1883 Element *next = ei;
1884
1885 for (int j = i + 1; j < m_alphaRenderList.size(); ++j) {
1887 if (!ej)
1888 continue;
1889 if (ej->root != ei->root || ej->isRenderNode)
1890 break;
1891 if (ej->batch) {
1892#if !defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1894#endif
1895 continue;
1896 }
1897
1899 if (gnj->geometry()->vertexCount() == 0)
1900 continue;
1901
1902 const QSGGeometry *gniGeometry = gni->geometry();
1904 const QSGGeometry *gnjGeometry = gnj->geometry();
1906 if (gni->clipList() == gnj->clipList()
1910 // Must not do overlap checks when the line width is not 1,
1911 // we have no knowledge how such lines are rasterized.
1912 && gniGeometry->lineWidth() == 1.0f))
1916 && gniMaterial->type() == gnjMaterial->type()
1919 {
1920 if (!overlapBounds.intersects(ej->bounds) || !checkOverlap(i+1, j - 1, ej->bounds)) {
1921 ej->batch = batch;
1922 next->nextInBatch = ej;
1923 next = ej;
1924 } else {
1925 /* When we come across a compatible element which hits an overlap, we
1926 * need to stop the batch right away. We cannot add more elements
1927 * to the current batch as they will be rendered before the batch that the
1928 * current 'ej' will be added to.
1929 */
1930 break;
1931 }
1932 } else {
1934 }
1935 }
1936
1938 }
1939
1940
1941}
1942
1943static inline int qsg_fixIndexCount(int iCount, int drawMode)
1944{
1945 switch (drawMode) {
1946 case QSGGeometry::DrawTriangleStrip:
1947 // Merged triangle strips need to contain degenerate triangles at the beginning and end.
1948 // One could save 2 uploaded ushorts here by ditching the padding for the front of the
1949 // first and the end of the last, but for simplicity, we simply don't care.
1950 // Those extra triangles will be skipped while drawing to preserve the strip's parity
1951 // anyhow.
1952 return iCount + 2;
1953 case QSGGeometry::DrawLines:
1954 // For lines we drop the last vertex if the number of vertices is uneven.
1955 return iCount - (iCount % 2);
1956 case QSGGeometry::DrawTriangles:
1957 // For triangles we drop trailing vertices until the result is divisible by 3.
1958 return iCount - (iCount % 3);
1959 default:
1960 return iCount;
1961 }
1962}
1963
1964static inline float calculateElementZOrder(const Element *e, qreal zRange)
1965{
1966 // Clamp the zOrder to within the min and max depth of the viewport.
1967 return std::clamp(1.0f - float(e->order * zRange), VIEWPORT_MIN_DEPTH, VIEWPORT_MAX_DEPTH);
1968}
1969
1970/* These parameters warrant some explanation...
1971 *
1972 * vaOffset: The byte offset into the vertex data to the location of the
1973 * 2D float point vertex attributes.
1974 *
1975 * vertexData: destination where the geometry's vertex data should go
1976 *
1977 * zData: destination of geometries injected Z positioning
1978 *
1979 * indexData: destination of the indices for this element
1980 *
1981 * iBase: The starting index for this element in the batch
1982 */
1983
1984void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, void *iBasePtr, int *indexCount)
1985{
1986 if (Q_UNLIKELY(debug_upload())) qDebug() << " - uploading element:" << e << e->node << (void *) *vertexData << (qintptr) (*zData - *vertexData) << (qintptr) (*indexData - *vertexData);
1987 QSGGeometry *g = e->node->geometry();
1988
1989 const QMatrix4x4 &localx = *e->node->matrix();
1990 const float *localxdata = localx.constData();
1991
1992 const int vCount = g->vertexCount();
1993 const int vSize = g->sizeOfVertex();
1995
1996 // apply vertex transform..
1997 char *vdata = *vertexData + vaOffset;
1998 if (localx.flags() == QMatrix4x4::Translation) {
1999 for (int i=0; i<vCount; ++i) {
2000 Pt *p = (Pt *) vdata;
2001 p->x += localxdata[12];
2002 p->y += localxdata[13];
2003 vdata += vSize;
2004 }
2005 } else if (localx.flags() > QMatrix4x4::Translation) {
2006 for (int i=0; i<vCount; ++i) {
2007 ((Pt *) vdata)->map(localx);
2008 vdata += vSize;
2009 }
2010 }
2011
2012 if (useDepthBuffer()) {
2013 float *vzorder = (float *) *zData;
2015 for (int i=0; i<vCount; ++i)
2016 vzorder[i] = zorder;
2017 *zData += vCount * sizeof(float);
2018 }
2019
2020 int iCount = g->indexCount();
2021 if (m_uint32IndexForRhi) {
2022 // can only happen when using the rhi
2025 if (iCount == 0) {
2026 iCount = vCount;
2028 *indices++ = *iBase;
2029 else
2031
2032 for (int i=0; i<iCount; ++i)
2033 indices[i] = *iBase + i;
2034 } else {
2035 // source index data in QSGGeometry is always ushort (we would not merge otherwise)
2038 *indices++ = *iBase + srcIndices[0];
2039 else
2041
2042 for (int i=0; i<iCount; ++i)
2043 indices[i] = *iBase + srcIndices[i];
2044 }
2046 indices[iCount] = indices[iCount - 1];
2047 iCount += 2;
2048 }
2049 *iBase += vCount;
2050 } else {
2051 // normally batching is only done for ushort index data
2054 if (iCount == 0) {
2055 iCount = vCount;
2057 *indices++ = *iBase;
2058 else
2060
2061 for (int i=0; i<iCount; ++i)
2062 indices[i] = *iBase + i;
2063 } else {
2066 *indices++ = *iBase + srcIndices[0];
2067 else
2069
2070 for (int i=0; i<iCount; ++i)
2071 indices[i] = *iBase + srcIndices[i];
2072 }
2074 indices[iCount] = indices[iCount - 1];
2075 iCount += 2;
2076 }
2077 *iBase += vCount;
2078 }
2079
2080 *vertexData += vCount * vSize;
2082 *indexCount += iCount;
2083}
2084
2086{
2087 if (node->type() == QSGNode::TransformNodeType)
2088 return static_cast<QSGTransformNode *>(node->sgNode)->combinedMatrix();
2089 Q_ASSERT(node->type() == QSGNode::ClipNodeType);
2090 QSGClipNode *c = static_cast<QSGClipNode *>(node->sgNode);
2091 return *c->matrix();
2092}
2093
2095{
2096 // Early out if nothing has changed in this batch..
2097 if (!b->needsUpload) {
2098 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "already uploaded...";
2099 return;
2100 }
2101
2102 if (!b->first) {
2103 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "is invalid...";
2104 return;
2105 }
2106
2107 if (b->isRenderNode) {
2108 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch: " << b << "is a render node...";
2109 return;
2110 }
2111
2112 // Figure out if we can merge or not, if not, then just render the batch as is..
2113 Q_ASSERT(b->first);
2114 Q_ASSERT(b->first->node);
2115
2117 QSGGeometry *g = gn->geometry();
2121 && b->positionAttribute >= 0
2125 && b->isSafeToBatch();
2126
2127 b->merged = canMerge;
2128
2129 // Figure out how much memory we need...
2130 b->vertexCount = 0;
2131 b->indexCount = 0;
2132 int unmergedIndexSize = 0;
2133 Element *e = b->first;
2134
2135 // Merged batches always do indexed draw calls. Non-indexed geometry gets
2136 // indices generated automatically, when merged.
2137 while (e) {
2138 QSGGeometry *eg = e->node->geometry();
2139 b->vertexCount += eg->vertexCount();
2140 int iCount = eg->indexCount();
2141 if (b->merged) {
2142 if (iCount == 0)
2143 iCount = eg->vertexCount();
2145 } else {
2146 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : eg->sizeOfIndex();
2148 }
2149 b->indexCount += iCount;
2150 e = e->nextInBatch;
2151 }
2152
2153 // Abort if there are no vertices in this batch.. We abort this late as
2154 // this is a broken usecase which we do not care to optimize for...
2155 if (b->vertexCount == 0 || (b->merged && b->indexCount == 0))
2156 return;
2157
2158 /* Allocate memory for this batch. Merged batches are divided into three separate blocks
2159 1. Vertex data for all elements, as they were in the QSGGeometry object, but
2160 with the tranform relative to this batch's root applied. The vertex data
2161 is otherwise unmodified.
2162 2. Z data for all elements, derived from each elements "render order".
2163 This is present for merged data only.
2164 3. Indices for all elements, as they were in the QSGGeometry object, but
2165 adjusted so that each index matches its.
2166 And for TRIANGLE_STRIPs, we need to insert degenerate between each
2167 primitive. These are unsigned shorts for merged and arbitrary for
2168 non-merged.
2169 */
2171 int ibufferSize = 0;
2172 if (b->merged) {
2174 if (useDepthBuffer())
2175 bufferSize += b->vertexCount * sizeof(float);
2176 } else {
2178 }
2179
2180 map(&b->ibo, ibufferSize, true);
2181 map(&b->vbo, bufferSize);
2182
2183 if (Q_UNLIKELY(debug_upload())) qDebug() << " - batch" << b << " first:" << b->first << " root:"
2184 << b->root << " merged:" << b->merged << " positionAttribute" << b->positionAttribute
2185 << " vbo:" << b->vbo.buf << ":" << b->vbo.size;
2186
2187 if (b->merged) {
2188 char *vertexData = b->vbo.data;
2189 char *zData = vertexData + b->vertexCount * g->sizeOfVertex();
2190 char *indexData = b->ibo.data;
2191
2192 quint16 iOffset16 = 0;
2193 quint32 iOffset32 = 0;
2194 e = b->first;
2195 uint verticesInSet = 0;
2196 // Start a new set already after 65534 vertices because 0xFFFF may be
2197 // used for an always-on primitive restart with some apis (adapt for
2198 // uint32 indices as appropriate).
2199 const uint verticesInSetLimit = m_uint32IndexForRhi ? 0xfffffffe : 0xfffe;
2200 int indicesInSet = 0;
2201 b->drawSets.reset();
2202 int drawSetIndices = 0;
2203 const char *indexBase = b->ibo.data;
2205 while (e) {
2211 b->drawSets.last().indexCount -= 2;
2212 }
2215 zData - b->vbo.data,
2217 iOffset16 = 0;
2218 iOffset32 = 0;
2220 indicesInSet = 0;
2221 }
2222 void *iBasePtr = &iOffset16;
2226 e = e->nextInBatch;
2227 }
2229 // We skip the very first and very last degenerate triangles since they aren't needed
2230 // and the first one would reverse the vertex ordering of the merged strips.
2233 b->drawSets.last().indexCount -= 2;
2234 }
2235 } else {
2236 char *vboData = b->vbo.data;
2237 char *iboData = b->ibo.data;
2238 Element *e = b->first;
2239 while (e) {
2240 QSGGeometry *g = e->node->geometry();
2241 int vbs = g->vertexCount() * g->sizeOfVertex();
2243 vboData = vboData + vbs;
2244 const int indexCount = g->indexCount();
2245 if (indexCount) {
2246 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
2247 const int ibs = indexCount * effectiveIndexSize;
2248 if (g->sizeOfIndex() == effectiveIndexSize) {
2250 } else {
2251 if (g->sizeOfIndex() == sizeof(quint16) && effectiveIndexSize == sizeof(quint32)) {
2253 quint32 *dst = (quint32 *) iboData;
2254 for (int i = 0; i < indexCount; ++i)
2255 dst[i] = src[i];
2256 } else {
2257 Q_ASSERT_X(false, "uploadBatch (unmerged)", "uint index with ushort effective index - cannot happen");
2258 }
2259 }
2260 iboData += ibs;
2261 }
2262 e = e->nextInBatch;
2263 }
2264 }
2265#ifndef QT_NO_DEBUG_OUTPUT
2266 if (Q_UNLIKELY(debug_upload())) {
2267 const char *vd = b->vbo.data;
2268 qDebug() << " -- Vertex Data, count:" << b->vertexCount << " - " << g->sizeOfVertex() << "bytes/vertex";
2269 for (int i=0; i<b->vertexCount; ++i) {
2270 QDebug dump = qDebug().nospace();
2271 dump << " --- " << i << ": ";
2272 int offset = 0;
2273 for (int a=0; a<g->attributeCount(); ++a) {
2274 const QSGGeometry::Attribute &attr = g->attributes()[a];
2275 dump << attr.position << ":(" << attr.tupleSize << ",";
2276 if (attr.type == QSGGeometry::FloatType) {
2277 dump << "float ";
2279 dump << "* ";
2280 for (int t=0; t<attr.tupleSize; ++t)
2281 dump << *(const float *)(vd + offset + t * sizeof(float)) << " ";
2282 } else if (attr.type == QSGGeometry::UnsignedByteType) {
2283 dump << "ubyte ";
2284 for (int t=0; t<attr.tupleSize; ++t)
2285 dump << *(const unsigned char *)(vd + offset + t * sizeof(unsigned char)) << " ";
2286 }
2287 dump << ") ";
2289 }
2290 if (b->merged && useDepthBuffer()) {
2291 float zorder = ((float*)(b->vbo.data + b->vertexCount * g->sizeOfVertex()))[i];
2292 dump << " Z:(" << zorder << ")";
2293 }
2294 vd += g->sizeOfVertex();
2295 }
2296
2297 if (!b->drawSets.isEmpty()) {
2298 if (m_uint32IndexForRhi) {
2299 const quint32 *id = (const quint32 *) b->ibo.data;
2300 {
2301 QDebug iDump = qDebug();
2302 iDump << " -- Index Data, count:" << b->indexCount;
2303 for (int i=0; i<b->indexCount; ++i) {
2304 if ((i % 24) == 0)
2305 iDump << Qt::endl << " --- ";
2306 iDump << id[i];
2307 }
2308 }
2309 } else {
2310 const quint16 *id = (const quint16 *) b->ibo.data;
2311 {
2312 QDebug iDump = qDebug();
2313 iDump << " -- Index Data, count:" << b->indexCount;
2314 for (int i=0; i<b->indexCount; ++i) {
2315 if ((i % 24) == 0)
2316 iDump << Qt::endl << " --- ";
2317 iDump << id[i];
2318 }
2319 }
2320 }
2321
2322 for (int i=0; i<b->drawSets.size(); ++i) {
2323 const DrawSet &s = b->drawSets.at(i);
2324 qDebug() << " -- DrawSet: indexCount:" << s.indexCount << " vertices:" << s.vertices << " z:" << s.zorders << " indices:" << s.indices;
2325 }
2326 }
2327 }
2328#endif // QT_NO_DEBUG_OUTPUT
2329
2330 unmap(&b->vbo);
2331 unmap(&b->ibo, true);
2332
2334 qDebug() << " --- vertex/index buffers unmapped, batch upload completed... vbo pool size" << m_vboPoolCost << "ibo pool size" << m_iboPoolCost;
2335
2336 b->needsUpload = false;
2337
2338 if (Q_UNLIKELY(debug_render()))
2339 b->uploadedThisFrame = true;
2340}
2341
2343{
2346}
2347
2349{
2353 blend.colorWrite = {};
2354 ps->setTargetBlends({ blend });
2356 ps->setStencilTest(true);
2363 } else {
2368 }
2371
2373
2375
2379 ps->setShaderResourceBindings(batch->stencilClipState.srb); // use something, it just needs to be layout-compatible
2381
2382 if (!ps->create()) {
2383 qWarning("Failed to build stencil clip pipeline");
2384 delete ps;
2385 return nullptr;
2386 }
2387
2388 return ps;
2389}
2390
2392{
2393 // Note: No use of the clip-related speparate m_current* vars is allowed
2394 // here. All stored in batch->clipState instead. To collect state during
2395 // the prepare steps, m_currentClipState is used. It should not be used in
2396 // the render steps afterwards.
2397
2398 // The stenciling logic is slightly different from Qt 5's direct OpenGL version
2399 // as we cannot just randomly clear the stencil buffer. We now put all clip
2400 // shapes into the stencil buffer for all batches in the frame. This means
2401 // that the number of total clips in a scene is reduced (since the stencil
2402 // value cannot exceed 255) but we do not need any clears inbetween.
2403
2404 Q_ASSERT(m_rhi);
2409 return;
2410 }
2411
2415 const QSGClipNode *clip = clipList;
2416
2418 quint32 totalVSize = 0;
2419 quint32 totalISize = 0;
2420 quint32 totalUSize = 0;
2421 const quint32 StencilClipUbufSize = 64;
2422
2423 while (clip) {
2424 QMatrix4x4 m = m_current_projection_matrix_native_ndc[0]; // never hit for 3D and so multiview
2425 if (clip->matrix())
2426 m *= *clip->matrix();
2427
2429 && qFuzzyIsNull(m(3, 0)) && qFuzzyIsNull(m(3, 1));
2430 bool noRotate = qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(1, 0));
2431 bool isRotate90 = qFuzzyIsNull(m(0, 0)) && qFuzzyIsNull(m(1, 1));
2432
2434 QRectF bbox = clip->clipRect();
2435 qreal invW = 1 / m(3, 3);
2436 qreal fx1, fy1, fx2, fy2;
2437 if (noRotate) {
2438 fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
2439 fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
2440 fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
2441 fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
2442 } else {
2444 fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
2445 fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
2446 fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
2447 fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
2448 }
2449
2450 if (fx1 > fx2)
2451 qSwap(fx1, fx2);
2452 if (fy1 > fy2)
2453 qSwap(fy1, fy2);
2454
2455 QRect deviceRect = this->deviceRect();
2456
2457 qint32 ix1 = qRound((fx1 + 1) * deviceRect.width() * qreal(0.5));
2458 qint32 iy1 = qRound((fy1 + 1) * deviceRect.height() * qreal(0.5));
2459 qint32 ix2 = qRound((fx2 + 1) * deviceRect.width() * qreal(0.5));
2460 qint32 iy2 = qRound((fy2 + 1) * deviceRect.height() * qreal(0.5));
2461
2462 if (!(clipType & ClipState::ScissorClip)) {
2464 scissorRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2465 } else {
2466 scissorRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2467 }
2468 } else {
2470
2471 const QSGGeometry *g = clip->geometry();
2472 Q_ASSERT(g->attributeCount() > 0);
2473
2474 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2475 // the 4 byte alignment may not actually be needed here
2477 if (g->indexCount()) {
2478 const int indexByteSize = g->sizeOfIndex() * g->indexCount();
2479 // so no need to worry about NonFourAlignedEffectiveIndexBufferOffset
2481 }
2482 // ubuf start offsets must be aligned (typically to 256 bytes)
2484
2486 }
2487
2488 clip = clip->clipList();
2489 }
2490
2492 bool rebuildVBuf = false;
2493 if (!batch->stencilClipState.vbuf) {
2495 rebuildVBuf = true;
2496 } else if (batch->stencilClipState.vbuf->size() < totalVSize) {
2498 rebuildVBuf = true;
2499 }
2500 if (rebuildVBuf) {
2501 if (!batch->stencilClipState.vbuf->create()) {
2502 qWarning("Failed to build stencil clip vertex buffer");
2503 delete batch->stencilClipState.vbuf;
2504 batch->stencilClipState.vbuf = nullptr;
2505 return;
2506 }
2507 }
2508
2509 if (totalISize) {
2510 bool rebuildIBuf = false;
2511 if (!batch->stencilClipState.ibuf) {
2513 rebuildIBuf = true;
2514 } else if (batch->stencilClipState.ibuf->size() < totalISize) {
2516 rebuildIBuf = true;
2517 }
2518 if (rebuildIBuf) {
2519 if (!batch->stencilClipState.ibuf->create()) {
2520 qWarning("Failed to build stencil clip index buffer");
2521 delete batch->stencilClipState.ibuf;
2522 batch->stencilClipState.ibuf = nullptr;
2523 return;
2524 }
2525 }
2526 }
2527
2528 bool rebuildUBuf = false;
2529 if (!batch->stencilClipState.ubuf) {
2531 rebuildUBuf = true;
2532 } else if (batch->stencilClipState.ubuf->size() < totalUSize) {
2534 rebuildUBuf = true;
2535 }
2536 if (rebuildUBuf) {
2537 if (!batch->stencilClipState.ubuf->create()) {
2538 qWarning("Failed to build stencil clip uniform buffer");
2539 delete batch->stencilClipState.ubuf;
2540 batch->stencilClipState.ubuf = nullptr;
2541 return;
2542 }
2543 }
2544
2545 if (!batch->stencilClipState.srb) {
2550 if (!batch->stencilClipState.srb->create()) {
2551 qWarning("Failed to build stencil clip srb");
2552 delete batch->stencilClipState.srb;
2553 batch->stencilClipState.srb = nullptr;
2554 return;
2555 }
2556 }
2557
2558 quint32 vOffset = 0;
2559 quint32 iOffset = 0;
2560 quint32 uOffset = 0;
2561 for (const QSGClipNode *clip : stencilClipNodes) {
2562 const QSGGeometry *g = clip->geometry();
2563 const QSGGeometry::Attribute *a = g->attributes();
2566
2571 }
2572#ifndef QT_NO_DEBUG
2573 else {
2575 qWarning("updateClipState: Clip list entries have different primitive topologies, this is not currently supported.");
2577 qWarning("updateClipState: Clip list entries have different vertex input layouts, this is must not happen.");
2578 }
2579#endif
2580
2582 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2584
2585 int indexByteSize = 0;
2586 if (g->indexCount()) {
2590 }
2591
2594
2596 if (clip->matrix())
2597 matrixYUpNDC *= *clip->matrix();
2598
2601 if (indexByteSize)
2603
2604 // stencil ref goes 1, 1, 2, 3, 4, ..., N for the clips in the first batch,
2605 // then N+1, N+1, N+2, N+3, ... for the next batch,
2606 // and so on.
2607 // Note the different stencilOp for the first and the subsequent clips.
2610
2615 }
2616
2618 m_stencilClipCommon.vs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.vert.qsb"));
2619
2621 m_stencilClipCommon.fs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.frag.qsb"));
2622
2625
2628
2630 }
2631
2636
2639}
2640
2642{
2643 // cliptype stencil + updateStencilBuffer==false means the batch uses
2644 // stenciling but relies on the stencil data generated by a previous batch
2645 // (due to the having the same clip node). Do not enqueue draw calls for
2646 // stencil in this case as the stencil buffer is already up-to-date.
2648 return;
2649
2651 const int count = batch->stencilClipState.drawCalls.size();
2652 for (int i = 0; i < count; ++i) {
2656 if (i == 0) {
2659 } else if (i == 1) {
2662 }
2663 // else incrPs is already bound
2667 if (drawCall.indexCount) {
2671 } else {
2672 cb->setVertexInput(0, 1, &vbufBinding);
2674 }
2675 }
2676}
2677
2679{
2680 Q_ASSERT(m_rhi);
2683 m_currentMaterial = nullptr;
2684}
2685
2686static inline bool needsBlendConstant(QRhiGraphicsPipeline::BlendFactor f)
2687{
2688 return f == QRhiGraphicsPipeline::ConstantColor
2689 || f == QRhiGraphicsPipeline::OneMinusConstantColor
2690 || f == QRhiGraphicsPipeline::ConstantAlpha
2691 || f == QRhiGraphicsPipeline::OneMinusConstantAlpha;
2692}
2693
2694// With QRhi renderBatches() is split to two steps: prepare and render.
2695//
2696// Prepare goes through the batches and elements, and set up a graphics
2697// pipeline, srb, uniform buffer, calculates clipping, based on m_gstate, the
2698// material (shaders), and the batches. This step does not touch the command
2699// buffer or renderpass-related state (m_pstate).
2700//
2701// The render step then starts a renderpass, and goes through all
2702// batches/elements again and records setGraphicsPipeline, drawIndexed, etc. on
2703// the command buffer. The prepare step's accumulated global state like
2704// m_gstate must not be used here. Rather, all data needed for rendering is
2705// available from Batch/Element at this stage. Bookkeeping of state in the
2706// renderpass is done via m_pstate.
2707
2709{
2710 // Note the key's == and qHash implementations: the renderpass descriptor
2711 // and srb are tested for compatibility, not pointer equality.
2712 //
2713 // We do not store the srb pointer itself because the ownership stays with
2714 // the Element and that can go away more often that we would like it
2715 // to. (think scrolling a list view, constantly dropping and creating new
2716 // nodes) Rather, use an opaque blob of a few uints and store and compare
2717 // that. This works because once the pipeline is built, we will always call
2718 // setShaderResources with an explicitly specified srb which is fine even if
2719 // e->srb we used here to bake the pipeline is already gone by that point.
2720 //
2721 // A typical QSGMaterial's serialized srb layout is 8 uints. (uniform buffer
2722 // + texture, 4 fields each) Regardless, using an implicitly shared
2723 // container is essential here. (won't detach so no more allocs and copies
2724 // are done, unless the Element decides to rebake the srb with a different
2725 // layout - but then the detach is exactly what we need)
2726 //
2727 // Same story for the renderpass descriptor: the object can go away but
2728 // that's fine because that has no effect on an already built pipeline, and
2729 // for comparison we only rely on the serialized blob in order decide if the
2730 // render target is compatible with the pipeline.
2731
2733
2734 // Note: dynamic state (viewport rect, scissor rect, stencil ref, blend
2735 // constant) is never a part of GraphicsState/QRhiGraphicsPipeline.
2736
2737 // See if there is an existing, matching pipeline state object.
2740 if (depthPostPass)
2741 e->depthPostPassPs = *it;
2742 else
2743 e->ps = *it;
2744 return true;
2745 }
2746
2747 // Build a new one. This is potentially expensive.
2753
2757 {
2759 }
2764
2765 ps->setFlags(flags);
2771
2781 ps->setTargetBlends({ blend });
2782
2786
2787 if (m_gstate.stencilTest) {
2788 ps->setStencilTest(true);
2796 }
2797
2799
2801
2802 if (!ps->create()) {
2803 qWarning("Failed to build graphics pipeline state");
2804 delete ps;
2805 return false;
2806 }
2807
2809 if (depthPostPass)
2810 e->depthPostPassPs = ps;
2811 else
2812 e->ps = ps;
2813 return true;
2814}
2815
2816static QRhiSampler *newSampler(QRhi *rhi, const QSGSamplerDescription &desc)
2817{
2818 QRhiSampler::Filter magFilter;
2819 QRhiSampler::Filter minFilter;
2820 QRhiSampler::Filter mipmapMode;
2821 QRhiSampler::AddressMode u;
2822 QRhiSampler::AddressMode v;
2823
2824 switch (desc.filtering) {
2825 case QSGTexture::None:
2826 Q_FALLTHROUGH();
2827 case QSGTexture::Nearest:
2828 magFilter = minFilter = QRhiSampler::Nearest;
2829 break;
2830 case QSGTexture::Linear:
2831 magFilter = minFilter = QRhiSampler::Linear;
2832 break;
2833 default:
2834 Q_UNREACHABLE();
2835 magFilter = minFilter = QRhiSampler::Nearest;
2836 break;
2837 }
2838
2839 switch (desc.mipmapFiltering) {
2840 case QSGTexture::None:
2841 mipmapMode = QRhiSampler::None;
2842 break;
2843 case QSGTexture::Nearest:
2844 mipmapMode = QRhiSampler::Nearest;
2845 break;
2846 case QSGTexture::Linear:
2847 mipmapMode = QRhiSampler::Linear;
2848 break;
2849 default:
2850 Q_UNREACHABLE();
2851 mipmapMode = QRhiSampler::None;
2852 break;
2853 }
2854
2855 switch (desc.horizontalWrap) {
2856 case QSGTexture::Repeat:
2857 u = QRhiSampler::Repeat;
2858 break;
2859 case QSGTexture::ClampToEdge:
2860 u = QRhiSampler::ClampToEdge;
2861 break;
2862 case QSGTexture::MirroredRepeat:
2863 u = QRhiSampler::Mirror;
2864 break;
2865 default:
2866 Q_UNREACHABLE();
2867 u = QRhiSampler::ClampToEdge;
2868 break;
2869 }
2870
2871 switch (desc.verticalWrap) {
2872 case QSGTexture::Repeat:
2873 v = QRhiSampler::Repeat;
2874 break;
2875 case QSGTexture::ClampToEdge:
2876 v = QRhiSampler::ClampToEdge;
2877 break;
2878 case QSGTexture::MirroredRepeat:
2879 v = QRhiSampler::Mirror;
2880 break;
2881 default:
2882 Q_UNREACHABLE();
2883 v = QRhiSampler::ClampToEdge;
2884 break;
2885 }
2886
2887 return rhi->newSampler(magFilter, minFilter, mipmapMode, u, v);
2888}
2889
2891{
2892 if (!m_dummyTexture) {
2894 if (m_dummyTexture->create()) {
2895 if (m_resourceUpdates) {
2897 img.fill(0);
2899 }
2900 }
2901 }
2902 return m_dummyTexture;
2903}
2904
2905static void rendererToMaterialGraphicsState(QSGMaterialShader::GraphicsPipelineState *dst,
2906 GraphicsState *src)
2907{
2908 dst->blendEnable = src->blending;
2909
2910 // the enum values should match, sanity check it
2911 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::OneMinusSrc1Alpha) == int(QRhiGraphicsPipeline::OneMinusSrc1Alpha));
2912 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::BlendOp::Max) == int(QRhiGraphicsPipeline::Max));
2913 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::A) == int(QRhiGraphicsPipeline::A));
2914 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::CullBack) == int(QRhiGraphicsPipeline::Back));
2915 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::Line) == int(QRhiGraphicsPipeline::Line));
2916 dst->srcColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcColor);
2917 dst->dstColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstColor);
2918
2919 // For compatibility with any existing code, separateBlendFactors defaults
2920 // to _false_ which means that materials that do not touch srcAlpha and
2921 // dstAlpha will continue to use srcColor and dstColor as the alpha
2922 // blending factors. New code that needs different values for color/alpha,
2923 // can explicitly set separateBlendFactors to true and then set srcAlpha
2924 // and dstAlpha as well.
2925 dst->separateBlendFactors = false;
2926
2927 dst->srcAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcAlpha);
2928 dst->dstAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstAlpha);
2929
2930 dst->opColor = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opColor);
2931 dst->opAlpha = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opAlpha);
2932
2933 dst->colorWrite = QSGMaterialShader::GraphicsPipelineState::ColorMask(int(src->colorWrite));
2934
2935 dst->cullMode = QSGMaterialShader::GraphicsPipelineState::CullMode(src->cullMode);
2936 dst->polygonMode = QSGMaterialShader::GraphicsPipelineState::PolygonMode(src->polygonMode);
2937}
2938
2940 QSGMaterialShader::GraphicsPipelineState *src)
2941{
2942 dst->blending = src->blendEnable;
2943 dst->srcColor = QRhiGraphicsPipeline::BlendFactor(src->srcColor);
2944 dst->dstColor = QRhiGraphicsPipeline::BlendFactor(src->dstColor);
2945 if (src->separateBlendFactors) {
2946 dst->srcAlpha = QRhiGraphicsPipeline::BlendFactor(src->srcAlpha);
2947 dst->dstAlpha = QRhiGraphicsPipeline::BlendFactor(src->dstAlpha);
2948 } else {
2949 dst->srcAlpha = dst->srcColor;
2950 dst->dstAlpha = dst->dstColor;
2951 }
2952 dst->opColor = QRhiGraphicsPipeline::BlendOp(src->opColor);
2953 dst->opAlpha = QRhiGraphicsPipeline::BlendOp(src->opAlpha);
2954 dst->colorWrite = QRhiGraphicsPipeline::ColorMask(int(src->colorWrite));
2955 dst->cullMode = QRhiGraphicsPipeline::CullMode(src->cullMode);
2956 dst->polygonMode = QRhiGraphicsPipeline::PolygonMode(src->polygonMode);
2957}
2958
2962 const Batch *batch,
2963 Element *e,
2964 int ubufOffset,
2965 int ubufRegionSize,
2966 char *directUpdatePtr)
2967{
2969
2973
2974 if (pd->ubufBinding >= 0) {
2977 m_current_uniform_data = nullptr;
2978
2979 if (changed || !batch->ubufDataValid) {
2980 if (directUpdatePtr)
2982 else
2984 }
2985
2987 pd->ubufStages,
2988 batch->ubuf,
2989 ubufOffset,
2991 }
2992
2995 if (!stages)
2996 continue;
2997
3000
3003
3006
3007 if (nextTex.contains(nullptr)) {
3008 qWarning("No QSGTexture provided from updateSampledImage(). This is wrong.");
3009 continue;
3010 }
3011
3012 bool hasDirtySamplerOptions = false;
3013 bool isAnisotropic = false;
3014 for (QSGTexture *t : nextTex) {
3019 }
3020
3021 // prevTex may be invalid at this point, avoid dereferencing it
3023
3024 // The QSGTexture, and so the sampler parameters, may have changed.
3025 // The rhiTexture is not relevant here.
3026 pd->textureBindingTable[binding] = nextTex; // does not own
3028
3029 if (isAnisotropic) // ###
3030 qWarning("QSGTexture anisotropy levels are not currently supported");
3031
3033
3034 for (QSGTexture *t : nextTex) {
3036
3038
3039 if (!sampler) {
3041 if (!sampler->create()) {
3042 qWarning("Failed to build sampler");
3043 delete sampler;
3044 continue;
3045 }
3047 }
3049 }
3050
3051 pd->samplerBindingTable[binding] = samplers; // does not own
3052 }
3053
3055
3057
3058 for (int i = 0; i < pd->textureBindingTable[binding].size(); ++i) {
3059
3061
3062 // texture may be null if the update above failed for any reason,
3063 // or if the QSGTexture chose to return null intentionally. This is
3064 // valid and we still need to provide something to the shader.
3065 if (!texture)
3067
3069
3072 }
3073
3074 if (!textureSamplers.isEmpty())
3077 }
3078 }
3079
3080#ifndef QT_NO_DEBUG
3081 if (bindings.isEmpty())
3082 qWarning("No shader resources for material %p, this is odd.", material);
3083#endif
3084
3085 enum class SrbAction {
3086 Unknown,
3087 DoNothing,
3089 Rebake
3091
3092 // First, if the Element has no srb created at all, then try to find an existing,
3093 // currently unused srb that is layout-compatible with our binding list.
3094 if (!e->srb) {
3095 // reuse a QVector as our work area, thus possibly reusing the underlying allocation too
3097 layoutDesc.clear();
3100 if (e->srb) {
3101 // Here we know layout compatibility is satisfied, but do not spend time on full
3102 // comparison. The chance of getting an srb that refers to the same resources
3103 // (buffer, textures) is low in practice. So reuse, but write new resources.
3105 }
3106 }
3107
3108 // If the Element had an existing srb, investigate:
3109 // - It may be used as-is (when nothing changed in the scene regarding this node compared to the previous frame).
3110 // - Otherwise it may be able to go with a lightweight update (replace resources, binding list layout is the same).
3111 // - If all else fails rebake the full thing, meaning we reuse the memory allocation but will recreate everything underneath.
3112 if (srbAction == SrbAction::Unknown && e->srb) {
3116 [](const auto &a, const auto &b) { return a.isLayoutCompatible(b); }))
3117 {
3119 } else {
3121 }
3122 }
3123
3124 // If the Element had no srb associated at all and could not find a layout-compatible
3125 // one from the pool, then create a whole new object.
3126 if (!e->srb) {
3129 }
3130
3132
3133 switch (srbAction) {
3134 case SrbAction::DoNothing:
3135 break;
3137 {
3140 // Due to the way the binding list is built up above, if we have a uniform buffer
3141 // at binding point 0 (or none at all) then the sampledTexture bindings are added
3142 // with increasing binding points afterwards, so the list is already sorted based
3143 // on the binding points, thus we can save some time by telling the QRhi backend
3144 // not to sort again.
3145 if (pd->ubufBinding <= 0 || bindings.size() <= 1)
3147
3149 }
3150 break;
3151 case SrbAction::Rebake:
3153 if (!e->srb->create())
3154 qWarning("Failed to build srb");
3155 break;
3156 default:
3157 Q_ASSERT_X(false, "updateMaterialDynamicData", "No srb action set, this cannot happen");
3158 }
3159}
3160
3164 Batch *batch,
3165 bool *gstateChanged)
3166{
3168 *gstateChanged = false;
3170 // generate the public mini-state from m_gstate, invoke the material,
3171 // write the changes, if any, back to m_gstate, together with a way to
3172 // roll those back.
3176 if (changed) {
3181 {
3183 }
3184 *gstateChanged = true;
3185 }
3186 }
3187}
3188
3190{
3191 if (batch->vertexCount == 0 || batch->indexCount == 0)
3192 return false;
3193
3194 Element *e = batch->first;
3195 Q_ASSERT(e);
3196
3197#ifndef QT_NO_DEBUG_OUTPUT
3198 if (Q_UNLIKELY(debug_render())) {
3199 QDebug debug = qDebug();
3200 debug << " -"
3201 << batch
3202 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3203 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3204 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3205 << "[ merged]"
3206 << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
3207 << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
3208 << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
3209 << " root:" << batch->root;
3210 if (batch->drawSets.size() > 1)
3211 debug << "sets:" << batch->drawSets.size();
3212 if (!batch->isOpaque)
3213 debug << "opacity:" << e->node->inheritedOpacity();
3214 batch->uploadedThisFrame = false;
3215 }
3216#endif
3217
3219
3220 // We always have dirty matrix as all batches are at a unique z range.
3222 if (batch->root)
3224 else
3227
3228 const int viewCount = projectionMatrixCount();
3230 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3232
3236
3240
3241 const QSGGeometry *g = gn->geometry();
3245 if (!sms)
3246 return false;
3247
3249 if (m_currentShader != sms)
3251
3256 }
3257
3260 if (pd->ubufBinding >= 0) {
3261 bool ubufRebuild = false;
3262 if (!batch->ubuf) {
3264 ubufRebuild = true;
3265 } else {
3266 if (batch->ubuf->size() < ubufSize) {
3268 ubufRebuild = true;
3269 }
3270 }
3271 if (ubufRebuild) {
3272 batch->ubufDataValid = false;
3273 if (!batch->ubuf->create()) {
3274 qWarning("Failed to build uniform buffer of size %u bytes", ubufSize);
3275 delete batch->ubuf;
3276 batch->ubuf = nullptr;
3277 return false;
3278 }
3279 }
3280 }
3281
3283
3284 bool pendingGStatePop = false;
3286
3287 char *directUpdatePtr = nullptr;
3288 if (batch->ubuf->nativeBuffer().slotCount == 0)
3290
3292
3293 if (directUpdatePtr)
3295
3298
3299 const bool hasPipeline = ensurePipelineState(e, sms);
3300
3301 if (pendingGStatePop)
3303
3304 if (!hasPipeline)
3305 return false;
3306
3310 ensurePipelineState(e, sms, true);
3312 }
3313
3314 batch->ubufDataValid = true;
3315
3317
3319 renderBatch->sms = sms;
3320
3321 return true;
3322}
3323
3325{
3328 {
3329 if (g->lineWidth() != 1.0f) {
3330 static bool checkedWideLineSupport = false;
3334 qWarning("Line widths other than 1 are not supported by the graphics API");
3335 }
3336 }
3337 } else if (g->drawingMode() == QSGGeometry::DrawPoints) {
3338 if (g->lineWidth() != 1.0f) {
3339 static bool warnedPointSize = false;
3340 if (!warnedPointSize) {
3341 warnedPointSize = true;
3342 qWarning("Point size is not controllable by QSGGeometry. "
3343 "Set gl_PointSize from the vertex shader instead.");
3344 }
3345 }
3346 }
3347}
3348
3350{
3351 const Batch *batch = renderBatch->batch;
3352 if (!batch->vbo.buf || !batch->ibo.buf)
3353 return;
3354
3355 Element *e = batch->first;
3357 QSGGeometry *g = gn->geometry();
3359
3362
3365
3366 for (int i = 0, ie = batch->drawSets.size(); i != ie; ++i) {
3367 const DrawSet &draw = batch->drawSets.at(i);
3371 };
3376 }
3377}
3378
3380{
3381 if (batch->vertexCount == 0)
3382 return false;
3383
3384 Element *e = batch->first;
3385 Q_ASSERT(e);
3386
3387 if (Q_UNLIKELY(debug_render())) {
3388 qDebug() << " -"
3389 << batch
3390 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3391 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3392 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3393 << "[unmerged]"
3394 << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
3395 << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
3396 << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
3397 << " root:" << batch->root;
3398
3399 batch->uploadedThisFrame = false;
3400 }
3401
3402 const int viewCount = projectionMatrixCount();
3404 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3406
3410
3414
3415 // We always have dirty matrix as all batches are at a unique z range.
3417
3418 // The vertex attributes are assumed to be the same for all elements in the
3419 // unmerged batch since the material (and so the shaders) is the same.
3420 QSGGeometry *g = gn->geometry();
3423 if (!sms)
3424 return false;
3425
3427 if (m_currentShader != sms)
3429
3434 }
3435
3437
3440 if (pd->ubufBinding >= 0) {
3442 while (e) {
3444 e = e->nextInBatch;
3445 }
3446 bool ubufRebuild = false;
3447 if (!batch->ubuf) {
3449 ubufRebuild = true;
3450 } else {
3451 if (batch->ubuf->size() < totalUBufSize) {
3453 ubufRebuild = true;
3454 }
3455 }
3456 if (ubufRebuild) {
3457 batch->ubufDataValid = false;
3458 if (!batch->ubuf->create()) {
3459 qWarning("Failed to build uniform buffer of size %u bytes", totalUBufSize);
3460 delete batch->ubuf;
3461 batch->ubuf = nullptr;
3462 return false;
3463 }
3464 }
3465 }
3466
3468 bool pendingGStatePop = false;
3471
3472 int ubufOffset = 0;
3473 QRhiGraphicsPipeline *ps = nullptr;
3475 e = batch->first;
3476
3477 char *directUpdatePtr = nullptr;
3478 if (batch->ubuf->nativeBuffer().slotCount == 0)
3480
3481 while (e) {
3482 gn = e->node;
3483
3486
3487 const int viewCount = projectionMatrixCount();
3489 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3491
3495
3496 if (useDepthBuffer()) {
3497 // this cannot be multiview
3500 }
3501
3504
3506
3508 const float prevLineWidth = m_gstate.lineWidth;
3511
3512 // Do not bother even looking up the ps if the topology has not changed
3513 // since everything else is the same for all elements in the batch.
3514 // (except if the material modified blend state)
3516 if (!ensurePipelineState(e, sms)) {
3517 if (pendingGStatePop)
3519 return false;
3520 }
3521 ps = e->ps;
3525 ensurePipelineState(e, sms, true);
3528 }
3529 } else {
3530 e->ps = ps;
3533 }
3534
3535 // We don't need to bother with asking each node for its material as they
3536 // are all identical (compare==0) since they are in the same batch.
3538
3539 // We only need to push this on the very first iteration...
3541
3542 e = e->nextInBatch;
3543 }
3544
3545 if (directUpdatePtr)
3547
3548 if (pendingGStatePop)
3550
3551 batch->ubufDataValid = true;
3552
3554 renderBatch->sms = sms;
3555
3556 return true;
3557}
3558
3560{
3561 const Batch *batch = renderBatch->batch;
3562 if (!batch->vbo.buf)
3563 return;
3564
3565 Element *e = batch->first;
3566
3569
3570 quint32 vOffset = 0;
3571 quint32 iOffset = 0;
3573
3574 while (e) {
3575 QSGGeometry *g = e->node->geometry();
3577 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
3578
3580
3582 if (g->indexCount()) {
3583 if (batch->ibo.buf) {
3585 batch->ibo.buf, iOffset,
3589 }
3590 } else {
3592 cb->draw(g->vertexCount());
3593 }
3594
3595 vOffset += g->sizeOfVertex() * g->vertexCount();
3597
3598 e = e->nextInBatch;
3599 }
3600}
3601
3603{
3604 if (!m_pstate.viewportSet) {
3605 m_pstate.viewportSet = true;
3607 }
3609 m_pstate.scissorSet = true;
3611 } else {
3612 // Regardless of the ps not using scissor, the scissor may need to be
3613 // reset, depending on the backend. So set the viewport again, which in
3614 // turn also sets the scissor on backends where a scissor rect is
3615 // always-on (Vulkan).
3616 if (m_pstate.scissorSet) {
3617 m_pstate.scissorSet = false;
3619 }
3620 }
3621}
3622
3623
3625{
3627
3629
3633 }
3636
3638}
3639
3641{
3642 if (e->isRenderNode) {
3643 delete static_cast<RenderNodeElement *>(e);
3644 } else {
3645 if (e->srb) {
3646 if (!inDestructor) {
3649 else
3650 delete e->srb;
3651 } else {
3652 delete e->srb;
3653 }
3654 e->srb = nullptr;
3655 }
3657 }
3658}
3659
3661{
3662 if (!m_elementsToDelete.size())
3663 return;
3664
3665 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3667 if (*e && (*e)->removed)
3668 *e = nullptr;
3669 }
3670 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3672 if (*e && (*e)->removed)
3673 *e = nullptr;
3674 }
3675
3676 for (int i=0; i<m_elementsToDelete.size(); ++i)
3678
3680}
3681
3683{
3684 // Gracefully handle the lack of a render target - some autotests may rely
3685 // on this in odd cases.
3686 if (!renderTarget().rt)
3687 return;
3688
3693}
3694
3695// An alternative to render() is to call prepareInline() and renderInline() at
3696// the appropriate times (i.e. outside of a QRhi::beginPass() and then inside,
3697// respectively) These allow rendering within a render pass that is started by
3698// another component. In contrast, render() records a full render pass on its
3699// own.
3700
3705
3710
3712{
3713 if (ctx->valid)
3714 qWarning("prepareRenderPass() called with an already prepared render pass context");
3715
3716 ctx->valid = true;
3717
3718 if (Q_UNLIKELY(debug_dump())) {
3719 qDebug("\n");
3721 }
3722
3723 ctx->timeRenderLists = 0;
3725 ctx->timePrepareAlpha = 0;
3726 ctx->timeSorting = 0;
3727 ctx->timeUploadOpaque = 0;
3728 ctx->timeUploadAlpha = 0;
3729
3730 if (Q_UNLIKELY(debug_render() || debug_build())) {
3731 QByteArray type("rebuild:");
3732 if (m_rebuild == 0)
3733 type += " none";
3734 if (m_rebuild == FullRebuild)
3735 type += " full";
3736 else {
3738 type += " renderlists";
3740 type += " partial";
3741 else if (m_rebuild & BuildBatches)
3742 type += " batches";
3743 }
3744
3745 qDebug() << "Renderer::render()" << this << type;
3746 ctx->timer.start();
3747 }
3748
3750
3752 bool complete = (m_rebuild & BuildRenderLists) != 0;
3753 if (complete)
3755 else
3758
3759 if (Q_UNLIKELY(debug_build())) {
3760 qDebug("Opaque render lists %s:", (complete ? "(complete)" : "(partial)"));
3761 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3763 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3764 }
3765 qDebug("Alpha render list %s:", complete ? "(complete)" : "(partial)");
3766 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3768 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3769 }
3770 }
3771 }
3773
3774 for (int i=0; i<m_opaqueBatches.size(); ++i)
3776 for (int i=0; i<m_alphaBatches.size(); ++i)
3779
3782
3783 if (m_rebuild & BuildBatches) {
3788
3789 if (Q_UNLIKELY(debug_build())) {
3790 qDebug("Opaque Batches:");
3791 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3793 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3794 for (Element *e = b->first; e; e = e->nextInBatch) {
3795 qDebug() << " - element:" << e << " node:" << e->node << e->order;
3796 }
3797 }
3798 qDebug("Alpha Batches:");
3799 for (int i=0; i<m_alphaBatches.size(); ++i) {
3801 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3802 for (Element *e = b->first; e; e = e->nextInBatch) {
3803 qDebug() << " - element:" << e << e->bounds << " node:" << e->node << " order:" << e->order;
3804 }
3805 }
3806 }
3807 } else {
3809 }
3810
3811
3813
3814 if (m_rebuild != 0) {
3815 // Then sort opaque batches so that we're drawing the batches with the highest
3816 // order first, maximizing the benefit of front-to-back z-ordering.
3817 if (m_opaqueBatches.size())
3819
3820 // Sort alpha batches back to front so that they render correctly.
3821 if (m_alphaBatches.size())
3823
3825 ? 1.0 / (m_nextRenderOrder)
3826 : 0;
3827 }
3828
3830
3831 // Set size to 0, nothing is deallocated, they will "grow" again
3832 // as part of uploadBatch.
3835
3836 if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Opaque Batches:");
3837 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3839 uploadBatch(b);
3840 }
3842
3843 if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Alpha Batches:");
3844 for (int i=0; i<m_alphaBatches.size(); ++i) {
3846 uploadBatch(b);
3847 }
3849
3850 if (Q_UNLIKELY(debug_render())) {
3851 qDebug().nospace() << "Rendering:" << Qt::endl
3852 << " -> Opaque: " << qsg_countNodesInBatches(m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << Qt::endl
3853 << " -> Alpha: " << qsg_countNodesInBatches(m_alphaBatches) << " nodes in " << m_alphaBatches.size() << " batches...";
3854 }
3855
3857 m_currentMaterial = nullptr;
3858 m_currentShader = nullptr;
3859 m_currentProgram = nullptr;
3861
3862 const QRect viewport = viewportRect();
3863
3864 bool renderOpaque = !debug_noopaque();
3865 bool renderAlpha = !debug_noalpha();
3866
3872 m_pstate.viewportSet = false;
3873 m_pstate.scissorSet = false;
3874
3878 m_gstate.blending = false;
3879
3886 m_gstate.usesScissor = false;
3887 m_gstate.stencilTest = false;
3888
3891
3893 if (Q_LIKELY(renderOpaque)) {
3894 for (int i = 0, ie = m_opaqueBatches.size(); i != ie; ++i) {
3897 bool ok;
3898 if (b->merged)
3900 else
3902 if (ok)
3904 }
3905 }
3906
3907 m_gstate.blending = true;
3908 // factors never change, always set for premultiplied alpha based blending
3909
3910 // depth test stays enabled (if useDepthBuffer(), that is) but no need
3911 // to write out depth from the transparent (back-to-front) pass
3912 m_gstate.depthWrite = false;
3913
3914 // special case: the 3D plane mode tests against the depth buffer, but does
3915 // not write (and all batches are alpha because this render mode evaluates
3916 // to useDepthBuffer()==false)
3919 m_gstate.depthTest = true;
3920 }
3921
3923 if (Q_LIKELY(renderAlpha)) {
3924 for (int i = 0, ie = m_alphaBatches.size(); i != ie; ++i) {
3927 bool ok;
3928 if (b->merged)
3930 else if (b->isRenderNode)
3932 else
3934 if (ok)
3936 }
3937 }
3938
3939 m_rebuild = 0;
3940
3941#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
3944#endif
3945
3948
3950 m_resourceUpdates = nullptr;
3951}
3952
3954{
3957 // we cannot tell if the application will have
3958 // native rendering thrown in to this pass
3959 // (QQuickWindow::beginExternalCommands()), so
3960 // we have no choice but to set the flag always
3961 // (thus triggering using secondary command
3962 // buffers with Vulkan)
3964 // We do not use GPU compute at all at the moment, this means we can
3965 // get a small performance gain with OpenGL by declaring this.
3967
3970}
3971
3973{
3974 // prepareRenderPass and recordRenderPass must always be called together.
3975 // They are separate because beginRenderPass and endRenderPass are optional.
3976 //
3977 // The valid call sequence are therefore:
3978 // prepare, begin, record, end
3979 // or
3980 // prepare, record
3981
3982 if (!ctx->valid)
3983 qWarning("recordRenderPass() called without a prepared render pass context");
3984
3985 ctx->valid = false;
3986
3988 cb->debugMarkBegin(QByteArrayLiteral("Qt Quick scene render"));
3989
3990 for (int i = 0, ie = ctx->opaqueRenderBatches.size(); i != ie; ++i) {
3991 if (i == 0)
3992 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick opaque batches"));
3994 if (renderBatch->batch->merged)
3996 else
3998 }
3999
4000 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
4001 if (i == 0) {
4003 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D batches"));
4004 else
4005 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick alpha batches"));
4006 }
4008 if (renderBatch->batch->merged)
4010 else if (renderBatch->batch->isRenderNode)
4012 else
4014 }
4015
4017 // Depth post-pass to fill up the depth buffer in a way that it
4018 // corresponds to what got rendered to the color buffer in the previous
4019 // (alpha) pass. The previous pass cannot enable depth write due to Z
4020 // fighting. Rather, do it separately in a dedicated color-write-off,
4021 // depth-write-on pass. This enables the 3D content drawn afterwards to
4022 // depth test against the 2D items' rendering.
4023 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
4024 if (i == 0)
4025 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D depth post-pass"));
4027 if (renderBatch->batch->merged)
4029 else if (!renderBatch->batch->isRenderNode) // rendernodes are skipped here for now
4031 }
4032 }
4033
4034 if (m_currentShader)
4035 setActiveRhiShader(nullptr, nullptr);
4036
4037 cb->debugMarkEnd();
4038
4039 if (Q_UNLIKELY(debug_render())) {
4040 qDebug(" -> times: build: %d, prepare(opaque/alpha): %d/%d, sorting: %d, upload(opaque/alpha): %d/%d, record rendering: %d",
4041 (int) ctx->timeRenderLists,
4043 (int) ctx->timeSorting,
4044 (int) ctx->timeUploadOpaque, (int) ctx->timeUploadAlpha,
4045 (int) ctx->timer.elapsed());
4046 }
4047}
4048
4059
4061{
4062 const QMatrix4x4 *projectionMatrix() const override { return m_projectionMatrix; }
4063 QRect scissorRect() const override { return m_scissorRect; }
4065 int stencilValue() const override { return m_stencilValue; }
4067 const QRegion *clipRegion() const override { return nullptr; }
4068
4074};
4075
4077{
4078 if (Q_UNLIKELY(debug_render()))
4079 qDebug() << " -" << batch << "rendernode";
4080
4081 const int viewCount = projectionMatrixCount();
4083 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
4088
4090 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4091
4092 setActiveRhiShader(nullptr, nullptr);
4093
4095 rd->m_clip_list = nullptr;
4098 while (clip != rootNode()) {
4099 if (clip->type() == QSGNode::ClipNodeType) {
4100 rd->m_clip_list = static_cast<QSGClipNode *>(clip);
4101 break;
4102 }
4103 clip = clip->parent();
4104 }
4106 }
4107
4110 QSGNode *root = rootNode();
4111 if (e->root) {
4113 root = e->root->sgNode;
4114 }
4115 while (xform != root) {
4116 if (xform->type() == QSGNode::TransformNodeType) {
4117 matrix = matrix * static_cast<QSGTransformNode *>(xform)->combinedMatrix();
4118 break;
4119 }
4120 xform = xform->parent();
4121 }
4124
4126 rd->m_opacity = 1.0;
4127 while (opacity != rootNode()) {
4128 if (opacity->type() == QSGNode::OpacityNodeType) {
4129 rd->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity();
4130 break;
4131 }
4132 opacity = opacity->parent();
4133 }
4134
4135 rd->m_rt = renderTarget();
4136
4138 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
4140
4141 if (useDepthBuffer()) {
4142 // this cannot be multiview
4143 rd->m_projectionMatrix[0](2, 2) = m_zRange;
4145 }
4146
4147 e->renderNode->prepare();
4148
4150 renderBatch->sms = nullptr;
4151
4152 return true;
4153}
4154
4156{
4159
4160 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4162
4164 // Expose only the first matrix through the state object, the rest are
4165 // queriable through the QSGRenderNode getters anyway.
4167 const std::array<int, 4> scissor = batch->clipState.scissor.scissor();
4172
4174
4178 if (needsExternal)
4179 cb->beginExternal();
4181 if (needsExternal)
4182 cb->endExternal();
4183
4184 rd->m_matrix = nullptr;
4185 rd->m_clip_list = nullptr;
4186
4189 {
4190 // Reset both flags if either is reported as changed, since with the rhi
4191 // it could be setViewport() that will record the resetting of the scissor.
4192 m_pstate.viewportSet = false;
4193 m_pstate.scissorSet = false;
4194 }
4195
4196 // Do not bother with RenderTargetState. Where applicable, endExternal()
4197 // ensures the correct target is rebound. For others (like Vulkan) it makes
4198 // no sense since render() could not possibly do that on our command buffer
4199 // which is in renderpass recording state.
4200}
4201
4203{
4204 if (mode.isEmpty())
4206 else if (mode == "clip")
4208 else if (mode == "overdraw")
4210 else if (mode == "batches")
4212 else if (mode == "changes")
4214}
4215
4220
4221bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
4222{
4223 return a.depthTest == b.depthTest
4224 && a.depthWrite == b.depthWrite
4225 && a.depthFunc == b.depthFunc
4226 && a.blending == b.blending
4227 && a.srcColor == b.srcColor
4228 && a.dstColor == b.dstColor
4229 && a.srcAlpha == b.srcAlpha
4230 && a.dstAlpha == b.dstAlpha
4231 && a.opColor == b.opColor
4232 && a.opAlpha == b.opAlpha
4233 && a.colorWrite == b.colorWrite
4234 && a.cullMode == b.cullMode
4238 && a.drawMode == b.drawMode
4239 && a.lineWidth == b.lineWidth
4240 && a.polygonMode == b.polygonMode
4242}
4243
4244bool operator!=(const GraphicsState &a, const GraphicsState &b) noexcept
4245{
4246 return !(a == b);
4247}
4248
4249size_t qHash(const GraphicsState &s, size_t seed) noexcept
4250{
4251 // do not bother with all fields
4252 return seed
4253 + s.depthTest * 1000
4254 + s.depthWrite * 100
4255 + s.depthFunc
4256 + s.blending * 10
4257 + s.srcColor
4258 + s.cullMode
4259 + s.usesScissor
4260 + s.stencilTest
4261 + s.sampleCount
4262 + s.multiViewCount;
4263}
4264
4266{
4267 return a.state == b.state
4268 && a.sms->materialShader == b.sms->materialShader
4269 && a.renderTargetDescription == b.renderTargetDescription
4270 && a.srbLayoutDescription == b.srbLayoutDescription;
4271}
4272
4274{
4275 return !(a == b);
4276}
4277
4278size_t qHash(const GraphicsPipelineStateKey &k, size_t seed) noexcept
4279{
4280 return qHash(k.state, seed)
4281 ^ qHash(k.sms->materialShader)
4282 ^ k.extra.renderTargetDescriptionHash
4283 ^ k.extra.srbLayoutDescriptionHash;
4284}
4285
4286bool operator==(const ShaderKey &a, const ShaderKey &b) noexcept
4287{
4288 return a.type == b.type
4289 && a.renderMode == b.renderMode
4291}
4292
4293bool operator!=(const ShaderKey &a, const ShaderKey &b) noexcept
4294{
4295 return !(a == b);
4296}
4297
4298size_t qHash(const ShaderKey &k, size_t seed) noexcept
4299{
4300 return qHash(k.type, seed) ^ int(k.renderMode) ^ k.multiViewCount;
4301}
4302
4303Visualizer::Visualizer(Renderer *renderer)
4304 : m_renderer(renderer),
4306{
4307}
4308
4310{
4311}
4312
4313#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded
4314 | QSGNode::DirtyOpacity
4315 | QSGNode::DirtyMatrix
4316 | QSGNode::DirtyNodeRemoved)
4317
4318void Visualizer::visualizeChangesPrepare(Node *n, uint parentChanges)
4319{
4320 uint childDirty = (parentChanges | n->dirtyState) & QSGNODE_DIRTY_PARENT;
4321 uint selfDirty = n->dirtyState | parentChanges;
4322 if (n->type() == QSGNode::GeometryNodeType && selfDirty != 0)
4323 m_visualizeChangeSet.insert(n, selfDirty);
4325 visualizeChangesPrepare(child, childDirty);
4326 }
4327}
4328
4329} // namespace QSGBatchRenderer
4330
4331QT_END_NAMESPACE
4332
4333#include "moc_qsgbatchrenderer_p.cpp"
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:322
void updateRootTransforms(Node *n, Node *root, const QMatrix4x4 &combined)
void updateStates(QSGNode *n) override
void updateRootTransforms(Node *n)
const int ZORDER_BUFFER_BINDING
static void qsg_wipeBuffer(Buffer *buffer)
static void rendererToMaterialGraphicsState(QSGMaterialShader::GraphicsPipelineState *dst, GraphicsState *src)
size_t qHash(const GraphicsState &s, size_t seed) noexcept
static QRhiVertexInputLayout calculateVertexInputLayout(const QSGMaterialShader *s, const QSGGeometry *geometry, bool batchable)
static void qsg_addBackOrphanedElements(QDataBuffer< Element * > &orphans, QDataBuffer< Element * > &renderList)
bool operator!=(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) noexcept
const float VIEWPORT_MIN_DEPTH
QSGMaterial::Flag QSGMaterial_FullMatrix
bool qsg_sort_batch_decreasing_order(Batch *a, Batch *b)
bool operator==(const ShaderKey &a, const ShaderKey &b) noexcept
bool operator==(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) noexcept
bool operator!=(const ShaderKey &a, const ShaderKey &b) noexcept
static void qsg_wipeBatch(Batch *batch)
static QRhiSampler * newSampler(QRhi *rhi, const QSGSamplerDescription &desc)
QRhiVertexInputAttribute::Format qsg_vertexInputFormat(const QSGGeometry::Attribute &a)
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)
static int size_of_type(int type)
static bool isTranslate(const QMatrix4x4 &m)
static int qsg_countNodesInBatches(const QDataBuffer< Batch * > &batches)
static float calculateElementZOrder(const Element *e, qreal zRange)
static int qsg_fixIndexCount(int iCount, int drawMode)
bool qsg_sort_element_increasing_order(Element *a, Element *b)
static void materialToRendererGraphicsState(GraphicsState *dst, QSGMaterialShader::GraphicsPipelineState *src)
static bool needsBlendConstant(QRhiGraphicsPipeline::BlendFactor f)
const int VERTEX_BUFFER_BINDING
const uint DYNAMIC_VERTEX_INDEX_BUFFER_THRESHOLD
static int qsg_countNodesInBatch(const Batch *batch)
bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
QRhiCommandBuffer::IndexFormat qsg_indexFormat(const QSGGeometry *geometry)
bool operator!=(const GraphicsState &a, const GraphicsState &b) noexcept
bool qsg_sort_element_decreasing_order(Element *a, Element *b)
size_t qHash(const ShaderKey &k, size_t seed) noexcept
void qsg_dumpShadowRoots(BatchRootInfo *i, int indent)
void qsg_dumpShadowRoots(Node *n)
static bool isScale(const QMatrix4x4 &m)
static bool is2DSafe(const QMatrix4x4 &m)
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:2568
#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
void operator|=(const Pt &pt)
void map(const QMatrix4x4 &m)
const QMatrix4x4 * projectionMatrix() const override
const QRegion * clipRegion() const override