Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsgbatchrenderer.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
3// Copyright (C) 2016 Robin Burchell <robin.burchell@viroteck.net>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5// Qt-Security score:significant reason:default
6
8
9#include <qmath.h>
10
11#include <QtCore/QElapsedTimer>
12#include <QtCore/QtNumeric>
13
14#include <QtGui/QGuiApplication>
15
16#include <private/qnumeric_p.h>
18
20
21#include <QtQuick/private/qsgnode_p.h>
22
23#include <algorithm>
24
26
27int qt_sg_envInt(const char *name, int defaultValue);
28
30{
31
32#define DECLARE_DEBUG_VAR(variable)
33 static bool debug_ ## variable()
34 { static bool value = qgetenv("QSG_RENDERER_DEBUG").contains(QT_STRINGIFY(variable)); return value; }
42DECLARE_DEBUG_VAR(noalpha)
43DECLARE_DEBUG_VAR(noopaque)
45#undef DECLARE_DEBUG_VAR
46
47#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling())
48#define SHADOWNODE_TRAVERSE(NODE) for (Node *child = NODE->firstChild(); child; child = child->sibling())
49
50static inline int size_of_type(int type)
51{
52 static int sizes[] = {
53 sizeof(char),
54 sizeof(unsigned char),
55 sizeof(short),
56 sizeof(unsigned short),
57 sizeof(int),
58 sizeof(unsigned int),
59 sizeof(float),
60 2,
61 3,
62 4,
63 sizeof(double)
64 };
65 Q_ASSERT(type >= QSGGeometry::ByteType && type <= QSGGeometry::DoubleType);
66 return sizes[type - QSGGeometry::ByteType];
67}
68
73
75
76static bool isTranslate(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Translation; }
77static bool isScale(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Scale; }
78static bool is2DSafe(const QMatrix4x4 &m) { return m.flags() < QMatrix4x4::Rotation; }
79
80const float OPAQUE_LIMIT = 0.999f;
81
85
86const float VIEWPORT_MIN_DEPTH = 0.0f;
87const float VIEWPORT_MAX_DEPTH = 1.0f;
88
89const quint32 DEFAULT_BUFFER_POOL_SIZE_LIMIT = 2 * 1024 * 1024; // 2 MB for m_vboPool and m_iboPool each
90
91template <class Int>
92inline Int aligned(Int v, Int byteAlign)
93{
94 return (v + byteAlign - 1) & ~(byteAlign - 1);
95}
96
97QRhiVertexInputAttribute::Format qsg_vertexInputFormat(const QSGGeometry::Attribute &a)
98{
99 switch (a.type) {
100 case QSGGeometry::FloatType:
101 if (a.tupleSize == 4)
102 return QRhiVertexInputAttribute::Float4;
103 if (a.tupleSize == 3)
104 return QRhiVertexInputAttribute::Float3;
105 if (a.tupleSize == 2)
106 return QRhiVertexInputAttribute::Float2;
107 if (a.tupleSize == 1)
108 return QRhiVertexInputAttribute::Float;
109 break;
110 case QSGGeometry::UnsignedByteType:
111 if (a.tupleSize == 4)
112 return QRhiVertexInputAttribute::UNormByte4;
113 if (a.tupleSize == 2)
114 return QRhiVertexInputAttribute::UNormByte2;
115 if (a.tupleSize == 1)
116 return QRhiVertexInputAttribute::UNormByte;
117 break;
118 default:
119 break;
120 }
121 qWarning("Unsupported attribute type 0x%x with %d components", a.type, a.tupleSize);
122 Q_UNREACHABLE_RETURN(QRhiVertexInputAttribute::Float);
123}
124
125static QRhiVertexInputLayout calculateVertexInputLayout(const QSGMaterialShader *s, const QSGGeometry *geometry, bool batchable)
126{
127 Q_ASSERT(geometry);
128 const QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
129 if (!sd->vertexShader) {
130 qWarning("No vertex shader in QSGMaterialShader %p", s);
131 return QRhiVertexInputLayout();
132 }
133
134 const int attrCount = geometry->attributeCount();
135 QVarLengthArray<QRhiVertexInputAttribute, 8> inputAttributes;
136 inputAttributes.reserve(attrCount + 1);
137 quint32 offset = 0;
138 for (int i = 0; i < attrCount; ++i) {
139 const QSGGeometry::Attribute &a = geometry->attributes()[i];
140 if (!sd->vertexShader->vertexInputLocations.contains(a.position)) {
141 qWarning("Vertex input %d is present in material but not in shader. This is wrong.",
142 a.position);
143 }
144 inputAttributes.append(QRhiVertexInputAttribute(VERTEX_BUFFER_BINDING, a.position, qsg_vertexInputFormat(a), offset));
145 offset += a.tupleSize * size_of_type(a.type);
146 }
147 if (batchable) {
148 inputAttributes.append(QRhiVertexInputAttribute(ZORDER_BUFFER_BINDING, sd->vertexShader->qt_order_attrib_location,
149 QRhiVertexInputAttribute::Float, 0));
150 }
151
152 Q_ASSERT(VERTEX_BUFFER_BINDING == 0 && ZORDER_BUFFER_BINDING == 1); // not very flexible
153 QVarLengthArray<QRhiVertexInputBinding, 2> inputBindings;
154 inputBindings.append(QRhiVertexInputBinding(geometry->sizeOfVertex()));
155 if (batchable)
156 inputBindings.append(QRhiVertexInputBinding(sizeof(float)));
157
158 QRhiVertexInputLayout inputLayout;
159 inputLayout.setBindings(inputBindings.cbegin(), inputBindings.cend());
160 inputLayout.setAttributes(inputAttributes.cbegin(), inputAttributes.cend());
161
162 return inputLayout;
163}
164
165QRhiCommandBuffer::IndexFormat qsg_indexFormat(const QSGGeometry *geometry)
166{
167 switch (geometry->indexType()) {
168 case QSGGeometry::UnsignedShortType:
169 return QRhiCommandBuffer::IndexUInt16;
170 break;
171 case QSGGeometry::UnsignedIntType:
172 return QRhiCommandBuffer::IndexUInt32;
173 break;
174 default:
175 Q_UNREACHABLE_RETURN(QRhiCommandBuffer::IndexUInt16);
176 }
177}
178
179QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode, QRhi *rhi)
180{
181 QRhiGraphicsPipeline::Topology topology = QRhiGraphicsPipeline::Triangles;
182 switch (geomDrawMode) {
183 case QSGGeometry::DrawPoints:
184 topology = QRhiGraphicsPipeline::Points;
185 break;
186 case QSGGeometry::DrawLines:
187 topology = QRhiGraphicsPipeline::Lines;
188 break;
189 case QSGGeometry::DrawLineStrip:
190 topology = QRhiGraphicsPipeline::LineStrip;
191 break;
192 case QSGGeometry::DrawTriangles:
193 topology = QRhiGraphicsPipeline::Triangles;
194 break;
195 case QSGGeometry::DrawTriangleStrip:
196 topology = QRhiGraphicsPipeline::TriangleStrip;
197 break;
198 case QSGGeometry::DrawTriangleFan:
199 {
200 static bool triangleFanSupported = false;
201 static bool triangleFanSupportChecked = false;
202 if (!triangleFanSupportChecked) {
203 triangleFanSupportChecked = true;
204 triangleFanSupported = rhi->isFeatureSupported(QRhi::TriangleFanTopology);
205 }
206 if (triangleFanSupported) {
207 topology = QRhiGraphicsPipeline::TriangleFan;
208 break;
209 }
210 Q_FALLTHROUGH();
211 }
212 default:
213 qWarning("Primitive topology 0x%x not supported", geomDrawMode);
214 break;
215 }
216 return topology;
217}
218
219void qsg_setMultiViewFlagsOnMaterial(QSGMaterial *material, int multiViewCount)
220{
221 material->setFlag(QSGMaterial::MultiView2, multiViewCount == 2);
222 material->setFlag(QSGMaterial::MultiView3, multiViewCount == 3);
223 material->setFlag(QSGMaterial::MultiView4, multiViewCount == 4);
224}
225
226ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material,
227 const QSGGeometry *geometry,
228 QSGRendererInterface::RenderMode renderMode,
229 int multiViewCount)
230{
231 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
232
233 QSGMaterialType *type = material->type();
234 ShaderKey key = { type, renderMode, multiViewCount };
235 Shader *shader = rewrittenShaders.value(key, nullptr);
236 if (shader)
237 return shader;
238
239 shader = new Shader;
240 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
241 context->initializeRhiShader(s, QShader::BatchableVertexShader);
242 shader->materialShader = s;
243 shader->inputLayout = calculateVertexInputLayout(s, geometry, true);
244 QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
245 shader->stages = {
246 { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage), QShader::BatchableVertexShader },
247 { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
248 };
249
250 shader->lastOpacity = 0;
251
252 rewrittenShaders[key] = shader;
253 return shader;
254}
255
256ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material,
257 const QSGGeometry *geometry,
258 QSGRendererInterface::RenderMode renderMode,
259 int multiViewCount)
260{
261 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
262
263 QSGMaterialType *type = material->type();
264 ShaderKey key = { type, renderMode, multiViewCount };
265 Shader *shader = stockShaders.value(key, nullptr);
266 if (shader)
267 return shader;
268
269 shader = new Shader;
270 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
271 context->initializeRhiShader(s, QShader::StandardShader);
272 shader->materialShader = s;
273 shader->inputLayout = calculateVertexInputLayout(s, geometry, false);
274 QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
275 shader->stages = {
276 { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage) },
277 { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
278 };
279
280 shader->lastOpacity = 0;
281
282 stockShaders[key] = shader;
283
284 return shader;
285}
286
287void ShaderManager::invalidated()
288{
289 qDeleteAll(stockShaders);
290 stockShaders.clear();
291 qDeleteAll(rewrittenShaders);
292 rewrittenShaders.clear();
293
294 qDeleteAll(pipelineCache);
295 pipelineCache.clear();
296
297 qDeleteAll(srbPool);
298 srbPool.clear();
299}
300
302{
303 for (ShaderManager::Shader *sms : std::as_const(stockShaders)) {
304 QSGMaterialShader *s = sms->materialShader;
305 if (s) {
306 QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
307 sd->clearCachedRendererData();
308 }
309 }
310 for (ShaderManager::Shader *sms : std::as_const(rewrittenShaders)) {
311 QSGMaterialShader *s = sms->materialShader;
312 if (s) {
313 QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
314 sd->clearCachedRendererData();
315 }
316 }
317}
318
320{
321 static int extraIndent = 0;
322 ++extraIndent;
323
324 QByteArray ind(indent + extraIndent + 10, ' ');
325
326 if (!i) {
327 qDebug("%s - no info", ind.constData());
328 } else {
329 qDebug() << ind.constData() << "- parent:" << i->parentRoot << "orders" << i->firstOrder << "->" << i->lastOrder << ", avail:" << i->availableOrders;
330 for (QSet<Node *>::const_iterator it = i->subRoots.constBegin();
331 it != i->subRoots.constEnd(); ++it) {
332 qDebug() << ind.constData() << "-" << *it;
333 qsg_dumpShadowRoots((*it)->rootInfo(), indent);
334 }
335 }
336
337 --extraIndent;
338}
339
341{
342#ifndef QT_NO_DEBUG_OUTPUT
343 static int indent = 0;
344 ++indent;
345
346 QByteArray ind(indent, ' ');
347
348 if (n->type() == QSGNode::ClipNodeType || n->isBatchRoot) {
349 qDebug() << ind.constData() << "[X]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
351 } else {
352 QDebug d = qDebug();
353 d << ind.constData() << "[ ]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
354 if (n->type() == QSGNode::GeometryNodeType)
355 d << "order" << Qt::dec << n->element()->order;
356 }
357
360
361 --indent;
362#else
363 Q_UNUSED(n);
364#endif
365}
366
367Updater::Updater(Renderer *r)
368 : renderer(r)
369 , m_roots(32)
370 , m_rootMatrices(8)
371{
372 m_roots.add(0);
373 m_combined_matrix_stack.add(&m_identityMatrix);
374 m_rootMatrices.add(m_identityMatrix);
375}
376
377void Updater::updateStates(QSGNode *n)
378{
379 m_current_clip = nullptr;
380
381 m_added = 0;
382 m_transformChange = 0;
383 m_opacityChange = 0;
384
385 Node *sn = renderer->m_nodes.value(n, 0);
386 Q_ASSERT(sn);
387
388 if (Q_UNLIKELY(debug_roots()))
390
391 if (Q_UNLIKELY(debug_build())) {
392 qDebug("Updater::updateStates()");
393 if (sn->dirtyState & (QSGNode::DirtyNodeAdded << 16))
394 qDebug(" - nodes have been added");
395 if (sn->dirtyState & (QSGNode::DirtyMatrix << 16))
396 qDebug(" - transforms have changed");
397 if (sn->dirtyState & (QSGNode::DirtyOpacity << 16))
398 qDebug(" - opacity has changed");
399 if (uint(sn->dirtyState) & uint(QSGNode::DirtyForceUpdate << 16))
400 qDebug(" - forceupdate");
401 }
402
403 if (Q_UNLIKELY(renderer->m_visualizer->mode() == Visualizer::VisualizeChanges))
404 renderer->m_visualizer->visualizeChangesPrepare(sn);
405
406 visitNode(sn);
407}
408
410{
411 if (m_added == 0 && n->dirtyState == 0 && m_force_update == 0 && m_transformChange == 0 && m_opacityChange == 0)
412 return;
413
414 int count = m_added;
415 if (n->dirtyState & QSGNode::DirtyNodeAdded)
416 ++m_added;
417
418 int force = m_force_update;
419 if (n->dirtyState & QSGNode::DirtyForceUpdate)
420 ++m_force_update;
421
422 switch (n->type()) {
423 case QSGNode::OpacityNodeType:
425 break;
426 case QSGNode::TransformNodeType:
428 break;
429 case QSGNode::GeometryNodeType:
431 break;
432 case QSGNode::ClipNodeType:
434 break;
435 case QSGNode::RenderNodeType:
436 if (m_added)
437 n->renderNodeElement()->root = m_roots.last();
438 Q_FALLTHROUGH(); // to visit children
439 default:
441 break;
442 }
443
444 m_added = count;
445 m_force_update = force;
446 n->dirtyState = {};
447}
448
450{
452
453 QSGClipNode *cn = static_cast<QSGClipNode *>(n->sgNode);
454
455 if (m_roots.last() && m_added > 0)
456 renderer->registerBatchRoot(n, m_roots.last());
457
458 cn->setRendererClipList(m_current_clip);
459 m_current_clip = cn;
460 m_roots << n;
461 m_rootMatrices.add(m_rootMatrices.last() * *m_combined_matrix_stack.last());
462 extra->matrix = m_rootMatrices.last();
463 cn->setRendererMatrix(&extra->matrix);
464 m_combined_matrix_stack << &m_identityMatrix;
465
467
468 m_current_clip = cn->clipList();
469 m_rootMatrices.pop_back();
470 m_combined_matrix_stack.pop_back();
471 m_roots.pop_back();
472}
473
475{
476 QSGOpacityNode *on = static_cast<QSGOpacityNode *>(n->sgNode);
477
478 qreal combined = m_opacity_stack.last() * on->opacity();
479 on->setCombinedOpacity(combined);
480 m_opacity_stack.add(combined);
481
482 if (m_added == 0 && n->dirtyState & QSGNode::DirtyOpacity) {
483 bool was = n->isOpaque;
484 bool is = on->opacity() > OPAQUE_LIMIT;
485 if (was != is) {
486 renderer->m_rebuild = Renderer::FullRebuild;
487 n->isOpaque = is;
488 }
489 ++m_opacityChange;
491 --m_opacityChange;
492 } else {
493 if (m_added > 0)
494 n->isOpaque = on->opacity() > OPAQUE_LIMIT;
496 }
497
498 m_opacity_stack.pop_back();
499}
500
502{
503 bool popMatrixStack = false;
504 bool popRootStack = false;
505 bool dirty = n->dirtyState & QSGNode::DirtyMatrix;
506
507 QSGTransformNode *tn = static_cast<QSGTransformNode *>(n->sgNode);
508
509 if (n->isBatchRoot) {
510 if (m_added > 0 && m_roots.last())
511 renderer->registerBatchRoot(n, m_roots.last());
512 tn->setCombinedMatrix(m_rootMatrices.last() * *m_combined_matrix_stack.last() * tn->matrix());
513
514 // The only change in this subtree is ourselves and we are a batch root, so
515 // only update subroots and return, saving tons of child-processing (flickable-panning)
516
517 if (!n->becameBatchRoot && m_added == 0 && m_force_update == 0 && m_opacityChange == 0 && dirty && (n->dirtyState & ~QSGNode::DirtyMatrix) == 0) {
518 BatchRootInfo *info = renderer->batchRootInfo(n);
519 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
520 it != info->subRoots.constEnd(); ++it) {
521 updateRootTransforms(*it, n, tn->combinedMatrix());
522 }
523 return;
524 }
525
526 n->becameBatchRoot = false;
527
528 m_combined_matrix_stack.add(&m_identityMatrix);
529 m_roots.add(n);
530 m_rootMatrices.add(tn->combinedMatrix());
531
532 popMatrixStack = true;
533 popRootStack = true;
534 } else if (!tn->matrix().isIdentity()) {
535 tn->setCombinedMatrix(*m_combined_matrix_stack.last() * tn->matrix());
536 m_combined_matrix_stack.add(&tn->combinedMatrix());
537 popMatrixStack = true;
538 } else {
539 tn->setCombinedMatrix(*m_combined_matrix_stack.last());
540 }
541
542 if (dirty)
543 ++m_transformChange;
544
546
547 if (dirty)
548 --m_transformChange;
549 if (popMatrixStack)
550 m_combined_matrix_stack.pop_back();
551 if (popRootStack) {
552 m_roots.pop_back();
553 m_rootMatrices.pop_back();
554 }
555}
556
558{
559 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode);
560
561 gn->setRendererMatrix(m_combined_matrix_stack.last());
562 gn->setRendererClipList(m_current_clip);
563 gn->setInheritedOpacity(m_opacity_stack.last());
564
565 if (m_added) {
566 Element *e = n->element();
567 e->root = m_roots.last();
568 e->translateOnlyToRoot = isTranslate(*gn->matrix());
569
570 if (e->root) {
571 BatchRootInfo *info = renderer->batchRootInfo(e->root);
572 while (info != nullptr) {
573 info->availableOrders--;
574 if (info->availableOrders < 0) {
575 renderer->m_rebuild |= Renderer::BuildRenderLists;
576 } else {
577 renderer->m_rebuild |= Renderer::BuildRenderListsForTaggedRoots;
578 renderer->m_taggedRoots << e->root;
579 }
580 if (info->parentRoot != nullptr)
581 info = renderer->batchRootInfo(info->parentRoot);
582 else
583 info = nullptr;
584 }
585 } else {
586 renderer->m_rebuild |= Renderer::FullRebuild;
587 }
588 } else {
589 if (m_transformChange) {
590 Element *e = n->element();
591 e->translateOnlyToRoot = isTranslate(*gn->matrix());
592 }
593 if (m_opacityChange) {
594 Element *e = n->element();
595 if (e->batch)
596 renderer->invalidateBatchAndOverlappingRenderOrders(e->batch);
597 }
598 }
599
601}
602
603void Updater::updateRootTransforms(Node *node, Node *root, const QMatrix4x4 &combined)
604{
605 BatchRootInfo *info = renderer->batchRootInfo(node);
606 QMatrix4x4 m;
607 Node *n = node;
608
609 while (n != root) {
610 if (n->type() == QSGNode::TransformNodeType)
611 m = static_cast<QSGTransformNode *>(n->sgNode)->matrix() * m;
612 n = n->parent();
613 }
614
615 m = combined * m;
616
617 if (node->type() == QSGNode::ClipNodeType) {
618 static_cast<ClipBatchRootInfo *>(info)->matrix = m;
619 } else {
620 Q_ASSERT(node->type() == QSGNode::TransformNodeType);
621 static_cast<QSGTransformNode *>(node->sgNode)->setCombinedMatrix(m);
622 }
623
624 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
625 it != info->subRoots.constEnd(); ++it) {
626 updateRootTransforms(*it, node, m);
627 }
628}
629
630int qsg_positionAttribute(QSGGeometry *g)
631{
632 int vaOffset = 0;
633 for (int a=0; a<g->attributeCount(); ++a) {
634 const QSGGeometry::Attribute &attr = g->attributes()[a];
635 if (attr.isVertexCoordinate && attr.tupleSize == 2 && attr.type == QSGGeometry::FloatType) {
636 return vaOffset;
637 }
638 vaOffset += attr.tupleSize * size_of_type(attr.type);
639 }
640 return -1;
641}
642
643
644void Rect::map(const QMatrix4x4 &matrix)
645{
646 const float *m = matrix.constData();
647 if (isScale(matrix)) {
648 tl.x = tl.x * m[0] + m[12];
649 tl.y = tl.y * m[5] + m[13];
650 br.x = br.x * m[0] + m[12];
651 br.y = br.y * m[5] + m[13];
652 if (tl.x > br.x)
653 qSwap(tl.x, br.x);
654 if (tl.y > br.y)
655 qSwap(tl.y, br.y);
656 } else {
657 Pt mtl = tl;
658 Pt mtr = { br.x, tl.y };
659 Pt mbl = { tl.x, br.y };
660 Pt mbr = br;
661
662 mtl.map(matrix);
663 mtr.map(matrix);
664 mbl.map(matrix);
665 mbr.map(matrix);
666
667 set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
668 (*this) |= mtl;
669 (*this) |= mtr;
670 (*this) |= mbl;
671 (*this) |= mbr;
672 }
673}
674
676{
677 Q_ASSERT(!boundsComputed);
678 boundsComputed = true;
679
680 QSGGeometry *g = node->geometry();
681 int offset = qsg_positionAttribute(g);
682 if (offset == -1) {
683 // No position attribute means overlaps with everything..
684 bounds.set(-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX);
685 return;
686 }
687
688 bounds.set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
689 char *vd = (char *) g->vertexData() + offset;
690 for (int i=0; i<g->vertexCount(); ++i) {
691 bounds |= *(Pt *) vd;
692 vd += g->sizeOfVertex();
693 }
694 bounds.map(*node->matrix());
695
696 if (!qt_is_finite(bounds.tl.x) || bounds.tl.x == FLT_MAX)
697 bounds.tl.x = -FLT_MAX;
698 if (!qt_is_finite(bounds.tl.y) || bounds.tl.y == FLT_MAX)
699 bounds.tl.y = -FLT_MAX;
700 if (!qt_is_finite(bounds.br.x) || bounds.br.x == -FLT_MAX)
701 bounds.br.x = FLT_MAX;
702 if (!qt_is_finite(bounds.br.y) || bounds.br.y == -FLT_MAX)
703 bounds.br.y = FLT_MAX;
704
705 Q_ASSERT(bounds.tl.x <= bounds.br.x);
706 Q_ASSERT(bounds.tl.y <= bounds.br.y);
707
708 boundsOutsideFloatRange = bounds.isOutsideFloatRange();
709}
710
712{
713 Element *n = first;
714 // Skip to the first node other than e which has not been removed
715 while (n && (n == e || n->removed))
716 n = n->nextInBatch;
717
718 // Only 'e' in this batch, so a material change doesn't change anything as long as
719 // its blending is still in sync with this batch...
720 if (!n)
721 return BatchIsCompatible;
722
723 QSGMaterial *m = e->node->activeMaterial();
724 QSGMaterial *nm = n->node->activeMaterial();
725 return (nm->type() == m->type() && nm->viewCount() == m->viewCount() && nm->compare(m) == 0)
728}
729
730/*
731 * Marks this batch as dirty or in the case where the geometry node has
732 * changed to be incompatible with this batch, return false so that
733 * the caller can mark the entire sg for a full rebuild...
734 */
735bool Batch::geometryWasChanged(QSGGeometryNode *gn)
736{
737 Element *e = first;
738 Q_ASSERT_X(e, "Batch::geometryWasChanged", "Batch is expected to 'valid' at this time");
739 // 'gn' is the first node in the batch, compare against the next one.
740 while (e && (e->node == gn || e->removed))
741 e = e->nextInBatch;
742 if (!e || e->node->geometry()->attributes() == gn->geometry()->attributes()) {
743 needsUpload = true;
744 return true;
745 } else {
746 return false;
747 }
748}
749
751{
752 if (!needsPurge)
753 return;
754
755 // remove from front of batch..
756 while (first && first->removed) {
758 }
759
760 // Then continue and remove other nodes further out in the batch..
761 if (first) {
762 Element *e = first;
763 while (e->nextInBatch) {
764 if (e->nextInBatch->removed)
766 else
767 e = e->nextInBatch;
768
769 }
770 }
771
772 needsPurge = false;
773}
774
775/*
776 * Iterates through all geometry nodes in this batch and unsets their batch,
777 * thus forcing them to be rebuilt
778 */
780{
782 Element *e = first;
783 first = nullptr;
784 root = nullptr;
785 while (e) {
786 e->batch = nullptr;
788 e->nextInBatch = nullptr;
789 e = n;
790 }
791}
792
794 bool only = true;
795 Element *e = first;
796 while (e && only) {
797 only &= e->translateOnlyToRoot;
798 e = e->nextInBatch;
799 }
800 return only;
801}
802
803/*
804 * Iterates through all the nodes in the batch and returns true if the
805 * nodes are all safe to batch. There are two separate criteria:
806 *
807 * - The matrix is such that the z component of the result is of no
808 * consequence.
809 *
810 * - The bounds are inside the stable floating point range. This applies
811 * to desktop only where we in this case can trigger a fallback to
812 * unmerged in which case we pass the geometry straight through and
813 * just apply the matrix.
814 *
815 * NOTE: This also means a slight performance impact for geometries which
816 * are defined to be outside the stable floating point range and still
817 * use single precision float, but given that this implicitly fixes
818 * huge lists and tables, it is worth it.
819 */
820bool Batch::isSafeToBatch() const {
821 Element *e = first;
822 while (e) {
823 if (e->boundsOutsideFloatRange)
824 return false;
825 if (!is2DSafe(*e->node->matrix()))
826 return false;
827 e = e->nextInBatch;
828 }
829 return true;
830}
831
832static int qsg_countNodesInBatch(const Batch *batch)
833{
834 int sum = 0;
835 Element *e = batch->first;
836 while (e) {
837 ++sum;
838 e = e->nextInBatch;
839 }
840 return sum;
841}
842
843static int qsg_countNodesInBatches(const QDataBuffer<Batch *> &batches)
844{
845 int sum = 0;
846 for (int i=0; i<batches.size(); ++i) {
847 sum += qsg_countNodesInBatch(batches.at(i));
848 }
849 return sum;
850}
851
854 , m_context(ctx)
859 , m_partialRebuild(false)
860 , m_partialRebuildRoot(nullptr)
861 , m_forceNoDepthBuffer(false)
862 , m_opaqueBatches(16)
863 , m_alphaBatches(16)
864 , m_batchPool(16)
868 , m_vboPool(16)
869 , m_iboPool(16)
870 , m_vboPoolCost(0)
871 , m_iboPoolCost(0)
873 , m_zRange(0)
874#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
877#endif
879 , m_currentMaterial(nullptr)
880 , m_currentShader(nullptr)
881 , m_vertexUploadPool(256)
883{
884 m_rhi = m_context->rhi();
885 Q_ASSERT(m_rhi); // no more direct OpenGL code path in Qt 6
886
888
890 if (qEnvironmentVariableIntValue("QSG_RHI_UINT32_INDEX"))
891 m_uint32IndexForRhi = true;
892
893 bool ok = false;
894 int padding = qEnvironmentVariableIntValue("QSG_BATCHRENDERER_MINIMUM_ORDER_PADDING", &ok);
895 if (ok)
897
898 m_visualizer = new RhiVisualizer(this);
899
900 setNodeUpdater(new Updater(this));
901
902 // The shader manager is shared between renderers (think for example Item
903 // layers that create a new Renderer each) with the same rendercontext (and
904 // so same QRhi).
906 if (!m_shaderManager) {
908 m_shaderManager->setObjectName(QStringLiteral("__qt_ShaderManager"));
911 }
912
913 m_batchNodeThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_NODE_THRESHOLD", 64);
914 m_batchVertexThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_VERTEX_THRESHOLD", 1024);
915 m_srbPoolThreshold = qt_sg_envInt("QSG_RENDERER_SRB_POOL_THRESHOLD", 1024);
916 m_bufferPoolSizeLimit = qt_sg_envInt("QSG_RENDERER_BUFFER_POOL_LIMIT", DEFAULT_BUFFER_POOL_SIZE_LIMIT);
917
919 qDebug("Batch thresholds: nodes: %d vertices: %d srb pool: %d buffer pool: %d",
921 }
922}
923
924static void qsg_wipeBuffer(Buffer *buffer)
925{
926 delete buffer->buf;
927
928 // The free here is ok because we're in one of two situations.
929 // 1. We're using the upload pool in which case unmap will have set the
930 // data pointer to 0 and calling free on 0 is ok.
931 // 2. We're using dedicated buffers because of visualization or IBO workaround
932 // and the data something we malloced and must be freed.
933 free(buffer->data);
934}
935
936static void qsg_wipeBatch(Batch *batch)
937{
938 qsg_wipeBuffer(&batch->vbo);
939 qsg_wipeBuffer(&batch->ibo);
940 delete batch->ubuf;
941 batch->stencilClipState.reset();
942 delete batch;
943}
944
946{
947 if (m_rhi) {
948 // Clean up batches and buffers
949 for (int i = 0; i < m_opaqueBatches.size(); ++i)
951 for (int i = 0; i < m_alphaBatches.size(); ++i)
953 for (int i = 0; i < m_batchPool.size(); ++i)
955 for (int i = 0; i < m_vboPool.size(); ++i)
956 delete m_vboPool.at(i);
957 for (int i = 0; i < m_iboPool.size(); ++i)
958 delete m_iboPool.at(i);
959 }
960
961 for (Node *n : std::as_const(m_nodes)) {
962 if (n->type() == QSGNode::GeometryNodeType) {
963 Element *e = n->element();
964 if (!e->removed)
966 } else if (n->type() == QSGNode::ClipNodeType) {
967 delete n->clipInfo();
968 } else if (n->type() == QSGNode::RenderNodeType) {
970 if (!e->removed)
972 }
973
975 }
976
977 // Remaining elements...
978 for (int i=0; i<m_elementsToDelete.size(); ++i)
980
982
983 delete m_visualizer;
984}
985
987{
988 // If this is from the dtor, then the shader manager and its already
989 // prepared shaders will stay around for other renderers -> the cached data
990 // in the rhi shaders have to be purged as it may refer to samplers we
991 // are going to destroy.
993
996 delete m_dummyTexture;
998}
999
1001{
1003
1005
1006 m_samplers.clear();
1007 m_dummyTexture = nullptr;
1008
1010
1015
1016 for (int i = 0; i < m_vboPool.size(); ++i)
1017 delete m_vboPool.at(i);
1018 m_vboPool.reset();
1019 m_vboPoolCost = 0;
1020
1021 for (int i = 0; i < m_iboPool.size(); ++i)
1022 delete m_iboPool.at(i);
1023 m_iboPool.reset();
1024 m_iboPoolCost = 0;
1025}
1026
1028{
1029 if (b->vbo.buf != nullptr && m_vboPoolCost + b->vbo.buf->size() <= quint32(m_bufferPoolSizeLimit)) {
1030 m_vboPool.add(b->vbo.buf);
1031 m_vboPoolCost += b->vbo.buf->size();
1032 } else {
1033 delete b->vbo.buf;
1034 }
1035 if (b->ibo.buf != nullptr && m_iboPoolCost + b->ibo.buf->size() <= quint32(m_bufferPoolSizeLimit)) {
1036 m_iboPool.add(b->ibo.buf);
1037 m_iboPoolCost += b->ibo.buf->size();
1038 } else {
1039 delete b->ibo.buf;
1040 }
1041 b->vbo.buf = nullptr;
1042 b->ibo.buf = nullptr;
1043 b->invalidate();
1044 for (int i=0; i<m_batchPool.size(); ++i)
1045 if (b == m_batchPool.at(i))
1046 return;
1047 m_batchPool.add(b);
1048}
1049
1051{
1053 // Common case, use a shared memory pool for uploading vertex data to avoid
1054 // excessive reevaluation
1056 if (byteSize > quint32(pool.size()))
1058 buffer->data = pool.data();
1059 } else if (buffer->size != byteSize) {
1060 free(buffer->data);
1061 buffer->data = (char *) malloc(byteSize);
1063 }
1064 buffer->size = byteSize;
1065}
1066
1068{
1069 // Batches are pooled and reused which means the QRhiBuffer will be
1070 // still valid in a recycled Batch. We only hit the newBuffer() path
1071 // when there are no buffers to recycle.
1073 if (!buffer->buf && bufferPool->isEmpty()) {
1076 buffer->size);
1077 if (!buffer->buf->create()) {
1078 qWarning("Failed to build vertex/index buffer of size %u", buffer->size);
1079 delete buffer->buf;
1080 buffer->buf = nullptr;
1081 }
1082 } else {
1083 if (!buffer->buf) {
1086 for (qsizetype i = 0; i < bufferPool->size(); ++i) {
1088 if (!buffer->buf
1089 || (testBuffer->size() >= expectedSize && testBuffer->size() < buffer->buf->size())
1090 || (testBuffer->size() < expectedSize && testBuffer->size() > buffer->buf->size())) {
1093 if (buffer->buf->size() == expectedSize)
1094 break;
1095 }
1096 }
1097
1102 }
1103 if (isIndexBuf)
1105 else
1108 }
1109
1110 bool needsRebuild = false;
1111 if (buffer->buf->size() < buffer->size) {
1113 needsRebuild = true;
1114 }
1115 if (buffer->buf->type() != QRhiBuffer::Dynamic
1117 {
1120 needsRebuild = true;
1121 }
1122 if (needsRebuild) {
1123 if (!buffer->buf->create()) {
1124 qWarning("Failed to (re)build vertex/index buffer of size %u", buffer->size);
1125 delete buffer->buf;
1126 buffer->buf = nullptr;
1127 }
1128 }
1129 }
1130 if (buffer->buf) {
1131 if (buffer->buf->type() != QRhiBuffer::Dynamic) {
1134 } else {
1137 else
1139 }
1140 }
1142 buffer->data = nullptr;
1143}
1144
1146{
1148 if (!info) {
1149 if (node->type() == QSGNode::ClipNodeType)
1150 info = new ClipBatchRootInfo;
1151 else {
1153 info = new BatchRootInfo;
1154 }
1155 node->data = info;
1156 }
1157 return info;
1158}
1159
1161{
1163 if (!childInfo->parentRoot)
1164 return;
1166
1169 childInfo->parentRoot = nullptr;
1170}
1171
1173{
1178}
1179
1181{
1183 if (subInfo->parentRoot == root)
1184 return false;
1185 if (subInfo->parentRoot) {
1188 }
1192 return true;
1193}
1194
1196{
1197 if (node->type() == QSGNode::ClipNodeType || node->isBatchRoot) {
1198 // When we reach a batchroot, we only need to update it. Its subtree
1199 // is relative to that root, so no need to recurse further.
1201 return;
1202 } else if (node->type() == QSGNode::GeometryNodeType) {
1203 // Only need to change the root as nodeChanged anyway flags a full update.
1204 Element *e = node->element();
1205 if (e) {
1206 e->root = root;
1207 e->boundsComputed = false;
1208 }
1209 } else if (node->type() == QSGNode::RenderNodeType) {
1211 if (e)
1212 e->root = root;
1213 }
1214
1217}
1218
1220{
1221 if (node->type() == QSGNode::GeometryNodeType) {
1222 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node->sgNode);
1224 Element *e = node->element();
1225 if (e) {
1226 e->boundsComputed = false;
1227 if (e->batch) {
1228 if (!e->batch->isOpaque) {
1230 } else if (e->batch->merged) {
1231 e->batch->needsUpload = true;
1232 }
1233 }
1234 }
1235 }
1236
1239}
1240
1242{
1244 if (node->isSubtreeBlocked())
1245 return;
1246
1248 snode->sgNode = node;
1250 if (shadowParent)
1252
1253 if (node->type() == QSGNode::GeometryNodeType) {
1255 snode->element()->setNode(static_cast<QSGGeometryNode *>(node));
1256
1257 } else if (node->type() == QSGNode::ClipNodeType) {
1260
1261 } else if (node->type() == QSGNode::RenderNodeType) {
1262 QSGRenderNode *rn = static_cast<QSGRenderNode *>(node);
1264 snode->data = e;
1268 m_forceNoDepthBuffer = true;
1270 }
1271
1274}
1275
1277{
1278 // Prefix traversal as removeBatchRootFromParent below removes nodes
1279 // in a bottom-up manner. Note that we *cannot* use SHADOWNODE_TRAVERSE
1280 // here, because we delete 'child' (when recursed, down below), so we'd
1281 // have a use-after-free.
1282 {
1283 Node *child = node->firstChild();
1284 while (child) {
1285 // Remove (and delete) child
1286 node->remove(child);
1288 child = node->firstChild();
1289 }
1290 }
1291
1292 if (node->type() == QSGNode::GeometryNodeType) {
1293 Element *e = node->element();
1294 if (e) {
1295 e->removed = true;
1297 e->node = nullptr;
1298 if (e->root) {
1301 }
1302 if (e->batch) {
1303 e->batch->needsUpload = true;
1304 e->batch->needsPurge = true;
1305 }
1306
1307 }
1308
1309 } else if (node->type() == QSGNode::ClipNodeType) {
1311 delete node->clipInfo();
1314
1315 } else if (node->isBatchRoot) {
1317 delete node->rootInfo();
1320
1321 } else if (node->type() == QSGNode::RenderNodeType) {
1323 if (e) {
1324 e->removed = true;
1327 m_forceNoDepthBuffer = false;
1328 // Must have a full rebuild given useDepthBuffer() now returns
1329 // a different value than before, meaning there can once again
1330 // be an opaque pass.
1332 }
1333
1334 if (e->batch != nullptr)
1335 e->batch->needsPurge = true;
1336 }
1337 }
1338
1340
1342}
1343
1345{
1346 if (Q_UNLIKELY(debug_change())) qDebug(" - new batch root");
1348 node->isBatchRoot = true;
1349 node->becameBatchRoot = true;
1350
1351 Node *p = node->parent();
1352 while (p) {
1353 if (p->type() == QSGNode::ClipNodeType || p->isBatchRoot) {
1355 break;
1356 }
1357 p = p->parent();
1358 }
1359
1362}
1363
1364
1366{
1367#ifndef QT_NO_DEBUG_OUTPUT
1368 if (Q_UNLIKELY(debug_change())) {
1369 QDebug debug = qDebug();
1370 debug << "dirty:";
1372 debug << "Geometry";
1374 debug << "Material";
1375 if (state & QSGNode::DirtyMatrix)
1376 debug << "Matrix";
1378 debug << "Added";
1380 debug << "Removed";
1381 if (state & QSGNode::DirtyOpacity)
1382 debug << "Opacity";
1384 debug << "SubtreeBlocked";
1386 debug << "ForceUpdate";
1387
1388 // when removed, some parts of the node could already have been destroyed
1389 // so don't debug it out.
1391 debug << (void *) node << node->type();
1392 else
1393 debug << node;
1394 }
1395#endif
1396 // As this function calls nodeChanged recursively, we do it at the top
1397 // to avoid that any of the others are processed twice.
1399 Node *sn = m_nodes.value(node);
1400
1401 // Force a batch rebuild if this includes an opacity change
1402 if (state & QSGNode::DirtyOpacity)
1404
1405 bool blocked = node->isSubtreeBlocked();
1406 if (blocked && sn) {
1408 Q_ASSERT(m_nodes.value(node) == 0);
1409 } else if (!blocked && !sn) {
1411 }
1412 return;
1413 }
1414
1415 if (state & QSGNode::DirtyNodeAdded) {
1418 return;
1419 }
1420 if (node == rootNode())
1421 nodeWasAdded(node, nullptr);
1422 else
1424 }
1425
1426 // Mark this node dirty in the shadow tree.
1428
1429 // Blocked subtrees won't have shadow nodes, so we can safely abort
1430 // here..
1431 if (!shadowNode) {
1433 return;
1434 }
1435
1437
1442 } else {
1443 int vertices = 0;
1447 }
1448 }
1449 }
1450
1452 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1454
1455 if (e) {
1460 }
1461
1462 e->boundsComputed = false;
1463 Batch *b = e->batch;
1464 if (b) {
1465 if (!e->batch->geometryWasChanged(gn) || !e->batch->isOpaque) {
1467 } else {
1468 b->needsUpload = true;
1469 }
1470 }
1471 }
1472 }
1473
1476 if (e) {
1477 bool blended = hasMaterialWithBlending(static_cast<QSGGeometryNode *>(node));
1478 if (e->isMaterialBlended != blended) {
1481 } else if (e->batch) {
1484 } else {
1486 }
1487 }
1488 }
1489
1490 // Mark the shadow tree dirty all the way back to the root...
1496 if (dirtyChain != 0) {
1498 Node *sn = shadowNode->parent();
1499 while (sn) {
1501 sn = sn->parent();
1502 }
1503 }
1504
1505 // Delete happens at the very end because it deletes the shadownode.
1508 if (parent)
1511 Q_ASSERT(m_nodes.value(node) == 0);
1512 }
1513
1515}
1516
1517/*
1518 * Traverses the tree and builds two list of geometry nodes. One for
1519 * the opaque and one for the translucent. These are populated
1520 * in the order they should visually appear in, meaning first
1521 * to the back and last to the front.
1522 *
1523 * We split opaque and translucent as we can perform different
1524 * types of reordering / batching strategies on them, depending
1525 *
1526 * Note: It would be tempting to use the shadow nodes instead of the QSGNodes
1527 * for traversal to avoid hash lookups, but the order of the children
1528 * is important and they are not preserved in the shadow tree, so we must
1529 * use the actual QSGNode tree.
1530 */
1532{
1533 if (node->isSubtreeBlocked())
1534 return;
1535
1538
1539 if (node->type() == QSGNode::GeometryNodeType) {
1540 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1541
1543 Q_ASSERT(e);
1544
1546 if (opaque && useDepthBuffer())
1548 else
1550
1552 // Used while rebuilding partial roots.
1553 if (m_partialRebuild)
1554 e->orphaned = false;
1555
1556 } else if (node->type() == QSGNode::ClipNodeType || shadowNode->isBatchRoot) {
1559 if (node == m_partialRebuildRoot) {
1564 } else {
1573 }
1574 return;
1575 } else if (node->type() == QSGNode::RenderNodeType) {
1579 Q_ASSERT(e);
1580 }
1581
1584}
1585
1587{
1591 it != i->subRoots.constEnd(); ++it) {
1592 tagSubRoots(*it);
1593 }
1594}
1595
1596static void qsg_addOrphanedElements(QDataBuffer<Element *> &orphans, const QDataBuffer<Element *> &renderList)
1597{
1598 orphans.reset();
1599 for (int i=0; i<renderList.size(); ++i) {
1600 Element *e = renderList.at(i);
1601 if (e && !e->removed) {
1602 e->orphaned = true;
1603 orphans.add(e);
1604 }
1605 }
1606}
1607
1608static void qsg_addBackOrphanedElements(QDataBuffer<Element *> &orphans, QDataBuffer<Element *> &renderList)
1609{
1610 for (int i=0; i<orphans.size(); ++i) {
1611 Element *e = orphans.at(i);
1612 if (e->orphaned)
1613 renderList.add(e);
1614 }
1615 orphans.reset();
1616}
1617
1618/*
1619 * To rebuild the tagged roots, we start by putting all subroots of tagged
1620 * roots into the list of tagged roots. This is to make the rest of the
1621 * algorithm simpler.
1622 *
1623 * Second, we invalidate all batches which belong to tagged roots, which now
1624 * includes the entire subtree under a given root
1625 *
1626 * Then we call buildRenderLists for all tagged subroots which do not have
1627 * parents which are tagged, aka, we traverse only the topmosts roots.
1628 *
1629 * Then we sort the render lists based on their render order, to restore the
1630 * right order for rendering.
1631 */
1633{
1634 // Flag any element that is currently in the render lists, but which
1635 // is not in a batch. This happens when we have a partial rebuild
1636 // in one sub tree while we have a BuildBatches change in another
1637 // isolated subtree. So that batch-building takes into account
1638 // these "orphaned" nodes, we flag them now. The ones under tagged
1639 // roots will be cleared again. The remaining ones are added into the
1640 // render lists so that they contain all visual nodes after the
1641 // function completes.
1644
1645 // Take a copy now, as we will be adding to this while traversing..
1648 it != roots.constEnd(); ++it) {
1649 tagSubRoots(*it);
1650 }
1651
1652 for (int i=0; i<m_opaqueBatches.size(); ++i) {
1656
1657 }
1658 for (int i=0; i<m_alphaBatches.size(); ++i) {
1662 }
1663
1667 m_partialRebuild = true;
1668 // Traverse each root, assigning it
1670 it != m_taggedRoots.constEnd(); ++it) {
1671 Node *root = *it;
1678 }
1679 }
1680 m_partialRebuild = false;
1681 m_partialRebuildRoot = nullptr;
1684
1685 // Add orphaned elements back into the list and then sort it..
1688
1691 if (m_alphaRenderList.size())
1693
1694}
1695
1697{
1700
1701 for (int i=0; i<m_opaqueBatches.size(); ++i)
1703 for (int i=0; i<m_alphaBatches.size(); ++i)
1707
1709
1711}
1712
1714{
1715 Q_ASSERT(batch);
1717
1718#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1723
1726#else
1727 int first = batch->first->order;
1729#endif
1730
1731 batch->invalidate();
1732
1733 for (int i=0; i<m_alphaBatches.size(); ++i) {
1735 if (b->first) {
1736 int bf = b->first->order;
1737 int bl = b->lastOrderInBatch;
1738 if (bl > first && bf < last)
1739 b->invalidate();
1740 }
1741 }
1742
1744}
1745
1746/* Clean up batches by making it a consecutive list of "valid"
1747 * batches and moving all invalidated batches to the batches pool.
1748 */
1750 qsizetype n = batches->size();
1751 if (n == 0)
1752 return;
1753
1755 for (qsizetype i = 0; i < n; ++i) {
1756 Batch *b = batches->at(i);
1757 if (b->first)
1758 (*batches).data()[writeIndex++] = b;
1759 else
1761 }
1763}
1764
1766{
1767 for (int i=m_opaqueRenderList.size() - 1; i >= 0; --i) {
1769 if (!ei || ei->batch || ei->node->geometry()->vertexCount() == 0)
1770 continue;
1771 Batch *batch = newBatch();
1772 batch->first = ei;
1773 batch->root = ei->root;
1774 batch->isOpaque = true;
1775 batch->needsUpload = true;
1777
1779
1780 ei->batch = batch;
1781 Element *next = ei;
1782
1784
1785 for (int j = i - 1; j >= 0; --j) {
1787 if (!ej)
1788 continue;
1789 if (ej->root != ei->root)
1790 break;
1791 if (ej->batch || ej->node->geometry()->vertexCount() == 0)
1792 continue;
1793
1795
1796 const QSGGeometry *gniGeometry = gni->geometry();
1798 const QSGGeometry *gnjGeometry = gnj->geometry();
1800 if (gni->clipList() == gnj->clipList()
1808 && gniMaterial->type() == gnjMaterial->type()
1812 {
1813 ej->batch = batch;
1814 next->nextInBatch = ej;
1815 next = ej;
1816 }
1817 }
1818
1820 }
1821}
1822
1823bool Renderer::checkOverlap(int first, int last, const Rect &bounds)
1824{
1825 for (int i=first; i<=last; ++i) {
1827#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1828 if (!e || e->batch)
1829#else
1830 if (!e)
1831#endif
1832 continue;
1834 if (e->bounds.intersects(bounds))
1835 return true;
1836 }
1837 return false;
1838}
1839
1840/*
1841 *
1842 * To avoid the O(n^2) checkOverlap check in most cases, we have the
1843 * overlapBounds which is the union of all bounding rects to check overlap
1844 * for. We know that if it does not overlap, then none of the individual
1845 * ones will either. For the typical list case, this results in no calls
1846 * to checkOverlap what-so-ever. This also ensures that when all consecutive
1847 * items are matching (such as a table of text), we don't build up an
1848 * overlap bounds and thus do not require full overlap checks.
1849 */
1850
1852{
1853 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1855 if (!e || e->isRenderNode)
1856 continue;
1857 Q_ASSERT(!e->removed);
1859 }
1860
1861 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1863 if (!ei || ei->batch)
1864 continue;
1865
1866 if (ei->isRenderNode) {
1867 Batch *rnb = newBatch();
1868 rnb->first = ei;
1869 rnb->root = ei->root;
1870 rnb->isOpaque = false;
1871 rnb->isRenderNode = true;
1872 ei->batch = rnb;
1874 continue;
1875 }
1876
1877 if (ei->node->geometry()->vertexCount() == 0)
1878 continue;
1879
1880 Batch *batch = newBatch();
1881 batch->first = ei;
1882 batch->root = ei->root;
1883 batch->isOpaque = false;
1884 batch->needsUpload = true;
1886 ei->batch = batch;
1887
1890
1893
1894 Element *next = ei;
1895
1896 for (int j = i + 1; j < m_alphaRenderList.size(); ++j) {
1898 if (!ej)
1899 continue;
1900 if (ej->root != ei->root || ej->isRenderNode)
1901 break;
1902 if (ej->batch) {
1903#if !defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1905#endif
1906 continue;
1907 }
1908
1910 if (gnj->geometry()->vertexCount() == 0)
1911 continue;
1912
1913 const QSGGeometry *gniGeometry = gni->geometry();
1915 const QSGGeometry *gnjGeometry = gnj->geometry();
1917 if (gni->clipList() == gnj->clipList()
1921 // Must not do overlap checks when the line width is not 1,
1922 // we have no knowledge how such lines are rasterized.
1923 && gniGeometry->lineWidth() == 1.0f))
1927 && gniMaterial->type() == gnjMaterial->type()
1931 {
1932 if (!overlapBounds.intersects(ej->bounds) || !checkOverlap(i+1, j - 1, ej->bounds)) {
1933 ej->batch = batch;
1934 next->nextInBatch = ej;
1935 next = ej;
1936 } else {
1937 /* When we come across a compatible element which hits an overlap, we
1938 * need to stop the batch right away. We cannot add more elements
1939 * to the current batch as they will be rendered before the batch that the
1940 * current 'ej' will be added to.
1941 */
1942 break;
1943 }
1944 } else {
1946 }
1947 }
1948
1950 }
1951
1952
1953}
1954
1955static inline int qsg_fixIndexCount(int iCount, int drawMode)
1956{
1957 switch (drawMode) {
1958 case QSGGeometry::DrawTriangleStrip:
1959 // Merged triangle strips need to contain degenerate triangles at the beginning and end.
1960 // One could save 2 uploaded ushorts here by ditching the padding for the front of the
1961 // first and the end of the last, but for simplicity, we simply don't care.
1962 // Those extra triangles will be skipped while drawing to preserve the strip's parity
1963 // anyhow.
1964 return iCount + 2;
1965 case QSGGeometry::DrawLines:
1966 // For lines we drop the last vertex if the number of vertices is uneven.
1967 return iCount - (iCount % 2);
1968 case QSGGeometry::DrawTriangles:
1969 // For triangles we drop trailing vertices until the result is divisible by 3.
1970 return iCount - (iCount % 3);
1971 default:
1972 return iCount;
1973 }
1974}
1975
1976static inline float calculateElementZOrder(const Element *e, qreal zRange)
1977{
1978 // Clamp the zOrder to within the min and max depth of the viewport.
1979 return std::clamp(1.0f - float(e->order * zRange), VIEWPORT_MIN_DEPTH, VIEWPORT_MAX_DEPTH);
1980}
1981
1982/* These parameters warrant some explanation...
1983 *
1984 * vaOffset: The byte offset into the vertex data to the location of the
1985 * 2D float point vertex attributes.
1986 *
1987 * vertexData: destination where the geometry's vertex data should go
1988 *
1989 * zData: destination of geometries injected Z positioning
1990 *
1991 * indexData: destination of the indices for this element
1992 *
1993 * iBase: The starting index for this element in the batch
1994 */
1995
1996void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, void *iBasePtr, int *indexCount)
1997{
1998 if (Q_UNLIKELY(debug_upload())) qDebug() << " - uploading element:" << e << e->node << (void *) *vertexData << (qintptr) (*zData - *vertexData) << (qintptr) (*indexData - *vertexData);
1999 QSGGeometry *g = e->node->geometry();
2000
2001 const QMatrix4x4 &localx = *e->node->matrix();
2002 const float *localxdata = localx.constData();
2003
2004 const int vCount = g->vertexCount();
2005 const int vSize = g->sizeOfVertex();
2007
2008 // apply vertex transform..
2009 char *vdata = *vertexData + vaOffset;
2010 if (localx.flags() == QMatrix4x4::Translation) {
2011 for (int i=0; i<vCount; ++i) {
2012 Pt *p = (Pt *) vdata;
2013 p->x += localxdata[12];
2014 p->y += localxdata[13];
2015 vdata += vSize;
2016 }
2017 } else if (localx.flags() > QMatrix4x4::Translation) {
2018 for (int i=0; i<vCount; ++i) {
2019 ((Pt *) vdata)->map(localx);
2020 vdata += vSize;
2021 }
2022 }
2023
2024 if (useDepthBuffer()) {
2025 float *vzorder = (float *) *zData;
2027 for (int i=0; i<vCount; ++i)
2028 vzorder[i] = zorder;
2029 *zData += vCount * sizeof(float);
2030 }
2031
2032 int iCount = g->indexCount();
2033 if (m_uint32IndexForRhi) {
2034 // can only happen when using the rhi
2037 if (iCount == 0) {
2038 iCount = vCount;
2040 *indices++ = *iBase;
2041 else
2043
2044 for (int i=0; i<iCount; ++i)
2045 indices[i] = *iBase + i;
2046 } else {
2047 // source index data in QSGGeometry is always ushort (we would not merge otherwise)
2050 *indices++ = *iBase + srcIndices[0];
2051 else
2053
2054 for (int i=0; i<iCount; ++i)
2055 indices[i] = *iBase + srcIndices[i];
2056 }
2058 indices[iCount] = indices[iCount - 1];
2059 iCount += 2;
2060 }
2061 *iBase += vCount;
2062 } else {
2063 // normally batching is only done for ushort index data
2066 if (iCount == 0) {
2067 iCount = vCount;
2069 *indices++ = *iBase;
2070 else
2072
2073 for (int i=0; i<iCount; ++i)
2074 indices[i] = *iBase + i;
2075 } else {
2078 *indices++ = *iBase + srcIndices[0];
2079 else
2081
2082 for (int i=0; i<iCount; ++i)
2083 indices[i] = *iBase + srcIndices[i];
2084 }
2086 indices[iCount] = indices[iCount - 1];
2087 iCount += 2;
2088 }
2089 *iBase += vCount;
2090 }
2091
2092 *vertexData += vCount * vSize;
2094 *indexCount += iCount;
2095}
2096
2098{
2099 if (node->type() == QSGNode::TransformNodeType)
2100 return static_cast<QSGTransformNode *>(node->sgNode)->combinedMatrix();
2101 Q_ASSERT(node->type() == QSGNode::ClipNodeType);
2102 QSGClipNode *c = static_cast<QSGClipNode *>(node->sgNode);
2103 return *c->matrix();
2104}
2105
2107{
2108 // Early out if nothing has changed in this batch..
2109 if (!b->needsUpload) {
2110 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "already uploaded...";
2111 return;
2112 }
2113
2114 if (!b->first) {
2115 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "is invalid...";
2116 return;
2117 }
2118
2119 if (b->isRenderNode) {
2120 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch: " << b << "is a render node...";
2121 return;
2122 }
2123
2124 // Figure out if we can merge or not, if not, then just render the batch as is..
2125 Q_ASSERT(b->first);
2126 Q_ASSERT(b->first->node);
2127
2129 QSGGeometry *g = gn->geometry();
2133 && b->positionAttribute >= 0
2137 && b->isSafeToBatch();
2138
2139 b->merged = canMerge;
2140
2141 // Figure out how much memory we need...
2142 b->vertexCount = 0;
2143 b->indexCount = 0;
2144 int unmergedIndexSize = 0;
2145 Element *e = b->first;
2146
2147 // Merged batches always do indexed draw calls. Non-indexed geometry gets
2148 // indices generated automatically, when merged.
2149 while (e) {
2150 QSGGeometry *eg = e->node->geometry();
2151 b->vertexCount += eg->vertexCount();
2152 int iCount = eg->indexCount();
2153 if (b->merged) {
2154 if (iCount == 0)
2155 iCount = eg->vertexCount();
2157 } else {
2158 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : eg->sizeOfIndex();
2160 }
2161 b->indexCount += iCount;
2162 e = e->nextInBatch;
2163 }
2164
2165 // Abort if there are no vertices in this batch.. We abort this late as
2166 // this is a broken usecase which we do not care to optimize for...
2167 if (b->vertexCount == 0 || (b->merged && b->indexCount == 0))
2168 return;
2169
2170 /* Allocate memory for this batch. Merged batches are divided into three separate blocks
2171 1. Vertex data for all elements, as they were in the QSGGeometry object, but
2172 with the tranform relative to this batch's root applied. The vertex data
2173 is otherwise unmodified.
2174 2. Z data for all elements, derived from each elements "render order".
2175 This is present for merged data only.
2176 3. Indices for all elements, as they were in the QSGGeometry object, but
2177 adjusted so that each index matches its.
2178 And for TRIANGLE_STRIPs, we need to insert degenerate between each
2179 primitive. These are unsigned shorts for merged and arbitrary for
2180 non-merged.
2181 */
2183 int ibufferSize = 0;
2184 if (b->merged) {
2186 if (useDepthBuffer())
2187 bufferSize += b->vertexCount * sizeof(float);
2188 } else {
2190 }
2191
2192 map(&b->ibo, ibufferSize, true);
2193 map(&b->vbo, bufferSize);
2194
2195 if (Q_UNLIKELY(debug_upload())) qDebug() << " - batch" << b << " first:" << b->first << " root:"
2196 << b->root << " merged:" << b->merged << " positionAttribute" << b->positionAttribute
2197 << " vbo:" << b->vbo.buf << ":" << b->vbo.size;
2198
2199 if (b->merged) {
2200 char *vertexData = b->vbo.data;
2201 char *zData = vertexData + b->vertexCount * g->sizeOfVertex();
2202 char *indexData = b->ibo.data;
2203
2204 quint16 iOffset16 = 0;
2205 quint32 iOffset32 = 0;
2206 e = b->first;
2207 uint verticesInSet = 0;
2208 // Start a new set already after 65534 vertices because 0xFFFF may be
2209 // used for an always-on primitive restart with some apis (adapt for
2210 // uint32 indices as appropriate).
2211 const uint verticesInSetLimit = m_uint32IndexForRhi ? 0xfffffffe : 0xfffe;
2212 int indicesInSet = 0;
2213 b->drawSets.reset();
2214 int drawSetIndices = 0;
2215 const char *indexBase = b->ibo.data;
2217 while (e) {
2223 b->drawSets.last().indexCount -= 2;
2224 }
2227 zData - b->vbo.data,
2229 iOffset16 = 0;
2230 iOffset32 = 0;
2232 indicesInSet = 0;
2233 }
2234 void *iBasePtr = &iOffset16;
2238 e = e->nextInBatch;
2239 }
2241 // We skip the very first and very last degenerate triangles since they aren't needed
2242 // and the first one would reverse the vertex ordering of the merged strips.
2245 b->drawSets.last().indexCount -= 2;
2246 }
2247 } else {
2248 char *vboData = b->vbo.data;
2249 char *iboData = b->ibo.data;
2250 Element *e = b->first;
2251 while (e) {
2252 QSGGeometry *g = e->node->geometry();
2253 int vbs = g->vertexCount() * g->sizeOfVertex();
2255 vboData = vboData + vbs;
2256 const int indexCount = g->indexCount();
2257 if (indexCount) {
2258 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
2259 const int ibs = indexCount * effectiveIndexSize;
2260 if (g->sizeOfIndex() == effectiveIndexSize) {
2262 } else {
2263 if (g->sizeOfIndex() == sizeof(quint16) && effectiveIndexSize == sizeof(quint32)) {
2265 quint32 *dst = (quint32 *) iboData;
2266 for (int i = 0; i < indexCount; ++i)
2267 dst[i] = src[i];
2268 } else {
2269 Q_ASSERT_X(false, "uploadBatch (unmerged)", "uint index with ushort effective index - cannot happen");
2270 }
2271 }
2272 iboData += ibs;
2273 }
2274 e = e->nextInBatch;
2275 }
2276 }
2277#ifndef QT_NO_DEBUG_OUTPUT
2278 if (Q_UNLIKELY(debug_upload())) {
2279 const char *vd = b->vbo.data;
2280 qDebug() << " -- Vertex Data, count:" << b->vertexCount << " - " << g->sizeOfVertex() << "bytes/vertex";
2281 for (int i=0; i<b->vertexCount; ++i) {
2282 QDebug dump = qDebug().nospace();
2283 dump << " --- " << i << ": ";
2284 int offset = 0;
2285 for (int a=0; a<g->attributeCount(); ++a) {
2286 const QSGGeometry::Attribute &attr = g->attributes()[a];
2287 dump << attr.position << ":(" << attr.tupleSize << ",";
2288 if (attr.type == QSGGeometry::FloatType) {
2289 dump << "float ";
2291 dump << "* ";
2292 for (int t=0; t<attr.tupleSize; ++t)
2293 dump << *(const float *)(vd + offset + t * sizeof(float)) << " ";
2294 } else if (attr.type == QSGGeometry::UnsignedByteType) {
2295 dump << "ubyte ";
2296 for (int t=0; t<attr.tupleSize; ++t)
2297 dump << *(const unsigned char *)(vd + offset + t * sizeof(unsigned char)) << " ";
2298 }
2299 dump << ") ";
2301 }
2302 if (b->merged && useDepthBuffer()) {
2303 float zorder = ((float*)(b->vbo.data + b->vertexCount * g->sizeOfVertex()))[i];
2304 dump << " Z:(" << zorder << ")";
2305 }
2306 vd += g->sizeOfVertex();
2307 }
2308
2309 if (!b->drawSets.isEmpty()) {
2310 if (m_uint32IndexForRhi) {
2311 const quint32 *id = (const quint32 *) b->ibo.data;
2312 {
2313 QDebug iDump = qDebug();
2314 iDump << " -- Index Data, count:" << b->indexCount;
2315 for (int i=0; i<b->indexCount; ++i) {
2316 if ((i % 24) == 0)
2317 iDump << Qt::endl << " --- ";
2318 iDump << id[i];
2319 }
2320 }
2321 } else {
2322 const quint16 *id = (const quint16 *) b->ibo.data;
2323 {
2324 QDebug iDump = qDebug();
2325 iDump << " -- Index Data, count:" << b->indexCount;
2326 for (int i=0; i<b->indexCount; ++i) {
2327 if ((i % 24) == 0)
2328 iDump << Qt::endl << " --- ";
2329 iDump << id[i];
2330 }
2331 }
2332 }
2333
2334 for (int i=0; i<b->drawSets.size(); ++i) {
2335 const DrawSet &s = b->drawSets.at(i);
2336 qDebug() << " -- DrawSet: indexCount:" << s.indexCount << " vertices:" << s.vertices << " z:" << s.zorders << " indices:" << s.indices;
2337 }
2338 }
2339 }
2340#endif // QT_NO_DEBUG_OUTPUT
2341
2342 unmap(&b->vbo);
2343 unmap(&b->ibo, true);
2344
2346 qDebug() << " --- vertex/index buffers unmapped, batch upload completed... vbo pool size" << m_vboPoolCost << "ibo pool size" << m_iboPoolCost;
2347
2348 b->needsUpload = false;
2349
2350 if (Q_UNLIKELY(debug_render()))
2351 b->uploadedThisFrame = true;
2352}
2353
2355{
2358}
2359
2361{
2365 blend.colorWrite = {};
2366 ps->setTargetBlends({ blend });
2368 ps->setStencilTest(true);
2375 } else {
2380 }
2383
2385
2387
2391 ps->setShaderResourceBindings(batch->stencilClipState.srb); // use something, it just needs to be layout-compatible
2393
2394 if (!ps->create()) {
2395 qWarning("Failed to build stencil clip pipeline");
2396 delete ps;
2397 return nullptr;
2398 }
2399
2400 return ps;
2401}
2402
2404{
2405 // Note: No use of the clip-related speparate m_current* vars is allowed
2406 // here. All stored in batch->clipState instead. To collect state during
2407 // the prepare steps, m_currentClipState is used. It should not be used in
2408 // the render steps afterwards.
2409
2410 // The stenciling logic is slightly different from Qt 5's direct OpenGL version
2411 // as we cannot just randomly clear the stencil buffer. We now put all clip
2412 // shapes into the stencil buffer for all batches in the frame. This means
2413 // that the number of total clips in a scene is reduced (since the stencil
2414 // value cannot exceed 255) but we do not need any clears inbetween.
2415
2416 Q_ASSERT(m_rhi);
2421 return;
2422 }
2423
2427 const QSGClipNode *clip = clipList;
2428
2430 quint32 totalVSize = 0;
2431 quint32 totalISize = 0;
2432 quint32 totalUSize = 0;
2433 const quint32 StencilClipUbufSize = 64;
2434
2435 while (clip) {
2436 QMatrix4x4 m = m_current_projection_matrix_native_ndc[0]; // never hit for 3D and so multiview
2437 if (clip->matrix())
2438 m *= *clip->matrix();
2439
2441 && qFuzzyIsNull(m(3, 0)) && qFuzzyIsNull(m(3, 1));
2442 bool noRotate = qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(1, 0));
2443 bool isRotate90 = qFuzzyIsNull(m(0, 0)) && qFuzzyIsNull(m(1, 1));
2444
2446 QRectF bbox = clip->clipRect();
2447 qreal invW = 1 / m(3, 3);
2448 qreal fx1, fy1, fx2, fy2;
2449 if (noRotate) {
2450 fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
2451 fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
2452 fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
2453 fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
2454 } else {
2456 fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
2457 fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
2458 fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
2459 fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
2460 }
2461
2462 if (fx1 > fx2)
2463 qSwap(fx1, fx2);
2464 if (fy1 > fy2)
2465 qSwap(fy1, fy2);
2466
2467 QRect deviceRect = this->deviceRect();
2468
2469 qint32 ix1 = qRound((fx1 + 1) * deviceRect.width() * qreal(0.5));
2470 qint32 iy1 = qRound((fy1 + 1) * deviceRect.height() * qreal(0.5));
2471 qint32 ix2 = qRound((fx2 + 1) * deviceRect.width() * qreal(0.5));
2472 qint32 iy2 = qRound((fy2 + 1) * deviceRect.height() * qreal(0.5));
2473
2474 if (!(clipType & ClipState::ScissorClip)) {
2476 scissorRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2477 } else {
2478 scissorRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2479 }
2480 } else {
2482
2483 const QSGGeometry *g = clip->geometry();
2484 Q_ASSERT(g->attributeCount() > 0);
2485
2486 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2487 // the 4 byte alignment may not actually be needed here
2489 if (g->indexCount()) {
2490 const int indexByteSize = g->sizeOfIndex() * g->indexCount();
2491 // so no need to worry about NonFourAlignedEffectiveIndexBufferOffset
2493 }
2494 // ubuf start offsets must be aligned (typically to 256 bytes)
2496
2498 }
2499
2500 clip = clip->clipList();
2501 }
2502
2504 bool rebuildVBuf = false;
2505 if (!batch->stencilClipState.vbuf) {
2507 rebuildVBuf = true;
2508 } else if (batch->stencilClipState.vbuf->size() < totalVSize) {
2510 rebuildVBuf = true;
2511 }
2512 if (rebuildVBuf) {
2513 if (!batch->stencilClipState.vbuf->create()) {
2514 qWarning("Failed to build stencil clip vertex buffer");
2515 delete batch->stencilClipState.vbuf;
2516 batch->stencilClipState.vbuf = nullptr;
2517 return;
2518 }
2519 }
2520
2521 if (totalISize) {
2522 bool rebuildIBuf = false;
2523 if (!batch->stencilClipState.ibuf) {
2525 rebuildIBuf = true;
2526 } else if (batch->stencilClipState.ibuf->size() < totalISize) {
2528 rebuildIBuf = true;
2529 }
2530 if (rebuildIBuf) {
2531 if (!batch->stencilClipState.ibuf->create()) {
2532 qWarning("Failed to build stencil clip index buffer");
2533 delete batch->stencilClipState.ibuf;
2534 batch->stencilClipState.ibuf = nullptr;
2535 return;
2536 }
2537 }
2538 }
2539
2540 bool rebuildUBuf = false;
2541 if (!batch->stencilClipState.ubuf) {
2543 rebuildUBuf = true;
2544 } else if (batch->stencilClipState.ubuf->size() < totalUSize) {
2546 rebuildUBuf = true;
2547 }
2548 if (rebuildUBuf) {
2549 if (!batch->stencilClipState.ubuf->create()) {
2550 qWarning("Failed to build stencil clip uniform buffer");
2551 delete batch->stencilClipState.ubuf;
2552 batch->stencilClipState.ubuf = nullptr;
2553 return;
2554 }
2555 }
2556
2557 if (!batch->stencilClipState.srb) {
2562 if (!batch->stencilClipState.srb->create()) {
2563 qWarning("Failed to build stencil clip srb");
2564 delete batch->stencilClipState.srb;
2565 batch->stencilClipState.srb = nullptr;
2566 return;
2567 }
2568 }
2569
2570 quint32 vOffset = 0;
2571 quint32 iOffset = 0;
2572 quint32 uOffset = 0;
2573 for (const QSGClipNode *clip : stencilClipNodes) {
2574 const QSGGeometry *g = clip->geometry();
2575 const QSGGeometry::Attribute *a = g->attributes();
2578
2583 }
2584#ifndef QT_NO_DEBUG
2585 else {
2587 qWarning("updateClipState: Clip list entries have different primitive topologies, this is not currently supported.");
2589 qWarning("updateClipState: Clip list entries have different vertex input layouts, this is must not happen.");
2590 }
2591#endif
2592
2594 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2596
2597 int indexByteSize = 0;
2598 if (g->indexCount()) {
2602 }
2603
2606
2608 if (clip->matrix())
2609 matrixYUpNDC *= *clip->matrix();
2610
2613 if (indexByteSize)
2615
2616 // stencil ref goes 1, 1, 2, 3, 4, ..., N for the clips in the first batch,
2617 // then N+1, N+1, N+2, N+3, ... for the next batch,
2618 // and so on.
2619 // Note the different stencilOp for the first and the subsequent clips.
2622
2627 }
2628
2630 m_stencilClipCommon.vs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.vert.qsb"));
2631
2633 m_stencilClipCommon.fs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.frag.qsb"));
2634
2637
2640
2642 }
2643
2648
2651}
2652
2654{
2655 // cliptype stencil + updateStencilBuffer==false means the batch uses
2656 // stenciling but relies on the stencil data generated by a previous batch
2657 // (due to the having the same clip node). Do not enqueue draw calls for
2658 // stencil in this case as the stencil buffer is already up-to-date.
2660 return;
2661
2663 const int count = batch->stencilClipState.drawCalls.size();
2664 for (int i = 0; i < count; ++i) {
2668 if (i == 0) {
2671 } else if (i == 1) {
2674 }
2675 // else incrPs is already bound
2679 if (drawCall.indexCount) {
2683 } else {
2684 cb->setVertexInput(0, 1, &vbufBinding);
2686 }
2687 }
2688}
2689
2691{
2692 Q_ASSERT(m_rhi);
2695 m_currentMaterial = nullptr;
2696}
2697
2698static inline bool needsBlendConstant(QRhiGraphicsPipeline::BlendFactor f)
2699{
2700 return f == QRhiGraphicsPipeline::ConstantColor
2701 || f == QRhiGraphicsPipeline::OneMinusConstantColor
2702 || f == QRhiGraphicsPipeline::ConstantAlpha
2703 || f == QRhiGraphicsPipeline::OneMinusConstantAlpha;
2704}
2705
2706// With QRhi renderBatches() is split to two steps: prepare and render.
2707//
2708// Prepare goes through the batches and elements, and set up a graphics
2709// pipeline, srb, uniform buffer, calculates clipping, based on m_gstate, the
2710// material (shaders), and the batches. This step does not touch the command
2711// buffer or renderpass-related state (m_pstate).
2712//
2713// The render step then starts a renderpass, and goes through all
2714// batches/elements again and records setGraphicsPipeline, drawIndexed, etc. on
2715// the command buffer. The prepare step's accumulated global state like
2716// m_gstate must not be used here. Rather, all data needed for rendering is
2717// available from Batch/Element at this stage. Bookkeeping of state in the
2718// renderpass is done via m_pstate.
2719
2721{
2722 // Note the key's == and qHash implementations: the renderpass descriptor
2723 // and srb are tested for compatibility, not pointer equality.
2724 //
2725 // We do not store the srb pointer itself because the ownership stays with
2726 // the Element and that can go away more often that we would like it
2727 // to. (think scrolling a list view, constantly dropping and creating new
2728 // nodes) Rather, use an opaque blob of a few uints and store and compare
2729 // that. This works because once the pipeline is built, we will always call
2730 // setShaderResources with an explicitly specified srb which is fine even if
2731 // e->srb we used here to bake the pipeline is already gone by that point.
2732 //
2733 // A typical QSGMaterial's serialized srb layout is 8 uints. (uniform buffer
2734 // + texture, 4 fields each) Regardless, using an implicitly shared
2735 // container is essential here. (won't detach so no more allocs and copies
2736 // are done, unless the Element decides to rebake the srb with a different
2737 // layout - but then the detach is exactly what we need)
2738 //
2739 // Same story for the renderpass descriptor: the object can go away but
2740 // that's fine because that has no effect on an already built pipeline, and
2741 // for comparison we only rely on the serialized blob in order decide if the
2742 // render target is compatible with the pipeline.
2743
2745
2746 // Note: dynamic state (viewport rect, scissor rect, stencil ref, blend
2747 // constant) is never a part of GraphicsState/QRhiGraphicsPipeline.
2748
2749 // See if there is an existing, matching pipeline state object.
2752 if (depthPostPass)
2753 e->depthPostPassPs = *it;
2754 else
2755 e->ps = *it;
2756 return true;
2757 }
2758
2759 // Build a new one. This is potentially expensive.
2765
2769 {
2771 }
2776
2777 ps->setFlags(flags);
2783
2793 ps->setTargetBlends({ blend });
2794
2798
2799 if (m_gstate.stencilTest) {
2800 ps->setStencilTest(true);
2808 }
2809
2811
2813
2814 if (!ps->create()) {
2815 qWarning("Failed to build graphics pipeline state");
2816 delete ps;
2817 return false;
2818 }
2819
2821 if (depthPostPass)
2822 e->depthPostPassPs = ps;
2823 else
2824 e->ps = ps;
2825 return true;
2826}
2827
2828static QRhiSampler *newSampler(QRhi *rhi, const QSGSamplerDescription &desc)
2829{
2830 QRhiSampler::Filter magFilter;
2831 QRhiSampler::Filter minFilter;
2832 QRhiSampler::Filter mipmapMode;
2833 QRhiSampler::AddressMode u;
2834 QRhiSampler::AddressMode v;
2835
2836 switch (desc.filtering) {
2837 case QSGTexture::None:
2838 Q_FALLTHROUGH();
2839 case QSGTexture::Nearest:
2840 magFilter = minFilter = QRhiSampler::Nearest;
2841 break;
2842 case QSGTexture::Linear:
2843 magFilter = minFilter = QRhiSampler::Linear;
2844 break;
2845 default:
2846 Q_UNREACHABLE();
2847 magFilter = minFilter = QRhiSampler::Nearest;
2848 break;
2849 }
2850
2851 switch (desc.mipmapFiltering) {
2852 case QSGTexture::None:
2853 mipmapMode = QRhiSampler::None;
2854 break;
2855 case QSGTexture::Nearest:
2856 mipmapMode = QRhiSampler::Nearest;
2857 break;
2858 case QSGTexture::Linear:
2859 mipmapMode = QRhiSampler::Linear;
2860 break;
2861 default:
2862 Q_UNREACHABLE();
2863 mipmapMode = QRhiSampler::None;
2864 break;
2865 }
2866
2867 switch (desc.horizontalWrap) {
2868 case QSGTexture::Repeat:
2869 u = QRhiSampler::Repeat;
2870 break;
2871 case QSGTexture::ClampToEdge:
2872 u = QRhiSampler::ClampToEdge;
2873 break;
2874 case QSGTexture::MirroredRepeat:
2875 u = QRhiSampler::Mirror;
2876 break;
2877 default:
2878 Q_UNREACHABLE();
2879 u = QRhiSampler::ClampToEdge;
2880 break;
2881 }
2882
2883 switch (desc.verticalWrap) {
2884 case QSGTexture::Repeat:
2885 v = QRhiSampler::Repeat;
2886 break;
2887 case QSGTexture::ClampToEdge:
2888 v = QRhiSampler::ClampToEdge;
2889 break;
2890 case QSGTexture::MirroredRepeat:
2891 v = QRhiSampler::Mirror;
2892 break;
2893 default:
2894 Q_UNREACHABLE();
2895 v = QRhiSampler::ClampToEdge;
2896 break;
2897 }
2898
2899 return rhi->newSampler(magFilter, minFilter, mipmapMode, u, v);
2900}
2901
2903{
2904 if (!m_dummyTexture) {
2906 if (m_dummyTexture->create()) {
2907 if (m_resourceUpdates) {
2909 img.fill(0);
2911 }
2912 }
2913 }
2914 return m_dummyTexture;
2915}
2916
2917static void rendererToMaterialGraphicsState(QSGMaterialShader::GraphicsPipelineState *dst,
2918 GraphicsState *src)
2919{
2920 dst->blendEnable = src->blending;
2921
2922 // the enum values should match, sanity check it
2923 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::OneMinusSrc1Alpha) == int(QRhiGraphicsPipeline::OneMinusSrc1Alpha));
2924 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::BlendOp::Max) == int(QRhiGraphicsPipeline::Max));
2925 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::A) == int(QRhiGraphicsPipeline::A));
2926 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::CullBack) == int(QRhiGraphicsPipeline::Back));
2927 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::Line) == int(QRhiGraphicsPipeline::Line));
2928 dst->srcColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcColor);
2929 dst->dstColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstColor);
2930
2931 // For compatibility with any existing code, separateBlendFactors defaults
2932 // to _false_ which means that materials that do not touch srcAlpha and
2933 // dstAlpha will continue to use srcColor and dstColor as the alpha
2934 // blending factors. New code that needs different values for color/alpha,
2935 // can explicitly set separateBlendFactors to true and then set srcAlpha
2936 // and dstAlpha as well.
2937 dst->separateBlendFactors = false;
2938
2939 dst->srcAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcAlpha);
2940 dst->dstAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstAlpha);
2941
2942 dst->opColor = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opColor);
2943 dst->opAlpha = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opAlpha);
2944
2945 dst->colorWrite = QSGMaterialShader::GraphicsPipelineState::ColorMask(int(src->colorWrite));
2946
2947 dst->cullMode = QSGMaterialShader::GraphicsPipelineState::CullMode(src->cullMode);
2948 dst->polygonMode = QSGMaterialShader::GraphicsPipelineState::PolygonMode(src->polygonMode);
2949}
2950
2952 QSGMaterialShader::GraphicsPipelineState *src)
2953{
2954 dst->blending = src->blendEnable;
2955 dst->srcColor = QRhiGraphicsPipeline::BlendFactor(src->srcColor);
2956 dst->dstColor = QRhiGraphicsPipeline::BlendFactor(src->dstColor);
2957 if (src->separateBlendFactors) {
2958 dst->srcAlpha = QRhiGraphicsPipeline::BlendFactor(src->srcAlpha);
2959 dst->dstAlpha = QRhiGraphicsPipeline::BlendFactor(src->dstAlpha);
2960 } else {
2961 dst->srcAlpha = dst->srcColor;
2962 dst->dstAlpha = dst->dstColor;
2963 }
2964 dst->opColor = QRhiGraphicsPipeline::BlendOp(src->opColor);
2965 dst->opAlpha = QRhiGraphicsPipeline::BlendOp(src->opAlpha);
2966 dst->colorWrite = QRhiGraphicsPipeline::ColorMask(int(src->colorWrite));
2967 dst->cullMode = QRhiGraphicsPipeline::CullMode(src->cullMode);
2968 dst->polygonMode = QRhiGraphicsPipeline::PolygonMode(src->polygonMode);
2969}
2970
2974 const Batch *batch,
2975 Element *e,
2976 int ubufOffset,
2977 char *directUpdatePtr)
2978{
2980
2984
2985 if (pd->ubufBinding >= 0) {
2988 m_current_uniform_data = nullptr;
2989
2990 if (changed || !batch->ubufDataValid) {
2991 if (directUpdatePtr)
2993 else
2995 }
2996
2998 pd->ubufStages,
2999 batch->ubuf,
3000 ubufOffset,
3002 }
3003
3006 if (!stages)
3007 continue;
3008
3011
3014
3017
3018 if (nextTex.contains(nullptr)) {
3019 qWarning("No QSGTexture provided from updateSampledImage(). This is wrong.");
3020 continue;
3021 }
3022
3023 bool hasDirtySamplerOptions = false;
3024 bool isAnisotropic = false;
3025 for (QSGTexture *t : nextTex) {
3030 }
3031
3032 // prevTex may be invalid at this point, avoid dereferencing it
3034
3035 // The QSGTexture, and so the sampler parameters, may have changed.
3036 // The rhiTexture is not relevant here.
3037 pd->textureBindingTable[binding] = nextTex; // does not own
3039
3040 if (isAnisotropic) // ###
3041 qWarning("QSGTexture anisotropy levels are not currently supported");
3042
3044
3045 for (QSGTexture *t : nextTex) {
3047
3049
3050 if (!sampler) {
3052 if (!sampler->create()) {
3053 qWarning("Failed to build sampler");
3054 delete sampler;
3055 continue;
3056 }
3058 }
3060 }
3061
3062 pd->samplerBindingTable[binding] = samplers; // does not own
3063 }
3064
3066
3068
3069 for (int i = 0; i < pd->textureBindingTable[binding].size(); ++i) {
3070
3072
3073 // texture may be null if the update above failed for any reason,
3074 // or if the QSGTexture chose to return null intentionally. This is
3075 // valid and we still need to provide something to the shader.
3076 if (!texture)
3078
3080
3083 }
3084
3085 if (!textureSamplers.isEmpty())
3088 }
3089 }
3090
3091#ifndef QT_NO_DEBUG
3092 if (bindings.isEmpty())
3093 qWarning("No shader resources for material %p, this is odd.", material);
3094#endif
3095
3096 enum class SrbAction {
3097 Unknown,
3098 DoNothing,
3100 Rebake
3102
3103 // First, if the Element has no srb created at all, then try to find an existing,
3104 // currently unused srb that is layout-compatible with our binding list.
3105 if (!e->srb) {
3106 // reuse a QVector as our work area, thus possibly reusing the underlying allocation too
3108 layoutDesc.clear();
3111 if (e->srb) {
3112 // Here we know layout compatibility is satisfied, but do not spend time on full
3113 // comparison. The chance of getting an srb that refers to the same resources
3114 // (buffer, textures) is low in practice. So reuse, but write new resources.
3116 }
3117 }
3118
3119 // If the Element had an existing srb, investigate:
3120 // - It may be used as-is (when nothing changed in the scene regarding this node compared to the previous frame).
3121 // - Otherwise it may be able to go with a lightweight update (replace resources, binding list layout is the same).
3122 // - If all else fails rebake the full thing, meaning we reuse the memory allocation but will recreate everything underneath.
3123 if (srbAction == SrbAction::Unknown && e->srb) {
3127 [](const auto &a, const auto &b) { return a.isLayoutCompatible(b); }))
3128 {
3130 } else {
3132 }
3133 }
3134
3135 // If the Element had no srb associated at all and could not find a layout-compatible
3136 // one from the pool, then create a whole new object.
3137 if (!e->srb) {
3140 }
3141
3143
3144 switch (srbAction) {
3145 case SrbAction::DoNothing:
3146 break;
3148 {
3151 // Due to the way the binding list is built up above, if we have a uniform buffer
3152 // at binding point 0 (or none at all) then the sampledTexture bindings are added
3153 // with increasing binding points afterwards, so the list is already sorted based
3154 // on the binding points, thus we can save some time by telling the QRhi backend
3155 // not to sort again.
3156 if (pd->ubufBinding <= 0 || bindings.size() <= 1)
3158
3160 }
3161 break;
3162 case SrbAction::Rebake:
3164 if (!e->srb->create())
3165 qWarning("Failed to build srb");
3166 break;
3167 default:
3168 Q_ASSERT_X(false, "updateMaterialDynamicData", "No srb action set, this cannot happen");
3169 }
3170}
3171
3175 Batch *batch,
3176 bool *gstateChanged)
3177{
3179 *gstateChanged = false;
3181 // generate the public mini-state from m_gstate, invoke the material,
3182 // write the changes, if any, back to m_gstate, together with a way to
3183 // roll those back.
3187 if (changed) {
3192 {
3194 }
3195 *gstateChanged = true;
3196 }
3197 }
3198}
3199
3201{
3202 if (batch->vertexCount == 0 || batch->indexCount == 0)
3203 return false;
3204
3205 Element *e = batch->first;
3206 Q_ASSERT(e);
3207
3208#ifndef QT_NO_DEBUG_OUTPUT
3209 if (Q_UNLIKELY(debug_render())) {
3210 QDebug debug = qDebug();
3211 debug << " -"
3212 << batch
3213 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3214 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3215 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3216 << "[ merged]"
3217 << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
3218 << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
3219 << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
3220 << " root:" << batch->root
3221 << " mutability group: " << e->mutabilityGroup;
3222 if (batch->drawSets.size() > 1)
3223 debug << "sets:" << batch->drawSets.size();
3224 if (!batch->isOpaque)
3225 debug << "opacity:" << e->node->inheritedOpacity();
3226 batch->uploadedThisFrame = false;
3227 }
3228#endif
3229
3231
3232 // We always have dirty matrix as all batches are at a unique z range.
3234 if (batch->root)
3236 else
3239
3240 const int viewCount = projectionMatrixCount();
3242 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3244
3248
3252
3253 const QSGGeometry *g = gn->geometry();
3257 if (!sms)
3258 return false;
3259
3261 if (m_currentShader != sms)
3263
3268 }
3269
3272 if (pd->ubufBinding >= 0) {
3273 bool ubufRebuild = false;
3274 if (!batch->ubuf) {
3276 ubufRebuild = true;
3277 } else {
3278 if (batch->ubuf->size() < ubufSize) {
3280 ubufRebuild = true;
3281 }
3282 }
3283 if (ubufRebuild) {
3284 batch->ubufDataValid = false;
3285 if (!batch->ubuf->create()) {
3286 qWarning("Failed to build uniform buffer of size %u bytes", ubufSize);
3287 delete batch->ubuf;
3288 batch->ubuf = nullptr;
3289 return false;
3290 }
3291 }
3292 }
3293
3295
3296 bool pendingGStatePop = false;
3298
3299 char *directUpdatePtr = nullptr;
3300 if (batch->ubuf->nativeBuffer().slotCount == 0)
3302
3304
3305 if (directUpdatePtr)
3307
3310
3311 const bool hasPipeline = ensurePipelineState(e, sms);
3312
3313 if (pendingGStatePop)
3315
3316 if (!hasPipeline)
3317 return false;
3318
3322 ensurePipelineState(e, sms, true);
3324 }
3325
3326 batch->ubufDataValid = true;
3327
3329
3331 renderBatch->sms = sms;
3332
3333 return true;
3334}
3335
3337{
3340 {
3341 if (g->lineWidth() != 1.0f) {
3342 static bool checkedWideLineSupport = false;
3346 qWarning("Line widths other than 1 are not supported by the graphics API");
3347 }
3348 }
3349 } else if (g->drawingMode() == QSGGeometry::DrawPoints) {
3350 if (g->lineWidth() != 1.0f) {
3351 static bool warnedPointSize = false;
3352 if (!warnedPointSize) {
3353 warnedPointSize = true;
3354 qWarning("Point size is not controllable by QSGGeometry. "
3355 "Set gl_PointSize from the vertex shader instead.");
3356 }
3357 }
3358 }
3359}
3360
3362{
3363 const Batch *batch = renderBatch->batch;
3364 if (!batch->vbo.buf || !batch->ibo.buf)
3365 return;
3366
3367 Element *e = batch->first;
3369 QSGGeometry *g = gn->geometry();
3371
3374
3377
3378 for (int i = 0, ie = batch->drawSets.size(); i != ie; ++i) {
3379 const DrawSet &draw = batch->drawSets.at(i);
3383 };
3388 }
3389}
3390
3392{
3393 if (batch->vertexCount == 0)
3394 return false;
3395
3396 Element *e = batch->first;
3397 Q_ASSERT(e);
3398
3399 if (Q_UNLIKELY(debug_render())) {
3400 qDebug() << " -"
3401 << batch
3402 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3403 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3404 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3405 << "[unmerged]"
3406 << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
3407 << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
3408 << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
3409 << " root:" << batch->root
3410 << " mutability group: " << e->mutabilityGroup;
3411
3412
3413 batch->uploadedThisFrame = false;
3414 }
3415
3416 const int viewCount = projectionMatrixCount();
3418 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3420
3424
3428
3429 // We always have dirty matrix as all batches are at a unique z range.
3431
3432 // The vertex attributes are assumed to be the same for all elements in the
3433 // unmerged batch since the material (and so the shaders) is the same.
3434 QSGGeometry *g = gn->geometry();
3437 if (!sms)
3438 return false;
3439
3441 if (m_currentShader != sms)
3443
3448 }
3449
3451
3454 if (pd->ubufBinding >= 0) {
3456 while (e) {
3458 e = e->nextInBatch;
3459 }
3460 bool ubufRebuild = false;
3461 if (!batch->ubuf) {
3463 ubufRebuild = true;
3464 } else {
3465 if (batch->ubuf->size() < totalUBufSize) {
3467 ubufRebuild = true;
3468 }
3469 }
3470 if (ubufRebuild) {
3471 batch->ubufDataValid = false;
3472 if (!batch->ubuf->create()) {
3473 qWarning("Failed to build uniform buffer of size %u bytes", totalUBufSize);
3474 delete batch->ubuf;
3475 batch->ubuf = nullptr;
3476 return false;
3477 }
3478 }
3479 }
3480
3482 bool pendingGStatePop = false;
3485
3486 int ubufOffset = 0;
3487 QRhiGraphicsPipeline *ps = nullptr;
3489 e = batch->first;
3490
3491 char *directUpdatePtr = nullptr;
3492 if (batch->ubuf->nativeBuffer().slotCount == 0)
3494
3495 while (e) {
3496 gn = e->node;
3497
3500
3501 const int viewCount = projectionMatrixCount();
3503 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3505
3509
3510 if (useDepthBuffer()) {
3511 // this cannot be multiview
3514 }
3515
3518
3520
3522 const float prevLineWidth = m_gstate.lineWidth;
3525
3526 // Do not bother even looking up the ps if the topology has not changed
3527 // since everything else is the same for all elements in the batch.
3528 // (except if the material modified blend state)
3530 if (!ensurePipelineState(e, sms)) {
3531 if (pendingGStatePop)
3533 return false;
3534 }
3535 ps = e->ps;
3539 ensurePipelineState(e, sms, true);
3542 }
3543 } else {
3544 e->ps = ps;
3547 }
3548
3549 // We don't need to bother with asking each node for its material as they
3550 // are all identical (compare==0) since they are in the same batch.
3552
3553 // We only need to push this on the very first iteration...
3555
3556 e = e->nextInBatch;
3557 }
3558
3559 if (directUpdatePtr)
3561
3562 if (pendingGStatePop)
3564
3565 batch->ubufDataValid = true;
3566
3568 renderBatch->sms = sms;
3569
3570 return true;
3571}
3572
3574{
3575 const Batch *batch = renderBatch->batch;
3576 if (!batch->vbo.buf)
3577 return;
3578
3579 Element *e = batch->first;
3580
3583
3584 quint32 vOffset = 0;
3585 quint32 iOffset = 0;
3587
3588 while (e) {
3589 QSGGeometry *g = e->node->geometry();
3591 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
3592
3594
3596 if (g->indexCount()) {
3597 if (batch->ibo.buf) {
3599 batch->ibo.buf, iOffset,
3603 }
3604 } else {
3606 cb->draw(g->vertexCount());
3607 }
3608
3609 vOffset += g->sizeOfVertex() * g->vertexCount();
3611
3612 e = e->nextInBatch;
3613 }
3614}
3615
3617{
3618 if (!m_pstate.viewportSet) {
3619 m_pstate.viewportSet = true;
3621 }
3623 m_pstate.scissorSet = true;
3625 } else {
3626 // Regardless of the ps not using scissor, the scissor may need to be
3627 // reset, depending on the backend. So set the viewport again, which in
3628 // turn also sets the scissor on backends where a scissor rect is
3629 // always-on (Vulkan).
3630 if (m_pstate.scissorSet) {
3631 m_pstate.scissorSet = false;
3633 }
3634 }
3635}
3636
3637
3639{
3641
3643
3647 }
3650
3652}
3653
3655{
3656 if (e->isRenderNode) {
3657 delete static_cast<RenderNodeElement *>(e);
3658 } else {
3659 if (e->srb) {
3660 if (!inDestructor) {
3663 else
3664 delete e->srb;
3665 } else {
3666 delete e->srb;
3667 }
3668 e->srb = nullptr;
3669 }
3671 }
3672}
3673
3675{
3676 if (!m_elementsToDelete.size())
3677 return;
3678
3679 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3681 if (*e && (*e)->removed)
3682 *e = nullptr;
3683 }
3684 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3686 if (*e && (*e)->removed)
3687 *e = nullptr;
3688 }
3689
3690 for (int i=0; i<m_elementsToDelete.size(); ++i)
3692
3694}
3695
3697{
3698 // Gracefully handle the lack of a render target - some autotests may rely
3699 // on this in odd cases.
3700 if (!renderTarget().rt)
3701 return;
3702
3707}
3708
3709// An alternative to render() is to call prepareInline() and renderInline() at
3710// the appropriate times (i.e. outside of a QRhi::beginPass() and then inside,
3711// respectively) These allow rendering within a render pass that is started by
3712// another component. In contrast, render() records a full render pass on its
3713// own.
3714
3719
3724
3726{
3727 if (ctx->valid)
3728 qWarning("prepareRenderPass() called with an already prepared render pass context");
3729
3730 ctx->valid = true;
3731
3732 if (Q_UNLIKELY(debug_dump())) {
3733 qDebug("\n");
3735 }
3736
3737 ctx->timeRenderLists = 0;
3739 ctx->timePrepareAlpha = 0;
3740 ctx->timeSorting = 0;
3741 ctx->timeUploadOpaque = 0;
3742 ctx->timeUploadAlpha = 0;
3743
3744 if (Q_UNLIKELY(debug_render() || debug_build())) {
3745 QByteArray type("rebuild:");
3746 if (m_rebuild == 0)
3747 type += " none";
3748 if (m_rebuild == FullRebuild)
3749 type += " full";
3750 else {
3752 type += " renderlists";
3754 type += " partial";
3755 else if (m_rebuild & BuildBatches)
3756 type += " batches";
3757 }
3758
3759 qDebug() << "Renderer::render()" << this << type;
3760 ctx->timer.start();
3761 }
3762
3764
3766 bool complete = (m_rebuild & BuildRenderLists) != 0;
3767 if (complete)
3769 else
3772
3773 if (Q_UNLIKELY(debug_build())) {
3774 qDebug("Opaque render lists %s:", (complete ? "(complete)" : "(partial)"));
3775 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3777 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3778 }
3779 qDebug("Alpha render list %s:", complete ? "(complete)" : "(partial)");
3780 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3782 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3783 }
3784 }
3785 }
3787
3788 for (int i=0; i<m_opaqueBatches.size(); ++i)
3790 for (int i=0; i<m_alphaBatches.size(); ++i)
3793
3796
3797 if (m_rebuild & BuildBatches) {
3802
3803 if (Q_UNLIKELY(debug_build())) {
3804 qDebug("Opaque Batches:");
3805 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3807 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3808 for (Element *e = b->first; e; e = e->nextInBatch) {
3809 qDebug() << " - element:" << e << " node:" << e->node << e->order;
3810 }
3811 }
3812 qDebug("Alpha Batches:");
3813 for (int i=0; i<m_alphaBatches.size(); ++i) {
3815 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3816 for (Element *e = b->first; e; e = e->nextInBatch) {
3817 qDebug() << " - element:" << e << e->bounds << " node:" << e->node << " order:" << e->order;
3818 }
3819 }
3820 }
3821 } else {
3823 }
3824
3825
3827
3828 if (m_rebuild != 0) {
3829 // Then sort opaque batches so that we're drawing the batches with the highest
3830 // order first, maximizing the benefit of front-to-back z-ordering.
3831 if (m_opaqueBatches.size())
3833
3834 // Sort alpha batches back to front so that they render correctly.
3835 if (m_alphaBatches.size())
3837
3839 ? 1.0 / (m_nextRenderOrder)
3840 : 0;
3841 }
3842
3844
3845 // Set size to 0, nothing is deallocated, they will "grow" again
3846 // as part of uploadBatch.
3849
3850 if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Opaque Batches:");
3851 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3853 uploadBatch(b);
3854 }
3856
3857 if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Alpha Batches:");
3858 for (int i=0; i<m_alphaBatches.size(); ++i) {
3860 uploadBatch(b);
3861 }
3863
3864 if (Q_UNLIKELY(debug_render())) {
3865 qDebug().nospace() << "Rendering:" << Qt::endl
3866 << " -> Opaque: " << qsg_countNodesInBatches(m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << Qt::endl
3867 << " -> Alpha: " << qsg_countNodesInBatches(m_alphaBatches) << " nodes in " << m_alphaBatches.size() << " batches...";
3868 }
3869
3871 m_currentMaterial = nullptr;
3872 m_currentShader = nullptr;
3873 m_currentProgram = nullptr;
3875
3876 const QRect viewport = viewportRect();
3877
3878 bool renderOpaque = !debug_noopaque();
3879 bool renderAlpha = !debug_noalpha();
3880
3886 m_pstate.viewportSet = false;
3887 m_pstate.scissorSet = false;
3888
3892 m_gstate.blending = false;
3893
3900 m_gstate.usesScissor = false;
3901 m_gstate.stencilTest = false;
3902
3905
3907 if (Q_LIKELY(renderOpaque)) {
3908 for (int i = 0, ie = m_opaqueBatches.size(); i != ie; ++i) {
3911 bool ok;
3912 if (b->merged)
3914 else
3916 if (ok)
3918 }
3919 }
3920
3921 m_gstate.blending = true;
3922 // factors never change, always set for premultiplied alpha based blending
3923
3924 // depth test stays enabled (if useDepthBuffer(), that is) but no need
3925 // to write out depth from the transparent (back-to-front) pass
3926 m_gstate.depthWrite = false;
3927
3928 // special case: the 3D plane mode tests against the depth buffer, but does
3929 // not write (and all batches are alpha because this render mode evaluates
3930 // to useDepthBuffer()==false)
3933 m_gstate.depthTest = true;
3934 }
3935
3937 if (Q_LIKELY(renderAlpha)) {
3938 for (int i = 0, ie = m_alphaBatches.size(); i != ie; ++i) {
3941 bool ok;
3942 if (b->merged)
3944 else if (b->isRenderNode)
3946 else
3948 if (ok)
3950 }
3951 }
3952
3953 m_rebuild = 0;
3954
3955#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
3958#endif
3959
3962
3964 m_resourceUpdates = nullptr;
3965}
3966
3968{
3971 // we cannot tell if the application will have
3972 // native rendering thrown in to this pass
3973 // (QQuickWindow::beginExternalCommands()), so
3974 // we have no choice but to set the flag always
3975 // (thus triggering using secondary command
3976 // buffers with Vulkan)
3978 // We do not use GPU compute at all at the moment, this means we can
3979 // get a small performance gain with OpenGL by declaring this.
3981
3984}
3985
3987{
3988 // prepareRenderPass and recordRenderPass must always be called together.
3989 // They are separate because beginRenderPass and endRenderPass are optional.
3990 //
3991 // The valid call sequence are therefore:
3992 // prepare, begin, record, end
3993 // or
3994 // prepare, record
3995
3996 if (!ctx->valid)
3997 qWarning("recordRenderPass() called without a prepared render pass context");
3998
3999 ctx->valid = false;
4000
4002 cb->debugMarkBegin(QByteArrayLiteral("Qt Quick scene render"));
4003
4004 for (int i = 0, ie = ctx->opaqueRenderBatches.size(); i != ie; ++i) {
4005 if (i == 0)
4006 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick opaque batches"));
4008 if (renderBatch->batch->merged)
4010 else
4012 }
4013
4014 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
4015 if (i == 0) {
4017 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D batches"));
4018 else
4019 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick alpha batches"));
4020 }
4022 if (renderBatch->batch->merged)
4024 else if (renderBatch->batch->isRenderNode)
4026 else
4028 }
4029
4031 // Depth post-pass to fill up the depth buffer in a way that it
4032 // corresponds to what got rendered to the color buffer in the previous
4033 // (alpha) pass. The previous pass cannot enable depth write due to Z
4034 // fighting. Rather, do it separately in a dedicated color-write-off,
4035 // depth-write-on pass. This enables the 3D content drawn afterwards to
4036 // depth test against the 2D items' rendering.
4037 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
4038 if (i == 0)
4039 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D depth post-pass"));
4041 if (renderBatch->batch->merged)
4043 else if (!renderBatch->batch->isRenderNode) // rendernodes are skipped here for now
4045 }
4046 }
4047
4048 if (m_currentShader)
4049 setActiveRhiShader(nullptr, nullptr);
4050
4051 cb->debugMarkEnd();
4052
4053 if (Q_UNLIKELY(debug_render())) {
4054 qDebug(" -> times: build: %d, prepare(opaque/alpha): %d/%d, sorting: %d, upload(opaque/alpha): %d/%d, record rendering: %d",
4055 (int) ctx->timeRenderLists,
4057 (int) ctx->timeSorting,
4058 (int) ctx->timeUploadOpaque, (int) ctx->timeUploadAlpha,
4059 (int) ctx->timer.elapsed());
4060 }
4061}
4062
4073
4075{
4076 const QMatrix4x4 *projectionMatrix() const override { return m_projectionMatrix; }
4077 QRect scissorRect() const override { return m_scissorRect; }
4079 int stencilValue() const override { return m_stencilValue; }
4081 const QRegion *clipRegion() const override { return nullptr; }
4082
4088};
4089
4091{
4092 if (Q_UNLIKELY(debug_render()))
4093 qDebug() << " -" << batch << "rendernode";
4094
4095 const int viewCount = projectionMatrixCount();
4097 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
4102
4104 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4105
4106 setActiveRhiShader(nullptr, nullptr);
4107
4109 rd->m_clip_list = nullptr;
4112 while (clip != rootNode()) {
4113 if (clip->type() == QSGNode::ClipNodeType) {
4114 rd->m_clip_list = static_cast<QSGClipNode *>(clip);
4115 break;
4116 }
4117 clip = clip->parent();
4118 }
4120 }
4121
4124 QSGNode *root = rootNode();
4125 if (e->root) {
4127 root = e->root->sgNode;
4128 }
4129 while (xform != root) {
4130 if (xform->type() == QSGNode::TransformNodeType) {
4131 matrix = matrix * static_cast<QSGTransformNode *>(xform)->combinedMatrix();
4132 break;
4133 }
4134 xform = xform->parent();
4135 }
4138
4140 rd->m_opacity = 1.0;
4141 while (opacity != rootNode()) {
4142 if (opacity->type() == QSGNode::OpacityNodeType) {
4143 rd->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity();
4144 break;
4145 }
4146 opacity = opacity->parent();
4147 }
4148
4149 rd->m_rt = renderTarget();
4150
4152 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
4154
4155 if (useDepthBuffer()) {
4156 // this cannot be multiview
4157 rd->m_projectionMatrix[0](2, 2) = m_zRange;
4159 }
4160
4161 e->renderNode->prepare();
4162
4164 renderBatch->sms = nullptr;
4165
4166 return true;
4167}
4168
4170{
4173
4174 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4176
4178 // Expose only the first matrix through the state object, the rest are
4179 // queriable through the QSGRenderNode getters anyway.
4181 const std::array<int, 4> scissor = batch->clipState.scissor.scissor();
4186
4188
4192 if (needsExternal)
4193 cb->beginExternal();
4195 if (needsExternal)
4196 cb->endExternal();
4197
4198 rd->m_matrix = nullptr;
4199 rd->m_clip_list = nullptr;
4200
4203 {
4204 // Reset both flags if either is reported as changed, since with the rhi
4205 // it could be setViewport() that will record the resetting of the scissor.
4206 m_pstate.viewportSet = false;
4207 m_pstate.scissorSet = false;
4208 }
4209
4210 // Do not bother with RenderTargetState. Where applicable, endExternal()
4211 // ensures the correct target is rebound. For others (like Vulkan) it makes
4212 // no sense since render() could not possibly do that on our command buffer
4213 // which is in renderpass recording state.
4214}
4215
4217{
4218 if (mode.isEmpty())
4220 else if (mode == "clip")
4222 else if (mode == "overdraw")
4224 else if (mode == "batches")
4226 else if (mode == "changes")
4228}
4229
4234
4235bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
4236{
4237 return a.depthTest == b.depthTest
4238 && a.depthWrite == b.depthWrite
4239 && a.depthFunc == b.depthFunc
4240 && a.blending == b.blending
4241 && a.srcColor == b.srcColor
4242 && a.dstColor == b.dstColor
4243 && a.srcAlpha == b.srcAlpha
4244 && a.dstAlpha == b.dstAlpha
4245 && a.opColor == b.opColor
4246 && a.opAlpha == b.opAlpha
4247 && a.colorWrite == b.colorWrite
4248 && a.cullMode == b.cullMode
4252 && a.drawMode == b.drawMode
4253 && a.lineWidth == b.lineWidth
4254 && a.polygonMode == b.polygonMode
4256}
4257
4258bool operator!=(const GraphicsState &a, const GraphicsState &b) noexcept
4259{
4260 return !(a == b);
4261}
4262
4263size_t qHash(const GraphicsState &s, size_t seed) noexcept
4264{
4265 // do not bother with all fields
4266 return seed
4267 + s.depthTest * 1000
4268 + s.depthWrite * 100
4269 + s.depthFunc
4270 + s.blending * 10
4271 + s.srcColor
4272 + s.cullMode
4273 + s.usesScissor
4274 + s.stencilTest
4275 + s.sampleCount
4276 + s.multiViewCount;
4277}
4278
4280{
4281 return a.state == b.state
4282 && a.sms->materialShader == b.sms->materialShader
4283 && a.renderTargetDescription == b.renderTargetDescription
4284 && a.srbLayoutDescription == b.srbLayoutDescription;
4285}
4286
4288{
4289 return !(a == b);
4290}
4291
4292size_t qHash(const GraphicsPipelineStateKey &k, size_t seed) noexcept
4293{
4294 return qHash(k.state, seed)
4295 ^ qHash(k.sms->materialShader)
4296 ^ k.extra.renderTargetDescriptionHash
4297 ^ k.extra.srbLayoutDescriptionHash;
4298}
4299
4300bool operator==(const ShaderKey &a, const ShaderKey &b) noexcept
4301{
4302 return a.type == b.type
4303 && a.renderMode == b.renderMode
4305}
4306
4307bool operator!=(const ShaderKey &a, const ShaderKey &b) noexcept
4308{
4309 return !(a == b);
4310}
4311
4312size_t qHash(const ShaderKey &k, size_t seed) noexcept
4313{
4314 return qHash(k.type, seed) ^ int(k.renderMode) ^ k.multiViewCount;
4315}
4316
4317Visualizer::Visualizer(Renderer *renderer)
4318 : m_renderer(renderer),
4320{
4321}
4322
4324{
4325}
4326
4327#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded
4328 | QSGNode::DirtyOpacity
4329 | QSGNode::DirtyMatrix
4330 | QSGNode::DirtyNodeRemoved)
4331
4332void Visualizer::visualizeChangesPrepare(Node *n, uint parentChanges)
4333{
4334 uint childDirty = (parentChanges | n->dirtyState) & QSGNODE_DIRTY_PARENT;
4335 uint selfDirty = n->dirtyState | parentChanges;
4336 if (n->type() == QSGNode::GeometryNodeType && selfDirty != 0)
4337 m_visualizeChangeSet.insert(n, selfDirty);
4339 visualizeChangesPrepare(child, childDirty);
4340 }
4341}
4342
4343} // namespace QSGBatchRenderer
4344
4345QT_END_NAMESPACE
4346
4347#include "moc_qsgbatchrenderer_p.cpp"
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:323
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:2582
#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 set(float left, float top, float right, float bottom)
void map(const QMatrix4x4 &m)
const QMatrix4x4 * projectionMatrix() const override
const QRegion * clipRegion() const override