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 <algorithm>
22
24
25int qt_sg_envInt(const char *name, int defaultValue);
26
28{
29
30#define DECLARE_DEBUG_VAR(variable)
31 static bool debug_ ## variable()
32 { static bool value = qgetenv("QSG_RENDERER_DEBUG").contains(QT_STRINGIFY(variable)); return value; }
40DECLARE_DEBUG_VAR(noalpha)
41DECLARE_DEBUG_VAR(noopaque)
43#undef DECLARE_DEBUG_VAR
44
45#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling())
46#define SHADOWNODE_TRAVERSE(NODE) for (Node *child = NODE->firstChild(); child; child = child->sibling())
47
48static inline int size_of_type(int type)
49{
50 static int sizes[] = {
51 sizeof(char),
52 sizeof(unsigned char),
53 sizeof(short),
54 sizeof(unsigned short),
55 sizeof(int),
56 sizeof(unsigned int),
57 sizeof(float),
58 2,
59 3,
60 4,
61 sizeof(double)
62 };
63 Q_ASSERT(type >= QSGGeometry::ByteType && type <= QSGGeometry::DoubleType);
64 return sizes[type - QSGGeometry::ByteType];
65}
66
71
73
74static bool isTranslate(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Translation; }
75static bool isScale(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Scale; }
76static bool is2DSafe(const QMatrix4x4 &m) { return m.flags() < QMatrix4x4::Rotation; }
77
78const float OPAQUE_LIMIT = 0.999f;
79
83
84const float VIEWPORT_MIN_DEPTH = 0.0f;
85const float VIEWPORT_MAX_DEPTH = 1.0f;
86
87const quint32 DEFAULT_BUFFER_POOL_SIZE_LIMIT = 2 * 1024 * 1024; // 2 MB for m_vboPool and m_iboPool each
88
89template <class Int>
90inline Int aligned(Int v, Int byteAlign)
91{
92 return (v + byteAlign - 1) & ~(byteAlign - 1);
93}
94
95QRhiVertexInputAttribute::Format qsg_vertexInputFormat(const QSGGeometry::Attribute &a)
96{
97 switch (a.type) {
98 case QSGGeometry::FloatType:
99 if (a.tupleSize == 4)
100 return QRhiVertexInputAttribute::Float4;
101 if (a.tupleSize == 3)
102 return QRhiVertexInputAttribute::Float3;
103 if (a.tupleSize == 2)
104 return QRhiVertexInputAttribute::Float2;
105 if (a.tupleSize == 1)
106 return QRhiVertexInputAttribute::Float;
107 break;
108 case QSGGeometry::UnsignedByteType:
109 if (a.tupleSize == 4)
110 return QRhiVertexInputAttribute::UNormByte4;
111 if (a.tupleSize == 2)
112 return QRhiVertexInputAttribute::UNormByte2;
113 if (a.tupleSize == 1)
114 return QRhiVertexInputAttribute::UNormByte;
115 break;
116 default:
117 break;
118 }
119 qWarning("Unsupported attribute type 0x%x with %d components", a.type, a.tupleSize);
120 Q_UNREACHABLE_RETURN(QRhiVertexInputAttribute::Float);
121}
122
123static QRhiVertexInputLayout calculateVertexInputLayout(const QSGMaterialShader *s, const QSGGeometry *geometry, bool batchable)
124{
125 Q_ASSERT(geometry);
126 const QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
127 if (!sd->vertexShader) {
128 qWarning("No vertex shader in QSGMaterialShader %p", s);
129 return QRhiVertexInputLayout();
130 }
131
132 const int attrCount = geometry->attributeCount();
133 QVarLengthArray<QRhiVertexInputAttribute, 8> inputAttributes;
134 inputAttributes.reserve(attrCount + 1);
135 quint32 offset = 0;
136 for (int i = 0; i < attrCount; ++i) {
137 const QSGGeometry::Attribute &a = geometry->attributes()[i];
138 if (!sd->vertexShader->vertexInputLocations.contains(a.position)) {
139 qWarning("Vertex input %d is present in material but not in shader. This is wrong.",
140 a.position);
141 }
142 inputAttributes.append(QRhiVertexInputAttribute(VERTEX_BUFFER_BINDING, a.position, qsg_vertexInputFormat(a), offset));
143 offset += a.tupleSize * size_of_type(a.type);
144 }
145 if (batchable) {
146 inputAttributes.append(QRhiVertexInputAttribute(ZORDER_BUFFER_BINDING, sd->vertexShader->qt_order_attrib_location,
147 QRhiVertexInputAttribute::Float, 0));
148 }
149
150 Q_ASSERT(VERTEX_BUFFER_BINDING == 0 && ZORDER_BUFFER_BINDING == 1); // not very flexible
151 QVarLengthArray<QRhiVertexInputBinding, 2> inputBindings;
152 inputBindings.append(QRhiVertexInputBinding(geometry->sizeOfVertex()));
153 if (batchable)
154 inputBindings.append(QRhiVertexInputBinding(sizeof(float)));
155
156 QRhiVertexInputLayout inputLayout;
157 inputLayout.setBindings(inputBindings.cbegin(), inputBindings.cend());
158 inputLayout.setAttributes(inputAttributes.cbegin(), inputAttributes.cend());
159
160 return inputLayout;
161}
162
163QRhiCommandBuffer::IndexFormat qsg_indexFormat(const QSGGeometry *geometry)
164{
165 switch (geometry->indexType()) {
166 case QSGGeometry::UnsignedShortType:
167 return QRhiCommandBuffer::IndexUInt16;
168 break;
169 case QSGGeometry::UnsignedIntType:
170 return QRhiCommandBuffer::IndexUInt32;
171 break;
172 default:
173 Q_UNREACHABLE_RETURN(QRhiCommandBuffer::IndexUInt16);
174 }
175}
176
177QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode, QRhi *rhi)
178{
179 QRhiGraphicsPipeline::Topology topology = QRhiGraphicsPipeline::Triangles;
180 switch (geomDrawMode) {
181 case QSGGeometry::DrawPoints:
182 topology = QRhiGraphicsPipeline::Points;
183 break;
184 case QSGGeometry::DrawLines:
185 topology = QRhiGraphicsPipeline::Lines;
186 break;
187 case QSGGeometry::DrawLineStrip:
188 topology = QRhiGraphicsPipeline::LineStrip;
189 break;
190 case QSGGeometry::DrawTriangles:
191 topology = QRhiGraphicsPipeline::Triangles;
192 break;
193 case QSGGeometry::DrawTriangleStrip:
194 topology = QRhiGraphicsPipeline::TriangleStrip;
195 break;
196 case QSGGeometry::DrawTriangleFan:
197 {
198 static bool triangleFanSupported = false;
199 static bool triangleFanSupportChecked = false;
200 if (!triangleFanSupportChecked) {
201 triangleFanSupportChecked = true;
202 triangleFanSupported = rhi->isFeatureSupported(QRhi::TriangleFanTopology);
203 }
204 if (triangleFanSupported) {
205 topology = QRhiGraphicsPipeline::TriangleFan;
206 break;
207 }
208 Q_FALLTHROUGH();
209 }
210 default:
211 qWarning("Primitive topology 0x%x not supported", geomDrawMode);
212 break;
213 }
214 return topology;
215}
216
217void qsg_setMultiViewFlagsOnMaterial(QSGMaterial *material, int multiViewCount)
218{
219 material->setFlag(QSGMaterial::MultiView2, multiViewCount == 2);
220 material->setFlag(QSGMaterial::MultiView3, multiViewCount == 3);
221 material->setFlag(QSGMaterial::MultiView4, multiViewCount == 4);
222}
223
224ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material,
225 const QSGGeometry *geometry,
226 QSGRendererInterface::RenderMode renderMode,
227 int multiViewCount)
228{
229 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
230
231 QSGMaterialType *type = material->type();
232 ShaderKey key = { type, renderMode, multiViewCount };
233 Shader *shader = rewrittenShaders.value(key, nullptr);
234 if (shader)
235 return shader;
236
237 shader = new Shader;
238 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
239 context->initializeRhiShader(s, QShader::BatchableVertexShader);
240 shader->materialShader = s;
241 shader->inputLayout = calculateVertexInputLayout(s, geometry, true);
242 QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
243 shader->stages = {
244 { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage), QShader::BatchableVertexShader },
245 { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
246 };
247
248 shader->lastOpacity = 0;
249
250 rewrittenShaders[key] = shader;
251 return shader;
252}
253
254ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material,
255 const QSGGeometry *geometry,
256 QSGRendererInterface::RenderMode renderMode,
257 int multiViewCount)
258{
259 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
260
261 QSGMaterialType *type = material->type();
262 ShaderKey key = { type, renderMode, multiViewCount };
263 Shader *shader = stockShaders.value(key, nullptr);
264 if (shader)
265 return shader;
266
267 shader = new Shader;
268 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
269 context->initializeRhiShader(s, QShader::StandardShader);
270 shader->materialShader = s;
271 shader->inputLayout = calculateVertexInputLayout(s, geometry, false);
272 QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
273 shader->stages = {
274 { QRhiShaderStage::Vertex, sD->shader(QShader::VertexStage) },
275 { QRhiShaderStage::Fragment, sD->shader(QShader::FragmentStage) }
276 };
277
278 shader->lastOpacity = 0;
279
280 stockShaders[key] = shader;
281
282 return shader;
283}
284
285void ShaderManager::invalidated()
286{
287 qDeleteAll(stockShaders);
288 stockShaders.clear();
289 qDeleteAll(rewrittenShaders);
290 rewrittenShaders.clear();
291
292 qDeleteAll(pipelineCache);
293 pipelineCache.clear();
294
295 qDeleteAll(srbPool);
296 srbPool.clear();
297}
298
300{
301 for (ShaderManager::Shader *sms : std::as_const(stockShaders)) {
302 QSGMaterialShader *s = sms->materialShader;
303 if (s) {
304 QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
305 sd->clearCachedRendererData();
306 }
307 }
308 for (ShaderManager::Shader *sms : std::as_const(rewrittenShaders)) {
309 QSGMaterialShader *s = sms->materialShader;
310 if (s) {
311 QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
312 sd->clearCachedRendererData();
313 }
314 }
315}
316
318{
319 static int extraIndent = 0;
320 ++extraIndent;
321
322 QByteArray ind(indent + extraIndent + 10, ' ');
323
324 if (!i) {
325 qDebug("%s - no info", ind.constData());
326 } else {
327 qDebug() << ind.constData() << "- parent:" << i->parentRoot << "orders" << i->firstOrder << "->" << i->lastOrder << ", avail:" << i->availableOrders;
328 for (QSet<Node *>::const_iterator it = i->subRoots.constBegin();
329 it != i->subRoots.constEnd(); ++it) {
330 qDebug() << ind.constData() << "-" << *it;
331 qsg_dumpShadowRoots((*it)->rootInfo(), indent);
332 }
333 }
334
335 --extraIndent;
336}
337
339{
340#ifndef QT_NO_DEBUG_OUTPUT
341 static int indent = 0;
342 ++indent;
343
344 QByteArray ind(indent, ' ');
345
346 if (n->type() == QSGNode::ClipNodeType || n->isBatchRoot) {
347 qDebug() << ind.constData() << "[X]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
349 } else {
350 QDebug d = qDebug();
351 d << ind.constData() << "[ ]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
352 if (n->type() == QSGNode::GeometryNodeType)
353 d << "order" << Qt::dec << n->element()->order;
354 }
355
358
359 --indent;
360#else
361 Q_UNUSED(n);
362#endif
363}
364
365Updater::Updater(Renderer *r)
366 : renderer(r)
367 , m_roots(32)
368 , m_rootMatrices(8)
369{
370 m_roots.add(0);
371 m_combined_matrix_stack.add(&m_identityMatrix);
372 m_rootMatrices.add(m_identityMatrix);
373}
374
375void Updater::updateStates(QSGNode *n)
376{
377 m_current_clip = nullptr;
378
379 m_added = 0;
380 m_transformChange = 0;
381 m_opacityChange = 0;
382
383 Node *sn = renderer->m_nodes.value(n, 0);
384 Q_ASSERT(sn);
385
386 if (Q_UNLIKELY(debug_roots()))
388
389 if (Q_UNLIKELY(debug_build())) {
390 qDebug("Updater::updateStates()");
391 if (sn->dirtyState & (QSGNode::DirtyNodeAdded << 16))
392 qDebug(" - nodes have been added");
393 if (sn->dirtyState & (QSGNode::DirtyMatrix << 16))
394 qDebug(" - transforms have changed");
395 if (sn->dirtyState & (QSGNode::DirtyOpacity << 16))
396 qDebug(" - opacity has changed");
397 if (uint(sn->dirtyState) & uint(QSGNode::DirtyForceUpdate << 16))
398 qDebug(" - forceupdate");
399 }
400
401 if (Q_UNLIKELY(renderer->m_visualizer->mode() == Visualizer::VisualizeChanges))
402 renderer->m_visualizer->visualizeChangesPrepare(sn);
403
404 visitNode(sn);
405}
406
408{
409 if (m_added == 0 && n->dirtyState == 0 && m_force_update == 0 && m_transformChange == 0 && m_opacityChange == 0)
410 return;
411
412 int count = m_added;
413 if (n->dirtyState & QSGNode::DirtyNodeAdded)
414 ++m_added;
415
416 int force = m_force_update;
417 if (n->dirtyState & QSGNode::DirtyForceUpdate)
418 ++m_force_update;
419
420 switch (n->type()) {
421 case QSGNode::OpacityNodeType:
423 break;
424 case QSGNode::TransformNodeType:
426 break;
427 case QSGNode::GeometryNodeType:
429 break;
430 case QSGNode::ClipNodeType:
432 break;
433 case QSGNode::RenderNodeType:
434 if (m_added)
435 n->renderNodeElement()->root = m_roots.last();
436 Q_FALLTHROUGH(); // to visit children
437 default:
439 break;
440 }
441
442 m_added = count;
443 m_force_update = force;
444 n->dirtyState = {};
445}
446
448{
450
451 QSGClipNode *cn = static_cast<QSGClipNode *>(n->sgNode);
452
453 if (m_roots.last() && m_added > 0)
454 renderer->registerBatchRoot(n, m_roots.last());
455
456 cn->setRendererClipList(m_current_clip);
457 m_current_clip = cn;
458 m_roots << n;
459 m_rootMatrices.add(m_rootMatrices.last() * *m_combined_matrix_stack.last());
460 extra->matrix = m_rootMatrices.last();
461 cn->setRendererMatrix(&extra->matrix);
462 m_combined_matrix_stack << &m_identityMatrix;
463
465
466 m_current_clip = cn->clipList();
467 m_rootMatrices.pop_back();
468 m_combined_matrix_stack.pop_back();
469 m_roots.pop_back();
470}
471
473{
474 QSGOpacityNode *on = static_cast<QSGOpacityNode *>(n->sgNode);
475
476 qreal combined = m_opacity_stack.last() * on->opacity();
477 on->setCombinedOpacity(combined);
478 m_opacity_stack.add(combined);
479
480 if (m_added == 0 && n->dirtyState & QSGNode::DirtyOpacity) {
481 bool was = n->isOpaque;
482 bool is = on->opacity() > OPAQUE_LIMIT;
483 if (was != is) {
484 renderer->m_rebuild = Renderer::FullRebuild;
485 n->isOpaque = is;
486 }
487 ++m_opacityChange;
489 --m_opacityChange;
490 } else {
491 if (m_added > 0)
492 n->isOpaque = on->opacity() > OPAQUE_LIMIT;
494 }
495
496 m_opacity_stack.pop_back();
497}
498
500{
501 bool popMatrixStack = false;
502 bool popRootStack = false;
503 bool dirty = n->dirtyState & QSGNode::DirtyMatrix;
504
505 QSGTransformNode *tn = static_cast<QSGTransformNode *>(n->sgNode);
506
507 if (n->isBatchRoot) {
508 if (m_added > 0 && m_roots.last())
509 renderer->registerBatchRoot(n, m_roots.last());
510 tn->setCombinedMatrix(m_rootMatrices.last() * *m_combined_matrix_stack.last() * tn->matrix());
511
512 // The only change in this subtree is ourselves and we are a batch root, so
513 // only update subroots and return, saving tons of child-processing (flickable-panning)
514
515 if (!n->becameBatchRoot && m_added == 0 && m_force_update == 0 && m_opacityChange == 0 && dirty && (n->dirtyState & ~QSGNode::DirtyMatrix) == 0) {
516 BatchRootInfo *info = renderer->batchRootInfo(n);
517 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
518 it != info->subRoots.constEnd(); ++it) {
519 updateRootTransforms(*it, n, tn->combinedMatrix());
520 }
521 return;
522 }
523
524 n->becameBatchRoot = false;
525
526 m_combined_matrix_stack.add(&m_identityMatrix);
527 m_roots.add(n);
528 m_rootMatrices.add(tn->combinedMatrix());
529
530 popMatrixStack = true;
531 popRootStack = true;
532 } else if (!tn->matrix().isIdentity()) {
533 tn->setCombinedMatrix(*m_combined_matrix_stack.last() * tn->matrix());
534 m_combined_matrix_stack.add(&tn->combinedMatrix());
535 popMatrixStack = true;
536 } else {
537 tn->setCombinedMatrix(*m_combined_matrix_stack.last());
538 }
539
540 if (dirty)
541 ++m_transformChange;
542
544
545 if (dirty)
546 --m_transformChange;
547 if (popMatrixStack)
548 m_combined_matrix_stack.pop_back();
549 if (popRootStack) {
550 m_roots.pop_back();
551 m_rootMatrices.pop_back();
552 }
553}
554
556{
557 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode);
558
559 gn->setRendererMatrix(m_combined_matrix_stack.last());
560 gn->setRendererClipList(m_current_clip);
561 gn->setInheritedOpacity(m_opacity_stack.last());
562
563 if (m_added) {
564 Element *e = n->element();
565 e->root = m_roots.last();
566 e->translateOnlyToRoot = isTranslate(*gn->matrix());
567
568 if (e->root) {
569 BatchRootInfo *info = renderer->batchRootInfo(e->root);
570 while (info != nullptr) {
571 info->availableOrders--;
572 if (info->availableOrders < 0) {
573 renderer->m_rebuild |= Renderer::BuildRenderLists;
574 } else {
575 renderer->m_rebuild |= Renderer::BuildRenderListsForTaggedRoots;
576 renderer->m_taggedRoots << e->root;
577 }
578 if (info->parentRoot != nullptr)
579 info = renderer->batchRootInfo(info->parentRoot);
580 else
581 info = nullptr;
582 }
583 } else {
584 renderer->m_rebuild |= Renderer::FullRebuild;
585 }
586 } else {
587 if (m_transformChange) {
588 Element *e = n->element();
589 e->translateOnlyToRoot = isTranslate(*gn->matrix());
590 }
591 if (m_opacityChange) {
592 Element *e = n->element();
593 if (e->batch)
594 renderer->invalidateBatchAndOverlappingRenderOrders(e->batch);
595 }
596 }
597
599}
600
601void Updater::updateRootTransforms(Node *node, Node *root, const QMatrix4x4 &combined)
602{
603 BatchRootInfo *info = renderer->batchRootInfo(node);
604 QMatrix4x4 m;
605 Node *n = node;
606
607 while (n != root) {
608 if (n->type() == QSGNode::TransformNodeType)
609 m = static_cast<QSGTransformNode *>(n->sgNode)->matrix() * m;
610 n = n->parent();
611 }
612
613 m = combined * m;
614
615 if (node->type() == QSGNode::ClipNodeType) {
616 static_cast<ClipBatchRootInfo *>(info)->matrix = m;
617 } else {
618 Q_ASSERT(node->type() == QSGNode::TransformNodeType);
619 static_cast<QSGTransformNode *>(node->sgNode)->setCombinedMatrix(m);
620 }
621
622 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
623 it != info->subRoots.constEnd(); ++it) {
624 updateRootTransforms(*it, node, m);
625 }
626}
627
628int qsg_positionAttribute(QSGGeometry *g)
629{
630 int vaOffset = 0;
631 for (int a=0; a<g->attributeCount(); ++a) {
632 const QSGGeometry::Attribute &attr = g->attributes()[a];
633 if (attr.isVertexCoordinate && attr.tupleSize == 2 && attr.type == QSGGeometry::FloatType) {
634 return vaOffset;
635 }
636 vaOffset += attr.tupleSize * size_of_type(attr.type);
637 }
638 return -1;
639}
640
641
642void Rect::map(const QMatrix4x4 &matrix)
643{
644 const float *m = matrix.constData();
645 if (isScale(matrix)) {
646 tl.x = tl.x * m[0] + m[12];
647 tl.y = tl.y * m[5] + m[13];
648 br.x = br.x * m[0] + m[12];
649 br.y = br.y * m[5] + m[13];
650 if (tl.x > br.x)
651 qSwap(tl.x, br.x);
652 if (tl.y > br.y)
653 qSwap(tl.y, br.y);
654 } else {
655 Pt mtl = tl;
656 Pt mtr = { br.x, tl.y };
657 Pt mbl = { tl.x, br.y };
658 Pt mbr = br;
659
660 mtl.map(matrix);
661 mtr.map(matrix);
662 mbl.map(matrix);
663 mbr.map(matrix);
664
665 set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
666 (*this) |= mtl;
667 (*this) |= mtr;
668 (*this) |= mbl;
669 (*this) |= mbr;
670 }
671}
672
674{
675 Q_ASSERT(!boundsComputed);
676 boundsComputed = true;
677
678 QSGGeometry *g = node->geometry();
679 int offset = qsg_positionAttribute(g);
680 if (offset == -1) {
681 // No position attribute means overlaps with everything..
682 bounds.set(-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX);
683 return;
684 }
685
686 bounds.set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
687 char *vd = (char *) g->vertexData() + offset;
688 for (int i=0; i<g->vertexCount(); ++i) {
689 bounds |= *(Pt *) vd;
690 vd += g->sizeOfVertex();
691 }
692 bounds.map(*node->matrix());
693
694 if (!qt_is_finite(bounds.tl.x) || bounds.tl.x == FLT_MAX)
695 bounds.tl.x = -FLT_MAX;
696 if (!qt_is_finite(bounds.tl.y) || bounds.tl.y == FLT_MAX)
697 bounds.tl.y = -FLT_MAX;
698 if (!qt_is_finite(bounds.br.x) || bounds.br.x == -FLT_MAX)
699 bounds.br.x = FLT_MAX;
700 if (!qt_is_finite(bounds.br.y) || bounds.br.y == -FLT_MAX)
701 bounds.br.y = FLT_MAX;
702
703 Q_ASSERT(bounds.tl.x <= bounds.br.x);
704 Q_ASSERT(bounds.tl.y <= bounds.br.y);
705
706 boundsOutsideFloatRange = bounds.isOutsideFloatRange();
707}
708
710{
711 Element *n = first;
712 // Skip to the first node other than e which has not been removed
713 while (n && (n == e || n->removed))
714 n = n->nextInBatch;
715
716 // Only 'e' in this batch, so a material change doesn't change anything as long as
717 // its blending is still in sync with this batch...
718 if (!n)
719 return BatchIsCompatible;
720
721 QSGMaterial *m = e->node->activeMaterial();
722 QSGMaterial *nm = n->node->activeMaterial();
723 return (nm->type() == m->type() && nm->viewCount() == m->viewCount() && nm->compare(m) == 0)
726}
727
728/*
729 * Marks this batch as dirty or in the case where the geometry node has
730 * changed to be incompatible with this batch, return false so that
731 * the caller can mark the entire sg for a full rebuild...
732 */
733bool Batch::geometryWasChanged(QSGGeometryNode *gn)
734{
735 Element *e = first;
736 Q_ASSERT_X(e, "Batch::geometryWasChanged", "Batch is expected to 'valid' at this time");
737 // 'gn' is the first node in the batch, compare against the next one.
738 while (e && (e->node == gn || e->removed))
739 e = e->nextInBatch;
740 if (!e || e->node->geometry()->attributes() == gn->geometry()->attributes()) {
741 needsUpload = true;
742 return true;
743 } else {
744 return false;
745 }
746}
747
749{
750 if (!needsPurge)
751 return;
752
753 // remove from front of batch..
754 while (first && first->removed) {
756 }
757
758 // Then continue and remove other nodes further out in the batch..
759 if (first) {
760 Element *e = first;
761 while (e->nextInBatch) {
762 if (e->nextInBatch->removed)
764 else
765 e = e->nextInBatch;
766
767 }
768 }
769
770 needsPurge = false;
771}
772
773/*
774 * Iterates through all geometry nodes in this batch and unsets their batch,
775 * thus forcing them to be rebuilt
776 */
778{
780 Element *e = first;
781 first = nullptr;
782 root = nullptr;
783 while (e) {
784 e->batch = nullptr;
786 e->nextInBatch = nullptr;
787 e = n;
788 }
789}
790
792 bool only = true;
793 Element *e = first;
794 while (e && only) {
795 only &= e->translateOnlyToRoot;
796 e = e->nextInBatch;
797 }
798 return only;
799}
800
801/*
802 * Iterates through all the nodes in the batch and returns true if the
803 * nodes are all safe to batch. There are two separate criteria:
804 *
805 * - The matrix is such that the z component of the result is of no
806 * consequence.
807 *
808 * - The bounds are inside the stable floating point range. This applies
809 * to desktop only where we in this case can trigger a fallback to
810 * unmerged in which case we pass the geometry straight through and
811 * just apply the matrix.
812 *
813 * NOTE: This also means a slight performance impact for geometries which
814 * are defined to be outside the stable floating point range and still
815 * use single precision float, but given that this implicitly fixes
816 * huge lists and tables, it is worth it.
817 */
818bool Batch::isSafeToBatch() const {
819 Element *e = first;
820 while (e) {
821 if (e->boundsOutsideFloatRange)
822 return false;
823 if (!is2DSafe(*e->node->matrix()))
824 return false;
825 e = e->nextInBatch;
826 }
827 return true;
828}
829
830static int qsg_countNodesInBatch(const Batch *batch)
831{
832 int sum = 0;
833 Element *e = batch->first;
834 while (e) {
835 ++sum;
836 e = e->nextInBatch;
837 }
838 return sum;
839}
840
841static int qsg_countNodesInBatches(const QDataBuffer<Batch *> &batches)
842{
843 int sum = 0;
844 for (int i=0; i<batches.size(); ++i) {
845 sum += qsg_countNodesInBatch(batches.at(i));
846 }
847 return sum;
848}
849
852 , m_context(ctx)
857 , m_partialRebuild(false)
858 , m_partialRebuildRoot(nullptr)
859 , m_forceNoDepthBuffer(false)
860 , m_opaqueBatches(16)
861 , m_alphaBatches(16)
862 , m_batchPool(16)
866 , m_vboPool(16)
867 , m_iboPool(16)
868 , m_vboPoolCost(0)
869 , m_iboPoolCost(0)
871 , m_zRange(0)
872#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
875#endif
877 , m_currentMaterial(nullptr)
878 , m_currentShader(nullptr)
879 , m_vertexUploadPool(256)
881{
882 m_rhi = m_context->rhi();
883 Q_ASSERT(m_rhi); // no more direct OpenGL code path in Qt 6
884
886
888 if (qEnvironmentVariableIntValue("QSG_RHI_UINT32_INDEX"))
889 m_uint32IndexForRhi = true;
890
891 bool ok = false;
892 int padding = qEnvironmentVariableIntValue("QSG_BATCHRENDERER_MINIMUM_ORDER_PADDING", &ok);
893 if (ok)
895
896 m_visualizer = new RhiVisualizer(this);
897
898 setNodeUpdater(new Updater(this));
899
900 // The shader manager is shared between renderers (think for example Item
901 // layers that create a new Renderer each) with the same rendercontext (and
902 // so same QRhi).
904 if (!m_shaderManager) {
906 m_shaderManager->setObjectName(QStringLiteral("__qt_ShaderManager"));
909 }
910
911 m_batchNodeThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_NODE_THRESHOLD", 64);
912 m_batchVertexThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_VERTEX_THRESHOLD", 1024);
913 m_srbPoolThreshold = qt_sg_envInt("QSG_RENDERER_SRB_POOL_THRESHOLD", 1024);
914 m_bufferPoolSizeLimit = qt_sg_envInt("QSG_RENDERER_BUFFER_POOL_LIMIT", DEFAULT_BUFFER_POOL_SIZE_LIMIT);
915
917 qDebug("Batch thresholds: nodes: %d vertices: %d srb pool: %d buffer pool: %d",
919 }
920}
921
922static void qsg_wipeBuffer(Buffer *buffer)
923{
924 delete buffer->buf;
925
926 // The free here is ok because we're in one of two situations.
927 // 1. We're using the upload pool in which case unmap will have set the
928 // data pointer to 0 and calling free on 0 is ok.
929 // 2. We're using dedicated buffers because of visualization or IBO workaround
930 // and the data something we malloced and must be freed.
931 free(buffer->data);
932}
933
934static void qsg_wipeBatch(Batch *batch)
935{
936 qsg_wipeBuffer(&batch->vbo);
937 qsg_wipeBuffer(&batch->ibo);
938 delete batch->ubuf;
939 batch->stencilClipState.reset();
940 delete batch;
941}
942
944{
945 if (m_rhi) {
946 // Clean up batches and buffers
947 for (int i = 0; i < m_opaqueBatches.size(); ++i)
949 for (int i = 0; i < m_alphaBatches.size(); ++i)
951 for (int i = 0; i < m_batchPool.size(); ++i)
953 for (int i = 0; i < m_vboPool.size(); ++i)
954 delete m_vboPool.at(i);
955 for (int i = 0; i < m_iboPool.size(); ++i)
956 delete m_iboPool.at(i);
957 }
958
959 for (Node *n : std::as_const(m_nodes)) {
960 if (n->type() == QSGNode::GeometryNodeType) {
961 Element *e = n->element();
962 if (!e->removed)
964 } else if (n->type() == QSGNode::ClipNodeType) {
965 delete n->clipInfo();
966 } else if (n->type() == QSGNode::RenderNodeType) {
968 if (!e->removed)
970 }
971
973 }
974
975 // Remaining elements...
976 for (int i=0; i<m_elementsToDelete.size(); ++i)
978
980
981 delete m_visualizer;
982}
983
985{
986 // If this is from the dtor, then the shader manager and its already
987 // prepared shaders will stay around for other renderers -> the cached data
988 // in the rhi shaders have to be purged as it may refer to samplers we
989 // are going to destroy.
991
994 delete m_dummyTexture;
996}
997
999{
1001
1003
1004 m_samplers.clear();
1005 m_dummyTexture = nullptr;
1006
1008
1013
1014 for (int i = 0; i < m_vboPool.size(); ++i)
1015 delete m_vboPool.at(i);
1016 m_vboPool.reset();
1017 m_vboPoolCost = 0;
1018
1019 for (int i = 0; i < m_iboPool.size(); ++i)
1020 delete m_iboPool.at(i);
1021 m_iboPool.reset();
1022 m_iboPoolCost = 0;
1023}
1024
1026{
1027 if (b->vbo.buf != nullptr && m_vboPoolCost + b->vbo.buf->size() <= quint32(m_bufferPoolSizeLimit)) {
1028 m_vboPool.add(b->vbo.buf);
1029 m_vboPoolCost += b->vbo.buf->size();
1030 } else {
1031 delete b->vbo.buf;
1032 }
1033 if (b->ibo.buf != nullptr && m_iboPoolCost + b->ibo.buf->size() <= quint32(m_bufferPoolSizeLimit)) {
1034 m_iboPool.add(b->ibo.buf);
1035 m_iboPoolCost += b->ibo.buf->size();
1036 } else {
1037 delete b->ibo.buf;
1038 }
1039 b->vbo.buf = nullptr;
1040 b->ibo.buf = nullptr;
1041 b->invalidate();
1042 for (int i=0; i<m_batchPool.size(); ++i)
1043 if (b == m_batchPool.at(i))
1044 return;
1045 m_batchPool.add(b);
1046}
1047
1049{
1051 // Common case, use a shared memory pool for uploading vertex data to avoid
1052 // excessive reevaluation
1054 if (byteSize > quint32(pool.size()))
1056 buffer->data = pool.data();
1057 } else if (buffer->size != byteSize) {
1058 free(buffer->data);
1059 buffer->data = (char *) malloc(byteSize);
1061 }
1062 buffer->size = byteSize;
1063}
1064
1066{
1067 // Batches are pooled and reused which means the QRhiBuffer will be
1068 // still valid in a recycled Batch. We only hit the newBuffer() path
1069 // when there are no buffers to recycle.
1071 if (!buffer->buf && bufferPool->isEmpty()) {
1074 buffer->size);
1075 if (!buffer->buf->create()) {
1076 qWarning("Failed to build vertex/index buffer of size %u", buffer->size);
1077 delete buffer->buf;
1078 buffer->buf = nullptr;
1079 }
1080 } else {
1081 if (!buffer->buf) {
1084 for (qsizetype i = 0; i < bufferPool->size(); ++i) {
1086 if (!buffer->buf
1087 || (testBuffer->size() >= expectedSize && testBuffer->size() < buffer->buf->size())
1088 || (testBuffer->size() < expectedSize && testBuffer->size() > buffer->buf->size())) {
1091 if (buffer->buf->size() == expectedSize)
1092 break;
1093 }
1094 }
1095
1100 }
1101 if (isIndexBuf)
1103 else
1106 }
1107
1108 bool needsRebuild = false;
1109 if (buffer->buf->size() < buffer->size) {
1111 needsRebuild = true;
1112 }
1113 if (buffer->buf->type() != QRhiBuffer::Dynamic
1115 {
1118 needsRebuild = true;
1119 }
1120 if (needsRebuild) {
1121 if (!buffer->buf->create()) {
1122 qWarning("Failed to (re)build vertex/index buffer of size %u", buffer->size);
1123 delete buffer->buf;
1124 buffer->buf = nullptr;
1125 }
1126 }
1127 }
1128 if (buffer->buf) {
1129 if (buffer->buf->type() != QRhiBuffer::Dynamic) {
1132 } else {
1135 else
1137 }
1138 }
1140 buffer->data = nullptr;
1141}
1142
1144{
1146 if (!info) {
1147 if (node->type() == QSGNode::ClipNodeType)
1148 info = new ClipBatchRootInfo;
1149 else {
1151 info = new BatchRootInfo;
1152 }
1153 node->data = info;
1154 }
1155 return info;
1156}
1157
1159{
1161 if (!childInfo->parentRoot)
1162 return;
1164
1167 childInfo->parentRoot = nullptr;
1168}
1169
1171{
1176}
1177
1179{
1181 if (subInfo->parentRoot == root)
1182 return false;
1183 if (subInfo->parentRoot) {
1186 }
1190 return true;
1191}
1192
1194{
1195 if (node->type() == QSGNode::ClipNodeType || node->isBatchRoot) {
1196 // When we reach a batchroot, we only need to update it. Its subtree
1197 // is relative to that root, so no need to recurse further.
1199 return;
1200 } else if (node->type() == QSGNode::GeometryNodeType) {
1201 // Only need to change the root as nodeChanged anyway flags a full update.
1202 Element *e = node->element();
1203 if (e) {
1204 e->root = root;
1205 e->boundsComputed = false;
1206 }
1207 } else if (node->type() == QSGNode::RenderNodeType) {
1209 if (e)
1210 e->root = root;
1211 }
1212
1215}
1216
1218{
1219 if (node->type() == QSGNode::GeometryNodeType) {
1220 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node->sgNode);
1222 Element *e = node->element();
1223 if (e) {
1224 e->boundsComputed = false;
1225 if (e->batch) {
1226 if (!e->batch->isOpaque) {
1228 } else if (e->batch->merged) {
1229 e->batch->needsUpload = true;
1230 }
1231 }
1232 }
1233 }
1234
1237}
1238
1240{
1242 if (node->isSubtreeBlocked())
1243 return;
1244
1246 snode->sgNode = node;
1248 if (shadowParent)
1250
1251 if (node->type() == QSGNode::GeometryNodeType) {
1253 snode->element()->setNode(static_cast<QSGGeometryNode *>(node));
1254
1255 } else if (node->type() == QSGNode::ClipNodeType) {
1258
1259 } else if (node->type() == QSGNode::RenderNodeType) {
1260 QSGRenderNode *rn = static_cast<QSGRenderNode *>(node);
1262 snode->data = e;
1266 m_forceNoDepthBuffer = true;
1268 }
1269
1272}
1273
1275{
1276 // Prefix traversal as removeBatchRootFromParent below removes nodes
1277 // in a bottom-up manner. Note that we *cannot* use SHADOWNODE_TRAVERSE
1278 // here, because we delete 'child' (when recursed, down below), so we'd
1279 // have a use-after-free.
1280 {
1281 Node *child = node->firstChild();
1282 while (child) {
1283 // Remove (and delete) child
1284 node->remove(child);
1286 child = node->firstChild();
1287 }
1288 }
1289
1290 if (node->type() == QSGNode::GeometryNodeType) {
1291 Element *e = node->element();
1292 if (e) {
1293 e->removed = true;
1295 e->node = nullptr;
1296 if (e->root) {
1299 }
1300 if (e->batch) {
1301 e->batch->needsUpload = true;
1302 e->batch->needsPurge = true;
1303 }
1304
1305 }
1306
1307 } else if (node->type() == QSGNode::ClipNodeType) {
1309 delete node->clipInfo();
1312
1313 } else if (node->isBatchRoot) {
1315 delete node->rootInfo();
1318
1319 } else if (node->type() == QSGNode::RenderNodeType) {
1321 if (e) {
1322 e->removed = true;
1325 m_forceNoDepthBuffer = false;
1326 // Must have a full rebuild given useDepthBuffer() now returns
1327 // a different value than before, meaning there can once again
1328 // be an opaque pass.
1330 }
1331
1332 if (e->batch != nullptr)
1333 e->batch->needsPurge = true;
1334 }
1335 }
1336
1338
1340}
1341
1343{
1344 if (Q_UNLIKELY(debug_change())) qDebug(" - new batch root");
1346 node->isBatchRoot = true;
1347 node->becameBatchRoot = true;
1348
1349 Node *p = node->parent();
1350 while (p) {
1351 if (p->type() == QSGNode::ClipNodeType || p->isBatchRoot) {
1353 break;
1354 }
1355 p = p->parent();
1356 }
1357
1360}
1361
1362
1364{
1365#ifndef QT_NO_DEBUG_OUTPUT
1366 if (Q_UNLIKELY(debug_change())) {
1367 QDebug debug = qDebug();
1368 debug << "dirty:";
1370 debug << "Geometry";
1372 debug << "Material";
1373 if (state & QSGNode::DirtyMatrix)
1374 debug << "Matrix";
1376 debug << "Added";
1378 debug << "Removed";
1379 if (state & QSGNode::DirtyOpacity)
1380 debug << "Opacity";
1382 debug << "SubtreeBlocked";
1384 debug << "ForceUpdate";
1385
1386 // when removed, some parts of the node could already have been destroyed
1387 // so don't debug it out.
1389 debug << (void *) node << node->type();
1390 else
1391 debug << node;
1392 }
1393#endif
1394 // As this function calls nodeChanged recursively, we do it at the top
1395 // to avoid that any of the others are processed twice.
1397 Node *sn = m_nodes.value(node);
1398
1399 // Force a batch rebuild if this includes an opacity change
1400 if (state & QSGNode::DirtyOpacity)
1402
1403 bool blocked = node->isSubtreeBlocked();
1404 if (blocked && sn) {
1406 Q_ASSERT(m_nodes.value(node) == 0);
1407 } else if (!blocked && !sn) {
1409 }
1410 return;
1411 }
1412
1413 if (state & QSGNode::DirtyNodeAdded) {
1416 return;
1417 }
1418 if (node == rootNode())
1419 nodeWasAdded(node, nullptr);
1420 else
1422 }
1423
1424 // Mark this node dirty in the shadow tree.
1426
1427 // Blocked subtrees won't have shadow nodes, so we can safely abort
1428 // here..
1429 if (!shadowNode) {
1431 return;
1432 }
1433
1435
1440 } else {
1441 int vertices = 0;
1445 }
1446 }
1447 }
1448
1450 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1452 if (e) {
1453 e->boundsComputed = false;
1454 Batch *b = e->batch;
1455 if (b) {
1456 if (!e->batch->geometryWasChanged(gn) || !e->batch->isOpaque) {
1458 } else {
1459 b->needsUpload = true;
1460 }
1461 }
1462 }
1463 }
1464
1467 if (e) {
1468 bool blended = hasMaterialWithBlending(static_cast<QSGGeometryNode *>(node));
1469 if (e->isMaterialBlended != blended) {
1472 } else if (e->batch) {
1475 } else {
1477 }
1478 }
1479 }
1480
1481 // Mark the shadow tree dirty all the way back to the root...
1487 if (dirtyChain != 0) {
1489 Node *sn = shadowNode->parent();
1490 while (sn) {
1492 sn = sn->parent();
1493 }
1494 }
1495
1496 // Delete happens at the very end because it deletes the shadownode.
1499 if (parent)
1502 Q_ASSERT(m_nodes.value(node) == 0);
1503 }
1504
1506}
1507
1508/*
1509 * Traverses the tree and builds two list of geometry nodes. One for
1510 * the opaque and one for the translucent. These are populated
1511 * in the order they should visually appear in, meaning first
1512 * to the back and last to the front.
1513 *
1514 * We split opaque and translucent as we can perform different
1515 * types of reordering / batching strategies on them, depending
1516 *
1517 * Note: It would be tempting to use the shadow nodes instead of the QSGNodes
1518 * for traversal to avoid hash lookups, but the order of the children
1519 * is important and they are not preserved in the shadow tree, so we must
1520 * use the actual QSGNode tree.
1521 */
1523{
1524 if (node->isSubtreeBlocked())
1525 return;
1526
1529
1530 if (node->type() == QSGNode::GeometryNodeType) {
1531 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1532
1534 Q_ASSERT(e);
1535
1537 if (opaque && useDepthBuffer())
1539 else
1541
1543 // Used while rebuilding partial roots.
1544 if (m_partialRebuild)
1545 e->orphaned = false;
1546
1547 } else if (node->type() == QSGNode::ClipNodeType || shadowNode->isBatchRoot) {
1550 if (node == m_partialRebuildRoot) {
1555 } else {
1564 }
1565 return;
1566 } else if (node->type() == QSGNode::RenderNodeType) {
1570 Q_ASSERT(e);
1571 }
1572
1575}
1576
1578{
1582 it != i->subRoots.constEnd(); ++it) {
1583 tagSubRoots(*it);
1584 }
1585}
1586
1587static void qsg_addOrphanedElements(QDataBuffer<Element *> &orphans, const QDataBuffer<Element *> &renderList)
1588{
1589 orphans.reset();
1590 for (int i=0; i<renderList.size(); ++i) {
1591 Element *e = renderList.at(i);
1592 if (e && !e->removed) {
1593 e->orphaned = true;
1594 orphans.add(e);
1595 }
1596 }
1597}
1598
1599static void qsg_addBackOrphanedElements(QDataBuffer<Element *> &orphans, QDataBuffer<Element *> &renderList)
1600{
1601 for (int i=0; i<orphans.size(); ++i) {
1602 Element *e = orphans.at(i);
1603 if (e->orphaned)
1604 renderList.add(e);
1605 }
1606 orphans.reset();
1607}
1608
1609/*
1610 * To rebuild the tagged roots, we start by putting all subroots of tagged
1611 * roots into the list of tagged roots. This is to make the rest of the
1612 * algorithm simpler.
1613 *
1614 * Second, we invalidate all batches which belong to tagged roots, which now
1615 * includes the entire subtree under a given root
1616 *
1617 * Then we call buildRenderLists for all tagged subroots which do not have
1618 * parents which are tagged, aka, we traverse only the topmosts roots.
1619 *
1620 * Then we sort the render lists based on their render order, to restore the
1621 * right order for rendering.
1622 */
1624{
1625 // Flag any element that is currently in the render lists, but which
1626 // is not in a batch. This happens when we have a partial rebuild
1627 // in one sub tree while we have a BuildBatches change in another
1628 // isolated subtree. So that batch-building takes into account
1629 // these "orphaned" nodes, we flag them now. The ones under tagged
1630 // roots will be cleared again. The remaining ones are added into the
1631 // render lists so that they contain all visual nodes after the
1632 // function completes.
1635
1636 // Take a copy now, as we will be adding to this while traversing..
1639 it != roots.constEnd(); ++it) {
1640 tagSubRoots(*it);
1641 }
1642
1643 for (int i=0; i<m_opaqueBatches.size(); ++i) {
1647
1648 }
1649 for (int i=0; i<m_alphaBatches.size(); ++i) {
1653 }
1654
1658 m_partialRebuild = true;
1659 // Traverse each root, assigning it
1661 it != m_taggedRoots.constEnd(); ++it) {
1662 Node *root = *it;
1669 }
1670 }
1671 m_partialRebuild = false;
1672 m_partialRebuildRoot = nullptr;
1675
1676 // Add orphaned elements back into the list and then sort it..
1679
1682 if (m_alphaRenderList.size())
1684
1685}
1686
1688{
1691
1692 for (int i=0; i<m_opaqueBatches.size(); ++i)
1694 for (int i=0; i<m_alphaBatches.size(); ++i)
1698
1700
1702}
1703
1705{
1706 Q_ASSERT(batch);
1708
1709#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1714
1717#else
1718 int first = batch->first->order;
1720#endif
1721
1722 batch->invalidate();
1723
1724 for (int i=0; i<m_alphaBatches.size(); ++i) {
1726 if (b->first) {
1727 int bf = b->first->order;
1728 int bl = b->lastOrderInBatch;
1729 if (bl > first && bf < last)
1730 b->invalidate();
1731 }
1732 }
1733
1735}
1736
1737/* Clean up batches by making it a consecutive list of "valid"
1738 * batches and moving all invalidated batches to the batches pool.
1739 */
1741 qsizetype n = batches->size();
1742 if (n == 0)
1743 return;
1744
1746 for (qsizetype i = 0; i < n; ++i) {
1747 Batch *b = batches->at(i);
1748 if (b->first)
1749 (*batches).data()[writeIndex++] = b;
1750 else
1752 }
1754}
1755
1757{
1758 for (int i=m_opaqueRenderList.size() - 1; i >= 0; --i) {
1760 if (!ei || ei->batch || ei->node->geometry()->vertexCount() == 0)
1761 continue;
1762 Batch *batch = newBatch();
1763 batch->first = ei;
1764 batch->root = ei->root;
1765 batch->isOpaque = true;
1766 batch->needsUpload = true;
1768
1770
1771 ei->batch = batch;
1772 Element *next = ei;
1773
1775
1776 for (int j = i - 1; j >= 0; --j) {
1778 if (!ej)
1779 continue;
1780 if (ej->root != ei->root)
1781 break;
1782 if (ej->batch || ej->node->geometry()->vertexCount() == 0)
1783 continue;
1784
1786
1787 const QSGGeometry *gniGeometry = gni->geometry();
1789 const QSGGeometry *gnjGeometry = gnj->geometry();
1791 if (gni->clipList() == gnj->clipList()
1799 && gniMaterial->type() == gnjMaterial->type()
1802 {
1803 ej->batch = batch;
1804 next->nextInBatch = ej;
1805 next = ej;
1806 }
1807 }
1808
1810 }
1811}
1812
1813bool Renderer::checkOverlap(int first, int last, const Rect &bounds)
1814{
1815 for (int i=first; i<=last; ++i) {
1817#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1818 if (!e || e->batch)
1819#else
1820 if (!e)
1821#endif
1822 continue;
1824 if (e->bounds.intersects(bounds))
1825 return true;
1826 }
1827 return false;
1828}
1829
1830/*
1831 *
1832 * To avoid the O(n^2) checkOverlap check in most cases, we have the
1833 * overlapBounds which is the union of all bounding rects to check overlap
1834 * for. We know that if it does not overlap, then none of the individual
1835 * ones will either. For the typical list case, this results in no calls
1836 * to checkOverlap what-so-ever. This also ensures that when all consecutive
1837 * items are matching (such as a table of text), we don't build up an
1838 * overlap bounds and thus do not require full overlap checks.
1839 */
1840
1842{
1843 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1845 if (!e || e->isRenderNode)
1846 continue;
1847 Q_ASSERT(!e->removed);
1849 }
1850
1851 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1853 if (!ei || ei->batch)
1854 continue;
1855
1856 if (ei->isRenderNode) {
1857 Batch *rnb = newBatch();
1858 rnb->first = ei;
1859 rnb->root = ei->root;
1860 rnb->isOpaque = false;
1861 rnb->isRenderNode = true;
1862 ei->batch = rnb;
1864 continue;
1865 }
1866
1867 if (ei->node->geometry()->vertexCount() == 0)
1868 continue;
1869
1870 Batch *batch = newBatch();
1871 batch->first = ei;
1872 batch->root = ei->root;
1873 batch->isOpaque = false;
1874 batch->needsUpload = true;
1876 ei->batch = batch;
1877
1880
1883
1884 Element *next = ei;
1885
1886 for (int j = i + 1; j < m_alphaRenderList.size(); ++j) {
1888 if (!ej)
1889 continue;
1890 if (ej->root != ei->root || ej->isRenderNode)
1891 break;
1892 if (ej->batch) {
1893#if !defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1895#endif
1896 continue;
1897 }
1898
1900 if (gnj->geometry()->vertexCount() == 0)
1901 continue;
1902
1903 const QSGGeometry *gniGeometry = gni->geometry();
1905 const QSGGeometry *gnjGeometry = gnj->geometry();
1907 if (gni->clipList() == gnj->clipList()
1911 // Must not do overlap checks when the line width is not 1,
1912 // we have no knowledge how such lines are rasterized.
1913 && gniGeometry->lineWidth() == 1.0f))
1917 && gniMaterial->type() == gnjMaterial->type()
1920 {
1921 if (!overlapBounds.intersects(ej->bounds) || !checkOverlap(i+1, j - 1, ej->bounds)) {
1922 ej->batch = batch;
1923 next->nextInBatch = ej;
1924 next = ej;
1925 } else {
1926 /* When we come across a compatible element which hits an overlap, we
1927 * need to stop the batch right away. We cannot add more elements
1928 * to the current batch as they will be rendered before the batch that the
1929 * current 'ej' will be added to.
1930 */
1931 break;
1932 }
1933 } else {
1935 }
1936 }
1937
1939 }
1940
1941
1942}
1943
1944static inline int qsg_fixIndexCount(int iCount, int drawMode)
1945{
1946 switch (drawMode) {
1947 case QSGGeometry::DrawTriangleStrip:
1948 // Merged triangle strips need to contain degenerate triangles at the beginning and end.
1949 // One could save 2 uploaded ushorts here by ditching the padding for the front of the
1950 // first and the end of the last, but for simplicity, we simply don't care.
1951 // Those extra triangles will be skipped while drawing to preserve the strip's parity
1952 // anyhow.
1953 return iCount + 2;
1954 case QSGGeometry::DrawLines:
1955 // For lines we drop the last vertex if the number of vertices is uneven.
1956 return iCount - (iCount % 2);
1957 case QSGGeometry::DrawTriangles:
1958 // For triangles we drop trailing vertices until the result is divisible by 3.
1959 return iCount - (iCount % 3);
1960 default:
1961 return iCount;
1962 }
1963}
1964
1965static inline float calculateElementZOrder(const Element *e, qreal zRange)
1966{
1967 // Clamp the zOrder to within the min and max depth of the viewport.
1968 return std::clamp(1.0f - float(e->order * zRange), VIEWPORT_MIN_DEPTH, VIEWPORT_MAX_DEPTH);
1969}
1970
1971/* These parameters warrant some explanation...
1972 *
1973 * vaOffset: The byte offset into the vertex data to the location of the
1974 * 2D float point vertex attributes.
1975 *
1976 * vertexData: destination where the geometry's vertex data should go
1977 *
1978 * zData: destination of geometries injected Z positioning
1979 *
1980 * indexData: destination of the indices for this element
1981 *
1982 * iBase: The starting index for this element in the batch
1983 */
1984
1985void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, void *iBasePtr, int *indexCount)
1986{
1987 if (Q_UNLIKELY(debug_upload())) qDebug() << " - uploading element:" << e << e->node << (void *) *vertexData << (qintptr) (*zData - *vertexData) << (qintptr) (*indexData - *vertexData);
1988 QSGGeometry *g = e->node->geometry();
1989
1990 const QMatrix4x4 &localx = *e->node->matrix();
1991 const float *localxdata = localx.constData();
1992
1993 const int vCount = g->vertexCount();
1994 const int vSize = g->sizeOfVertex();
1996
1997 // apply vertex transform..
1998 char *vdata = *vertexData + vaOffset;
1999 if (localx.flags() == QMatrix4x4::Translation) {
2000 for (int i=0; i<vCount; ++i) {
2001 Pt *p = (Pt *) vdata;
2002 p->x += localxdata[12];
2003 p->y += localxdata[13];
2004 vdata += vSize;
2005 }
2006 } else if (localx.flags() > QMatrix4x4::Translation) {
2007 for (int i=0; i<vCount; ++i) {
2008 ((Pt *) vdata)->map(localx);
2009 vdata += vSize;
2010 }
2011 }
2012
2013 if (useDepthBuffer()) {
2014 float *vzorder = (float *) *zData;
2016 for (int i=0; i<vCount; ++i)
2017 vzorder[i] = zorder;
2018 *zData += vCount * sizeof(float);
2019 }
2020
2021 int iCount = g->indexCount();
2022 if (m_uint32IndexForRhi) {
2023 // can only happen when using the rhi
2026 if (iCount == 0) {
2027 iCount = vCount;
2029 *indices++ = *iBase;
2030 else
2032
2033 for (int i=0; i<iCount; ++i)
2034 indices[i] = *iBase + i;
2035 } else {
2036 // source index data in QSGGeometry is always ushort (we would not merge otherwise)
2039 *indices++ = *iBase + srcIndices[0];
2040 else
2042
2043 for (int i=0; i<iCount; ++i)
2044 indices[i] = *iBase + srcIndices[i];
2045 }
2047 indices[iCount] = indices[iCount - 1];
2048 iCount += 2;
2049 }
2050 *iBase += vCount;
2051 } else {
2052 // normally batching is only done for ushort index data
2055 if (iCount == 0) {
2056 iCount = vCount;
2058 *indices++ = *iBase;
2059 else
2061
2062 for (int i=0; i<iCount; ++i)
2063 indices[i] = *iBase + i;
2064 } else {
2067 *indices++ = *iBase + srcIndices[0];
2068 else
2070
2071 for (int i=0; i<iCount; ++i)
2072 indices[i] = *iBase + srcIndices[i];
2073 }
2075 indices[iCount] = indices[iCount - 1];
2076 iCount += 2;
2077 }
2078 *iBase += vCount;
2079 }
2080
2081 *vertexData += vCount * vSize;
2083 *indexCount += iCount;
2084}
2085
2087{
2088 if (node->type() == QSGNode::TransformNodeType)
2089 return static_cast<QSGTransformNode *>(node->sgNode)->combinedMatrix();
2090 Q_ASSERT(node->type() == QSGNode::ClipNodeType);
2091 QSGClipNode *c = static_cast<QSGClipNode *>(node->sgNode);
2092 return *c->matrix();
2093}
2094
2096{
2097 // Early out if nothing has changed in this batch..
2098 if (!b->needsUpload) {
2099 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "already uploaded...";
2100 return;
2101 }
2102
2103 if (!b->first) {
2104 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "is invalid...";
2105 return;
2106 }
2107
2108 if (b->isRenderNode) {
2109 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch: " << b << "is a render node...";
2110 return;
2111 }
2112
2113 // Figure out if we can merge or not, if not, then just render the batch as is..
2114 Q_ASSERT(b->first);
2115 Q_ASSERT(b->first->node);
2116
2118 QSGGeometry *g = gn->geometry();
2122 && b->positionAttribute >= 0
2126 && b->isSafeToBatch();
2127
2128 b->merged = canMerge;
2129
2130 // Figure out how much memory we need...
2131 b->vertexCount = 0;
2132 b->indexCount = 0;
2133 int unmergedIndexSize = 0;
2134 Element *e = b->first;
2135
2136 // Merged batches always do indexed draw calls. Non-indexed geometry gets
2137 // indices generated automatically, when merged.
2138 while (e) {
2139 QSGGeometry *eg = e->node->geometry();
2140 b->vertexCount += eg->vertexCount();
2141 int iCount = eg->indexCount();
2142 if (b->merged) {
2143 if (iCount == 0)
2144 iCount = eg->vertexCount();
2146 } else {
2147 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : eg->sizeOfIndex();
2149 }
2150 b->indexCount += iCount;
2151 e = e->nextInBatch;
2152 }
2153
2154 // Abort if there are no vertices in this batch.. We abort this late as
2155 // this is a broken usecase which we do not care to optimize for...
2156 if (b->vertexCount == 0 || (b->merged && b->indexCount == 0))
2157 return;
2158
2159 /* Allocate memory for this batch. Merged batches are divided into three separate blocks
2160 1. Vertex data for all elements, as they were in the QSGGeometry object, but
2161 with the tranform relative to this batch's root applied. The vertex data
2162 is otherwise unmodified.
2163 2. Z data for all elements, derived from each elements "render order".
2164 This is present for merged data only.
2165 3. Indices for all elements, as they were in the QSGGeometry object, but
2166 adjusted so that each index matches its.
2167 And for TRIANGLE_STRIPs, we need to insert degenerate between each
2168 primitive. These are unsigned shorts for merged and arbitrary for
2169 non-merged.
2170 */
2172 int ibufferSize = 0;
2173 if (b->merged) {
2175 if (useDepthBuffer())
2176 bufferSize += b->vertexCount * sizeof(float);
2177 } else {
2179 }
2180
2181 map(&b->ibo, ibufferSize, true);
2182 map(&b->vbo, bufferSize);
2183
2184 if (Q_UNLIKELY(debug_upload())) qDebug() << " - batch" << b << " first:" << b->first << " root:"
2185 << b->root << " merged:" << b->merged << " positionAttribute" << b->positionAttribute
2186 << " vbo:" << b->vbo.buf << ":" << b->vbo.size;
2187
2188 if (b->merged) {
2189 char *vertexData = b->vbo.data;
2190 char *zData = vertexData + b->vertexCount * g->sizeOfVertex();
2191 char *indexData = b->ibo.data;
2192
2193 quint16 iOffset16 = 0;
2194 quint32 iOffset32 = 0;
2195 e = b->first;
2196 uint verticesInSet = 0;
2197 // Start a new set already after 65534 vertices because 0xFFFF may be
2198 // used for an always-on primitive restart with some apis (adapt for
2199 // uint32 indices as appropriate).
2200 const uint verticesInSetLimit = m_uint32IndexForRhi ? 0xfffffffe : 0xfffe;
2201 int indicesInSet = 0;
2202 b->drawSets.reset();
2203 int drawSetIndices = 0;
2204 const char *indexBase = b->ibo.data;
2206 while (e) {
2212 b->drawSets.last().indexCount -= 2;
2213 }
2216 zData - b->vbo.data,
2218 iOffset16 = 0;
2219 iOffset32 = 0;
2221 indicesInSet = 0;
2222 }
2223 void *iBasePtr = &iOffset16;
2227 e = e->nextInBatch;
2228 }
2230 // We skip the very first and very last degenerate triangles since they aren't needed
2231 // and the first one would reverse the vertex ordering of the merged strips.
2234 b->drawSets.last().indexCount -= 2;
2235 }
2236 } else {
2237 char *vboData = b->vbo.data;
2238 char *iboData = b->ibo.data;
2239 Element *e = b->first;
2240 while (e) {
2241 QSGGeometry *g = e->node->geometry();
2242 int vbs = g->vertexCount() * g->sizeOfVertex();
2244 vboData = vboData + vbs;
2245 const int indexCount = g->indexCount();
2246 if (indexCount) {
2247 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
2248 const int ibs = indexCount * effectiveIndexSize;
2249 if (g->sizeOfIndex() == effectiveIndexSize) {
2251 } else {
2252 if (g->sizeOfIndex() == sizeof(quint16) && effectiveIndexSize == sizeof(quint32)) {
2254 quint32 *dst = (quint32 *) iboData;
2255 for (int i = 0; i < indexCount; ++i)
2256 dst[i] = src[i];
2257 } else {
2258 Q_ASSERT_X(false, "uploadBatch (unmerged)", "uint index with ushort effective index - cannot happen");
2259 }
2260 }
2261 iboData += ibs;
2262 }
2263 e = e->nextInBatch;
2264 }
2265 }
2266#ifndef QT_NO_DEBUG_OUTPUT
2267 if (Q_UNLIKELY(debug_upload())) {
2268 const char *vd = b->vbo.data;
2269 qDebug() << " -- Vertex Data, count:" << b->vertexCount << " - " << g->sizeOfVertex() << "bytes/vertex";
2270 for (int i=0; i<b->vertexCount; ++i) {
2271 QDebug dump = qDebug().nospace();
2272 dump << " --- " << i << ": ";
2273 int offset = 0;
2274 for (int a=0; a<g->attributeCount(); ++a) {
2275 const QSGGeometry::Attribute &attr = g->attributes()[a];
2276 dump << attr.position << ":(" << attr.tupleSize << ",";
2277 if (attr.type == QSGGeometry::FloatType) {
2278 dump << "float ";
2280 dump << "* ";
2281 for (int t=0; t<attr.tupleSize; ++t)
2282 dump << *(const float *)(vd + offset + t * sizeof(float)) << " ";
2283 } else if (attr.type == QSGGeometry::UnsignedByteType) {
2284 dump << "ubyte ";
2285 for (int t=0; t<attr.tupleSize; ++t)
2286 dump << *(const unsigned char *)(vd + offset + t * sizeof(unsigned char)) << " ";
2287 }
2288 dump << ") ";
2290 }
2291 if (b->merged && useDepthBuffer()) {
2292 float zorder = ((float*)(b->vbo.data + b->vertexCount * g->sizeOfVertex()))[i];
2293 dump << " Z:(" << zorder << ")";
2294 }
2295 vd += g->sizeOfVertex();
2296 }
2297
2298 if (!b->drawSets.isEmpty()) {
2299 if (m_uint32IndexForRhi) {
2300 const quint32 *id = (const quint32 *) b->ibo.data;
2301 {
2302 QDebug iDump = qDebug();
2303 iDump << " -- Index Data, count:" << b->indexCount;
2304 for (int i=0; i<b->indexCount; ++i) {
2305 if ((i % 24) == 0)
2306 iDump << Qt::endl << " --- ";
2307 iDump << id[i];
2308 }
2309 }
2310 } else {
2311 const quint16 *id = (const quint16 *) 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 }
2322
2323 for (int i=0; i<b->drawSets.size(); ++i) {
2324 const DrawSet &s = b->drawSets.at(i);
2325 qDebug() << " -- DrawSet: indexCount:" << s.indexCount << " vertices:" << s.vertices << " z:" << s.zorders << " indices:" << s.indices;
2326 }
2327 }
2328 }
2329#endif // QT_NO_DEBUG_OUTPUT
2330
2331 unmap(&b->vbo);
2332 unmap(&b->ibo, true);
2333
2335 qDebug() << " --- vertex/index buffers unmapped, batch upload completed... vbo pool size" << m_vboPoolCost << "ibo pool size" << m_iboPoolCost;
2336
2337 b->needsUpload = false;
2338
2339 if (Q_UNLIKELY(debug_render()))
2340 b->uploadedThisFrame = true;
2341}
2342
2344{
2347}
2348
2350{
2354 blend.colorWrite = {};
2355 ps->setTargetBlends({ blend });
2357 ps->setStencilTest(true);
2364 } else {
2369 }
2372
2374
2376
2380 ps->setShaderResourceBindings(batch->stencilClipState.srb); // use something, it just needs to be layout-compatible
2382
2383 if (!ps->create()) {
2384 qWarning("Failed to build stencil clip pipeline");
2385 delete ps;
2386 return nullptr;
2387 }
2388
2389 return ps;
2390}
2391
2393{
2394 // Note: No use of the clip-related speparate m_current* vars is allowed
2395 // here. All stored in batch->clipState instead. To collect state during
2396 // the prepare steps, m_currentClipState is used. It should not be used in
2397 // the render steps afterwards.
2398
2399 // The stenciling logic is slightly different from Qt 5's direct OpenGL version
2400 // as we cannot just randomly clear the stencil buffer. We now put all clip
2401 // shapes into the stencil buffer for all batches in the frame. This means
2402 // that the number of total clips in a scene is reduced (since the stencil
2403 // value cannot exceed 255) but we do not need any clears inbetween.
2404
2405 Q_ASSERT(m_rhi);
2410 return;
2411 }
2412
2416 const QSGClipNode *clip = clipList;
2417
2419 quint32 totalVSize = 0;
2420 quint32 totalISize = 0;
2421 quint32 totalUSize = 0;
2422 const quint32 StencilClipUbufSize = 64;
2423
2424 while (clip) {
2425 QMatrix4x4 m = m_current_projection_matrix_native_ndc[0]; // never hit for 3D and so multiview
2426 if (clip->matrix())
2427 m *= *clip->matrix();
2428
2430 && qFuzzyIsNull(m(3, 0)) && qFuzzyIsNull(m(3, 1));
2431 bool noRotate = qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(1, 0));
2432 bool isRotate90 = qFuzzyIsNull(m(0, 0)) && qFuzzyIsNull(m(1, 1));
2433
2435 QRectF bbox = clip->clipRect();
2436 qreal invW = 1 / m(3, 3);
2437 qreal fx1, fy1, fx2, fy2;
2438 if (noRotate) {
2439 fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
2440 fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
2441 fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
2442 fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
2443 } else {
2445 fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
2446 fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
2447 fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
2448 fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
2449 }
2450
2451 if (fx1 > fx2)
2452 qSwap(fx1, fx2);
2453 if (fy1 > fy2)
2454 qSwap(fy1, fy2);
2455
2456 QRect deviceRect = this->deviceRect();
2457
2458 qint32 ix1 = qRound((fx1 + 1) * deviceRect.width() * qreal(0.5));
2459 qint32 iy1 = qRound((fy1 + 1) * deviceRect.height() * qreal(0.5));
2460 qint32 ix2 = qRound((fx2 + 1) * deviceRect.width() * qreal(0.5));
2461 qint32 iy2 = qRound((fy2 + 1) * deviceRect.height() * qreal(0.5));
2462
2463 if (!(clipType & ClipState::ScissorClip)) {
2465 scissorRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2466 } else {
2467 scissorRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2468 }
2469 } else {
2471
2472 const QSGGeometry *g = clip->geometry();
2473 Q_ASSERT(g->attributeCount() > 0);
2474
2475 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2476 // the 4 byte alignment may not actually be needed here
2478 if (g->indexCount()) {
2479 const int indexByteSize = g->sizeOfIndex() * g->indexCount();
2480 // so no need to worry about NonFourAlignedEffectiveIndexBufferOffset
2482 }
2483 // ubuf start offsets must be aligned (typically to 256 bytes)
2485
2487 }
2488
2489 clip = clip->clipList();
2490 }
2491
2493 bool rebuildVBuf = false;
2494 if (!batch->stencilClipState.vbuf) {
2496 rebuildVBuf = true;
2497 } else if (batch->stencilClipState.vbuf->size() < totalVSize) {
2499 rebuildVBuf = true;
2500 }
2501 if (rebuildVBuf) {
2502 if (!batch->stencilClipState.vbuf->create()) {
2503 qWarning("Failed to build stencil clip vertex buffer");
2504 delete batch->stencilClipState.vbuf;
2505 batch->stencilClipState.vbuf = nullptr;
2506 return;
2507 }
2508 }
2509
2510 if (totalISize) {
2511 bool rebuildIBuf = false;
2512 if (!batch->stencilClipState.ibuf) {
2514 rebuildIBuf = true;
2515 } else if (batch->stencilClipState.ibuf->size() < totalISize) {
2517 rebuildIBuf = true;
2518 }
2519 if (rebuildIBuf) {
2520 if (!batch->stencilClipState.ibuf->create()) {
2521 qWarning("Failed to build stencil clip index buffer");
2522 delete batch->stencilClipState.ibuf;
2523 batch->stencilClipState.ibuf = nullptr;
2524 return;
2525 }
2526 }
2527 }
2528
2529 bool rebuildUBuf = false;
2530 if (!batch->stencilClipState.ubuf) {
2532 rebuildUBuf = true;
2533 } else if (batch->stencilClipState.ubuf->size() < totalUSize) {
2535 rebuildUBuf = true;
2536 }
2537 if (rebuildUBuf) {
2538 if (!batch->stencilClipState.ubuf->create()) {
2539 qWarning("Failed to build stencil clip uniform buffer");
2540 delete batch->stencilClipState.ubuf;
2541 batch->stencilClipState.ubuf = nullptr;
2542 return;
2543 }
2544 }
2545
2546 if (!batch->stencilClipState.srb) {
2551 if (!batch->stencilClipState.srb->create()) {
2552 qWarning("Failed to build stencil clip srb");
2553 delete batch->stencilClipState.srb;
2554 batch->stencilClipState.srb = nullptr;
2555 return;
2556 }
2557 }
2558
2559 quint32 vOffset = 0;
2560 quint32 iOffset = 0;
2561 quint32 uOffset = 0;
2562 for (const QSGClipNode *clip : stencilClipNodes) {
2563 const QSGGeometry *g = clip->geometry();
2564 const QSGGeometry::Attribute *a = g->attributes();
2567
2572 }
2573#ifndef QT_NO_DEBUG
2574 else {
2576 qWarning("updateClipState: Clip list entries have different primitive topologies, this is not currently supported.");
2578 qWarning("updateClipState: Clip list entries have different vertex input layouts, this is must not happen.");
2579 }
2580#endif
2581
2583 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2585
2586 int indexByteSize = 0;
2587 if (g->indexCount()) {
2591 }
2592
2595
2597 if (clip->matrix())
2598 matrixYUpNDC *= *clip->matrix();
2599
2602 if (indexByteSize)
2604
2605 // stencil ref goes 1, 1, 2, 3, 4, ..., N for the clips in the first batch,
2606 // then N+1, N+1, N+2, N+3, ... for the next batch,
2607 // and so on.
2608 // Note the different stencilOp for the first and the subsequent clips.
2611
2616 }
2617
2619 m_stencilClipCommon.vs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.vert.qsb"));
2620
2622 m_stencilClipCommon.fs = QSGMaterialShaderPrivate::loadShader(QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.frag.qsb"));
2623
2626
2629
2631 }
2632
2637
2640}
2641
2643{
2644 // cliptype stencil + updateStencilBuffer==false means the batch uses
2645 // stenciling but relies on the stencil data generated by a previous batch
2646 // (due to the having the same clip node). Do not enqueue draw calls for
2647 // stencil in this case as the stencil buffer is already up-to-date.
2649 return;
2650
2652 const int count = batch->stencilClipState.drawCalls.size();
2653 for (int i = 0; i < count; ++i) {
2657 if (i == 0) {
2660 } else if (i == 1) {
2663 }
2664 // else incrPs is already bound
2668 if (drawCall.indexCount) {
2672 } else {
2673 cb->setVertexInput(0, 1, &vbufBinding);
2675 }
2676 }
2677}
2678
2680{
2681 Q_ASSERT(m_rhi);
2684 m_currentMaterial = nullptr;
2685}
2686
2687static inline bool needsBlendConstant(QRhiGraphicsPipeline::BlendFactor f)
2688{
2689 return f == QRhiGraphicsPipeline::ConstantColor
2690 || f == QRhiGraphicsPipeline::OneMinusConstantColor
2691 || f == QRhiGraphicsPipeline::ConstantAlpha
2692 || f == QRhiGraphicsPipeline::OneMinusConstantAlpha;
2693}
2694
2695// With QRhi renderBatches() is split to two steps: prepare and render.
2696//
2697// Prepare goes through the batches and elements, and set up a graphics
2698// pipeline, srb, uniform buffer, calculates clipping, based on m_gstate, the
2699// material (shaders), and the batches. This step does not touch the command
2700// buffer or renderpass-related state (m_pstate).
2701//
2702// The render step then starts a renderpass, and goes through all
2703// batches/elements again and records setGraphicsPipeline, drawIndexed, etc. on
2704// the command buffer. The prepare step's accumulated global state like
2705// m_gstate must not be used here. Rather, all data needed for rendering is
2706// available from Batch/Element at this stage. Bookkeeping of state in the
2707// renderpass is done via m_pstate.
2708
2710{
2711 // Note the key's == and qHash implementations: the renderpass descriptor
2712 // and srb are tested for compatibility, not pointer equality.
2713 //
2714 // We do not store the srb pointer itself because the ownership stays with
2715 // the Element and that can go away more often that we would like it
2716 // to. (think scrolling a list view, constantly dropping and creating new
2717 // nodes) Rather, use an opaque blob of a few uints and store and compare
2718 // that. This works because once the pipeline is built, we will always call
2719 // setShaderResources with an explicitly specified srb which is fine even if
2720 // e->srb we used here to bake the pipeline is already gone by that point.
2721 //
2722 // A typical QSGMaterial's serialized srb layout is 8 uints. (uniform buffer
2723 // + texture, 4 fields each) Regardless, using an implicitly shared
2724 // container is essential here. (won't detach so no more allocs and copies
2725 // are done, unless the Element decides to rebake the srb with a different
2726 // layout - but then the detach is exactly what we need)
2727 //
2728 // Same story for the renderpass descriptor: the object can go away but
2729 // that's fine because that has no effect on an already built pipeline, and
2730 // for comparison we only rely on the serialized blob in order decide if the
2731 // render target is compatible with the pipeline.
2732
2734
2735 // Note: dynamic state (viewport rect, scissor rect, stencil ref, blend
2736 // constant) is never a part of GraphicsState/QRhiGraphicsPipeline.
2737
2738 // See if there is an existing, matching pipeline state object.
2741 if (depthPostPass)
2742 e->depthPostPassPs = *it;
2743 else
2744 e->ps = *it;
2745 return true;
2746 }
2747
2748 // Build a new one. This is potentially expensive.
2754
2758 {
2760 }
2765
2766 ps->setFlags(flags);
2772
2782 ps->setTargetBlends({ blend });
2783
2787
2788 if (m_gstate.stencilTest) {
2789 ps->setStencilTest(true);
2797 }
2798
2800
2802
2803 if (!ps->create()) {
2804 qWarning("Failed to build graphics pipeline state");
2805 delete ps;
2806 return false;
2807 }
2808
2810 if (depthPostPass)
2811 e->depthPostPassPs = ps;
2812 else
2813 e->ps = ps;
2814 return true;
2815}
2816
2817static QRhiSampler *newSampler(QRhi *rhi, const QSGSamplerDescription &desc)
2818{
2819 QRhiSampler::Filter magFilter;
2820 QRhiSampler::Filter minFilter;
2821 QRhiSampler::Filter mipmapMode;
2822 QRhiSampler::AddressMode u;
2823 QRhiSampler::AddressMode v;
2824
2825 switch (desc.filtering) {
2826 case QSGTexture::None:
2827 Q_FALLTHROUGH();
2828 case QSGTexture::Nearest:
2829 magFilter = minFilter = QRhiSampler::Nearest;
2830 break;
2831 case QSGTexture::Linear:
2832 magFilter = minFilter = QRhiSampler::Linear;
2833 break;
2834 default:
2835 Q_UNREACHABLE();
2836 magFilter = minFilter = QRhiSampler::Nearest;
2837 break;
2838 }
2839
2840 switch (desc.mipmapFiltering) {
2841 case QSGTexture::None:
2842 mipmapMode = QRhiSampler::None;
2843 break;
2844 case QSGTexture::Nearest:
2845 mipmapMode = QRhiSampler::Nearest;
2846 break;
2847 case QSGTexture::Linear:
2848 mipmapMode = QRhiSampler::Linear;
2849 break;
2850 default:
2851 Q_UNREACHABLE();
2852 mipmapMode = QRhiSampler::None;
2853 break;
2854 }
2855
2856 switch (desc.horizontalWrap) {
2857 case QSGTexture::Repeat:
2858 u = QRhiSampler::Repeat;
2859 break;
2860 case QSGTexture::ClampToEdge:
2861 u = QRhiSampler::ClampToEdge;
2862 break;
2863 case QSGTexture::MirroredRepeat:
2864 u = QRhiSampler::Mirror;
2865 break;
2866 default:
2867 Q_UNREACHABLE();
2868 u = QRhiSampler::ClampToEdge;
2869 break;
2870 }
2871
2872 switch (desc.verticalWrap) {
2873 case QSGTexture::Repeat:
2874 v = QRhiSampler::Repeat;
2875 break;
2876 case QSGTexture::ClampToEdge:
2877 v = QRhiSampler::ClampToEdge;
2878 break;
2879 case QSGTexture::MirroredRepeat:
2880 v = QRhiSampler::Mirror;
2881 break;
2882 default:
2883 Q_UNREACHABLE();
2884 v = QRhiSampler::ClampToEdge;
2885 break;
2886 }
2887
2888 return rhi->newSampler(magFilter, minFilter, mipmapMode, u, v);
2889}
2890
2892{
2893 if (!m_dummyTexture) {
2895 if (m_dummyTexture->create()) {
2896 if (m_resourceUpdates) {
2898 img.fill(0);
2900 }
2901 }
2902 }
2903 return m_dummyTexture;
2904}
2905
2906static void rendererToMaterialGraphicsState(QSGMaterialShader::GraphicsPipelineState *dst,
2907 GraphicsState *src)
2908{
2909 dst->blendEnable = src->blending;
2910
2911 // the enum values should match, sanity check it
2912 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::OneMinusSrc1Alpha) == int(QRhiGraphicsPipeline::OneMinusSrc1Alpha));
2913 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::BlendOp::Max) == int(QRhiGraphicsPipeline::Max));
2914 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::A) == int(QRhiGraphicsPipeline::A));
2915 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::CullBack) == int(QRhiGraphicsPipeline::Back));
2916 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::Line) == int(QRhiGraphicsPipeline::Line));
2917 dst->srcColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcColor);
2918 dst->dstColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstColor);
2919
2920 // For compatibility with any existing code, separateBlendFactors defaults
2921 // to _false_ which means that materials that do not touch srcAlpha and
2922 // dstAlpha will continue to use srcColor and dstColor as the alpha
2923 // blending factors. New code that needs different values for color/alpha,
2924 // can explicitly set separateBlendFactors to true and then set srcAlpha
2925 // and dstAlpha as well.
2926 dst->separateBlendFactors = false;
2927
2928 dst->srcAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcAlpha);
2929 dst->dstAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstAlpha);
2930
2931 dst->opColor = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opColor);
2932 dst->opAlpha = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opAlpha);
2933
2934 dst->colorWrite = QSGMaterialShader::GraphicsPipelineState::ColorMask(int(src->colorWrite));
2935
2936 dst->cullMode = QSGMaterialShader::GraphicsPipelineState::CullMode(src->cullMode);
2937 dst->polygonMode = QSGMaterialShader::GraphicsPipelineState::PolygonMode(src->polygonMode);
2938}
2939
2941 QSGMaterialShader::GraphicsPipelineState *src)
2942{
2943 dst->blending = src->blendEnable;
2944 dst->srcColor = QRhiGraphicsPipeline::BlendFactor(src->srcColor);
2945 dst->dstColor = QRhiGraphicsPipeline::BlendFactor(src->dstColor);
2946 if (src->separateBlendFactors) {
2947 dst->srcAlpha = QRhiGraphicsPipeline::BlendFactor(src->srcAlpha);
2948 dst->dstAlpha = QRhiGraphicsPipeline::BlendFactor(src->dstAlpha);
2949 } else {
2950 dst->srcAlpha = dst->srcColor;
2951 dst->dstAlpha = dst->dstColor;
2952 }
2953 dst->opColor = QRhiGraphicsPipeline::BlendOp(src->opColor);
2954 dst->opAlpha = QRhiGraphicsPipeline::BlendOp(src->opAlpha);
2955 dst->colorWrite = QRhiGraphicsPipeline::ColorMask(int(src->colorWrite));
2956 dst->cullMode = QRhiGraphicsPipeline::CullMode(src->cullMode);
2957 dst->polygonMode = QRhiGraphicsPipeline::PolygonMode(src->polygonMode);
2958}
2959
2963 const Batch *batch,
2964 Element *e,
2965 int ubufOffset,
2966 int ubufRegionSize,
2967 char *directUpdatePtr)
2968{
2970
2974
2975 if (pd->ubufBinding >= 0) {
2978 m_current_uniform_data = nullptr;
2979
2980 if (changed || !batch->ubufDataValid) {
2981 if (directUpdatePtr)
2983 else
2985 }
2986
2988 pd->ubufStages,
2989 batch->ubuf,
2990 ubufOffset,
2992 }
2993
2996 if (!stages)
2997 continue;
2998
3001
3004
3007
3008 if (nextTex.contains(nullptr)) {
3009 qWarning("No QSGTexture provided from updateSampledImage(). This is wrong.");
3010 continue;
3011 }
3012
3013 bool hasDirtySamplerOptions = false;
3014 bool isAnisotropic = false;
3015 for (QSGTexture *t : nextTex) {
3020 }
3021
3022 // prevTex may be invalid at this point, avoid dereferencing it
3024
3025 // The QSGTexture, and so the sampler parameters, may have changed.
3026 // The rhiTexture is not relevant here.
3027 pd->textureBindingTable[binding] = nextTex; // does not own
3029
3030 if (isAnisotropic) // ###
3031 qWarning("QSGTexture anisotropy levels are not currently supported");
3032
3034
3035 for (QSGTexture *t : nextTex) {
3037
3039
3040 if (!sampler) {
3042 if (!sampler->create()) {
3043 qWarning("Failed to build sampler");
3044 delete sampler;
3045 continue;
3046 }
3048 }
3050 }
3051
3052 pd->samplerBindingTable[binding] = samplers; // does not own
3053 }
3054
3056
3058
3059 for (int i = 0; i < pd->textureBindingTable[binding].size(); ++i) {
3060
3062
3063 // texture may be null if the update above failed for any reason,
3064 // or if the QSGTexture chose to return null intentionally. This is
3065 // valid and we still need to provide something to the shader.
3066 if (!texture)
3068
3070
3073 }
3074
3075 if (!textureSamplers.isEmpty())
3078 }
3079 }
3080
3081#ifndef QT_NO_DEBUG
3082 if (bindings.isEmpty())
3083 qWarning("No shader resources for material %p, this is odd.", material);
3084#endif
3085
3086 enum class SrbAction {
3087 Unknown,
3088 DoNothing,
3090 Rebake
3092
3093 // First, if the Element has no srb created at all, then try to find an existing,
3094 // currently unused srb that is layout-compatible with our binding list.
3095 if (!e->srb) {
3096 // reuse a QVector as our work area, thus possibly reusing the underlying allocation too
3098 layoutDesc.clear();
3101 if (e->srb) {
3102 // Here we know layout compatibility is satisfied, but do not spend time on full
3103 // comparison. The chance of getting an srb that refers to the same resources
3104 // (buffer, textures) is low in practice. So reuse, but write new resources.
3106 }
3107 }
3108
3109 // If the Element had an existing srb, investigate:
3110 // - It may be used as-is (when nothing changed in the scene regarding this node compared to the previous frame).
3111 // - Otherwise it may be able to go with a lightweight update (replace resources, binding list layout is the same).
3112 // - If all else fails rebake the full thing, meaning we reuse the memory allocation but will recreate everything underneath.
3113 if (srbAction == SrbAction::Unknown && e->srb) {
3117 [](const auto &a, const auto &b) { return a.isLayoutCompatible(b); }))
3118 {
3120 } else {
3122 }
3123 }
3124
3125 // If the Element had no srb associated at all and could not find a layout-compatible
3126 // one from the pool, then create a whole new object.
3127 if (!e->srb) {
3130 }
3131
3133
3134 switch (srbAction) {
3135 case SrbAction::DoNothing:
3136 break;
3138 {
3141 // Due to the way the binding list is built up above, if we have a uniform buffer
3142 // at binding point 0 (or none at all) then the sampledTexture bindings are added
3143 // with increasing binding points afterwards, so the list is already sorted based
3144 // on the binding points, thus we can save some time by telling the QRhi backend
3145 // not to sort again.
3146 if (pd->ubufBinding <= 0 || bindings.size() <= 1)
3148
3150 }
3151 break;
3152 case SrbAction::Rebake:
3154 if (!e->srb->create())
3155 qWarning("Failed to build srb");
3156 break;
3157 default:
3158 Q_ASSERT_X(false, "updateMaterialDynamicData", "No srb action set, this cannot happen");
3159 }
3160}
3161
3165 Batch *batch,
3166 bool *gstateChanged)
3167{
3169 *gstateChanged = false;
3171 // generate the public mini-state from m_gstate, invoke the material,
3172 // write the changes, if any, back to m_gstate, together with a way to
3173 // roll those back.
3177 if (changed) {
3182 {
3184 }
3185 *gstateChanged = true;
3186 }
3187 }
3188}
3189
3191{
3192 if (batch->vertexCount == 0 || batch->indexCount == 0)
3193 return false;
3194
3195 Element *e = batch->first;
3196 Q_ASSERT(e);
3197
3198#ifndef QT_NO_DEBUG_OUTPUT
3199 if (Q_UNLIKELY(debug_render())) {
3200 QDebug debug = qDebug();
3201 debug << " -"
3202 << batch
3203 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3204 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3205 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3206 << "[ merged]"
3207 << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
3208 << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
3209 << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
3210 << " root:" << batch->root;
3211 if (batch->drawSets.size() > 1)
3212 debug << "sets:" << batch->drawSets.size();
3213 if (!batch->isOpaque)
3214 debug << "opacity:" << e->node->inheritedOpacity();
3215 batch->uploadedThisFrame = false;
3216 }
3217#endif
3218
3220
3221 // We always have dirty matrix as all batches are at a unique z range.
3223 if (batch->root)
3225 else
3228
3229 const int viewCount = projectionMatrixCount();
3231 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3233
3237
3241
3242 const QSGGeometry *g = gn->geometry();
3246 if (!sms)
3247 return false;
3248
3250 if (m_currentShader != sms)
3252
3257 }
3258
3261 if (pd->ubufBinding >= 0) {
3262 bool ubufRebuild = false;
3263 if (!batch->ubuf) {
3265 ubufRebuild = true;
3266 } else {
3267 if (batch->ubuf->size() < ubufSize) {
3269 ubufRebuild = true;
3270 }
3271 }
3272 if (ubufRebuild) {
3273 batch->ubufDataValid = false;
3274 if (!batch->ubuf->create()) {
3275 qWarning("Failed to build uniform buffer of size %u bytes", ubufSize);
3276 delete batch->ubuf;
3277 batch->ubuf = nullptr;
3278 return false;
3279 }
3280 }
3281 }
3282
3284
3285 bool pendingGStatePop = false;
3287
3288 char *directUpdatePtr = nullptr;
3289 if (batch->ubuf->nativeBuffer().slotCount == 0)
3291
3293
3294 if (directUpdatePtr)
3296
3299
3300 const bool hasPipeline = ensurePipelineState(e, sms);
3301
3302 if (pendingGStatePop)
3304
3305 if (!hasPipeline)
3306 return false;
3307
3311 ensurePipelineState(e, sms, true);
3313 }
3314
3315 batch->ubufDataValid = true;
3316
3318
3320 renderBatch->sms = sms;
3321
3322 return true;
3323}
3324
3326{
3329 {
3330 if (g->lineWidth() != 1.0f) {
3331 static bool checkedWideLineSupport = false;
3335 qWarning("Line widths other than 1 are not supported by the graphics API");
3336 }
3337 }
3338 } else if (g->drawingMode() == QSGGeometry::DrawPoints) {
3339 if (g->lineWidth() != 1.0f) {
3340 static bool warnedPointSize = false;
3341 if (!warnedPointSize) {
3342 warnedPointSize = true;
3343 qWarning("Point size is not controllable by QSGGeometry. "
3344 "Set gl_PointSize from the vertex shader instead.");
3345 }
3346 }
3347 }
3348}
3349
3351{
3352 const Batch *batch = renderBatch->batch;
3353 if (!batch->vbo.buf || !batch->ibo.buf)
3354 return;
3355
3356 Element *e = batch->first;
3358 QSGGeometry *g = gn->geometry();
3360
3363
3366
3367 for (int i = 0, ie = batch->drawSets.size(); i != ie; ++i) {
3368 const DrawSet &draw = batch->drawSets.at(i);
3372 };
3377 }
3378}
3379
3381{
3382 if (batch->vertexCount == 0)
3383 return false;
3384
3385 Element *e = batch->first;
3386 Q_ASSERT(e);
3387
3388 if (Q_UNLIKELY(debug_render())) {
3389 qDebug() << " -"
3390 << batch
3391 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3392 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3393 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3394 << "[unmerged]"
3395 << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
3396 << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
3397 << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
3398 << " root:" << batch->root;
3399
3400 batch->uploadedThisFrame = false;
3401 }
3402
3403 const int viewCount = projectionMatrixCount();
3405 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3407
3411
3415
3416 // We always have dirty matrix as all batches are at a unique z range.
3418
3419 // The vertex attributes are assumed to be the same for all elements in the
3420 // unmerged batch since the material (and so the shaders) is the same.
3421 QSGGeometry *g = gn->geometry();
3424 if (!sms)
3425 return false;
3426
3428 if (m_currentShader != sms)
3430
3435 }
3436
3438
3441 if (pd->ubufBinding >= 0) {
3443 while (e) {
3445 e = e->nextInBatch;
3446 }
3447 bool ubufRebuild = false;
3448 if (!batch->ubuf) {
3450 ubufRebuild = true;
3451 } else {
3452 if (batch->ubuf->size() < totalUBufSize) {
3454 ubufRebuild = true;
3455 }
3456 }
3457 if (ubufRebuild) {
3458 batch->ubufDataValid = false;
3459 if (!batch->ubuf->create()) {
3460 qWarning("Failed to build uniform buffer of size %u bytes", totalUBufSize);
3461 delete batch->ubuf;
3462 batch->ubuf = nullptr;
3463 return false;
3464 }
3465 }
3466 }
3467
3469 bool pendingGStatePop = false;
3472
3473 int ubufOffset = 0;
3474 QRhiGraphicsPipeline *ps = nullptr;
3476 e = batch->first;
3477
3478 char *directUpdatePtr = nullptr;
3479 if (batch->ubuf->nativeBuffer().slotCount == 0)
3481
3482 while (e) {
3483 gn = e->node;
3484
3487
3488 const int viewCount = projectionMatrixCount();
3490 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3492
3496
3497 if (useDepthBuffer()) {
3498 // this cannot be multiview
3501 }
3502
3505
3507
3509 const float prevLineWidth = m_gstate.lineWidth;
3512
3513 // Do not bother even looking up the ps if the topology has not changed
3514 // since everything else is the same for all elements in the batch.
3515 // (except if the material modified blend state)
3517 if (!ensurePipelineState(e, sms)) {
3518 if (pendingGStatePop)
3520 return false;
3521 }
3522 ps = e->ps;
3526 ensurePipelineState(e, sms, true);
3529 }
3530 } else {
3531 e->ps = ps;
3534 }
3535
3536 // We don't need to bother with asking each node for its material as they
3537 // are all identical (compare==0) since they are in the same batch.
3539
3540 // We only need to push this on the very first iteration...
3542
3543 e = e->nextInBatch;
3544 }
3545
3546 if (directUpdatePtr)
3548
3549 if (pendingGStatePop)
3551
3552 batch->ubufDataValid = true;
3553
3555 renderBatch->sms = sms;
3556
3557 return true;
3558}
3559
3561{
3562 const Batch *batch = renderBatch->batch;
3563 if (!batch->vbo.buf)
3564 return;
3565
3566 Element *e = batch->first;
3567
3570
3571 quint32 vOffset = 0;
3572 quint32 iOffset = 0;
3574
3575 while (e) {
3576 QSGGeometry *g = e->node->geometry();
3578 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
3579
3581
3583 if (g->indexCount()) {
3584 if (batch->ibo.buf) {
3586 batch->ibo.buf, iOffset,
3590 }
3591 } else {
3593 cb->draw(g->vertexCount());
3594 }
3595
3596 vOffset += g->sizeOfVertex() * g->vertexCount();
3598
3599 e = e->nextInBatch;
3600 }
3601}
3602
3604{
3605 if (!m_pstate.viewportSet) {
3606 m_pstate.viewportSet = true;
3608 }
3610 m_pstate.scissorSet = true;
3612 } else {
3613 // Regardless of the ps not using scissor, the scissor may need to be
3614 // reset, depending on the backend. So set the viewport again, which in
3615 // turn also sets the scissor on backends where a scissor rect is
3616 // always-on (Vulkan).
3617 if (m_pstate.scissorSet) {
3618 m_pstate.scissorSet = false;
3620 }
3621 }
3622}
3623
3624
3626{
3628
3630
3634 }
3637
3639}
3640
3642{
3643 if (e->isRenderNode) {
3644 delete static_cast<RenderNodeElement *>(e);
3645 } else {
3646 if (e->srb) {
3647 if (!inDestructor) {
3650 else
3651 delete e->srb;
3652 } else {
3653 delete e->srb;
3654 }
3655 e->srb = nullptr;
3656 }
3658 }
3659}
3660
3662{
3663 if (!m_elementsToDelete.size())
3664 return;
3665
3666 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3668 if (*e && (*e)->removed)
3669 *e = nullptr;
3670 }
3671 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3673 if (*e && (*e)->removed)
3674 *e = nullptr;
3675 }
3676
3677 for (int i=0; i<m_elementsToDelete.size(); ++i)
3679
3681}
3682
3684{
3685 // Gracefully handle the lack of a render target - some autotests may rely
3686 // on this in odd cases.
3687 if (!renderTarget().rt)
3688 return;
3689
3694}
3695
3696// An alternative to render() is to call prepareInline() and renderInline() at
3697// the appropriate times (i.e. outside of a QRhi::beginPass() and then inside,
3698// respectively) These allow rendering within a render pass that is started by
3699// another component. In contrast, render() records a full render pass on its
3700// own.
3701
3706
3711
3713{
3714 if (ctx->valid)
3715 qWarning("prepareRenderPass() called with an already prepared render pass context");
3716
3717 ctx->valid = true;
3718
3719 if (Q_UNLIKELY(debug_dump())) {
3720 qDebug("\n");
3722 }
3723
3724 ctx->timeRenderLists = 0;
3726 ctx->timePrepareAlpha = 0;
3727 ctx->timeSorting = 0;
3728 ctx->timeUploadOpaque = 0;
3729 ctx->timeUploadAlpha = 0;
3730
3731 if (Q_UNLIKELY(debug_render() || debug_build())) {
3732 QByteArray type("rebuild:");
3733 if (m_rebuild == 0)
3734 type += " none";
3735 if (m_rebuild == FullRebuild)
3736 type += " full";
3737 else {
3739 type += " renderlists";
3741 type += " partial";
3742 else if (m_rebuild & BuildBatches)
3743 type += " batches";
3744 }
3745
3746 qDebug() << "Renderer::render()" << this << type;
3747 ctx->timer.start();
3748 }
3749
3751
3753 bool complete = (m_rebuild & BuildRenderLists) != 0;
3754 if (complete)
3756 else
3759
3760 if (Q_UNLIKELY(debug_build())) {
3761 qDebug("Opaque render lists %s:", (complete ? "(complete)" : "(partial)"));
3762 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3764 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3765 }
3766 qDebug("Alpha render list %s:", complete ? "(complete)" : "(partial)");
3767 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3769 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3770 }
3771 }
3772 }
3774
3775 for (int i=0; i<m_opaqueBatches.size(); ++i)
3777 for (int i=0; i<m_alphaBatches.size(); ++i)
3780
3783
3784 if (m_rebuild & BuildBatches) {
3789
3790 if (Q_UNLIKELY(debug_build())) {
3791 qDebug("Opaque Batches:");
3792 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3794 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3795 for (Element *e = b->first; e; e = e->nextInBatch) {
3796 qDebug() << " - element:" << e << " node:" << e->node << e->order;
3797 }
3798 }
3799 qDebug("Alpha Batches:");
3800 for (int i=0; i<m_alphaBatches.size(); ++i) {
3802 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3803 for (Element *e = b->first; e; e = e->nextInBatch) {
3804 qDebug() << " - element:" << e << e->bounds << " node:" << e->node << " order:" << e->order;
3805 }
3806 }
3807 }
3808 } else {
3810 }
3811
3812
3814
3815 if (m_rebuild != 0) {
3816 // Then sort opaque batches so that we're drawing the batches with the highest
3817 // order first, maximizing the benefit of front-to-back z-ordering.
3818 if (m_opaqueBatches.size())
3820
3821 // Sort alpha batches back to front so that they render correctly.
3822 if (m_alphaBatches.size())
3824
3826 ? 1.0 / (m_nextRenderOrder)
3827 : 0;
3828 }
3829
3831
3832 // Set size to 0, nothing is deallocated, they will "grow" again
3833 // as part of uploadBatch.
3836
3837 if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Opaque Batches:");
3838 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3840 uploadBatch(b);
3841 }
3843
3844 if (Q_UNLIKELY(debug_upload())) qDebug("Uploading Alpha Batches:");
3845 for (int i=0; i<m_alphaBatches.size(); ++i) {
3847 uploadBatch(b);
3848 }
3850
3851 if (Q_UNLIKELY(debug_render())) {
3852 qDebug().nospace() << "Rendering:" << Qt::endl
3853 << " -> Opaque: " << qsg_countNodesInBatches(m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << Qt::endl
3854 << " -> Alpha: " << qsg_countNodesInBatches(m_alphaBatches) << " nodes in " << m_alphaBatches.size() << " batches...";
3855 }
3856
3858 m_currentMaterial = nullptr;
3859 m_currentShader = nullptr;
3860 m_currentProgram = nullptr;
3862
3863 const QRect viewport = viewportRect();
3864
3865 bool renderOpaque = !debug_noopaque();
3866 bool renderAlpha = !debug_noalpha();
3867
3873 m_pstate.viewportSet = false;
3874 m_pstate.scissorSet = false;
3875
3879 m_gstate.blending = false;
3880
3887 m_gstate.usesScissor = false;
3888 m_gstate.stencilTest = false;
3889
3892
3894 if (Q_LIKELY(renderOpaque)) {
3895 for (int i = 0, ie = m_opaqueBatches.size(); i != ie; ++i) {
3898 bool ok;
3899 if (b->merged)
3901 else
3903 if (ok)
3905 }
3906 }
3907
3908 m_gstate.blending = true;
3909 // factors never change, always set for premultiplied alpha based blending
3910
3911 // depth test stays enabled (if useDepthBuffer(), that is) but no need
3912 // to write out depth from the transparent (back-to-front) pass
3913 m_gstate.depthWrite = false;
3914
3915 // special case: the 3D plane mode tests against the depth buffer, but does
3916 // not write (and all batches are alpha because this render mode evaluates
3917 // to useDepthBuffer()==false)
3920 m_gstate.depthTest = true;
3921 }
3922
3924 if (Q_LIKELY(renderAlpha)) {
3925 for (int i = 0, ie = m_alphaBatches.size(); i != ie; ++i) {
3928 bool ok;
3929 if (b->merged)
3931 else if (b->isRenderNode)
3933 else
3935 if (ok)
3937 }
3938 }
3939
3940 m_rebuild = 0;
3941
3942#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
3945#endif
3946
3949
3951 m_resourceUpdates = nullptr;
3952}
3953
3955{
3958 // we cannot tell if the application will have
3959 // native rendering thrown in to this pass
3960 // (QQuickWindow::beginExternalCommands()), so
3961 // we have no choice but to set the flag always
3962 // (thus triggering using secondary command
3963 // buffers with Vulkan)
3965 // We do not use GPU compute at all at the moment, this means we can
3966 // get a small performance gain with OpenGL by declaring this.
3968
3971}
3972
3974{
3975 // prepareRenderPass and recordRenderPass must always be called together.
3976 // They are separate because beginRenderPass and endRenderPass are optional.
3977 //
3978 // The valid call sequence are therefore:
3979 // prepare, begin, record, end
3980 // or
3981 // prepare, record
3982
3983 if (!ctx->valid)
3984 qWarning("recordRenderPass() called without a prepared render pass context");
3985
3986 ctx->valid = false;
3987
3989 cb->debugMarkBegin(QByteArrayLiteral("Qt Quick scene render"));
3990
3991 for (int i = 0, ie = ctx->opaqueRenderBatches.size(); i != ie; ++i) {
3992 if (i == 0)
3993 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick opaque batches"));
3995 if (renderBatch->batch->merged)
3997 else
3999 }
4000
4001 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
4002 if (i == 0) {
4004 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D batches"));
4005 else
4006 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick alpha batches"));
4007 }
4009 if (renderBatch->batch->merged)
4011 else if (renderBatch->batch->isRenderNode)
4013 else
4015 }
4016
4018 // Depth post-pass to fill up the depth buffer in a way that it
4019 // corresponds to what got rendered to the color buffer in the previous
4020 // (alpha) pass. The previous pass cannot enable depth write due to Z
4021 // fighting. Rather, do it separately in a dedicated color-write-off,
4022 // depth-write-on pass. This enables the 3D content drawn afterwards to
4023 // depth test against the 2D items' rendering.
4024 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
4025 if (i == 0)
4026 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D depth post-pass"));
4028 if (renderBatch->batch->merged)
4030 else if (!renderBatch->batch->isRenderNode) // rendernodes are skipped here for now
4032 }
4033 }
4034
4035 if (m_currentShader)
4036 setActiveRhiShader(nullptr, nullptr);
4037
4038 cb->debugMarkEnd();
4039
4040 if (Q_UNLIKELY(debug_render())) {
4041 qDebug(" -> times: build: %d, prepare(opaque/alpha): %d/%d, sorting: %d, upload(opaque/alpha): %d/%d, record rendering: %d",
4042 (int) ctx->timeRenderLists,
4044 (int) ctx->timeSorting,
4045 (int) ctx->timeUploadOpaque, (int) ctx->timeUploadAlpha,
4046 (int) ctx->timer.elapsed());
4047 }
4048}
4049
4060
4062{
4063 const QMatrix4x4 *projectionMatrix() const override { return m_projectionMatrix; }
4064 QRect scissorRect() const override { return m_scissorRect; }
4066 int stencilValue() const override { return m_stencilValue; }
4068 const QRegion *clipRegion() const override { return nullptr; }
4069
4075};
4076
4078{
4079 if (Q_UNLIKELY(debug_render()))
4080 qDebug() << " -" << batch << "rendernode";
4081
4082 const int viewCount = projectionMatrixCount();
4084 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
4089
4091 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4092
4093 setActiveRhiShader(nullptr, nullptr);
4094
4096 rd->m_clip_list = nullptr;
4099 while (clip != rootNode()) {
4100 if (clip->type() == QSGNode::ClipNodeType) {
4101 rd->m_clip_list = static_cast<QSGClipNode *>(clip);
4102 break;
4103 }
4104 clip = clip->parent();
4105 }
4107 }
4108
4111 QSGNode *root = rootNode();
4112 if (e->root) {
4114 root = e->root->sgNode;
4115 }
4116 while (xform != root) {
4117 if (xform->type() == QSGNode::TransformNodeType) {
4118 matrix = matrix * static_cast<QSGTransformNode *>(xform)->combinedMatrix();
4119 break;
4120 }
4121 xform = xform->parent();
4122 }
4125
4127 rd->m_opacity = 1.0;
4128 while (opacity != rootNode()) {
4129 if (opacity->type() == QSGNode::OpacityNodeType) {
4130 rd->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity();
4131 break;
4132 }
4133 opacity = opacity->parent();
4134 }
4135
4136 rd->m_rt = renderTarget();
4137
4139 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
4141
4142 if (useDepthBuffer()) {
4143 // this cannot be multiview
4144 rd->m_projectionMatrix[0](2, 2) = m_zRange;
4146 }
4147
4148 e->renderNode->prepare();
4149
4151 renderBatch->sms = nullptr;
4152
4153 return true;
4154}
4155
4157{
4160
4161 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4163
4165 // Expose only the first matrix through the state object, the rest are
4166 // queriable through the QSGRenderNode getters anyway.
4168 const std::array<int, 4> scissor = batch->clipState.scissor.scissor();
4173
4175
4179 if (needsExternal)
4180 cb->beginExternal();
4182 if (needsExternal)
4183 cb->endExternal();
4184
4185 rd->m_matrix = nullptr;
4186 rd->m_clip_list = nullptr;
4187
4190 {
4191 // Reset both flags if either is reported as changed, since with the rhi
4192 // it could be setViewport() that will record the resetting of the scissor.
4193 m_pstate.viewportSet = false;
4194 m_pstate.scissorSet = false;
4195 }
4196
4197 // Do not bother with RenderTargetState. Where applicable, endExternal()
4198 // ensures the correct target is rebound. For others (like Vulkan) it makes
4199 // no sense since render() could not possibly do that on our command buffer
4200 // which is in renderpass recording state.
4201}
4202
4204{
4205 if (mode.isEmpty())
4207 else if (mode == "clip")
4209 else if (mode == "overdraw")
4211 else if (mode == "batches")
4213 else if (mode == "changes")
4215}
4216
4221
4222bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
4223{
4224 return a.depthTest == b.depthTest
4225 && a.depthWrite == b.depthWrite
4226 && a.depthFunc == b.depthFunc
4227 && a.blending == b.blending
4228 && a.srcColor == b.srcColor
4229 && a.dstColor == b.dstColor
4230 && a.srcAlpha == b.srcAlpha
4231 && a.dstAlpha == b.dstAlpha
4232 && a.opColor == b.opColor
4233 && a.opAlpha == b.opAlpha
4234 && a.colorWrite == b.colorWrite
4235 && a.cullMode == b.cullMode
4239 && a.drawMode == b.drawMode
4240 && a.lineWidth == b.lineWidth
4241 && a.polygonMode == b.polygonMode
4243}
4244
4245bool operator!=(const GraphicsState &a, const GraphicsState &b) noexcept
4246{
4247 return !(a == b);
4248}
4249
4250size_t qHash(const GraphicsState &s, size_t seed) noexcept
4251{
4252 // do not bother with all fields
4253 return seed
4254 + s.depthTest * 1000
4255 + s.depthWrite * 100
4256 + s.depthFunc
4257 + s.blending * 10
4258 + s.srcColor
4259 + s.cullMode
4260 + s.usesScissor
4261 + s.stencilTest
4262 + s.sampleCount
4263 + s.multiViewCount;
4264}
4265
4267{
4268 return a.state == b.state
4269 && a.sms->materialShader == b.sms->materialShader
4270 && a.renderTargetDescription == b.renderTargetDescription
4271 && a.srbLayoutDescription == b.srbLayoutDescription;
4272}
4273
4275{
4276 return !(a == b);
4277}
4278
4279size_t qHash(const GraphicsPipelineStateKey &k, size_t seed) noexcept
4280{
4281 return qHash(k.state, seed)
4282 ^ qHash(k.sms->materialShader)
4283 ^ k.extra.renderTargetDescriptionHash
4284 ^ k.extra.srbLayoutDescriptionHash;
4285}
4286
4287bool operator==(const ShaderKey &a, const ShaderKey &b) noexcept
4288{
4289 return a.type == b.type
4290 && a.renderMode == b.renderMode
4292}
4293
4294bool operator!=(const ShaderKey &a, const ShaderKey &b) noexcept
4295{
4296 return !(a == b);
4297}
4298
4299size_t qHash(const ShaderKey &k, size_t seed) noexcept
4300{
4301 return qHash(k.type, seed) ^ int(k.renderMode) ^ k.multiViewCount;
4302}
4303
4304Visualizer::Visualizer(Renderer *renderer)
4305 : m_renderer(renderer),
4307{
4308}
4309
4311{
4312}
4313
4314#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded
4315 | QSGNode::DirtyOpacity
4316 | QSGNode::DirtyMatrix
4317 | QSGNode::DirtyNodeRemoved)
4318
4319void Visualizer::visualizeChangesPrepare(Node *n, uint parentChanges)
4320{
4321 uint childDirty = (parentChanges | n->dirtyState) & QSGNODE_DIRTY_PARENT;
4322 uint selfDirty = n->dirtyState | parentChanges;
4323 if (n->type() == QSGNode::GeometryNodeType && selfDirty != 0)
4324 m_visualizeChangeSet.insert(n, selfDirty);
4326 visualizeChangesPrepare(child, childDirty);
4327 }
4328}
4329
4330} // namespace QSGBatchRenderer
4331
4332QT_END_NAMESPACE
4333
4334#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