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
6#include "qsgbatchrenderer_p.h"
7
8#include <qmath.h>
9
10#include <QtCore/QElapsedTimer>
11#include <QtCore/QtNumeric>
12
13#include <QtGui/QGuiApplication>
14
15#include <private/qnumeric_p.h>
16#include "qsgmaterialshader_p.h"
17
18#include "qsgrhivisualizer_p.h"
19
20#include <algorithm>
21
22QT_BEGIN_NAMESPACE
23
24#ifndef QT_NO_DEBUG
25Q_QUICK_EXPORT bool qsg_test_and_clear_material_failure();
26#endif
27
28int qt_sg_envInt(const char *name, int defaultValue);
29
30namespace QSGBatchRenderer
31{
32
33#define DECLARE_DEBUG_VAR(variable) \
34 static bool debug_ ## variable() \
35 { static bool value = qgetenv("QSG_RENDERER_DEBUG").contains(QT_STRINGIFY(variable)); return value; }
36DECLARE_DEBUG_VAR(render)
37DECLARE_DEBUG_VAR(build)
38DECLARE_DEBUG_VAR(change)
39DECLARE_DEBUG_VAR(upload)
40DECLARE_DEBUG_VAR(roots)
41DECLARE_DEBUG_VAR(dump)
42DECLARE_DEBUG_VAR(pools)
43DECLARE_DEBUG_VAR(noalpha)
44DECLARE_DEBUG_VAR(noopaque)
45DECLARE_DEBUG_VAR(noclip)
46#undef DECLARE_DEBUG_VAR
47
48#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling())
49#define SHADOWNODE_TRAVERSE(NODE) for (Node *child = NODE->firstChild(); child; child = child->sibling())
50
51static inline int size_of_type(int type)
52{
53 static int sizes[] = {
54 sizeof(char),
55 sizeof(unsigned char),
56 sizeof(short),
57 sizeof(unsigned short),
58 sizeof(int),
59 sizeof(unsigned int),
60 sizeof(float),
61 2,
62 3,
63 4,
64 sizeof(double)
65 };
66 Q_ASSERT(type >= QSGGeometry::ByteType && type <= QSGGeometry::DoubleType);
67 return sizes[type - QSGGeometry::ByteType];
68}
69
70bool qsg_sort_element_increasing_order(Element *a, Element *b) { return a->order < b->order; }
71bool qsg_sort_element_decreasing_order(Element *a, Element *b) { return a->order > b->order; }
72bool qsg_sort_batch_is_valid(Batch *a, Batch *b) { return a->first && !b->first; }
73bool qsg_sort_batch_increasing_order(Batch *a, Batch *b) { return a->first->order < b->first->order; }
74bool qsg_sort_batch_decreasing_order(Batch *a, Batch *b) { return a->first->order > b->first->order; }
75
76QSGMaterial::Flag QSGMaterial_FullMatrix = (QSGMaterial::Flag) (QSGMaterial::RequiresFullMatrix & ~QSGMaterial::RequiresFullMatrixExceptTranslate);
77
78static bool isTranslate(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Translation; }
79static bool isScale(const QMatrix4x4 &m) { return m.flags() <= QMatrix4x4::Scale; }
80static bool is2DSafe(const QMatrix4x4 &m) { return m.flags() < QMatrix4x4::Rotation; }
81
82const float OPAQUE_LIMIT = 0.999f;
83
84const uint DYNAMIC_VERTEX_INDEX_BUFFER_THRESHOLD = 4;
85const int VERTEX_BUFFER_BINDING = 0;
86const int ZORDER_BUFFER_BINDING = VERTEX_BUFFER_BINDING + 1;
87
88const float VIEWPORT_MIN_DEPTH = 0.0f;
89const float VIEWPORT_MAX_DEPTH = 1.0f;
90
91const quint32 DEFAULT_BUFFER_POOL_SIZE_LIMIT = 2 * 1024 * 1024; // 2 MB for m_vboPool and m_iboPool each
92
93template <class Int>
94inline Int aligned(Int v, Int byteAlign)
95{
96 return (v + byteAlign - 1) & ~(byteAlign - 1);
97}
98
99QRhiVertexInputAttribute::Format qsg_vertexInputFormat(const QSGGeometry::Attribute &a)
100{
101 switch (a.type) {
102 case QSGGeometry::FloatType:
103 if (a.tupleSize == 4)
104 return QRhiVertexInputAttribute::Float4;
105 if (a.tupleSize == 3)
106 return QRhiVertexInputAttribute::Float3;
107 if (a.tupleSize == 2)
108 return QRhiVertexInputAttribute::Float2;
109 if (a.tupleSize == 1)
110 return QRhiVertexInputAttribute::Float;
111 break;
112 case QSGGeometry::UnsignedByteType:
113 if (a.tupleSize == 4)
114 return QRhiVertexInputAttribute::UNormByte4;
115 if (a.tupleSize == 2)
116 return QRhiVertexInputAttribute::UNormByte2;
117 if (a.tupleSize == 1)
118 return QRhiVertexInputAttribute::UNormByte;
119 break;
120 default:
121 break;
122 }
123 qWarning(msg: "Unsupported attribute type 0x%x with %d components", a.type, a.tupleSize);
124 Q_UNREACHABLE_RETURN(QRhiVertexInputAttribute::Float);
125}
126
127static QRhiVertexInputLayout calculateVertexInputLayout(const QSGMaterialShader *s, const QSGGeometry *geometry, bool batchable)
128{
129 Q_ASSERT(geometry);
130 const QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
131 if (!sd->vertexShader) {
132 qWarning(msg: "No vertex shader in QSGMaterialShader %p", s);
133 return QRhiVertexInputLayout();
134 }
135
136 const int attrCount = geometry->attributeCount();
137 QVarLengthArray<QRhiVertexInputAttribute, 8> inputAttributes;
138 inputAttributes.reserve(sz: attrCount + 1);
139 quint32 offset = 0;
140 for (int i = 0; i < attrCount; ++i) {
141 const QSGGeometry::Attribute &a = geometry->attributes()[i];
142 if (!sd->vertexShader->vertexInputLocations.contains(t: a.position)) {
143 qWarning(msg: "Vertex input %d is present in material but not in shader. This is wrong.",
144 a.position);
145 }
146 inputAttributes.append(t: QRhiVertexInputAttribute(VERTEX_BUFFER_BINDING, a.position, qsg_vertexInputFormat(a), offset));
147 offset += a.tupleSize * size_of_type(type: a.type);
148 }
149 if (batchable) {
150 inputAttributes.append(t: QRhiVertexInputAttribute(ZORDER_BUFFER_BINDING, sd->vertexShader->qt_order_attrib_location,
151 QRhiVertexInputAttribute::Float, 0));
152 }
153
154 Q_ASSERT(VERTEX_BUFFER_BINDING == 0 && ZORDER_BUFFER_BINDING == 1); // not very flexible
155 QVarLengthArray<QRhiVertexInputBinding, 2> inputBindings;
156 inputBindings.append(t: QRhiVertexInputBinding(geometry->sizeOfVertex()));
157 if (batchable)
158 inputBindings.append(t: QRhiVertexInputBinding(sizeof(float)));
159
160 QRhiVertexInputLayout inputLayout;
161 inputLayout.setBindings(first: inputBindings.cbegin(), last: inputBindings.cend());
162 inputLayout.setAttributes(first: inputAttributes.cbegin(), last: inputAttributes.cend());
163
164 return inputLayout;
165}
166
167QRhiCommandBuffer::IndexFormat qsg_indexFormat(const QSGGeometry *geometry)
168{
169 switch (geometry->indexType()) {
170 case QSGGeometry::UnsignedShortType:
171 return QRhiCommandBuffer::IndexUInt16;
172 break;
173 case QSGGeometry::UnsignedIntType:
174 return QRhiCommandBuffer::IndexUInt32;
175 break;
176 default:
177 Q_UNREACHABLE_RETURN(QRhiCommandBuffer::IndexUInt16);
178 }
179}
180
181QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode)
182{
183 QRhiGraphicsPipeline::Topology topology = QRhiGraphicsPipeline::Triangles;
184 switch (geomDrawMode) {
185 case QSGGeometry::DrawPoints:
186 topology = QRhiGraphicsPipeline::Points;
187 break;
188 case QSGGeometry::DrawLines:
189 topology = QRhiGraphicsPipeline::Lines;
190 break;
191 case QSGGeometry::DrawLineStrip:
192 topology = QRhiGraphicsPipeline::LineStrip;
193 break;
194 case QSGGeometry::DrawTriangles:
195 topology = QRhiGraphicsPipeline::Triangles;
196 break;
197 case QSGGeometry::DrawTriangleStrip:
198 topology = QRhiGraphicsPipeline::TriangleStrip;
199 break;
200 default:
201 qWarning(msg: "Primitive topology 0x%x not supported", geomDrawMode);
202 break;
203 }
204 return topology;
205}
206
207void qsg_setMultiViewFlagsOnMaterial(QSGMaterial *material, int multiViewCount)
208{
209 material->setFlag(flags: QSGMaterial::MultiView2, on: multiViewCount == 2);
210 material->setFlag(flags: QSGMaterial::MultiView3, on: multiViewCount == 3);
211 material->setFlag(flags: QSGMaterial::MultiView4, on: multiViewCount == 4);
212}
213
214ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material,
215 const QSGGeometry *geometry,
216 QSGRendererInterface::RenderMode renderMode,
217 int multiViewCount)
218{
219 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
220
221 QSGMaterialType *type = material->type();
222 ShaderKey key = { .type: type, .renderMode: renderMode, .multiViewCount: multiViewCount };
223 Shader *shader = rewrittenShaders.value(key, defaultValue: nullptr);
224 if (shader)
225 return shader;
226
227 shader = new Shader;
228 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
229 context->initializeRhiShader(shader: s, shaderVariant: QShader::BatchableVertexShader);
230 shader->materialShader = s;
231 shader->inputLayout = calculateVertexInputLayout(s, geometry, batchable: true);
232 QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
233 shader->stages = {
234 { QRhiShaderStage::Vertex, sD->shader(stage: QShader::VertexStage), QShader::BatchableVertexShader },
235 { QRhiShaderStage::Fragment, sD->shader(stage: QShader::FragmentStage) }
236 };
237
238 shader->lastOpacity = 0;
239
240 rewrittenShaders[key] = shader;
241 return shader;
242}
243
244ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material,
245 const QSGGeometry *geometry,
246 QSGRendererInterface::RenderMode renderMode,
247 int multiViewCount)
248{
249 qsg_setMultiViewFlagsOnMaterial(material, multiViewCount);
250
251 QSGMaterialType *type = material->type();
252 ShaderKey key = { .type: type, .renderMode: renderMode, .multiViewCount: multiViewCount };
253 Shader *shader = stockShaders.value(key, defaultValue: nullptr);
254 if (shader)
255 return shader;
256
257 shader = new Shader;
258 QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader(renderMode));
259 context->initializeRhiShader(shader: s, shaderVariant: QShader::StandardShader);
260 shader->materialShader = s;
261 shader->inputLayout = calculateVertexInputLayout(s, geometry, batchable: false);
262 QSGMaterialShaderPrivate *sD = QSGMaterialShaderPrivate::get(s);
263 shader->stages = {
264 { QRhiShaderStage::Vertex, sD->shader(stage: QShader::VertexStage) },
265 { QRhiShaderStage::Fragment, sD->shader(stage: QShader::FragmentStage) }
266 };
267
268 shader->lastOpacity = 0;
269
270 stockShaders[key] = shader;
271
272 return shader;
273}
274
275void ShaderManager::invalidated()
276{
277 qDeleteAll(c: stockShaders);
278 stockShaders.clear();
279 qDeleteAll(c: rewrittenShaders);
280 rewrittenShaders.clear();
281
282 qDeleteAll(c: pipelineCache);
283 pipelineCache.clear();
284
285 qDeleteAll(c: srbPool);
286 srbPool.clear();
287}
288
289void ShaderManager::clearCachedRendererData()
290{
291 for (ShaderManager::Shader *sms : std::as_const(t&: stockShaders)) {
292 QSGMaterialShader *s = sms->materialShader;
293 if (s) {
294 QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
295 sd->clearCachedRendererData();
296 }
297 }
298 for (ShaderManager::Shader *sms : std::as_const(t&: rewrittenShaders)) {
299 QSGMaterialShader *s = sms->materialShader;
300 if (s) {
301 QSGMaterialShaderPrivate *sd = QSGMaterialShaderPrivate::get(s);
302 sd->clearCachedRendererData();
303 }
304 }
305}
306
307void qsg_dumpShadowRoots(BatchRootInfo *i, int indent)
308{
309 static int extraIndent = 0;
310 ++extraIndent;
311
312 QByteArray ind(indent + extraIndent + 10, ' ');
313
314 if (!i) {
315 qDebug(msg: "%s - no info", ind.constData());
316 } else {
317 qDebug() << ind.constData() << "- parent:" << i->parentRoot << "orders" << i->firstOrder << "->" << i->lastOrder << ", avail:" << i->availableOrders;
318 for (QSet<Node *>::const_iterator it = i->subRoots.constBegin();
319 it != i->subRoots.constEnd(); ++it) {
320 qDebug() << ind.constData() << "-" << *it;
321 qsg_dumpShadowRoots(i: (*it)->rootInfo(), indent);
322 }
323 }
324
325 --extraIndent;
326}
327
328void qsg_dumpShadowRoots(Node *n)
329{
330#ifndef QT_NO_DEBUG_OUTPUT
331 static int indent = 0;
332 ++indent;
333
334 QByteArray ind(indent, ' ');
335
336 if (n->type() == QSGNode::ClipNodeType || n->isBatchRoot) {
337 qDebug() << ind.constData() << "[X]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
338 qsg_dumpShadowRoots(i: n->rootInfo(), indent);
339 } else {
340 QDebug d = qDebug();
341 d << ind.constData() << "[ ]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
342 if (n->type() == QSGNode::GeometryNodeType)
343 d << "order" << Qt::dec << n->element()->order;
344 }
345
346 SHADOWNODE_TRAVERSE(n)
347 qsg_dumpShadowRoots(n: child);
348
349 --indent;
350#else
351 Q_UNUSED(n);
352#endif
353}
354
355Updater::Updater(Renderer *r)
356 : renderer(r)
357 , m_roots(32)
358 , m_rootMatrices(8)
359{
360 m_roots.add(t: 0);
361 m_combined_matrix_stack.add(t: &m_identityMatrix);
362 m_rootMatrices.add(t: m_identityMatrix);
363}
364
365void Updater::updateStates(QSGNode *n)
366{
367 m_current_clip = nullptr;
368
369 m_added = 0;
370 m_transformChange = 0;
371 m_opacityChange = 0;
372
373 Node *sn = renderer->m_nodes.value(key: n, defaultValue: 0);
374 Q_ASSERT(sn);
375
376 if (Q_UNLIKELY(debug_roots()))
377 qsg_dumpShadowRoots(n: sn);
378
379 if (Q_UNLIKELY(debug_build())) {
380 qDebug(msg: "Updater::updateStates()");
381 if (sn->dirtyState & (QSGNode::DirtyNodeAdded << 16))
382 qDebug(msg: " - nodes have been added");
383 if (sn->dirtyState & (QSGNode::DirtyMatrix << 16))
384 qDebug(msg: " - transforms have changed");
385 if (sn->dirtyState & (QSGNode::DirtyOpacity << 16))
386 qDebug(msg: " - opacity has changed");
387 if (uint(sn->dirtyState) & uint(QSGNode::DirtyForceUpdate << 16))
388 qDebug(msg: " - forceupdate");
389 }
390
391 if (Q_UNLIKELY(renderer->m_visualizer->mode() == Visualizer::VisualizeChanges))
392 renderer->m_visualizer->visualizeChangesPrepare(n: sn);
393
394 visitNode(n: sn);
395}
396
397void Updater::visitNode(Node *n)
398{
399 if (m_added == 0 && n->dirtyState == 0 && m_force_update == 0 && m_transformChange == 0 && m_opacityChange == 0)
400 return;
401
402 int count = m_added;
403 if (n->dirtyState & QSGNode::DirtyNodeAdded)
404 ++m_added;
405
406 int force = m_force_update;
407 if (n->dirtyState & QSGNode::DirtyForceUpdate)
408 ++m_force_update;
409
410 switch (n->type()) {
411 case QSGNode::OpacityNodeType:
412 visitOpacityNode(n);
413 break;
414 case QSGNode::TransformNodeType:
415 visitTransformNode(n);
416 break;
417 case QSGNode::GeometryNodeType:
418 visitGeometryNode(n);
419 break;
420 case QSGNode::ClipNodeType:
421 visitClipNode(n);
422 break;
423 case QSGNode::RenderNodeType:
424 if (m_added)
425 n->renderNodeElement()->root = m_roots.last();
426 Q_FALLTHROUGH(); // to visit children
427 default:
428 SHADOWNODE_TRAVERSE(n) visitNode(n: child);
429 break;
430 }
431
432 m_added = count;
433 m_force_update = force;
434 n->dirtyState = {};
435}
436
437void Updater::visitClipNode(Node *n)
438{
439 ClipBatchRootInfo *extra = n->clipInfo();
440
441 QSGClipNode *cn = static_cast<QSGClipNode *>(n->sgNode);
442
443 if (m_roots.last() && m_added > 0)
444 renderer->registerBatchRoot(childRoot: n, parentRoot: m_roots.last());
445
446 cn->setRendererClipList(m_current_clip);
447 m_current_clip = cn;
448 m_roots << n;
449 m_rootMatrices.add(t: m_rootMatrices.last() * *m_combined_matrix_stack.last());
450 extra->matrix = m_rootMatrices.last();
451 cn->setRendererMatrix(&extra->matrix);
452 m_combined_matrix_stack << &m_identityMatrix;
453
454 SHADOWNODE_TRAVERSE(n) visitNode(n: child);
455
456 m_current_clip = cn->clipList();
457 m_rootMatrices.pop_back();
458 m_combined_matrix_stack.pop_back();
459 m_roots.pop_back();
460}
461
462void Updater::visitOpacityNode(Node *n)
463{
464 QSGOpacityNode *on = static_cast<QSGOpacityNode *>(n->sgNode);
465
466 qreal combined = m_opacity_stack.last() * on->opacity();
467 on->setCombinedOpacity(combined);
468 m_opacity_stack.add(t: combined);
469
470 if (m_added == 0 && n->dirtyState & QSGNode::DirtyOpacity) {
471 bool was = n->isOpaque;
472 bool is = on->opacity() > OPAQUE_LIMIT;
473 if (was != is) {
474 renderer->m_rebuild = Renderer::FullRebuild;
475 n->isOpaque = is;
476 }
477 ++m_opacityChange;
478 SHADOWNODE_TRAVERSE(n) visitNode(n: child);
479 --m_opacityChange;
480 } else {
481 if (m_added > 0)
482 n->isOpaque = on->opacity() > OPAQUE_LIMIT;
483 SHADOWNODE_TRAVERSE(n) visitNode(n: child);
484 }
485
486 m_opacity_stack.pop_back();
487}
488
489void Updater::visitTransformNode(Node *n)
490{
491 bool popMatrixStack = false;
492 bool popRootStack = false;
493 bool dirty = n->dirtyState & QSGNode::DirtyMatrix;
494
495 QSGTransformNode *tn = static_cast<QSGTransformNode *>(n->sgNode);
496
497 if (n->isBatchRoot) {
498 if (m_added > 0 && m_roots.last())
499 renderer->registerBatchRoot(childRoot: n, parentRoot: m_roots.last());
500 tn->setCombinedMatrix(m_rootMatrices.last() * *m_combined_matrix_stack.last() * tn->matrix());
501
502 // The only change in this subtree is ourselves and we are a batch root, so
503 // only update subroots and return, saving tons of child-processing (flickable-panning)
504
505 if (!n->becameBatchRoot && m_added == 0 && m_force_update == 0 && m_opacityChange == 0 && dirty && (n->dirtyState & ~QSGNode::DirtyMatrix) == 0) {
506 BatchRootInfo *info = renderer->batchRootInfo(node: n);
507 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
508 it != info->subRoots.constEnd(); ++it) {
509 updateRootTransforms(n: *it, root: n, combined: tn->combinedMatrix());
510 }
511 return;
512 }
513
514 n->becameBatchRoot = false;
515
516 m_combined_matrix_stack.add(t: &m_identityMatrix);
517 m_roots.add(t: n);
518 m_rootMatrices.add(t: tn->combinedMatrix());
519
520 popMatrixStack = true;
521 popRootStack = true;
522 } else if (!tn->matrix().isIdentity()) {
523 tn->setCombinedMatrix(*m_combined_matrix_stack.last() * tn->matrix());
524 m_combined_matrix_stack.add(t: &tn->combinedMatrix());
525 popMatrixStack = true;
526 } else {
527 tn->setCombinedMatrix(*m_combined_matrix_stack.last());
528 }
529
530 if (dirty)
531 ++m_transformChange;
532
533 SHADOWNODE_TRAVERSE(n) visitNode(n: child);
534
535 if (dirty)
536 --m_transformChange;
537 if (popMatrixStack)
538 m_combined_matrix_stack.pop_back();
539 if (popRootStack) {
540 m_roots.pop_back();
541 m_rootMatrices.pop_back();
542 }
543}
544
545void Updater::visitGeometryNode(Node *n)
546{
547 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode);
548
549 gn->setRendererMatrix(m_combined_matrix_stack.last());
550 gn->setRendererClipList(m_current_clip);
551 gn->setInheritedOpacity(m_opacity_stack.last());
552
553 if (m_added) {
554 Element *e = n->element();
555 e->root = m_roots.last();
556 e->translateOnlyToRoot = isTranslate(m: *gn->matrix());
557
558 if (e->root) {
559 BatchRootInfo *info = renderer->batchRootInfo(node: e->root);
560 while (info != nullptr) {
561 info->availableOrders--;
562 if (info->availableOrders < 0) {
563 renderer->m_rebuild |= Renderer::BuildRenderLists;
564 } else {
565 renderer->m_rebuild |= Renderer::BuildRenderListsForTaggedRoots;
566 renderer->m_taggedRoots << e->root;
567 }
568 if (info->parentRoot != nullptr)
569 info = renderer->batchRootInfo(node: info->parentRoot);
570 else
571 info = nullptr;
572 }
573 } else {
574 renderer->m_rebuild |= Renderer::FullRebuild;
575 }
576 } else {
577 if (m_transformChange) {
578 Element *e = n->element();
579 e->translateOnlyToRoot = isTranslate(m: *gn->matrix());
580 }
581 if (m_opacityChange) {
582 Element *e = n->element();
583 if (e->batch)
584 renderer->invalidateBatchAndOverlappingRenderOrders(batch: e->batch);
585 }
586 }
587
588 SHADOWNODE_TRAVERSE(n) visitNode(n: child);
589}
590
591void Updater::updateRootTransforms(Node *node, Node *root, const QMatrix4x4 &combined)
592{
593 BatchRootInfo *info = renderer->batchRootInfo(node);
594 QMatrix4x4 m;
595 Node *n = node;
596
597 while (n != root) {
598 if (n->type() == QSGNode::TransformNodeType)
599 m = static_cast<QSGTransformNode *>(n->sgNode)->matrix() * m;
600 n = n->parent();
601 }
602
603 m = combined * m;
604
605 if (node->type() == QSGNode::ClipNodeType) {
606 static_cast<ClipBatchRootInfo *>(info)->matrix = m;
607 } else {
608 Q_ASSERT(node->type() == QSGNode::TransformNodeType);
609 static_cast<QSGTransformNode *>(node->sgNode)->setCombinedMatrix(m);
610 }
611
612 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
613 it != info->subRoots.constEnd(); ++it) {
614 updateRootTransforms(node: *it, root: node, combined: m);
615 }
616}
617
618int qsg_positionAttribute(QSGGeometry *g)
619{
620 int vaOffset = 0;
621 for (int a=0; a<g->attributeCount(); ++a) {
622 const QSGGeometry::Attribute &attr = g->attributes()[a];
623 if (attr.isVertexCoordinate && attr.tupleSize == 2 && attr.type == QSGGeometry::FloatType) {
624 return vaOffset;
625 }
626 vaOffset += attr.tupleSize * size_of_type(type: attr.type);
627 }
628 return -1;
629}
630
631
632void Rect::map(const QMatrix4x4 &matrix)
633{
634 const float *m = matrix.constData();
635 if (isScale(m: matrix)) {
636 tl.x = tl.x * m[0] + m[12];
637 tl.y = tl.y * m[5] + m[13];
638 br.x = br.x * m[0] + m[12];
639 br.y = br.y * m[5] + m[13];
640 if (tl.x > br.x)
641 qSwap(value1&: tl.x, value2&: br.x);
642 if (tl.y > br.y)
643 qSwap(value1&: tl.y, value2&: br.y);
644 } else {
645 Pt mtl = tl;
646 Pt mtr = { .x: br.x, .y: tl.y };
647 Pt mbl = { .x: tl.x, .y: br.y };
648 Pt mbr = br;
649
650 mtl.map(mat: matrix);
651 mtr.map(mat: matrix);
652 mbl.map(mat: matrix);
653 mbr.map(mat: matrix);
654
655 set(FLT_MAX, FLT_MAX, right: -FLT_MAX, bottom: -FLT_MAX);
656 (*this) |= mtl;
657 (*this) |= mtr;
658 (*this) |= mbl;
659 (*this) |= mbr;
660 }
661}
662
663void Element::computeBounds()
664{
665 Q_ASSERT(!boundsComputed);
666 boundsComputed = true;
667
668 QSGGeometry *g = node->geometry();
669 int offset = qsg_positionAttribute(g);
670 if (offset == -1) {
671 // No position attribute means overlaps with everything..
672 bounds.set(left: -FLT_MAX, top: -FLT_MAX, FLT_MAX, FLT_MAX);
673 return;
674 }
675
676 bounds.set(FLT_MAX, FLT_MAX, right: -FLT_MAX, bottom: -FLT_MAX);
677 char *vd = (char *) g->vertexData() + offset;
678 for (int i=0; i<g->vertexCount(); ++i) {
679 bounds |= *(Pt *) vd;
680 vd += g->sizeOfVertex();
681 }
682 bounds.map(matrix: *node->matrix());
683
684 if (!qt_is_finite(f: bounds.tl.x) || bounds.tl.x == FLT_MAX)
685 bounds.tl.x = -FLT_MAX;
686 if (!qt_is_finite(f: bounds.tl.y) || bounds.tl.y == FLT_MAX)
687 bounds.tl.y = -FLT_MAX;
688 if (!qt_is_finite(f: bounds.br.x) || bounds.br.x == -FLT_MAX)
689 bounds.br.x = FLT_MAX;
690 if (!qt_is_finite(f: bounds.br.y) || bounds.br.y == -FLT_MAX)
691 bounds.br.y = FLT_MAX;
692
693 Q_ASSERT(bounds.tl.x <= bounds.br.x);
694 Q_ASSERT(bounds.tl.y <= bounds.br.y);
695
696 boundsOutsideFloatRange = bounds.isOutsideFloatRange();
697}
698
699BatchCompatibility Batch::isMaterialCompatible(Element *e) const
700{
701 Element *n = first;
702 // Skip to the first node other than e which has not been removed
703 while (n && (n == e || n->removed))
704 n = n->nextInBatch;
705
706 // Only 'e' in this batch, so a material change doesn't change anything as long as
707 // its blending is still in sync with this batch...
708 if (!n)
709 return BatchIsCompatible;
710
711 QSGMaterial *m = e->node->activeMaterial();
712 QSGMaterial *nm = n->node->activeMaterial();
713 return (nm->type() == m->type() && nm->viewCount() == m->viewCount() && nm->compare(other: m) == 0)
714 ? BatchIsCompatible
715 : BatchBreaksOnCompare;
716}
717
718/*
719 * Marks this batch as dirty or in the case where the geometry node has
720 * changed to be incompatible with this batch, return false so that
721 * the caller can mark the entire sg for a full rebuild...
722 */
723bool Batch::geometryWasChanged(QSGGeometryNode *gn)
724{
725 Element *e = first;
726 Q_ASSERT_X(e, "Batch::geometryWasChanged", "Batch is expected to 'valid' at this time");
727 // 'gn' is the first node in the batch, compare against the next one.
728 while (e && (e->node == gn || e->removed))
729 e = e->nextInBatch;
730 if (!e || e->node->geometry()->attributes() == gn->geometry()->attributes()) {
731 needsUpload = true;
732 return true;
733 } else {
734 return false;
735 }
736}
737
738void Batch::cleanupRemovedElements()
739{
740 if (!needsPurge)
741 return;
742
743 // remove from front of batch..
744 while (first && first->removed) {
745 first = first->nextInBatch;
746 }
747
748 // Then continue and remove other nodes further out in the batch..
749 if (first) {
750 Element *e = first;
751 while (e->nextInBatch) {
752 if (e->nextInBatch->removed)
753 e->nextInBatch = e->nextInBatch->nextInBatch;
754 else
755 e = e->nextInBatch;
756
757 }
758 }
759
760 needsPurge = false;
761}
762
763/*
764 * Iterates through all geometry nodes in this batch and unsets their batch,
765 * thus forcing them to be rebuilt
766 */
767void Batch::invalidate()
768{
769 cleanupRemovedElements();
770 Element *e = first;
771 first = nullptr;
772 root = nullptr;
773 while (e) {
774 e->batch = nullptr;
775 Element *n = e->nextInBatch;
776 e->nextInBatch = nullptr;
777 e = n;
778 }
779}
780
781bool Batch::isTranslateOnlyToRoot() const {
782 bool only = true;
783 Element *e = first;
784 while (e && only) {
785 only &= e->translateOnlyToRoot;
786 e = e->nextInBatch;
787 }
788 return only;
789}
790
791/*
792 * Iterates through all the nodes in the batch and returns true if the
793 * nodes are all safe to batch. There are two separate criteria:
794 *
795 * - The matrix is such that the z component of the result is of no
796 * consequence.
797 *
798 * - The bounds are inside the stable floating point range. This applies
799 * to desktop only where we in this case can trigger a fallback to
800 * unmerged in which case we pass the geometry straight through and
801 * just apply the matrix.
802 *
803 * NOTE: This also means a slight performance impact for geometries which
804 * are defined to be outside the stable floating point range and still
805 * use single precision float, but given that this implicitly fixes
806 * huge lists and tables, it is worth it.
807 */
808bool Batch::isSafeToBatch() const {
809 Element *e = first;
810 while (e) {
811 if (e->boundsOutsideFloatRange)
812 return false;
813 if (!is2DSafe(m: *e->node->matrix()))
814 return false;
815 e = e->nextInBatch;
816 }
817 return true;
818}
819
820static int qsg_countNodesInBatch(const Batch *batch)
821{
822 int sum = 0;
823 Element *e = batch->first;
824 while (e) {
825 ++sum;
826 e = e->nextInBatch;
827 }
828 return sum;
829}
830
831static int qsg_countNodesInBatches(const QDataBuffer<Batch *> &batches)
832{
833 int sum = 0;
834 for (int i=0; i<batches.size(); ++i) {
835 sum += qsg_countNodesInBatch(batch: batches.at(i));
836 }
837 return sum;
838}
839
840Renderer::Renderer(QSGDefaultRenderContext *ctx, QSGRendererInterface::RenderMode renderMode)
841 : QSGRenderer(ctx)
842 , m_context(ctx)
843 , m_renderMode(renderMode)
844 , m_opaqueRenderList(64)
845 , m_alphaRenderList(64)
846 , m_nextRenderOrder(0)
847 , m_partialRebuild(false)
848 , m_partialRebuildRoot(nullptr)
849 , m_forceNoDepthBuffer(false)
850 , m_opaqueBatches(16)
851 , m_alphaBatches(16)
852 , m_batchPool(16)
853 , m_elementsToDelete(64)
854 , m_tmpAlphaElements(16)
855 , m_tmpOpaqueElements(16)
856 , m_vboPool(16)
857 , m_iboPool(16)
858 , m_vboPoolCost(0)
859 , m_iboPoolCost(0)
860 , m_rebuild(FullRebuild)
861 , m_zRange(0)
862#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
863 , m_renderOrderRebuildLower(-1)
864 , m_renderOrderRebuildUpper(-1)
865#endif
866 , m_currentMaterial(nullptr)
867 , m_currentShader(nullptr)
868 , m_vertexUploadPool(256)
869 , m_indexUploadPool(64)
870{
871 m_rhi = m_context->rhi();
872 Q_ASSERT(m_rhi); // no more direct OpenGL code path in Qt 6
873
874 m_ubufAlignment = m_rhi->ubufAlignment();
875
876 m_uint32IndexForRhi = !m_rhi->isFeatureSupported(feature: QRhi::NonFourAlignedEffectiveIndexBufferOffset);
877 if (qEnvironmentVariableIntValue(varName: "QSG_RHI_UINT32_INDEX"))
878 m_uint32IndexForRhi = true;
879
880 m_visualizer = new RhiVisualizer(this);
881
882 setNodeUpdater(new Updater(this));
883
884 // The shader manager is shared between renderers (think for example Item
885 // layers that create a new Renderer each) with the same rendercontext (and
886 // so same QRhi).
887 m_shaderManager = ctx->findChild<ShaderManager *>(aName: QString(), options: Qt::FindDirectChildrenOnly);
888 if (!m_shaderManager) {
889 m_shaderManager = new ShaderManager(ctx);
890 m_shaderManager->setObjectName(QStringLiteral("__qt_ShaderManager"));
891 m_shaderManager->setParent(ctx);
892 QObject::connect(sender: ctx, SIGNAL(invalidated()), receiver: m_shaderManager, SLOT(invalidated()), Qt::DirectConnection);
893 }
894
895 m_batchNodeThreshold = qt_sg_envInt(name: "QSG_RENDERER_BATCH_NODE_THRESHOLD", defaultValue: 64);
896 m_batchVertexThreshold = qt_sg_envInt(name: "QSG_RENDERER_BATCH_VERTEX_THRESHOLD", defaultValue: 1024);
897 m_srbPoolThreshold = qt_sg_envInt(name: "QSG_RENDERER_SRB_POOL_THRESHOLD", defaultValue: 1024);
898 m_bufferPoolSizeLimit = qt_sg_envInt(name: "QSG_RENDERER_BUFFER_POOL_LIMIT", defaultValue: DEFAULT_BUFFER_POOL_SIZE_LIMIT);
899
900 if (Q_UNLIKELY(debug_build() || debug_render() || debug_pools())) {
901 qDebug(msg: "Batch thresholds: nodes: %d vertices: %d srb pool: %d buffer pool: %d",
902 m_batchNodeThreshold, m_batchVertexThreshold, m_srbPoolThreshold, m_bufferPoolSizeLimit);
903 }
904}
905
906static void qsg_wipeBuffer(Buffer *buffer)
907{
908 delete buffer->buf;
909
910 // The free here is ok because we're in one of two situations.
911 // 1. We're using the upload pool in which case unmap will have set the
912 // data pointer to 0 and calling free on 0 is ok.
913 // 2. We're using dedicated buffers because of visualization or IBO workaround
914 // and the data something we malloced and must be freed.
915 free(ptr: buffer->data);
916}
917
918static void qsg_wipeBatch(Batch *batch)
919{
920 qsg_wipeBuffer(buffer: &batch->vbo);
921 qsg_wipeBuffer(buffer: &batch->ibo);
922 delete batch->ubuf;
923 batch->stencilClipState.reset();
924 delete batch;
925}
926
927Renderer::~Renderer()
928{
929 if (m_rhi) {
930 // Clean up batches and buffers
931 for (int i = 0; i < m_opaqueBatches.size(); ++i)
932 qsg_wipeBatch(batch: m_opaqueBatches.at(i));
933 for (int i = 0; i < m_alphaBatches.size(); ++i)
934 qsg_wipeBatch(batch: m_alphaBatches.at(i));
935 for (int i = 0; i < m_batchPool.size(); ++i)
936 qsg_wipeBatch(batch: m_batchPool.at(i));
937 for (int i = 0; i < m_vboPool.size(); ++i)
938 delete m_vboPool.at(i);
939 for (int i = 0; i < m_iboPool.size(); ++i)
940 delete m_iboPool.at(i);
941 }
942
943 for (Node *n : std::as_const(t&: m_nodes)) {
944 if (n->type() == QSGNode::GeometryNodeType) {
945 Element *e = n->element();
946 if (!e->removed)
947 m_elementsToDelete.add(t: e);
948 } else if (n->type() == QSGNode::ClipNodeType) {
949 delete n->clipInfo();
950 } else if (n->type() == QSGNode::RenderNodeType) {
951 RenderNodeElement *e = n->renderNodeElement();
952 if (!e->removed)
953 m_elementsToDelete.add(t: e);
954 }
955
956 m_nodeAllocator.release(t: n);
957 }
958
959 // Remaining elements...
960 for (int i=0; i<m_elementsToDelete.size(); ++i)
961 releaseElement(e: m_elementsToDelete.at(i), inDestructor: true);
962
963 destroyGraphicsResources();
964
965 delete m_visualizer;
966}
967
968void Renderer::destroyGraphicsResources()
969{
970 // If this is from the dtor, then the shader manager and its already
971 // prepared shaders will stay around for other renderers -> the cached data
972 // in the rhi shaders have to be purged as it may refer to samplers we
973 // are going to destroy.
974 m_shaderManager->clearCachedRendererData();
975
976 qDeleteAll(c: m_samplers);
977 m_stencilClipCommon.reset();
978 delete m_dummyTexture;
979 m_visualizer->releaseResources();
980}
981
982void Renderer::releaseCachedResources()
983{
984 m_shaderManager->invalidated();
985
986 destroyGraphicsResources();
987
988 m_samplers.clear();
989 m_dummyTexture = nullptr;
990
991 m_rhi->releaseCachedResources();
992
993 m_vertexUploadPool.shrink(size: 0);
994 m_vertexUploadPool.reset();
995 m_indexUploadPool.shrink(size: 0);
996 m_indexUploadPool.reset();
997
998 for (int i = 0; i < m_vboPool.size(); ++i)
999 delete m_vboPool.at(i);
1000 m_vboPool.reset();
1001 m_vboPoolCost = 0;
1002
1003 for (int i = 0; i < m_iboPool.size(); ++i)
1004 delete m_iboPool.at(i);
1005 m_iboPool.reset();
1006 m_iboPoolCost = 0;
1007}
1008
1009void Renderer::invalidateAndRecycleBatch(Batch *b)
1010{
1011 if (b->vbo.buf != nullptr && m_vboPoolCost + b->vbo.buf->size() <= quint32(m_bufferPoolSizeLimit)) {
1012 m_vboPool.add(t: b->vbo.buf);
1013 m_vboPoolCost += b->vbo.buf->size();
1014 } else {
1015 delete b->vbo.buf;
1016 }
1017 if (b->ibo.buf != nullptr && m_iboPoolCost + b->ibo.buf->size() <= quint32(m_bufferPoolSizeLimit)) {
1018 m_iboPool.add(t: b->ibo.buf);
1019 m_iboPoolCost += b->ibo.buf->size();
1020 } else {
1021 delete b->ibo.buf;
1022 }
1023 b->vbo.buf = nullptr;
1024 b->ibo.buf = nullptr;
1025 b->invalidate();
1026 for (int i=0; i<m_batchPool.size(); ++i)
1027 if (b == m_batchPool.at(i))
1028 return;
1029 m_batchPool.add(t: b);
1030}
1031
1032void Renderer::map(Buffer *buffer, quint32 byteSize, bool isIndexBuf)
1033{
1034 if (m_visualizer->mode() == Visualizer::VisualizeNothing) {
1035 // Common case, use a shared memory pool for uploading vertex data to avoid
1036 // excessive reevaluation
1037 QDataBuffer<char> &pool = isIndexBuf ? m_indexUploadPool : m_vertexUploadPool;
1038 if (byteSize > quint32(pool.size()))
1039 pool.resize(size: byteSize);
1040 buffer->data = pool.data();
1041 } else if (buffer->size != byteSize) {
1042 free(ptr: buffer->data);
1043 buffer->data = (char *) malloc(size: byteSize);
1044 Q_CHECK_PTR(buffer->data);
1045 }
1046 buffer->size = byteSize;
1047}
1048
1049void Renderer::unmap(Buffer *buffer, bool isIndexBuf)
1050{
1051 // Batches are pooled and reused which means the QRhiBuffer will be
1052 // still valid in a recycled Batch. We only hit the newBuffer() path
1053 // when there are no buffers to recycle.
1054 QDataBuffer<QRhiBuffer *> *bufferPool = isIndexBuf ? &m_iboPool : &m_vboPool;
1055 if (!buffer->buf && bufferPool->isEmpty()) {
1056 buffer->buf = m_rhi->newBuffer(type: QRhiBuffer::Immutable,
1057 usage: isIndexBuf ? QRhiBuffer::IndexBuffer : QRhiBuffer::VertexBuffer,
1058 size: buffer->size);
1059 if (!buffer->buf->create()) {
1060 qWarning(msg: "Failed to build vertex/index buffer of size %u", buffer->size);
1061 delete buffer->buf;
1062 buffer->buf = nullptr;
1063 }
1064 } else {
1065 if (!buffer->buf) {
1066 const quint32 expectedSize = buffer->size;
1067 qsizetype foundBufferIndex = 0;
1068 for (qsizetype i = 0; i < bufferPool->size(); ++i) {
1069 QRhiBuffer *testBuffer = bufferPool->at(i);
1070 if (!buffer->buf
1071 || (testBuffer->size() >= expectedSize && testBuffer->size() < buffer->buf->size())
1072 || (testBuffer->size() < expectedSize && testBuffer->size() > buffer->buf->size())) {
1073 foundBufferIndex = i;
1074 buffer->buf = testBuffer;
1075 if (buffer->buf->size() == expectedSize)
1076 break;
1077 }
1078 }
1079
1080 const qsizetype lastBufferIndex = bufferPool->size() - 1;
1081 if (foundBufferIndex < lastBufferIndex) {
1082 qSwap(value1&: bufferPool->data()[foundBufferIndex],
1083 value2&: bufferPool->data()[lastBufferIndex]);
1084 }
1085 if (isIndexBuf)
1086 m_iboPoolCost -= bufferPool->data()[lastBufferIndex]->size();
1087 else
1088 m_vboPoolCost -= bufferPool->data()[lastBufferIndex]->size();
1089 bufferPool->pop_back();
1090 }
1091
1092 bool needsRebuild = false;
1093 if (buffer->buf->size() < buffer->size) {
1094 buffer->buf->setSize(buffer->size);
1095 needsRebuild = true;
1096 }
1097 if (buffer->buf->type() != QRhiBuffer::Dynamic
1098 && buffer->nonDynamicChangeCount > DYNAMIC_VERTEX_INDEX_BUFFER_THRESHOLD)
1099 {
1100 buffer->buf->setType(QRhiBuffer::Dynamic);
1101 buffer->nonDynamicChangeCount = 0;
1102 needsRebuild = true;
1103 }
1104 if (needsRebuild) {
1105 if (!buffer->buf->create()) {
1106 qWarning(msg: "Failed to (re)build vertex/index buffer of size %u", buffer->size);
1107 delete buffer->buf;
1108 buffer->buf = nullptr;
1109 }
1110 }
1111 }
1112 if (buffer->buf) {
1113 if (buffer->buf->type() != QRhiBuffer::Dynamic) {
1114 m_resourceUpdates->uploadStaticBuffer(buf: buffer->buf, offset: 0, size: buffer->size, data: buffer->data);
1115 buffer->nonDynamicChangeCount += 1;
1116 } else {
1117 if (m_rhi->resourceLimit(limit: QRhi::FramesInFlight) == 1)
1118 buffer->buf->fullDynamicBufferUpdateForCurrentFrame(data: buffer->data);
1119 else
1120 m_resourceUpdates->updateDynamicBuffer(buf: buffer->buf, offset: 0, size: buffer->size, data: buffer->data);
1121 }
1122 }
1123 if (m_visualizer->mode() == Visualizer::VisualizeNothing)
1124 buffer->data = nullptr;
1125}
1126
1127BatchRootInfo *Renderer::batchRootInfo(Node *node)
1128{
1129 BatchRootInfo *info = node->rootInfo();
1130 if (!info) {
1131 if (node->type() == QSGNode::ClipNodeType)
1132 info = new ClipBatchRootInfo;
1133 else {
1134 Q_ASSERT(node->type() == QSGNode::TransformNodeType);
1135 info = new BatchRootInfo;
1136 }
1137 node->data = info;
1138 }
1139 return info;
1140}
1141
1142void Renderer::removeBatchRootFromParent(Node *childRoot)
1143{
1144 BatchRootInfo *childInfo = batchRootInfo(node: childRoot);
1145 if (!childInfo->parentRoot)
1146 return;
1147 BatchRootInfo *parentInfo = batchRootInfo(node: childInfo->parentRoot);
1148
1149 Q_ASSERT(parentInfo->subRoots.contains(childRoot));
1150 parentInfo->subRoots.remove(value: childRoot);
1151 childInfo->parentRoot = nullptr;
1152}
1153
1154void Renderer::registerBatchRoot(Node *subRoot, Node *parentRoot)
1155{
1156 BatchRootInfo *subInfo = batchRootInfo(node: subRoot);
1157 BatchRootInfo *parentInfo = batchRootInfo(node: parentRoot);
1158 subInfo->parentRoot = parentRoot;
1159 parentInfo->subRoots << subRoot;
1160}
1161
1162bool Renderer::changeBatchRoot(Node *node, Node *root)
1163{
1164 BatchRootInfo *subInfo = batchRootInfo(node);
1165 if (subInfo->parentRoot == root)
1166 return false;
1167 if (subInfo->parentRoot) {
1168 BatchRootInfo *oldRootInfo = batchRootInfo(node: subInfo->parentRoot);
1169 oldRootInfo->subRoots.remove(value: node);
1170 }
1171 BatchRootInfo *newRootInfo = batchRootInfo(node: root);
1172 newRootInfo->subRoots << node;
1173 subInfo->parentRoot = root;
1174 return true;
1175}
1176
1177void Renderer::nodeChangedBatchRoot(Node *node, Node *root)
1178{
1179 if (node->type() == QSGNode::ClipNodeType || node->isBatchRoot) {
1180 // When we reach a batchroot, we only need to update it. Its subtree
1181 // is relative to that root, so no need to recurse further.
1182 changeBatchRoot(node, root);
1183 return;
1184 } else if (node->type() == QSGNode::GeometryNodeType) {
1185 // Only need to change the root as nodeChanged anyway flags a full update.
1186 Element *e = node->element();
1187 if (e) {
1188 e->root = root;
1189 e->boundsComputed = false;
1190 }
1191 } else if (node->type() == QSGNode::RenderNodeType) {
1192 RenderNodeElement *e = node->renderNodeElement();
1193 if (e)
1194 e->root = root;
1195 }
1196
1197 SHADOWNODE_TRAVERSE(node)
1198 nodeChangedBatchRoot(node: child, root);
1199}
1200
1201void Renderer::nodeWasTransformed(Node *node, int *vertexCount)
1202{
1203 if (node->type() == QSGNode::GeometryNodeType) {
1204 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node->sgNode);
1205 *vertexCount += gn->geometry()->vertexCount();
1206 Element *e = node->element();
1207 if (e) {
1208 e->boundsComputed = false;
1209 if (e->batch) {
1210 if (!e->batch->isOpaque) {
1211 invalidateBatchAndOverlappingRenderOrders(batch: e->batch);
1212 } else if (e->batch->merged) {
1213 e->batch->needsUpload = true;
1214 }
1215 }
1216 }
1217 }
1218
1219 SHADOWNODE_TRAVERSE(node)
1220 nodeWasTransformed(node: child, vertexCount);
1221}
1222
1223void Renderer::nodeWasAdded(QSGNode *node, Node *shadowParent)
1224{
1225 Q_ASSERT(!m_nodes.contains(node));
1226 if (node->isSubtreeBlocked())
1227 return;
1228
1229 Node *snode = m_nodeAllocator.allocate();
1230 snode->sgNode = node;
1231 m_nodes.insert(key: node, value: snode);
1232 if (shadowParent)
1233 shadowParent->append(child: snode);
1234
1235 if (node->type() == QSGNode::GeometryNodeType) {
1236 snode->data = m_elementAllocator.allocate();
1237 snode->element()->setNode(static_cast<QSGGeometryNode *>(node));
1238
1239 } else if (node->type() == QSGNode::ClipNodeType) {
1240 snode->data = new ClipBatchRootInfo;
1241 m_rebuild |= FullRebuild;
1242
1243 } else if (node->type() == QSGNode::RenderNodeType) {
1244 QSGRenderNode *rn = static_cast<QSGRenderNode *>(node);
1245 RenderNodeElement *e = new RenderNodeElement(rn);
1246 snode->data = e;
1247 Q_ASSERT(!m_renderNodeElements.contains(rn));
1248 m_renderNodeElements.insert(key: e->renderNode, value: e);
1249 if (!rn->flags().testFlag(flag: QSGRenderNode::DepthAwareRendering))
1250 m_forceNoDepthBuffer = true;
1251 m_rebuild |= FullRebuild;
1252 }
1253
1254 QSGNODE_TRAVERSE(node)
1255 nodeWasAdded(node: child, shadowParent: snode);
1256}
1257
1258void Renderer::nodeWasRemoved(Node *node)
1259{
1260 // Prefix traversal as removeBatchRootFromParent below removes nodes
1261 // in a bottom-up manner. Note that we *cannot* use SHADOWNODE_TRAVERSE
1262 // here, because we delete 'child' (when recursed, down below), so we'd
1263 // have a use-after-free.
1264 {
1265 Node *child = node->firstChild();
1266 while (child) {
1267 // Remove (and delete) child
1268 node->remove(child);
1269 nodeWasRemoved(node: child);
1270 child = node->firstChild();
1271 }
1272 }
1273
1274 if (node->type() == QSGNode::GeometryNodeType) {
1275 Element *e = node->element();
1276 if (e) {
1277 e->removed = true;
1278 m_elementsToDelete.add(t: e);
1279 e->node = nullptr;
1280 if (e->root) {
1281 BatchRootInfo *info = batchRootInfo(node: e->root);
1282 info->availableOrders++;
1283 }
1284 if (e->batch) {
1285 e->batch->needsUpload = true;
1286 e->batch->needsPurge = true;
1287 }
1288
1289 }
1290
1291 } else if (node->type() == QSGNode::ClipNodeType) {
1292 removeBatchRootFromParent(childRoot: node);
1293 delete node->clipInfo();
1294 m_rebuild |= FullRebuild;
1295 m_taggedRoots.remove(value: node);
1296
1297 } else if (node->isBatchRoot) {
1298 removeBatchRootFromParent(childRoot: node);
1299 delete node->rootInfo();
1300 m_rebuild |= FullRebuild;
1301 m_taggedRoots.remove(value: node);
1302
1303 } else if (node->type() == QSGNode::RenderNodeType) {
1304 RenderNodeElement *e = m_renderNodeElements.take(key: static_cast<QSGRenderNode *>(node->sgNode));
1305 if (e) {
1306 e->removed = true;
1307 m_elementsToDelete.add(t: e);
1308 if (m_renderNodeElements.isEmpty()) {
1309 m_forceNoDepthBuffer = false;
1310 // Must have a full rebuild given useDepthBuffer() now returns
1311 // a different value than before, meaning there can once again
1312 // be an opaque pass.
1313 m_rebuild |= FullRebuild;
1314 }
1315
1316 if (e->batch != nullptr)
1317 e->batch->needsPurge = true;
1318 }
1319 }
1320
1321 Q_ASSERT(m_nodes.contains(node->sgNode));
1322
1323 m_nodeAllocator.release(t: m_nodes.take(key: node->sgNode));
1324}
1325
1326void Renderer::turnNodeIntoBatchRoot(Node *node)
1327{
1328 if (Q_UNLIKELY(debug_change())) qDebug(msg: " - new batch root");
1329 m_rebuild |= FullRebuild;
1330 node->isBatchRoot = true;
1331 node->becameBatchRoot = true;
1332
1333 Node *p = node->parent();
1334 while (p) {
1335 if (p->type() == QSGNode::ClipNodeType || p->isBatchRoot) {
1336 registerBatchRoot(subRoot: node, parentRoot: p);
1337 break;
1338 }
1339 p = p->parent();
1340 }
1341
1342 SHADOWNODE_TRAVERSE(node)
1343 nodeChangedBatchRoot(node: child, root: node);
1344}
1345
1346
1347void Renderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state)
1348{
1349#ifndef QT_NO_DEBUG_OUTPUT
1350 if (Q_UNLIKELY(debug_change())) {
1351 QDebug debug = qDebug();
1352 debug << "dirty:";
1353 if (state & QSGNode::DirtyGeometry)
1354 debug << "Geometry";
1355 if (state & QSGNode::DirtyMaterial)
1356 debug << "Material";
1357 if (state & QSGNode::DirtyMatrix)
1358 debug << "Matrix";
1359 if (state & QSGNode::DirtyNodeAdded)
1360 debug << "Added";
1361 if (state & QSGNode::DirtyNodeRemoved)
1362 debug << "Removed";
1363 if (state & QSGNode::DirtyOpacity)
1364 debug << "Opacity";
1365 if (state & QSGNode::DirtySubtreeBlocked)
1366 debug << "SubtreeBlocked";
1367 if (state & QSGNode::DirtyForceUpdate)
1368 debug << "ForceUpdate";
1369
1370 // when removed, some parts of the node could already have been destroyed
1371 // so don't debug it out.
1372 if (state & QSGNode::DirtyNodeRemoved)
1373 debug << (void *) node << node->type();
1374 else
1375 debug << node;
1376 }
1377#endif
1378 // As this function calls nodeChanged recursively, we do it at the top
1379 // to avoid that any of the others are processed twice.
1380 if (state & QSGNode::DirtySubtreeBlocked) {
1381 Node *sn = m_nodes.value(key: node);
1382
1383 // Force a batch rebuild if this includes an opacity change
1384 if (state & QSGNode::DirtyOpacity)
1385 m_rebuild |= FullRebuild;
1386
1387 bool blocked = node->isSubtreeBlocked();
1388 if (blocked && sn) {
1389 nodeChanged(node, state: QSGNode::DirtyNodeRemoved);
1390 Q_ASSERT(m_nodes.value(node) == 0);
1391 } else if (!blocked && !sn) {
1392 nodeChanged(node, state: QSGNode::DirtyNodeAdded);
1393 }
1394 return;
1395 }
1396
1397 if (state & QSGNode::DirtyNodeAdded) {
1398 if (nodeUpdater()->isNodeBlocked(n: node, root: rootNode())) {
1399 QSGRenderer::nodeChanged(node, state);
1400 return;
1401 }
1402 if (node == rootNode())
1403 nodeWasAdded(node, shadowParent: nullptr);
1404 else
1405 nodeWasAdded(node, shadowParent: m_nodes.value(key: node->parent()));
1406 }
1407
1408 // Mark this node dirty in the shadow tree.
1409 Node *shadowNode = m_nodes.value(key: node);
1410
1411 // Blocked subtrees won't have shadow nodes, so we can safely abort
1412 // here..
1413 if (!shadowNode) {
1414 QSGRenderer::nodeChanged(node, state);
1415 return;
1416 }
1417
1418 shadowNode->dirtyState |= state;
1419
1420 if (state & QSGNode::DirtyMatrix && !shadowNode->isBatchRoot) {
1421 Q_ASSERT(node->type() == QSGNode::TransformNodeType);
1422 if (node->m_subtreeRenderableCount > m_batchNodeThreshold) {
1423 turnNodeIntoBatchRoot(node: shadowNode);
1424 } else {
1425 int vertices = 0;
1426 nodeWasTransformed(node: shadowNode, vertexCount: &vertices);
1427 if (vertices > m_batchVertexThreshold) {
1428 turnNodeIntoBatchRoot(node: shadowNode);
1429 }
1430 }
1431 }
1432
1433 if (state & QSGNode::DirtyGeometry && node->type() == QSGNode::GeometryNodeType) {
1434 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1435 Element *e = shadowNode->element();
1436 if (e) {
1437 e->boundsComputed = false;
1438 Batch *b = e->batch;
1439 if (b) {
1440 if (!e->batch->geometryWasChanged(gn) || !e->batch->isOpaque) {
1441 invalidateBatchAndOverlappingRenderOrders(batch: e->batch);
1442 } else {
1443 b->needsUpload = true;
1444 }
1445 }
1446 }
1447 }
1448
1449 if (state & QSGNode::DirtyMaterial && node->type() == QSGNode::GeometryNodeType) {
1450 Element *e = shadowNode->element();
1451 if (e) {
1452 bool blended = hasMaterialWithBlending(n: static_cast<QSGGeometryNode *>(node));
1453 if (e->isMaterialBlended != blended) {
1454 m_rebuild |= Renderer::FullRebuild;
1455 e->isMaterialBlended = blended;
1456 } else if (e->batch) {
1457 if (e->batch->isMaterialCompatible(e) == BatchBreaksOnCompare)
1458 invalidateBatchAndOverlappingRenderOrders(batch: e->batch);
1459 } else {
1460 m_rebuild |= Renderer::BuildBatches;
1461 }
1462 }
1463 }
1464
1465 // Mark the shadow tree dirty all the way back to the root...
1466 QSGNode::DirtyState dirtyChain = state & (QSGNode::DirtyNodeAdded
1467 | QSGNode::DirtyOpacity
1468 | QSGNode::DirtyMatrix
1469 | QSGNode::DirtySubtreeBlocked
1470 | QSGNode::DirtyForceUpdate);
1471 if (dirtyChain != 0) {
1472 dirtyChain = QSGNode::DirtyState(dirtyChain << 16);
1473 Node *sn = shadowNode->parent();
1474 while (sn) {
1475 sn->dirtyState |= dirtyChain;
1476 sn = sn->parent();
1477 }
1478 }
1479
1480 // Delete happens at the very end because it deletes the shadownode.
1481 if (state & QSGNode::DirtyNodeRemoved) {
1482 Node *parent = shadowNode->parent();
1483 if (parent)
1484 parent->remove(child: shadowNode);
1485 nodeWasRemoved(node: shadowNode);
1486 Q_ASSERT(m_nodes.value(node) == 0);
1487 }
1488
1489 QSGRenderer::nodeChanged(node, state);
1490}
1491
1492/*
1493 * Traverses the tree and builds two list of geometry nodes. One for
1494 * the opaque and one for the translucent. These are populated
1495 * in the order they should visually appear in, meaning first
1496 * to the back and last to the front.
1497 *
1498 * We split opaque and translucent as we can perform different
1499 * types of reordering / batching strategies on them, depending
1500 *
1501 * Note: It would be tempting to use the shadow nodes instead of the QSGNodes
1502 * for traversal to avoid hash lookups, but the order of the children
1503 * is important and they are not preserved in the shadow tree, so we must
1504 * use the actual QSGNode tree.
1505 */
1506void Renderer::buildRenderLists(QSGNode *node)
1507{
1508 if (node->isSubtreeBlocked())
1509 return;
1510
1511 Node *shadowNode = m_nodes.value(key: node);
1512 Q_ASSERT(shadowNode);
1513
1514 if (node->type() == QSGNode::GeometryNodeType) {
1515 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1516
1517 Element *e = shadowNode->element();
1518 Q_ASSERT(e);
1519
1520 bool opaque = gn->inheritedOpacity() > OPAQUE_LIMIT && !(gn->activeMaterial()->flags() & QSGMaterial::Blending);
1521 if (opaque && useDepthBuffer())
1522 m_opaqueRenderList << e;
1523 else
1524 m_alphaRenderList << e;
1525
1526 e->order = ++m_nextRenderOrder;
1527 // Used while rebuilding partial roots.
1528 if (m_partialRebuild)
1529 e->orphaned = false;
1530
1531 } else if (node->type() == QSGNode::ClipNodeType || shadowNode->isBatchRoot) {
1532 Q_ASSERT(m_nodes.contains(node));
1533 BatchRootInfo *info = batchRootInfo(node: shadowNode);
1534 if (node == m_partialRebuildRoot) {
1535 m_nextRenderOrder = info->firstOrder;
1536 QSGNODE_TRAVERSE(node)
1537 buildRenderLists(node: child);
1538 m_nextRenderOrder = info->lastOrder + 1;
1539 } else {
1540 int currentOrder = m_nextRenderOrder;
1541 QSGNODE_TRAVERSE(node)
1542 buildRenderLists(node: child);
1543 int padding = (m_nextRenderOrder - currentOrder) >> 2;
1544 info->firstOrder = currentOrder;
1545 info->availableOrders = padding;
1546 info->lastOrder = m_nextRenderOrder + padding;
1547 m_nextRenderOrder = info->lastOrder;
1548 }
1549 return;
1550 } else if (node->type() == QSGNode::RenderNodeType) {
1551 RenderNodeElement *e = shadowNode->renderNodeElement();
1552 m_alphaRenderList << e;
1553 e->order = ++m_nextRenderOrder;
1554 Q_ASSERT(e);
1555 }
1556
1557 QSGNODE_TRAVERSE(node)
1558 buildRenderLists(node: child);
1559}
1560
1561void Renderer::tagSubRoots(Node *node)
1562{
1563 BatchRootInfo *i = batchRootInfo(node);
1564 m_taggedRoots << node;
1565 for (QSet<Node *>::const_iterator it = i->subRoots.constBegin();
1566 it != i->subRoots.constEnd(); ++it) {
1567 tagSubRoots(node: *it);
1568 }
1569}
1570
1571static void qsg_addOrphanedElements(QDataBuffer<Element *> &orphans, const QDataBuffer<Element *> &renderList)
1572{
1573 orphans.reset();
1574 for (int i=0; i<renderList.size(); ++i) {
1575 Element *e = renderList.at(i);
1576 if (e && !e->removed) {
1577 e->orphaned = true;
1578 orphans.add(t: e);
1579 }
1580 }
1581}
1582
1583static void qsg_addBackOrphanedElements(QDataBuffer<Element *> &orphans, QDataBuffer<Element *> &renderList)
1584{
1585 for (int i=0; i<orphans.size(); ++i) {
1586 Element *e = orphans.at(i);
1587 if (e->orphaned)
1588 renderList.add(t: e);
1589 }
1590 orphans.reset();
1591}
1592
1593/*
1594 * To rebuild the tagged roots, we start by putting all subroots of tagged
1595 * roots into the list of tagged roots. This is to make the rest of the
1596 * algorithm simpler.
1597 *
1598 * Second, we invalidate all batches which belong to tagged roots, which now
1599 * includes the entire subtree under a given root
1600 *
1601 * Then we call buildRenderLists for all tagged subroots which do not have
1602 * parents which are tagged, aka, we traverse only the topmosts roots.
1603 *
1604 * Then we sort the render lists based on their render order, to restore the
1605 * right order for rendering.
1606 */
1607void Renderer::buildRenderListsForTaggedRoots()
1608{
1609 // Flag any element that is currently in the render lists, but which
1610 // is not in a batch. This happens when we have a partial rebuild
1611 // in one sub tree while we have a BuildBatches change in another
1612 // isolated subtree. So that batch-building takes into account
1613 // these "orphaned" nodes, we flag them now. The ones under tagged
1614 // roots will be cleared again. The remaining ones are added into the
1615 // render lists so that they contain all visual nodes after the
1616 // function completes.
1617 qsg_addOrphanedElements(orphans&: m_tmpOpaqueElements, renderList: m_opaqueRenderList);
1618 qsg_addOrphanedElements(orphans&: m_tmpAlphaElements, renderList: m_alphaRenderList);
1619
1620 // Take a copy now, as we will be adding to this while traversing..
1621 QSet<Node *> roots = m_taggedRoots;
1622 for (QSet<Node *>::const_iterator it = roots.constBegin();
1623 it != roots.constEnd(); ++it) {
1624 tagSubRoots(node: *it);
1625 }
1626
1627 for (int i=0; i<m_opaqueBatches.size(); ++i) {
1628 Batch *b = m_opaqueBatches.at(i);
1629 if (m_taggedRoots.contains(value: b->root))
1630 invalidateAndRecycleBatch(b);
1631
1632 }
1633 for (int i=0; i<m_alphaBatches.size(); ++i) {
1634 Batch *b = m_alphaBatches.at(i);
1635 if (m_taggedRoots.contains(value: b->root))
1636 invalidateAndRecycleBatch(b);
1637 }
1638
1639 m_opaqueRenderList.reset();
1640 m_alphaRenderList.reset();
1641 int maxRenderOrder = m_nextRenderOrder;
1642 m_partialRebuild = true;
1643 // Traverse each root, assigning it
1644 for (QSet<Node *>::const_iterator it = m_taggedRoots.constBegin();
1645 it != m_taggedRoots.constEnd(); ++it) {
1646 Node *root = *it;
1647 BatchRootInfo *i = batchRootInfo(node: root);
1648 if ((!i->parentRoot || !m_taggedRoots.contains(value: i->parentRoot))
1649 && !nodeUpdater()->isNodeBlocked(n: root->sgNode, root: rootNode())) {
1650 m_nextRenderOrder = i->firstOrder;
1651 m_partialRebuildRoot = root->sgNode;
1652 buildRenderLists(node: root->sgNode);
1653 }
1654 }
1655 m_partialRebuild = false;
1656 m_partialRebuildRoot = nullptr;
1657 m_taggedRoots.clear();
1658 m_nextRenderOrder = qMax(a: m_nextRenderOrder, b: maxRenderOrder);
1659
1660 // Add orphaned elements back into the list and then sort it..
1661 qsg_addBackOrphanedElements(orphans&: m_tmpOpaqueElements, renderList&: m_opaqueRenderList);
1662 qsg_addBackOrphanedElements(orphans&: m_tmpAlphaElements, renderList&: m_alphaRenderList);
1663
1664 if (m_opaqueRenderList.size())
1665 std::sort(first: &m_opaqueRenderList.first(), last: &m_opaqueRenderList.last() + 1, comp: qsg_sort_element_decreasing_order);
1666 if (m_alphaRenderList.size())
1667 std::sort(first: &m_alphaRenderList.first(), last: &m_alphaRenderList.last() + 1, comp: qsg_sort_element_increasing_order);
1668
1669}
1670
1671void Renderer::buildRenderListsFromScratch()
1672{
1673 m_opaqueRenderList.reset();
1674 m_alphaRenderList.reset();
1675
1676 for (int i=0; i<m_opaqueBatches.size(); ++i)
1677 invalidateAndRecycleBatch(b: m_opaqueBatches.at(i));
1678 for (int i=0; i<m_alphaBatches.size(); ++i)
1679 invalidateAndRecycleBatch(b: m_alphaBatches.at(i));
1680 m_opaqueBatches.reset();
1681 m_alphaBatches.reset();
1682
1683 m_nextRenderOrder = 0;
1684
1685 buildRenderLists(node: rootNode());
1686}
1687
1688void Renderer::invalidateBatchAndOverlappingRenderOrders(Batch *batch)
1689{
1690 Q_ASSERT(batch);
1691 Q_ASSERT(batch->first);
1692
1693#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1694 if (m_renderOrderRebuildLower < 0 || batch->first->order < m_renderOrderRebuildLower)
1695 m_renderOrderRebuildLower = batch->first->order;
1696 if (m_renderOrderRebuildUpper < 0 || batch->lastOrderInBatch > m_renderOrderRebuildUpper)
1697 m_renderOrderRebuildUpper = batch->lastOrderInBatch;
1698
1699 int first = m_renderOrderRebuildLower;
1700 int last = m_renderOrderRebuildUpper;
1701#else
1702 int first = batch->first->order;
1703 int last = batch->lastOrderInBatch;
1704#endif
1705
1706 batch->invalidate();
1707
1708 for (int i=0; i<m_alphaBatches.size(); ++i) {
1709 Batch *b = m_alphaBatches.at(i);
1710 if (b->first) {
1711 int bf = b->first->order;
1712 int bl = b->lastOrderInBatch;
1713 if (bl > first && bf < last)
1714 b->invalidate();
1715 }
1716 }
1717
1718 m_rebuild |= BuildBatches;
1719}
1720
1721/* Clean up batches by making it a consecutive list of "valid"
1722 * batches and moving all invalidated batches to the batches pool.
1723 */
1724void Renderer::cleanupBatches(QDataBuffer<Batch *> *batches) {
1725 if (batches->size()) {
1726 std::stable_sort(first: &batches->first(), last: &batches->last() + 1, comp: qsg_sort_batch_is_valid);
1727 int count = 0;
1728 while (count < batches->size() && batches->at(i: count)->first)
1729 ++count;
1730 for (int i=count; i<batches->size(); ++i)
1731 invalidateAndRecycleBatch(b: batches->at(i));
1732 batches->resize(size: count);
1733 }
1734}
1735
1736void Renderer::prepareOpaqueBatches()
1737{
1738 for (int i=m_opaqueRenderList.size() - 1; i >= 0; --i) {
1739 Element *ei = m_opaqueRenderList.at(i);
1740 if (!ei || ei->batch || ei->node->geometry()->vertexCount() == 0)
1741 continue;
1742 Batch *batch = newBatch();
1743 batch->first = ei;
1744 batch->root = ei->root;
1745 batch->isOpaque = true;
1746 batch->needsUpload = true;
1747 batch->positionAttribute = qsg_positionAttribute(g: ei->node->geometry());
1748
1749 m_opaqueBatches.add(t: batch);
1750
1751 ei->batch = batch;
1752 Element *next = ei;
1753
1754 QSGGeometryNode *gni = ei->node;
1755
1756 for (int j = i - 1; j >= 0; --j) {
1757 Element *ej = m_opaqueRenderList.at(i: j);
1758 if (!ej)
1759 continue;
1760 if (ej->root != ei->root)
1761 break;
1762 if (ej->batch || ej->node->geometry()->vertexCount() == 0)
1763 continue;
1764
1765 QSGGeometryNode *gnj = ej->node;
1766
1767 const QSGGeometry *gniGeometry = gni->geometry();
1768 const QSGMaterial *gniMaterial = gni->activeMaterial();
1769 const QSGGeometry *gnjGeometry = gnj->geometry();
1770 const QSGMaterial *gnjMaterial = gnj->activeMaterial();
1771 if (gni->clipList() == gnj->clipList()
1772 && gniGeometry->drawingMode() == gnjGeometry->drawingMode()
1773 && (gniGeometry->drawingMode() != QSGGeometry::DrawLines || gniGeometry->lineWidth() == gnjGeometry->lineWidth())
1774 && gniGeometry->attributes() == gnjGeometry->attributes()
1775 && gniGeometry->indexType() == gnjGeometry->indexType()
1776 && gni->inheritedOpacity() == gnj->inheritedOpacity()
1777 && gniMaterial->type() == gnjMaterial->type()
1778 && gniMaterial->viewCount() == gnjMaterial->viewCount()
1779 && gniMaterial->compare(other: gnjMaterial) == 0)
1780 {
1781 ej->batch = batch;
1782 next->nextInBatch = ej;
1783 next = ej;
1784 }
1785 }
1786
1787 batch->lastOrderInBatch = next->order;
1788 }
1789}
1790
1791bool Renderer::checkOverlap(int first, int last, const Rect &bounds)
1792{
1793 for (int i=first; i<=last; ++i) {
1794 Element *e = m_alphaRenderList.at(i);
1795#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1796 if (!e || e->batch)
1797#else
1798 if (!e)
1799#endif
1800 continue;
1801 Q_ASSERT(e->boundsComputed);
1802 if (e->bounds.intersects(r: bounds))
1803 return true;
1804 }
1805 return false;
1806}
1807
1808/*
1809 *
1810 * To avoid the O(n^2) checkOverlap check in most cases, we have the
1811 * overlapBounds which is the union of all bounding rects to check overlap
1812 * for. We know that if it does not overlap, then none of the individual
1813 * ones will either. For the typical list case, this results in no calls
1814 * to checkOverlap what-so-ever. This also ensures that when all consecutive
1815 * items are matching (such as a table of text), we don't build up an
1816 * overlap bounds and thus do not require full overlap checks.
1817 */
1818
1819void Renderer::prepareAlphaBatches()
1820{
1821 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1822 Element *e = m_alphaRenderList.at(i);
1823 if (!e || e->isRenderNode)
1824 continue;
1825 Q_ASSERT(!e->removed);
1826 e->ensureBoundsValid();
1827 }
1828
1829 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1830 Element *ei = m_alphaRenderList.at(i);
1831 if (!ei || ei->batch)
1832 continue;
1833
1834 if (ei->isRenderNode) {
1835 Batch *rnb = newBatch();
1836 rnb->first = ei;
1837 rnb->root = ei->root;
1838 rnb->isOpaque = false;
1839 rnb->isRenderNode = true;
1840 ei->batch = rnb;
1841 m_alphaBatches.add(t: rnb);
1842 continue;
1843 }
1844
1845 if (ei->node->geometry()->vertexCount() == 0)
1846 continue;
1847
1848 Batch *batch = newBatch();
1849 batch->first = ei;
1850 batch->root = ei->root;
1851 batch->isOpaque = false;
1852 batch->needsUpload = true;
1853 m_alphaBatches.add(t: batch);
1854 ei->batch = batch;
1855
1856 QSGGeometryNode *gni = ei->node;
1857 batch->positionAttribute = qsg_positionAttribute(g: gni->geometry());
1858
1859 Rect overlapBounds;
1860 overlapBounds.set(FLT_MAX, FLT_MAX, right: -FLT_MAX, bottom: -FLT_MAX);
1861
1862 Element *next = ei;
1863
1864 for (int j = i + 1; j < m_alphaRenderList.size(); ++j) {
1865 Element *ej = m_alphaRenderList.at(i: j);
1866 if (!ej)
1867 continue;
1868 if (ej->root != ei->root || ej->isRenderNode)
1869 break;
1870 if (ej->batch) {
1871#if !defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
1872 overlapBounds |= ej->bounds;
1873#endif
1874 continue;
1875 }
1876
1877 QSGGeometryNode *gnj = ej->node;
1878 if (gnj->geometry()->vertexCount() == 0)
1879 continue;
1880
1881 const QSGGeometry *gniGeometry = gni->geometry();
1882 const QSGMaterial *gniMaterial = gni->activeMaterial();
1883 const QSGGeometry *gnjGeometry = gnj->geometry();
1884 const QSGMaterial *gnjMaterial = gnj->activeMaterial();
1885 if (gni->clipList() == gnj->clipList()
1886 && gniGeometry->drawingMode() == gnjGeometry->drawingMode()
1887 && (gniGeometry->drawingMode() != QSGGeometry::DrawLines
1888 || (gniGeometry->lineWidth() == gnjGeometry->lineWidth()
1889 // Must not do overlap checks when the line width is not 1,
1890 // we have no knowledge how such lines are rasterized.
1891 && gniGeometry->lineWidth() == 1.0f))
1892 && gniGeometry->attributes() == gnjGeometry->attributes()
1893 && gniGeometry->indexType() == gnjGeometry->indexType()
1894 && gni->inheritedOpacity() == gnj->inheritedOpacity()
1895 && gniMaterial->type() == gnjMaterial->type()
1896 && gniMaterial->viewCount() == gnjMaterial->viewCount()
1897 && gniMaterial->compare(other: gnjMaterial) == 0)
1898 {
1899 if (!overlapBounds.intersects(r: ej->bounds) || !checkOverlap(first: i+1, last: j - 1, bounds: ej->bounds)) {
1900 ej->batch = batch;
1901 next->nextInBatch = ej;
1902 next = ej;
1903 } else {
1904 /* When we come across a compatible element which hits an overlap, we
1905 * need to stop the batch right away. We cannot add more elements
1906 * to the current batch as they will be rendered before the batch that the
1907 * current 'ej' will be added to.
1908 */
1909 break;
1910 }
1911 } else {
1912 overlapBounds |= ej->bounds;
1913 }
1914 }
1915
1916 batch->lastOrderInBatch = next->order;
1917 }
1918
1919
1920}
1921
1922static inline int qsg_fixIndexCount(int iCount, int drawMode)
1923{
1924 switch (drawMode) {
1925 case QSGGeometry::DrawTriangleStrip:
1926 // Merged triangle strips need to contain degenerate triangles at the beginning and end.
1927 // One could save 2 uploaded ushorts here by ditching the padding for the front of the
1928 // first and the end of the last, but for simplicity, we simply don't care.
1929 // Those extra triangles will be skipped while drawing to preserve the strip's parity
1930 // anyhow.
1931 return iCount + 2;
1932 case QSGGeometry::DrawLines:
1933 // For lines we drop the last vertex if the number of vertices is uneven.
1934 return iCount - (iCount % 2);
1935 case QSGGeometry::DrawTriangles:
1936 // For triangles we drop trailing vertices until the result is divisible by 3.
1937 return iCount - (iCount % 3);
1938 default:
1939 return iCount;
1940 }
1941}
1942
1943static inline float calculateElementZOrder(const Element *e, qreal zRange)
1944{
1945 // Clamp the zOrder to within the min and max depth of the viewport.
1946 return std::clamp(val: 1.0f - float(e->order * zRange), lo: VIEWPORT_MIN_DEPTH, hi: VIEWPORT_MAX_DEPTH);
1947}
1948
1949/* These parameters warrant some explanation...
1950 *
1951 * vaOffset: The byte offset into the vertex data to the location of the
1952 * 2D float point vertex attributes.
1953 *
1954 * vertexData: destination where the geometry's vertex data should go
1955 *
1956 * zData: destination of geometries injected Z positioning
1957 *
1958 * indexData: destination of the indices for this element
1959 *
1960 * iBase: The starting index for this element in the batch
1961 */
1962
1963void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, void *iBasePtr, int *indexCount)
1964{
1965 if (Q_UNLIKELY(debug_upload())) qDebug() << " - uploading element:" << e << e->node << (void *) *vertexData << (qintptr) (*zData - *vertexData) << (qintptr) (*indexData - *vertexData);
1966 QSGGeometry *g = e->node->geometry();
1967
1968 const QMatrix4x4 &localx = *e->node->matrix();
1969 const float *localxdata = localx.constData();
1970
1971 const int vCount = g->vertexCount();
1972 const int vSize = g->sizeOfVertex();
1973 memcpy(dest: *vertexData, src: g->vertexData(), n: vSize * vCount);
1974
1975 // apply vertex transform..
1976 char *vdata = *vertexData + vaOffset;
1977 if (localx.flags() == QMatrix4x4::Translation) {
1978 for (int i=0; i<vCount; ++i) {
1979 Pt *p = (Pt *) vdata;
1980 p->x += localxdata[12];
1981 p->y += localxdata[13];
1982 vdata += vSize;
1983 }
1984 } else if (localx.flags() > QMatrix4x4::Translation) {
1985 for (int i=0; i<vCount; ++i) {
1986 ((Pt *) vdata)->map(mat: localx);
1987 vdata += vSize;
1988 }
1989 }
1990
1991 if (useDepthBuffer()) {
1992 float *vzorder = (float *) *zData;
1993 float zorder = calculateElementZOrder(e, zRange: m_zRange);
1994 for (int i=0; i<vCount; ++i)
1995 vzorder[i] = zorder;
1996 *zData += vCount * sizeof(float);
1997 }
1998
1999 int iCount = g->indexCount();
2000 if (m_uint32IndexForRhi) {
2001 // can only happen when using the rhi
2002 quint32 *iBase = (quint32 *) iBasePtr;
2003 quint32 *indices = (quint32 *) *indexData;
2004 if (iCount == 0) {
2005 iCount = vCount;
2006 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
2007 *indices++ = *iBase;
2008 else
2009 iCount = qsg_fixIndexCount(iCount, drawMode: g->drawingMode());
2010
2011 for (int i=0; i<iCount; ++i)
2012 indices[i] = *iBase + i;
2013 } else {
2014 // source index data in QSGGeometry is always ushort (we would not merge otherwise)
2015 const quint16 *srcIndices = g->indexDataAsUShort();
2016 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
2017 *indices++ = *iBase + srcIndices[0];
2018 else
2019 iCount = qsg_fixIndexCount(iCount, drawMode: g->drawingMode());
2020
2021 for (int i=0; i<iCount; ++i)
2022 indices[i] = *iBase + srcIndices[i];
2023 }
2024 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
2025 indices[iCount] = indices[iCount - 1];
2026 iCount += 2;
2027 }
2028 *iBase += vCount;
2029 } else {
2030 // normally batching is only done for ushort index data
2031 quint16 *iBase = (quint16 *) iBasePtr;
2032 quint16 *indices = (quint16 *) *indexData;
2033 if (iCount == 0) {
2034 iCount = vCount;
2035 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
2036 *indices++ = *iBase;
2037 else
2038 iCount = qsg_fixIndexCount(iCount, drawMode: g->drawingMode());
2039
2040 for (int i=0; i<iCount; ++i)
2041 indices[i] = *iBase + i;
2042 } else {
2043 const quint16 *srcIndices = g->indexDataAsUShort();
2044 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
2045 *indices++ = *iBase + srcIndices[0];
2046 else
2047 iCount = qsg_fixIndexCount(iCount, drawMode: g->drawingMode());
2048
2049 for (int i=0; i<iCount; ++i)
2050 indices[i] = *iBase + srcIndices[i];
2051 }
2052 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
2053 indices[iCount] = indices[iCount - 1];
2054 iCount += 2;
2055 }
2056 *iBase += vCount;
2057 }
2058
2059 *vertexData += vCount * vSize;
2060 *indexData += iCount * mergedIndexElemSize();
2061 *indexCount += iCount;
2062}
2063
2064QMatrix4x4 qsg_matrixForRoot(Node *node)
2065{
2066 if (node->type() == QSGNode::TransformNodeType)
2067 return static_cast<QSGTransformNode *>(node->sgNode)->combinedMatrix();
2068 Q_ASSERT(node->type() == QSGNode::ClipNodeType);
2069 QSGClipNode *c = static_cast<QSGClipNode *>(node->sgNode);
2070 return *c->matrix();
2071}
2072
2073void Renderer::uploadBatch(Batch *b)
2074{
2075 // Early out if nothing has changed in this batch..
2076 if (!b->needsUpload) {
2077 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "already uploaded...";
2078 return;
2079 }
2080
2081 if (!b->first) {
2082 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "is invalid...";
2083 return;
2084 }
2085
2086 if (b->isRenderNode) {
2087 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch: " << b << "is a render node...";
2088 return;
2089 }
2090
2091 // Figure out if we can merge or not, if not, then just render the batch as is..
2092 Q_ASSERT(b->first);
2093 Q_ASSERT(b->first->node);
2094
2095 QSGGeometryNode *gn = b->first->node;
2096 QSGGeometry *g = gn->geometry();
2097 QSGMaterial::Flags flags = gn->activeMaterial()->flags();
2098 bool canMerge = (g->drawingMode() == QSGGeometry::DrawTriangles || g->drawingMode() == QSGGeometry::DrawTriangleStrip ||
2099 g->drawingMode() == QSGGeometry::DrawLines || g->drawingMode() == QSGGeometry::DrawPoints)
2100 && b->positionAttribute >= 0
2101 && g->indexType() == QSGGeometry::UnsignedShortType
2102 && (flags & (QSGMaterial::NoBatching | QSGMaterial_FullMatrix)) == 0
2103 && ((flags & QSGMaterial::RequiresFullMatrixExceptTranslate) == 0 || b->isTranslateOnlyToRoot())
2104 && b->isSafeToBatch();
2105
2106 b->merged = canMerge;
2107
2108 // Figure out how much memory we need...
2109 b->vertexCount = 0;
2110 b->indexCount = 0;
2111 int unmergedIndexSize = 0;
2112 Element *e = b->first;
2113
2114 // Merged batches always do indexed draw calls. Non-indexed geometry gets
2115 // indices generated automatically, when merged.
2116 while (e) {
2117 QSGGeometry *eg = e->node->geometry();
2118 b->vertexCount += eg->vertexCount();
2119 int iCount = eg->indexCount();
2120 if (b->merged) {
2121 if (iCount == 0)
2122 iCount = eg->vertexCount();
2123 iCount = qsg_fixIndexCount(iCount, drawMode: g->drawingMode());
2124 } else {
2125 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : eg->sizeOfIndex();
2126 unmergedIndexSize += iCount * effectiveIndexSize;
2127 }
2128 b->indexCount += iCount;
2129 e = e->nextInBatch;
2130 }
2131
2132 // Abort if there are no vertices in this batch.. We abort this late as
2133 // this is a broken usecase which we do not care to optimize for...
2134 if (b->vertexCount == 0 || (b->merged && b->indexCount == 0))
2135 return;
2136
2137 /* Allocate memory for this batch. Merged batches are divided into three separate blocks
2138 1. Vertex data for all elements, as they were in the QSGGeometry object, but
2139 with the tranform relative to this batch's root applied. The vertex data
2140 is otherwise unmodified.
2141 2. Z data for all elements, derived from each elements "render order".
2142 This is present for merged data only.
2143 3. Indices for all elements, as they were in the QSGGeometry object, but
2144 adjusted so that each index matches its.
2145 And for TRIANGLE_STRIPs, we need to insert degenerate between each
2146 primitive. These are unsigned shorts for merged and arbitrary for
2147 non-merged.
2148 */
2149 int bufferSize = b->vertexCount * g->sizeOfVertex();
2150 int ibufferSize = 0;
2151 if (b->merged) {
2152 ibufferSize = b->indexCount * mergedIndexElemSize();
2153 if (useDepthBuffer())
2154 bufferSize += b->vertexCount * sizeof(float);
2155 } else {
2156 ibufferSize = unmergedIndexSize;
2157 }
2158
2159 map(buffer: &b->ibo, byteSize: ibufferSize, isIndexBuf: true);
2160 map(buffer: &b->vbo, byteSize: bufferSize);
2161
2162 if (Q_UNLIKELY(debug_upload())) qDebug() << " - batch" << b << " first:" << b->first << " root:"
2163 << b->root << " merged:" << b->merged << " positionAttribute" << b->positionAttribute
2164 << " vbo:" << b->vbo.buf << ":" << b->vbo.size;
2165
2166 if (b->merged) {
2167 char *vertexData = b->vbo.data;
2168 char *zData = vertexData + b->vertexCount * g->sizeOfVertex();
2169 char *indexData = b->ibo.data;
2170
2171 quint16 iOffset16 = 0;
2172 quint32 iOffset32 = 0;
2173 e = b->first;
2174 uint verticesInSet = 0;
2175 // Start a new set already after 65534 vertices because 0xFFFF may be
2176 // used for an always-on primitive restart with some apis (adapt for
2177 // uint32 indices as appropriate).
2178 const uint verticesInSetLimit = m_uint32IndexForRhi ? 0xfffffffe : 0xfffe;
2179 int indicesInSet = 0;
2180 b->drawSets.reset();
2181 int drawSetIndices = 0;
2182 const char *indexBase = b->ibo.data;
2183 b->drawSets << DrawSet(0, zData - vertexData, drawSetIndices);
2184 while (e) {
2185 verticesInSet += e->node->geometry()->vertexCount();
2186 if (verticesInSet > verticesInSetLimit) {
2187 b->drawSets.last().indexCount = indicesInSet;
2188 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
2189 b->drawSets.last().indices += 1 * mergedIndexElemSize();
2190 b->drawSets.last().indexCount -= 2;
2191 }
2192 drawSetIndices = indexData - indexBase;
2193 b->drawSets << DrawSet(vertexData - b->vbo.data,
2194 zData - b->vbo.data,
2195 drawSetIndices);
2196 iOffset16 = 0;
2197 iOffset32 = 0;
2198 verticesInSet = e->node->geometry()->vertexCount();
2199 indicesInSet = 0;
2200 }
2201 void *iBasePtr = &iOffset16;
2202 if (m_uint32IndexForRhi)
2203 iBasePtr = &iOffset32;
2204 uploadMergedElement(e, vaOffset: b->positionAttribute, vertexData: &vertexData, zData: &zData, indexData: &indexData, iBasePtr, indexCount: &indicesInSet);
2205 e = e->nextInBatch;
2206 }
2207 b->drawSets.last().indexCount = indicesInSet;
2208 // We skip the very first and very last degenerate triangles since they aren't needed
2209 // and the first one would reverse the vertex ordering of the merged strips.
2210 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
2211 b->drawSets.last().indices += 1 * mergedIndexElemSize();
2212 b->drawSets.last().indexCount -= 2;
2213 }
2214 } else {
2215 char *vboData = b->vbo.data;
2216 char *iboData = b->ibo.data;
2217 Element *e = b->first;
2218 while (e) {
2219 QSGGeometry *g = e->node->geometry();
2220 int vbs = g->vertexCount() * g->sizeOfVertex();
2221 memcpy(dest: vboData, src: g->vertexData(), n: vbs);
2222 vboData = vboData + vbs;
2223 const int indexCount = g->indexCount();
2224 if (indexCount) {
2225 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
2226 const int ibs = indexCount * effectiveIndexSize;
2227 if (g->sizeOfIndex() == effectiveIndexSize) {
2228 memcpy(dest: iboData, src: g->indexData(), n: ibs);
2229 } else {
2230 if (g->sizeOfIndex() == sizeof(quint16) && effectiveIndexSize == sizeof(quint32)) {
2231 quint16 *src = g->indexDataAsUShort();
2232 quint32 *dst = (quint32 *) iboData;
2233 for (int i = 0; i < indexCount; ++i)
2234 dst[i] = src[i];
2235 } else {
2236 Q_ASSERT_X(false, "uploadBatch (unmerged)", "uint index with ushort effective index - cannot happen");
2237 }
2238 }
2239 iboData += ibs;
2240 }
2241 e = e->nextInBatch;
2242 }
2243 }
2244#ifndef QT_NO_DEBUG_OUTPUT
2245 if (Q_UNLIKELY(debug_upload())) {
2246 const char *vd = b->vbo.data;
2247 qDebug() << " -- Vertex Data, count:" << b->vertexCount << " - " << g->sizeOfVertex() << "bytes/vertex";
2248 for (int i=0; i<b->vertexCount; ++i) {
2249 QDebug dump = qDebug().nospace();
2250 dump << " --- " << i << ": ";
2251 int offset = 0;
2252 for (int a=0; a<g->attributeCount(); ++a) {
2253 const QSGGeometry::Attribute &attr = g->attributes()[a];
2254 dump << attr.position << ":(" << attr.tupleSize << ",";
2255 if (attr.type == QSGGeometry::FloatType) {
2256 dump << "float ";
2257 if (attr.isVertexCoordinate)
2258 dump << "* ";
2259 for (int t=0; t<attr.tupleSize; ++t)
2260 dump << *(const float *)(vd + offset + t * sizeof(float)) << " ";
2261 } else if (attr.type == QSGGeometry::UnsignedByteType) {
2262 dump << "ubyte ";
2263 for (int t=0; t<attr.tupleSize; ++t)
2264 dump << *(const unsigned char *)(vd + offset + t * sizeof(unsigned char)) << " ";
2265 }
2266 dump << ") ";
2267 offset += attr.tupleSize * size_of_type(type: attr.type);
2268 }
2269 if (b->merged && useDepthBuffer()) {
2270 float zorder = ((float*)(b->vbo.data + b->vertexCount * g->sizeOfVertex()))[i];
2271 dump << " Z:(" << zorder << ")";
2272 }
2273 vd += g->sizeOfVertex();
2274 }
2275
2276 if (!b->drawSets.isEmpty()) {
2277 if (m_uint32IndexForRhi) {
2278 const quint32 *id = (const quint32 *) b->ibo.data;
2279 {
2280 QDebug iDump = qDebug();
2281 iDump << " -- Index Data, count:" << b->indexCount;
2282 for (int i=0; i<b->indexCount; ++i) {
2283 if ((i % 24) == 0)
2284 iDump << Qt::endl << " --- ";
2285 iDump << id[i];
2286 }
2287 }
2288 } else {
2289 const quint16 *id = (const quint16 *) b->ibo.data;
2290 {
2291 QDebug iDump = qDebug();
2292 iDump << " -- Index Data, count:" << b->indexCount;
2293 for (int i=0; i<b->indexCount; ++i) {
2294 if ((i % 24) == 0)
2295 iDump << Qt::endl << " --- ";
2296 iDump << id[i];
2297 }
2298 }
2299 }
2300
2301 for (int i=0; i<b->drawSets.size(); ++i) {
2302 const DrawSet &s = b->drawSets.at(i);
2303 qDebug() << " -- DrawSet: indexCount:" << s.indexCount << " vertices:" << s.vertices << " z:" << s.zorders << " indices:" << s.indices;
2304 }
2305 }
2306 }
2307#endif // QT_NO_DEBUG_OUTPUT
2308
2309 unmap(buffer: &b->vbo);
2310 unmap(buffer: &b->ibo, isIndexBuf: true);
2311
2312 if (Q_UNLIKELY(debug_upload() || debug_pools()))
2313 qDebug() << " --- vertex/index buffers unmapped, batch upload completed... vbo pool size" << m_vboPoolCost << "ibo pool size" << m_iboPoolCost;
2314
2315 b->needsUpload = false;
2316
2317 if (Q_UNLIKELY(debug_render()))
2318 b->uploadedThisFrame = true;
2319}
2320
2321void Renderer::applyClipStateToGraphicsState()
2322{
2323 m_gstate.usesScissor = (m_currentClipState.type & ClipState::ScissorClip);
2324 m_gstate.stencilTest = (m_currentClipState.type & ClipState::StencilClip);
2325}
2326
2327QRhiGraphicsPipeline *Renderer::buildStencilPipeline(const Batch *batch, bool firstStencilClipInBatch)
2328{
2329 QRhiGraphicsPipeline *ps = m_rhi->newGraphicsPipeline();
2330 ps->setFlags(QRhiGraphicsPipeline::UsesStencilRef);
2331 QRhiGraphicsPipeline::TargetBlend blend;
2332 blend.colorWrite = {};
2333 ps->setTargetBlends({ blend });
2334 ps->setSampleCount(renderTarget().rt->sampleCount());
2335 ps->setStencilTest(true);
2336 QRhiGraphicsPipeline::StencilOpState stencilOp;
2337 if (firstStencilClipInBatch) {
2338 stencilOp.compareOp = QRhiGraphicsPipeline::Always;
2339 stencilOp.failOp = QRhiGraphicsPipeline::Keep;
2340 stencilOp.depthFailOp = QRhiGraphicsPipeline::Keep;
2341 stencilOp.passOp = QRhiGraphicsPipeline::Replace;
2342 } else {
2343 stencilOp.compareOp = QRhiGraphicsPipeline::Equal;
2344 stencilOp.failOp = QRhiGraphicsPipeline::Keep;
2345 stencilOp.depthFailOp = QRhiGraphicsPipeline::Keep;
2346 stencilOp.passOp = QRhiGraphicsPipeline::IncrementAndClamp;
2347 }
2348 ps->setStencilFront(stencilOp);
2349 ps->setStencilBack(stencilOp);
2350
2351 ps->setTopology(m_stencilClipCommon.topology);
2352
2353 ps->setMultiViewCount(renderTarget().multiViewCount);
2354
2355 ps->setShaderStages({ QRhiShaderStage(QRhiShaderStage::Vertex, m_stencilClipCommon.vs),
2356 QRhiShaderStage(QRhiShaderStage::Fragment, m_stencilClipCommon.fs) });
2357 ps->setVertexInputLayout(m_stencilClipCommon.inputLayout);
2358 ps->setShaderResourceBindings(batch->stencilClipState.srb); // use something, it just needs to be layout-compatible
2359 ps->setRenderPassDescriptor(renderTarget().rpDesc);
2360
2361 if (!ps->create()) {
2362 qWarning(msg: "Failed to build stencil clip pipeline");
2363 delete ps;
2364 return nullptr;
2365 }
2366
2367 return ps;
2368}
2369
2370void Renderer::updateClipState(const QSGClipNode *clipList, Batch *batch)
2371{
2372 // Note: No use of the clip-related speparate m_current* vars is allowed
2373 // here. All stored in batch->clipState instead. To collect state during
2374 // the prepare steps, m_currentClipState is used. It should not be used in
2375 // the render steps afterwards.
2376
2377 // The stenciling logic is slightly different from Qt 5's direct OpenGL version
2378 // as we cannot just randomly clear the stencil buffer. We now put all clip
2379 // shapes into the stencil buffer for all batches in the frame. This means
2380 // that the number of total clips in a scene is reduced (since the stencil
2381 // value cannot exceed 255) but we do not need any clears inbetween.
2382
2383 Q_ASSERT(m_rhi);
2384 batch->stencilClipState.updateStencilBuffer = false;
2385 if (clipList == m_currentClipState.clipList || Q_UNLIKELY(debug_noclip())) {
2386 applyClipStateToGraphicsState();
2387 batch->clipState = m_currentClipState;
2388 return;
2389 }
2390
2391 ClipState::ClipType clipType = ClipState::NoClip;
2392 QRect scissorRect;
2393 QVarLengthArray<const QSGClipNode *, 4> stencilClipNodes;
2394 const QSGClipNode *clip = clipList;
2395
2396 batch->stencilClipState.drawCalls.reset();
2397 quint32 totalVSize = 0;
2398 quint32 totalISize = 0;
2399 quint32 totalUSize = 0;
2400 const quint32 StencilClipUbufSize = 64;
2401
2402 while (clip) {
2403 QMatrix4x4 m = m_current_projection_matrix_native_ndc[0]; // never hit for 3D and so multiview
2404 if (clip->matrix())
2405 m *= *clip->matrix();
2406
2407 bool isRectangleWithNoPerspective = clip->isRectangular()
2408 && qFuzzyIsNull(f: m(3, 0)) && qFuzzyIsNull(f: m(3, 1));
2409 bool noRotate = qFuzzyIsNull(f: m(0, 1)) && qFuzzyIsNull(f: m(1, 0));
2410 bool isRotate90 = qFuzzyIsNull(f: m(0, 0)) && qFuzzyIsNull(f: m(1, 1));
2411
2412 if (isRectangleWithNoPerspective && (noRotate || isRotate90)) {
2413 QRectF bbox = clip->clipRect();
2414 qreal invW = 1 / m(3, 3);
2415 qreal fx1, fy1, fx2, fy2;
2416 if (noRotate) {
2417 fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
2418 fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
2419 fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
2420 fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
2421 } else {
2422 Q_ASSERT(isRotate90);
2423 fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
2424 fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
2425 fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
2426 fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
2427 }
2428
2429 if (fx1 > fx2)
2430 qSwap(value1&: fx1, value2&: fx2);
2431 if (fy1 > fy2)
2432 qSwap(value1&: fy1, value2&: fy2);
2433
2434 QRect deviceRect = this->deviceRect();
2435
2436 qint32 ix1 = qRound(d: (fx1 + 1) * deviceRect.width() * qreal(0.5));
2437 qint32 iy1 = qRound(d: (fy1 + 1) * deviceRect.height() * qreal(0.5));
2438 qint32 ix2 = qRound(d: (fx2 + 1) * deviceRect.width() * qreal(0.5));
2439 qint32 iy2 = qRound(d: (fy2 + 1) * deviceRect.height() * qreal(0.5));
2440
2441 if (!(clipType & ClipState::ScissorClip)) {
2442 clipType |= ClipState::ScissorClip;
2443 scissorRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2444 } else {
2445 scissorRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2446 }
2447 } else {
2448 clipType |= ClipState::StencilClip;
2449
2450 const QSGGeometry *g = clip->geometry();
2451 Q_ASSERT(g->attributeCount() > 0);
2452
2453 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2454 // the 4 byte alignment may not actually be needed here
2455 totalVSize = aligned(v: totalVSize, byteAlign: 4u) + vertexByteSize;
2456 if (g->indexCount()) {
2457 const int indexByteSize = g->sizeOfIndex() * g->indexCount();
2458 // so no need to worry about NonFourAlignedEffectiveIndexBufferOffset
2459 totalISize = aligned(v: totalISize, byteAlign: 4u) + indexByteSize;
2460 }
2461 // ubuf start offsets must be aligned (typically to 256 bytes)
2462 totalUSize = aligned(v: totalUSize, byteAlign: m_ubufAlignment) + StencilClipUbufSize;
2463
2464 stencilClipNodes.append(t: clip);
2465 }
2466
2467 clip = clip->clipList();
2468 }
2469
2470 if (clipType & ClipState::StencilClip) {
2471 bool rebuildVBuf = false;
2472 if (!batch->stencilClipState.vbuf) {
2473 batch->stencilClipState.vbuf = m_rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::VertexBuffer, size: totalVSize);
2474 rebuildVBuf = true;
2475 } else if (batch->stencilClipState.vbuf->size() < totalVSize) {
2476 batch->stencilClipState.vbuf->setSize(totalVSize);
2477 rebuildVBuf = true;
2478 }
2479 if (rebuildVBuf) {
2480 if (!batch->stencilClipState.vbuf->create()) {
2481 qWarning(msg: "Failed to build stencil clip vertex buffer");
2482 delete batch->stencilClipState.vbuf;
2483 batch->stencilClipState.vbuf = nullptr;
2484 return;
2485 }
2486 }
2487
2488 if (totalISize) {
2489 bool rebuildIBuf = false;
2490 if (!batch->stencilClipState.ibuf) {
2491 batch->stencilClipState.ibuf = m_rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::IndexBuffer, size: totalISize);
2492 rebuildIBuf = true;
2493 } else if (batch->stencilClipState.ibuf->size() < totalISize) {
2494 batch->stencilClipState.ibuf->setSize(totalISize);
2495 rebuildIBuf = true;
2496 }
2497 if (rebuildIBuf) {
2498 if (!batch->stencilClipState.ibuf->create()) {
2499 qWarning(msg: "Failed to build stencil clip index buffer");
2500 delete batch->stencilClipState.ibuf;
2501 batch->stencilClipState.ibuf = nullptr;
2502 return;
2503 }
2504 }
2505 }
2506
2507 bool rebuildUBuf = false;
2508 if (!batch->stencilClipState.ubuf) {
2509 batch->stencilClipState.ubuf = m_rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: totalUSize);
2510 rebuildUBuf = true;
2511 } else if (batch->stencilClipState.ubuf->size() < totalUSize) {
2512 batch->stencilClipState.ubuf->setSize(totalUSize);
2513 rebuildUBuf = true;
2514 }
2515 if (rebuildUBuf) {
2516 if (!batch->stencilClipState.ubuf->create()) {
2517 qWarning(msg: "Failed to build stencil clip uniform buffer");
2518 delete batch->stencilClipState.ubuf;
2519 batch->stencilClipState.ubuf = nullptr;
2520 return;
2521 }
2522 }
2523
2524 if (!batch->stencilClipState.srb) {
2525 batch->stencilClipState.srb = m_rhi->newShaderResourceBindings();
2526 const QRhiShaderResourceBinding ubufBinding = QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(
2527 binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: batch->stencilClipState.ubuf, size: StencilClipUbufSize);
2528 batch->stencilClipState.srb->setBindings({ ubufBinding });
2529 if (!batch->stencilClipState.srb->create()) {
2530 qWarning(msg: "Failed to build stencil clip srb");
2531 delete batch->stencilClipState.srb;
2532 batch->stencilClipState.srb = nullptr;
2533 return;
2534 }
2535 }
2536
2537 quint32 vOffset = 0;
2538 quint32 iOffset = 0;
2539 quint32 uOffset = 0;
2540 for (const QSGClipNode *clip : stencilClipNodes) {
2541 const QSGGeometry *g = clip->geometry();
2542 const QSGGeometry::Attribute *a = g->attributes();
2543 StencilClipState::StencilDrawCall drawCall;
2544 const bool firstStencilClipInBatch = batch->stencilClipState.drawCalls.isEmpty();
2545
2546 if (firstStencilClipInBatch) {
2547 m_stencilClipCommon.inputLayout.setBindings({ QRhiVertexInputBinding(g->sizeOfVertex()) });
2548 m_stencilClipCommon.inputLayout.setAttributes({ QRhiVertexInputAttribute(0, 0, qsg_vertexInputFormat(a: *a), 0) });
2549 m_stencilClipCommon.topology = qsg_topology(geomDrawMode: g->drawingMode());
2550 }
2551#ifndef QT_NO_DEBUG
2552 else {
2553 if (qsg_topology(geomDrawMode: g->drawingMode()) != m_stencilClipCommon.topology)
2554 qWarning(msg: "updateClipState: Clip list entries have different primitive topologies, this is not currently supported.");
2555 if (qsg_vertexInputFormat(a: *a) != m_stencilClipCommon.inputLayout.cbeginAttributes()->format())
2556 qWarning(msg: "updateClipState: Clip list entries have different vertex input layouts, this is must not happen.");
2557 }
2558#endif
2559
2560 drawCall.vbufOffset = aligned(v: vOffset, byteAlign: 4u);
2561 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2562 vOffset = drawCall.vbufOffset + vertexByteSize;
2563
2564 int indexByteSize = 0;
2565 if (g->indexCount()) {
2566 drawCall.ibufOffset = aligned(v: iOffset, byteAlign: 4u);
2567 indexByteSize = g->sizeOfIndex() * g->indexCount();
2568 iOffset = drawCall.ibufOffset + indexByteSize;
2569 }
2570
2571 drawCall.ubufOffset = aligned(v: uOffset, byteAlign: m_ubufAlignment);
2572 uOffset = drawCall.ubufOffset + StencilClipUbufSize;
2573
2574 QMatrix4x4 matrixYUpNDC = m_current_projection_matrix[0];
2575 if (clip->matrix())
2576 matrixYUpNDC *= *clip->matrix();
2577
2578 m_resourceUpdates->updateDynamicBuffer(buf: batch->stencilClipState.ubuf, offset: drawCall.ubufOffset, size: 64, data: matrixYUpNDC.constData());
2579 m_resourceUpdates->updateDynamicBuffer(buf: batch->stencilClipState.vbuf, offset: drawCall.vbufOffset, size: vertexByteSize, data: g->vertexData());
2580 if (indexByteSize)
2581 m_resourceUpdates->updateDynamicBuffer(buf: batch->stencilClipState.ibuf, offset: drawCall.ibufOffset, size: indexByteSize, data: g->indexData());
2582
2583 // stencil ref goes 1, 1, 2, 3, 4, ..., N for the clips in the first batch,
2584 // then N+1, N+1, N+2, N+3, ... for the next batch,
2585 // and so on.
2586 // Note the different stencilOp for the first and the subsequent clips.
2587 drawCall.stencilRef = firstStencilClipInBatch ? m_currentClipState.stencilRef + 1 : m_currentClipState.stencilRef;
2588 m_currentClipState.stencilRef += 1;
2589
2590 drawCall.vertexCount = g->vertexCount();
2591 drawCall.indexCount = g->indexCount();
2592 drawCall.indexFormat = qsg_indexFormat(geometry: g);
2593 batch->stencilClipState.drawCalls.add(t: drawCall);
2594 }
2595
2596 if (!m_stencilClipCommon.vs.isValid())
2597 m_stencilClipCommon.vs = QSGMaterialShaderPrivate::loadShader(filename: QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.vert.qsb"));
2598
2599 if (!m_stencilClipCommon.fs.isValid())
2600 m_stencilClipCommon.fs = QSGMaterialShaderPrivate::loadShader(filename: QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.frag.qsb"));
2601
2602 if (!m_stencilClipCommon.replacePs)
2603 m_stencilClipCommon.replacePs = buildStencilPipeline(batch, firstStencilClipInBatch: true);
2604
2605 if (!m_stencilClipCommon.incrPs)
2606 m_stencilClipCommon.incrPs = buildStencilPipeline(batch, firstStencilClipInBatch: false);
2607
2608 batch->stencilClipState.updateStencilBuffer = true;
2609 }
2610
2611 m_currentClipState.clipList = clipList;
2612 m_currentClipState.type = clipType;
2613 m_currentClipState.scissor = QRhiScissor(scissorRect.x(), scissorRect.y(),
2614 scissorRect.width(), scissorRect.height());
2615
2616 applyClipStateToGraphicsState();
2617 batch->clipState = m_currentClipState;
2618}
2619
2620void Renderer::enqueueStencilDraw(const Batch *batch)
2621{
2622 // cliptype stencil + updateStencilBuffer==false means the batch uses
2623 // stenciling but relies on the stencil data generated by a previous batch
2624 // (due to the having the same clip node). Do not enqueue draw calls for
2625 // stencil in this case as the stencil buffer is already up-to-date.
2626 if (!batch->stencilClipState.updateStencilBuffer)
2627 return;
2628
2629 QRhiCommandBuffer *cb = renderTarget().cb;
2630 const int count = batch->stencilClipState.drawCalls.size();
2631 for (int i = 0; i < count; ++i) {
2632 const StencilClipState::StencilDrawCall &drawCall(batch->stencilClipState.drawCalls.at(i));
2633 QRhiShaderResourceBindings *srb = batch->stencilClipState.srb;
2634 QRhiCommandBuffer::DynamicOffset ubufOffset(0, drawCall.ubufOffset);
2635 if (i == 0) {
2636 cb->setGraphicsPipeline(m_stencilClipCommon.replacePs);
2637 cb->setViewport(m_pstate.viewport);
2638 } else if (i == 1) {
2639 cb->setGraphicsPipeline(m_stencilClipCommon.incrPs);
2640 cb->setViewport(m_pstate.viewport);
2641 }
2642 // else incrPs is already bound
2643 cb->setShaderResources(srb, dynamicOffsetCount: 1, dynamicOffsets: &ubufOffset);
2644 cb->setStencilRef(drawCall.stencilRef);
2645 const QRhiCommandBuffer::VertexInput vbufBinding(batch->stencilClipState.vbuf, drawCall.vbufOffset);
2646 if (drawCall.indexCount) {
2647 cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbufBinding,
2648 indexBuf: batch->stencilClipState.ibuf, indexOffset: drawCall.ibufOffset, indexFormat: drawCall.indexFormat);
2649 cb->drawIndexed(indexCount: drawCall.indexCount);
2650 } else {
2651 cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbufBinding);
2652 cb->draw(vertexCount: drawCall.vertexCount);
2653 }
2654 }
2655}
2656
2657void Renderer::setActiveRhiShader(QSGMaterialShader *program, ShaderManager::Shader *shader)
2658{
2659 Q_ASSERT(m_rhi);
2660 m_currentProgram = program;
2661 m_currentShader = shader;
2662 m_currentMaterial = nullptr;
2663}
2664
2665static inline bool needsBlendConstant(QRhiGraphicsPipeline::BlendFactor f)
2666{
2667 return f == QRhiGraphicsPipeline::ConstantColor
2668 || f == QRhiGraphicsPipeline::OneMinusConstantColor
2669 || f == QRhiGraphicsPipeline::ConstantAlpha
2670 || f == QRhiGraphicsPipeline::OneMinusConstantAlpha;
2671}
2672
2673// With QRhi renderBatches() is split to two steps: prepare and render.
2674//
2675// Prepare goes through the batches and elements, and set up a graphics
2676// pipeline, srb, uniform buffer, calculates clipping, based on m_gstate, the
2677// material (shaders), and the batches. This step does not touch the command
2678// buffer or renderpass-related state (m_pstate).
2679//
2680// The render step then starts a renderpass, and goes through all
2681// batches/elements again and records setGraphicsPipeline, drawIndexed, etc. on
2682// the command buffer. The prepare step's accumulated global state like
2683// m_gstate must not be used here. Rather, all data needed for rendering is
2684// available from Batch/Element at this stage. Bookkeeping of state in the
2685// renderpass is done via m_pstate.
2686
2687bool Renderer::ensurePipelineState(Element *e, const ShaderManager::Shader *sms, bool depthPostPass)
2688{
2689 // Note the key's == and qHash implementations: the renderpass descriptor
2690 // and srb are tested for compatibility, not pointer equality.
2691 //
2692 // We do not store the srb pointer itself because the ownership stays with
2693 // the Element and that can go away more often that we would like it
2694 // to. (think scrolling a list view, constantly dropping and creating new
2695 // nodes) Rather, use an opaque blob of a few uints and store and compare
2696 // that. This works because once the pipeline is built, we will always call
2697 // setShaderResources with an explicitly specified srb which is fine even if
2698 // e->srb we used here to bake the pipeline is already gone by that point.
2699 //
2700 // A typical QSGMaterial's serialized srb layout is 8 uints. (uniform buffer
2701 // + texture, 4 fields each) Regardless, using an implicitly shared
2702 // container is essential here. (won't detach so no more allocs and copies
2703 // are done, unless the Element decides to rebake the srb with a different
2704 // layout - but then the detach is exactly what we need)
2705 //
2706 // Same story for the renderpass descriptor: the object can go away but
2707 // that's fine because that has no effect on an already built pipeline, and
2708 // for comparison we only rely on the serialized blob in order decide if the
2709 // render target is compatible with the pipeline.
2710
2711 const GraphicsPipelineStateKey k = GraphicsPipelineStateKey::create(state: m_gstate, sms, rpDesc: renderTarget().rpDesc, srb: e->srb);
2712
2713 // Note: dynamic state (viewport rect, scissor rect, stencil ref, blend
2714 // constant) is never a part of GraphicsState/QRhiGraphicsPipeline.
2715
2716 // See if there is an existing, matching pipeline state object.
2717 auto it = m_shaderManager->pipelineCache.constFind(key: k);
2718 if (it != m_shaderManager->pipelineCache.constEnd()) {
2719 if (depthPostPass)
2720 e->depthPostPassPs = *it;
2721 else
2722 e->ps = *it;
2723 return true;
2724 }
2725
2726 // Build a new one. This is potentially expensive.
2727 QRhiGraphicsPipeline *ps = m_rhi->newGraphicsPipeline();
2728 ps->setShaderStages(first: sms->stages.cbegin(), last: sms->stages.cend());
2729 ps->setVertexInputLayout(sms->inputLayout);
2730 ps->setShaderResourceBindings(e->srb);
2731 ps->setRenderPassDescriptor(renderTarget().rpDesc);
2732
2733 QRhiGraphicsPipeline::Flags flags;
2734 if (needsBlendConstant(f: m_gstate.srcColor) || needsBlendConstant(f: m_gstate.dstColor)
2735 || needsBlendConstant(f: m_gstate.srcAlpha) || needsBlendConstant(f: m_gstate.dstAlpha))
2736 {
2737 flags |= QRhiGraphicsPipeline::UsesBlendConstants;
2738 }
2739 if (m_gstate.usesScissor)
2740 flags |= QRhiGraphicsPipeline::UsesScissor;
2741 if (m_gstate.stencilTest)
2742 flags |= QRhiGraphicsPipeline::UsesStencilRef;
2743
2744 ps->setFlags(flags);
2745 ps->setTopology(qsg_topology(geomDrawMode: m_gstate.drawMode));
2746 ps->setCullMode(m_gstate.cullMode);
2747 ps->setPolygonMode(m_gstate.polygonMode);
2748 ps->setMultiViewCount(m_gstate.multiViewCount);
2749
2750 QRhiGraphicsPipeline::TargetBlend blend;
2751 blend.colorWrite = m_gstate.colorWrite;
2752 blend.enable = m_gstate.blending;
2753 blend.srcColor = m_gstate.srcColor;
2754 blend.dstColor = m_gstate.dstColor;
2755 blend.srcAlpha = m_gstate.srcAlpha;
2756 blend.dstAlpha = m_gstate.dstAlpha;
2757 blend.opColor = m_gstate.opColor;
2758 blend.opAlpha = m_gstate.opAlpha;
2759 ps->setTargetBlends({ blend });
2760
2761 ps->setDepthTest(m_gstate.depthTest);
2762 ps->setDepthWrite(m_gstate.depthWrite);
2763 ps->setDepthOp(m_gstate.depthFunc);
2764
2765 if (m_gstate.stencilTest) {
2766 ps->setStencilTest(true);
2767 QRhiGraphicsPipeline::StencilOpState stencilOp;
2768 stencilOp.compareOp = QRhiGraphicsPipeline::Equal;
2769 stencilOp.failOp = QRhiGraphicsPipeline::Keep;
2770 stencilOp.depthFailOp = QRhiGraphicsPipeline::Keep;
2771 stencilOp.passOp = QRhiGraphicsPipeline::Keep;
2772 ps->setStencilFront(stencilOp);
2773 ps->setStencilBack(stencilOp);
2774 }
2775
2776 ps->setSampleCount(m_gstate.sampleCount);
2777
2778 ps->setLineWidth(m_gstate.lineWidth);
2779
2780 if (!ps->create()) {
2781 qWarning(msg: "Failed to build graphics pipeline state");
2782 delete ps;
2783 return false;
2784 }
2785
2786 m_shaderManager->pipelineCache.insert(key: k, value: ps);
2787 if (depthPostPass)
2788 e->depthPostPassPs = ps;
2789 else
2790 e->ps = ps;
2791 return true;
2792}
2793
2794static QRhiSampler *newSampler(QRhi *rhi, const QSGSamplerDescription &desc)
2795{
2796 QRhiSampler::Filter magFilter;
2797 QRhiSampler::Filter minFilter;
2798 QRhiSampler::Filter mipmapMode;
2799 QRhiSampler::AddressMode u;
2800 QRhiSampler::AddressMode v;
2801
2802 switch (desc.filtering) {
2803 case QSGTexture::None:
2804 Q_FALLTHROUGH();
2805 case QSGTexture::Nearest:
2806 magFilter = minFilter = QRhiSampler::Nearest;
2807 break;
2808 case QSGTexture::Linear:
2809 magFilter = minFilter = QRhiSampler::Linear;
2810 break;
2811 default:
2812 Q_UNREACHABLE();
2813 magFilter = minFilter = QRhiSampler::Nearest;
2814 break;
2815 }
2816
2817 switch (desc.mipmapFiltering) {
2818 case QSGTexture::None:
2819 mipmapMode = QRhiSampler::None;
2820 break;
2821 case QSGTexture::Nearest:
2822 mipmapMode = QRhiSampler::Nearest;
2823 break;
2824 case QSGTexture::Linear:
2825 mipmapMode = QRhiSampler::Linear;
2826 break;
2827 default:
2828 Q_UNREACHABLE();
2829 mipmapMode = QRhiSampler::None;
2830 break;
2831 }
2832
2833 switch (desc.horizontalWrap) {
2834 case QSGTexture::Repeat:
2835 u = QRhiSampler::Repeat;
2836 break;
2837 case QSGTexture::ClampToEdge:
2838 u = QRhiSampler::ClampToEdge;
2839 break;
2840 case QSGTexture::MirroredRepeat:
2841 u = QRhiSampler::Mirror;
2842 break;
2843 default:
2844 Q_UNREACHABLE();
2845 u = QRhiSampler::ClampToEdge;
2846 break;
2847 }
2848
2849 switch (desc.verticalWrap) {
2850 case QSGTexture::Repeat:
2851 v = QRhiSampler::Repeat;
2852 break;
2853 case QSGTexture::ClampToEdge:
2854 v = QRhiSampler::ClampToEdge;
2855 break;
2856 case QSGTexture::MirroredRepeat:
2857 v = QRhiSampler::Mirror;
2858 break;
2859 default:
2860 Q_UNREACHABLE();
2861 v = QRhiSampler::ClampToEdge;
2862 break;
2863 }
2864
2865 return rhi->newSampler(magFilter, minFilter, mipmapMode, addressU: u, addressV: v);
2866}
2867
2868QRhiTexture *Renderer::dummyTexture()
2869{
2870 if (!m_dummyTexture) {
2871 m_dummyTexture = m_rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: QSize(64, 64));
2872 if (m_dummyTexture->create()) {
2873 if (m_resourceUpdates) {
2874 QImage img(m_dummyTexture->pixelSize(), QImage::Format_RGBA8888_Premultiplied);
2875 img.fill(pixel: 0);
2876 m_resourceUpdates->uploadTexture(tex: m_dummyTexture, image: img);
2877 }
2878 }
2879 }
2880 return m_dummyTexture;
2881}
2882
2883static void rendererToMaterialGraphicsState(QSGMaterialShader::GraphicsPipelineState *dst,
2884 GraphicsState *src)
2885{
2886 dst->blendEnable = src->blending;
2887
2888 // the enum values should match, sanity check it
2889 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::OneMinusSrc1Alpha) == int(QRhiGraphicsPipeline::OneMinusSrc1Alpha));
2890 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::BlendOp::Max) == int(QRhiGraphicsPipeline::Max));
2891 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::A) == int(QRhiGraphicsPipeline::A));
2892 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::CullBack) == int(QRhiGraphicsPipeline::Back));
2893 Q_ASSERT(int(QSGMaterialShader::GraphicsPipelineState::Line) == int(QRhiGraphicsPipeline::Line));
2894 dst->srcColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcColor);
2895 dst->dstColor = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstColor);
2896
2897 // For compatibility with any existing code, separateBlendFactors defaults
2898 // to _false_ which means that materials that do not touch srcAlpha and
2899 // dstAlpha will continue to use srcColor and dstColor as the alpha
2900 // blending factors. New code that needs different values for color/alpha,
2901 // can explicitly set separateBlendFactors to true and then set srcAlpha
2902 // and dstAlpha as well.
2903 dst->separateBlendFactors = false;
2904
2905 dst->srcAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->srcAlpha);
2906 dst->dstAlpha = QSGMaterialShader::GraphicsPipelineState::BlendFactor(src->dstAlpha);
2907
2908 dst->opColor = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opColor);
2909 dst->opAlpha = QSGMaterialShader::GraphicsPipelineState::BlendOp(src->opAlpha);
2910
2911 dst->colorWrite = QSGMaterialShader::GraphicsPipelineState::ColorMask(int(src->colorWrite));
2912
2913 dst->cullMode = QSGMaterialShader::GraphicsPipelineState::CullMode(src->cullMode);
2914 dst->polygonMode = QSGMaterialShader::GraphicsPipelineState::PolygonMode(src->polygonMode);
2915}
2916
2917static void materialToRendererGraphicsState(GraphicsState *dst,
2918 QSGMaterialShader::GraphicsPipelineState *src)
2919{
2920 dst->blending = src->blendEnable;
2921 dst->srcColor = QRhiGraphicsPipeline::BlendFactor(src->srcColor);
2922 dst->dstColor = QRhiGraphicsPipeline::BlendFactor(src->dstColor);
2923 if (src->separateBlendFactors) {
2924 dst->srcAlpha = QRhiGraphicsPipeline::BlendFactor(src->srcAlpha);
2925 dst->dstAlpha = QRhiGraphicsPipeline::BlendFactor(src->dstAlpha);
2926 } else {
2927 dst->srcAlpha = dst->srcColor;
2928 dst->dstAlpha = dst->dstColor;
2929 }
2930 dst->opColor = QRhiGraphicsPipeline::BlendOp(src->opColor);
2931 dst->opAlpha = QRhiGraphicsPipeline::BlendOp(src->opAlpha);
2932 dst->colorWrite = QRhiGraphicsPipeline::ColorMask(int(src->colorWrite));
2933 dst->cullMode = QRhiGraphicsPipeline::CullMode(src->cullMode);
2934 dst->polygonMode = QRhiGraphicsPipeline::PolygonMode(src->polygonMode);
2935}
2936
2937void Renderer::updateMaterialDynamicData(ShaderManager::Shader *sms,
2938 QSGMaterialShader::RenderState &renderState,
2939 QSGMaterial *material,
2940 const Batch *batch,
2941 Element *e,
2942 int ubufOffset,
2943 int ubufRegionSize,
2944 char *directUpdatePtr)
2945{
2946 m_current_resource_update_batch = m_resourceUpdates;
2947
2948 QSGMaterialShader *shader = sms->materialShader;
2949 QSGMaterialShaderPrivate *pd = QSGMaterialShaderPrivate::get(s: shader);
2950 QVarLengthArray<QRhiShaderResourceBinding, 8> bindings;
2951
2952 if (pd->ubufBinding >= 0) {
2953 m_current_uniform_data = &pd->masterUniformData;
2954 const bool changed = shader->updateUniformData(state&: renderState, newMaterial: material, oldMaterial: m_currentMaterial);
2955 m_current_uniform_data = nullptr;
2956
2957 if (changed || !batch->ubufDataValid) {
2958 if (directUpdatePtr)
2959 memcpy(dest: directUpdatePtr + ubufOffset, src: pd->masterUniformData.constData(), n: ubufRegionSize);
2960 else
2961 m_resourceUpdates->updateDynamicBuffer(buf: batch->ubuf, offset: ubufOffset, size: ubufRegionSize, data: pd->masterUniformData.constData());
2962 }
2963
2964 bindings.append(t: QRhiShaderResourceBinding::uniformBuffer(binding: pd->ubufBinding,
2965 stage: pd->ubufStages,
2966 buf: batch->ubuf,
2967 offset: ubufOffset,
2968 size: ubufRegionSize));
2969 }
2970
2971 for (int binding = 0; binding < QSGMaterialShaderPrivate::MAX_SHADER_RESOURCE_BINDINGS; ++binding) {
2972 const QRhiShaderResourceBinding::StageFlags stages = pd->combinedImageSamplerBindings[binding];
2973 if (!stages)
2974 continue;
2975
2976 const QVarLengthArray<QSGTexture *, 4> &prevTex(pd->textureBindingTable[binding]);
2977 QVarLengthArray<QSGTexture *, 4> nextTex = prevTex;
2978
2979 const int count = pd->combinedImageSamplerCount[binding];
2980 nextTex.resize(sz: count);
2981
2982 shader->updateSampledImage(state&: renderState, binding, texture: nextTex.data(), newMaterial: material,
2983 oldMaterial: m_currentMaterial);
2984
2985 if (nextTex.contains(t: nullptr)) {
2986 qWarning(msg: "No QSGTexture provided from updateSampledImage(). This is wrong.");
2987 continue;
2988 }
2989
2990 bool hasDirtySamplerOptions = false;
2991 bool isAnisotropic = false;
2992 for (QSGTexture *t : nextTex) {
2993 QSGTexturePrivate *td = QSGTexturePrivate::get(t);
2994 hasDirtySamplerOptions |= td->hasDirtySamplerOptions();
2995 isAnisotropic |= t->anisotropyLevel() != QSGTexture::AnisotropyNone;
2996 td->resetDirtySamplerOptions();
2997 }
2998
2999 // prevTex may be invalid at this point, avoid dereferencing it
3000 if (nextTex != prevTex || hasDirtySamplerOptions) {
3001
3002 // The QSGTexture, and so the sampler parameters, may have changed.
3003 // The rhiTexture is not relevant here.
3004 pd->textureBindingTable[binding] = nextTex; // does not own
3005 pd->samplerBindingTable[binding].clear();
3006
3007 if (isAnisotropic) // ###
3008 qWarning(msg: "QSGTexture anisotropy levels are not currently supported");
3009
3010 QVarLengthArray<QRhiSampler *, 4> samplers;
3011
3012 for (QSGTexture *t : nextTex) {
3013 const QSGSamplerDescription samplerDesc = QSGSamplerDescription::fromTexture(t);
3014
3015 QRhiSampler *sampler = m_samplers[samplerDesc];
3016
3017 if (!sampler) {
3018 sampler = newSampler(rhi: m_rhi, desc: samplerDesc);
3019 if (!sampler->create()) {
3020 qWarning(msg: "Failed to build sampler");
3021 delete sampler;
3022 continue;
3023 }
3024 m_samplers[samplerDesc] = sampler;
3025 }
3026 samplers.append(t: sampler);
3027 }
3028
3029 pd->samplerBindingTable[binding] = samplers; // does not own
3030 }
3031
3032 if (pd->textureBindingTable[binding].size() == pd->samplerBindingTable[binding].size()) {
3033
3034 QVarLengthArray<QRhiShaderResourceBinding::TextureAndSampler, 4> textureSamplers;
3035
3036 for (int i = 0; i < pd->textureBindingTable[binding].size(); ++i) {
3037
3038 QRhiTexture *texture = pd->textureBindingTable[binding].at(idx: i)->rhiTexture();
3039
3040 // texture may be null if the update above failed for any reason,
3041 // or if the QSGTexture chose to return null intentionally. This is
3042 // valid and we still need to provide something to the shader.
3043 if (!texture)
3044 texture = dummyTexture();
3045
3046 QRhiSampler *sampler = pd->samplerBindingTable[binding].at(idx: i);
3047
3048 textureSamplers.append(
3049 t: QRhiShaderResourceBinding::TextureAndSampler { .tex: texture, .sampler: sampler });
3050 }
3051
3052 if (!textureSamplers.isEmpty())
3053 bindings.append(t: QRhiShaderResourceBinding::sampledTextures(
3054 binding, stage: stages, count, texSamplers: textureSamplers.constData()));
3055 }
3056 }
3057
3058#ifndef QT_NO_DEBUG
3059 if (bindings.isEmpty())
3060 qWarning(msg: "No shader resources for material %p, this is odd.", material);
3061#endif
3062
3063 enum class SrbAction {
3064 Unknown,
3065 DoNothing,
3066 UpdateResources,
3067 Rebake
3068 } srbAction = SrbAction::Unknown;
3069
3070 // First, if the Element has no srb created at all, then try to find an existing,
3071 // currently unused srb that is layout-compatible with our binding list.
3072 if (!e->srb) {
3073 // reuse a QVector as our work area, thus possibly reusing the underlying allocation too
3074 QVector<quint32> &layoutDesc(m_shaderManager->srbLayoutDescSerializeWorkspace);
3075 layoutDesc.clear();
3076 QRhiShaderResourceBinding::serializeLayoutDescription(first: bindings.cbegin(), last: bindings.cend(), dst: std::back_inserter(x&: layoutDesc));
3077 e->srb = m_shaderManager->srbPool.take(key: layoutDesc);
3078 if (e->srb) {
3079 // Here we know layout compatibility is satisfied, but do not spend time on full
3080 // comparison. The chance of getting an srb that refers to the same resources
3081 // (buffer, textures) is low in practice. So reuse, but write new resources.
3082 srbAction = SrbAction::UpdateResources;
3083 }
3084 }
3085
3086 // If the Element had an existing srb, investigate:
3087 // - It may be used as-is (when nothing changed in the scene regarding this node compared to the previous frame).
3088 // - Otherwise it may be able to go with a lightweight update (replace resources, binding list layout is the same).
3089 // - If all else fails rebake the full thing, meaning we reuse the memory allocation but will recreate everything underneath.
3090 if (srbAction == SrbAction::Unknown && e->srb) {
3091 if (std::equal(first1: e->srb->cbeginBindings(), last1: e->srb->cendBindings(), first2: bindings.cbegin(), last2: bindings.cend())) {
3092 srbAction = SrbAction::DoNothing;
3093 } else if (std::equal(first1: e->srb->cbeginBindings(), last1: e->srb->cendBindings(), first2: bindings.cbegin(), last2: bindings.cend(),
3094 binary_pred: [](const auto &a, const auto &b) { return a.isLayoutCompatible(b); }))
3095 {
3096 srbAction = SrbAction::UpdateResources;
3097 } else {
3098 srbAction = SrbAction::Rebake;
3099 }
3100 }
3101
3102 // If the Element had no srb associated at all and could not find a layout-compatible
3103 // one from the pool, then create a whole new object.
3104 if (!e->srb) {
3105 e->srb = m_rhi->newShaderResourceBindings();
3106 srbAction = SrbAction::Rebake;
3107 }
3108
3109 Q_ASSERT(srbAction != SrbAction::Unknown && e->srb);
3110
3111 switch (srbAction) {
3112 case SrbAction::DoNothing:
3113 break;
3114 case SrbAction::UpdateResources:
3115 {
3116 e->srb->setBindings(first: bindings.cbegin(), last: bindings.cend());
3117 QRhiShaderResourceBindings::UpdateFlags flags;
3118 // Due to the way the binding list is built up above, if we have a uniform buffer
3119 // at binding point 0 (or none at all) then the sampledTexture bindings are added
3120 // with increasing binding points afterwards, so the list is already sorted based
3121 // on the binding points, thus we can save some time by telling the QRhi backend
3122 // not to sort again.
3123 if (pd->ubufBinding <= 0 || bindings.size() <= 1)
3124 flags |= QRhiShaderResourceBindings::BindingsAreSorted;
3125
3126 e->srb->updateResources(flags);
3127 }
3128 break;
3129 case SrbAction::Rebake:
3130 e->srb->setBindings(first: bindings.cbegin(), last: bindings.cend());
3131 if (!e->srb->create())
3132 qWarning(msg: "Failed to build srb");
3133 break;
3134 default:
3135 Q_ASSERT_X(false, "updateMaterialDynamicData", "No srb action set, this cannot happen");
3136 }
3137}
3138
3139void Renderer::updateMaterialStaticData(ShaderManager::Shader *sms,
3140 QSGMaterialShader::RenderState &renderState,
3141 QSGMaterial *material,
3142 Batch *batch,
3143 bool *gstateChanged)
3144{
3145 QSGMaterialShader *shader = sms->materialShader;
3146 *gstateChanged = false;
3147 if (shader->flags().testFlag(flag: QSGMaterialShader::UpdatesGraphicsPipelineState)) {
3148 // generate the public mini-state from m_gstate, invoke the material,
3149 // write the changes, if any, back to m_gstate, together with a way to
3150 // roll those back.
3151 QSGMaterialShader::GraphicsPipelineState shaderPs;
3152 rendererToMaterialGraphicsState(dst: &shaderPs, src: &m_gstate);
3153 const bool changed = shader->updateGraphicsPipelineState(state&: renderState, ps: &shaderPs, newMaterial: material, oldMaterial: m_currentMaterial);
3154 if (changed) {
3155 m_gstateStack.push(t: m_gstate);
3156 materialToRendererGraphicsState(dst: &m_gstate, src: &shaderPs);
3157 if (needsBlendConstant(f: m_gstate.srcColor) || needsBlendConstant(f: m_gstate.dstColor)
3158 || needsBlendConstant(f: m_gstate.srcAlpha) || needsBlendConstant(f: m_gstate.dstAlpha))
3159 {
3160 batch->blendConstant = shaderPs.blendConstant;
3161 }
3162 *gstateChanged = true;
3163 }
3164 }
3165}
3166
3167bool Renderer::prepareRenderMergedBatch(Batch *batch, PreparedRenderBatch *renderBatch)
3168{
3169 if (batch->vertexCount == 0 || batch->indexCount == 0)
3170 return false;
3171
3172 Element *e = batch->first;
3173 Q_ASSERT(e);
3174
3175#ifndef QT_NO_DEBUG_OUTPUT
3176 if (Q_UNLIKELY(debug_render())) {
3177 QDebug debug = qDebug();
3178 debug << " -"
3179 << batch
3180 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3181 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3182 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3183 << "[ merged]"
3184 << " Nodes:" << QString::fromLatin1(ba: "%1").arg(a: qsg_countNodesInBatch(batch), fieldWidth: 4).toLatin1().constData()
3185 << " Vertices:" << QString::fromLatin1(ba: "%1").arg(a: batch->vertexCount, fieldWidth: 5).toLatin1().constData()
3186 << " Indices:" << QString::fromLatin1(ba: "%1").arg(a: batch->indexCount, fieldWidth: 5).toLatin1().constData()
3187 << " root:" << batch->root;
3188 if (batch->drawSets.size() > 1)
3189 debug << "sets:" << batch->drawSets.size();
3190 if (!batch->isOpaque)
3191 debug << "opacity:" << e->node->inheritedOpacity();
3192 batch->uploadedThisFrame = false;
3193 }
3194#endif
3195
3196 QSGGeometryNode *gn = e->node;
3197
3198 // We always have dirty matrix as all batches are at a unique z range.
3199 QSGMaterialShader::RenderState::DirtyStates dirty = QSGMaterialShader::RenderState::DirtyMatrix;
3200 if (batch->root)
3201 m_current_model_view_matrix = qsg_matrixForRoot(node: batch->root);
3202 else
3203 m_current_model_view_matrix.setToIdentity();
3204 m_current_determinant = m_current_model_view_matrix.determinant();
3205
3206 const int viewCount = projectionMatrixCount();
3207 m_current_projection_matrix.resize(sz: viewCount);
3208 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3209 m_current_projection_matrix[viewIndex] = projectionMatrix(index: viewIndex);
3210
3211 m_current_projection_matrix_native_ndc.resize(sz: projectionMatrixWithNativeNDCCount());
3212 for (int viewIndex = 0; viewIndex < projectionMatrixWithNativeNDCCount(); ++viewIndex)
3213 m_current_projection_matrix_native_ndc[viewIndex] = projectionMatrixWithNativeNDC(index: viewIndex);
3214
3215 QSGMaterial *material = gn->activeMaterial();
3216 if (m_renderMode != QSGRendererInterface::RenderMode3D)
3217 updateClipState(clipList: gn->clipList(), batch);
3218
3219 const QSGGeometry *g = gn->geometry();
3220 const int multiViewCount = renderTarget().multiViewCount;
3221 ShaderManager::Shader *sms = useDepthBuffer() ? m_shaderManager->prepareMaterial(material, geometry: g, renderMode: m_renderMode, multiViewCount)
3222 : m_shaderManager->prepareMaterialNoRewrite(material, geometry: g, renderMode: m_renderMode, multiViewCount);
3223 if (!sms)
3224 return false;
3225
3226 Q_ASSERT(sms->materialShader);
3227 if (m_currentShader != sms)
3228 setActiveRhiShader(program: sms->materialShader, shader: sms);
3229
3230 m_current_opacity = gn->inheritedOpacity();
3231 if (!qFuzzyCompare(p1: sms->lastOpacity, p2: float(m_current_opacity))) {
3232 dirty |= QSGMaterialShader::RenderState::DirtyOpacity;
3233 sms->lastOpacity = m_current_opacity;
3234 }
3235
3236 QSGMaterialShaderPrivate *pd = QSGMaterialShaderPrivate::get(s: sms->materialShader);
3237 const quint32 ubufSize = quint32(pd->masterUniformData.size());
3238 if (pd->ubufBinding >= 0) {
3239 bool ubufRebuild = false;
3240 if (!batch->ubuf) {
3241 batch->ubuf = m_rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: ubufSize);
3242 ubufRebuild = true;
3243 } else {
3244 if (batch->ubuf->size() < ubufSize) {
3245 batch->ubuf->setSize(ubufSize);
3246 ubufRebuild = true;
3247 }
3248 }
3249 if (ubufRebuild) {
3250 batch->ubufDataValid = false;
3251 if (!batch->ubuf->create()) {
3252 qWarning(msg: "Failed to build uniform buffer of size %u bytes", ubufSize);
3253 delete batch->ubuf;
3254 batch->ubuf = nullptr;
3255 return false;
3256 }
3257 }
3258 }
3259
3260 QSGMaterialShader::RenderState renderState = state(dirty: QSGMaterialShader::RenderState::DirtyStates(int(dirty)));
3261
3262 bool pendingGStatePop = false;
3263 updateMaterialStaticData(sms, renderState, material, batch, gstateChanged: &pendingGStatePop);
3264
3265 char *directUpdatePtr = nullptr;
3266 if (batch->ubuf->nativeBuffer().slotCount == 0)
3267 directUpdatePtr = batch->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
3268
3269 updateMaterialDynamicData(sms, renderState, material, batch, e, ubufOffset: 0, ubufRegionSize: ubufSize, directUpdatePtr);
3270
3271 if (directUpdatePtr)
3272 batch->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
3273
3274#ifndef QT_NO_DEBUG
3275 if (qsg_test_and_clear_material_failure()) {
3276 qDebug(msg: "QSGMaterial::updateState triggered an error (merged), batch will be skipped:");
3277 Element *ee = e;
3278 while (ee) {
3279 qDebug() << " -" << ee->node;
3280 ee = ee->nextInBatch;
3281 }
3282 QSGNodeDumper::dump(n: rootNode());
3283 qFatal(msg: "Aborting: scene graph is invalid...");
3284 }
3285#endif
3286
3287 m_gstate.drawMode = QSGGeometry::DrawingMode(g->drawingMode());
3288 m_gstate.lineWidth = g->lineWidth();
3289
3290 const bool hasPipeline = ensurePipelineState(e, sms);
3291
3292 if (pendingGStatePop)
3293 m_gstate = m_gstateStack.pop();
3294
3295 if (!hasPipeline)
3296 return false;
3297
3298 if (m_renderMode == QSGRendererInterface::RenderMode3D) {
3299 m_gstateStack.push(t: m_gstate);
3300 setStateForDepthPostPass();
3301 ensurePipelineState(e, sms, depthPostPass: true);
3302 m_gstate = m_gstateStack.pop();
3303 }
3304
3305 batch->ubufDataValid = true;
3306
3307 m_currentMaterial = material;
3308
3309 renderBatch->batch = batch;
3310 renderBatch->sms = sms;
3311
3312 return true;
3313}
3314
3315void Renderer::checkLineWidth(QSGGeometry *g)
3316{
3317 if (g->drawingMode() == QSGGeometry::DrawLines || g->drawingMode() == QSGGeometry::DrawLineLoop
3318 || g->drawingMode() == QSGGeometry::DrawLineStrip)
3319 {
3320 if (g->lineWidth() != 1.0f) {
3321 static bool checkedWideLineSupport = false;
3322 if (!checkedWideLineSupport) {
3323 checkedWideLineSupport = true;
3324 if (!m_rhi->isFeatureSupported(feature: QRhi::WideLines))
3325 qWarning(msg: "Line widths other than 1 are not supported by the graphics API");
3326 }
3327 }
3328 } else if (g->drawingMode() == QSGGeometry::DrawPoints) {
3329 if (g->lineWidth() != 1.0f) {
3330 static bool warnedPointSize = false;
3331 if (!warnedPointSize) {
3332 warnedPointSize = true;
3333 qWarning(msg: "Point size is not controllable by QSGGeometry. "
3334 "Set gl_PointSize from the vertex shader instead.");
3335 }
3336 }
3337 }
3338}
3339
3340void Renderer::renderMergedBatch(PreparedRenderBatch *renderBatch, bool depthPostPass)
3341{
3342 const Batch *batch = renderBatch->batch;
3343 if (!batch->vbo.buf || !batch->ibo.buf)
3344 return;
3345
3346 Element *e = batch->first;
3347 QSGGeometryNode *gn = e->node;
3348 QSGGeometry *g = gn->geometry();
3349 checkLineWidth(g);
3350
3351 if (batch->clipState.type & ClipState::StencilClip)
3352 enqueueStencilDraw(batch);
3353
3354 QRhiCommandBuffer *cb = renderTarget().cb;
3355 setGraphicsPipeline(cb, batch, e, depthPostPass);
3356
3357 for (int i = 0, ie = batch->drawSets.size(); i != ie; ++i) {
3358 const DrawSet &draw = batch->drawSets.at(i);
3359 const QRhiCommandBuffer::VertexInput vbufBindings[] = {
3360 { batch->vbo.buf, quint32(draw.vertices) },
3361 { batch->vbo.buf, quint32(draw.zorders) }
3362 };
3363 cb->setVertexInput(startBinding: VERTEX_BUFFER_BINDING, bindingCount: useDepthBuffer() ? 2 : 1, bindings: vbufBindings,
3364 indexBuf: batch->ibo.buf, indexOffset: draw.indices,
3365 indexFormat: m_uint32IndexForRhi ? QRhiCommandBuffer::IndexUInt32 : QRhiCommandBuffer::IndexUInt16);
3366 cb->drawIndexed(indexCount: draw.indexCount);
3367 }
3368}
3369
3370bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *renderBatch)
3371{
3372 if (batch->vertexCount == 0)
3373 return false;
3374
3375 Element *e = batch->first;
3376 Q_ASSERT(e);
3377
3378 if (Q_UNLIKELY(debug_render())) {
3379 qDebug() << " -"
3380 << batch
3381 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3382 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3383 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3384 << "[unmerged]"
3385 << " Nodes:" << QString::fromLatin1(ba: "%1").arg(a: qsg_countNodesInBatch(batch), fieldWidth: 4).toLatin1().constData()
3386 << " Vertices:" << QString::fromLatin1(ba: "%1").arg(a: batch->vertexCount, fieldWidth: 5).toLatin1().constData()
3387 << " Indices:" << QString::fromLatin1(ba: "%1").arg(a: batch->indexCount, fieldWidth: 5).toLatin1().constData()
3388 << " root:" << batch->root;
3389
3390 batch->uploadedThisFrame = false;
3391 }
3392
3393 const int viewCount = projectionMatrixCount();
3394 m_current_projection_matrix.resize(sz: viewCount);
3395 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3396 m_current_projection_matrix[viewIndex] = projectionMatrix(index: viewIndex);
3397
3398 m_current_projection_matrix_native_ndc.resize(sz: projectionMatrixWithNativeNDCCount());
3399 for (int viewIndex = 0; viewIndex < projectionMatrixWithNativeNDCCount(); ++viewIndex)
3400 m_current_projection_matrix_native_ndc[viewIndex] = projectionMatrixWithNativeNDC(index: viewIndex);
3401
3402 QSGGeometryNode *gn = e->node;
3403 if (m_renderMode != QSGRendererInterface::RenderMode3D)
3404 updateClipState(clipList: gn->clipList(), batch);
3405
3406 // We always have dirty matrix as all batches are at a unique z range.
3407 QSGMaterialShader::RenderState::DirtyStates dirty = QSGMaterialShader::RenderState::DirtyMatrix;
3408
3409 // The vertex attributes are assumed to be the same for all elements in the
3410 // unmerged batch since the material (and so the shaders) is the same.
3411 QSGGeometry *g = gn->geometry();
3412 QSGMaterial *material = gn->activeMaterial();
3413 ShaderManager::Shader *sms = m_shaderManager->prepareMaterialNoRewrite(material, geometry: g, renderMode: m_renderMode, multiViewCount: renderTarget().multiViewCount);
3414 if (!sms)
3415 return false;
3416
3417 Q_ASSERT(sms->materialShader);
3418 if (m_currentShader != sms)
3419 setActiveRhiShader(program: sms->materialShader, shader: sms);
3420
3421 m_current_opacity = gn->inheritedOpacity();
3422 if (sms->lastOpacity != m_current_opacity) {
3423 dirty |= QSGMaterialShader::RenderState::DirtyOpacity;
3424 sms->lastOpacity = m_current_opacity;
3425 }
3426
3427 QMatrix4x4 rootMatrix = batch->root ? qsg_matrixForRoot(node: batch->root) : QMatrix4x4();
3428
3429 QSGMaterialShaderPrivate *pd = QSGMaterialShaderPrivate::get(s: sms->materialShader);
3430 const quint32 ubufSize = quint32(pd->masterUniformData.size());
3431 if (pd->ubufBinding >= 0) {
3432 quint32 totalUBufSize = 0;
3433 while (e) {
3434 totalUBufSize += aligned(v: ubufSize, byteAlign: m_ubufAlignment);
3435 e = e->nextInBatch;
3436 }
3437 bool ubufRebuild = false;
3438 if (!batch->ubuf) {
3439 batch->ubuf = m_rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: totalUBufSize);
3440 ubufRebuild = true;
3441 } else {
3442 if (batch->ubuf->size() < totalUBufSize) {
3443 batch->ubuf->setSize(totalUBufSize);
3444 ubufRebuild = true;
3445 }
3446 }
3447 if (ubufRebuild) {
3448 batch->ubufDataValid = false;
3449 if (!batch->ubuf->create()) {
3450 qWarning(msg: "Failed to build uniform buffer of size %u bytes", totalUBufSize);
3451 delete batch->ubuf;
3452 batch->ubuf = nullptr;
3453 return false;
3454 }
3455 }
3456 }
3457
3458 QSGMaterialShader::RenderState renderState = state(dirty: QSGMaterialShader::RenderState::DirtyStates(int(dirty)));
3459 bool pendingGStatePop = false;
3460 updateMaterialStaticData(sms, renderState,
3461 material, batch, gstateChanged: &pendingGStatePop);
3462
3463 int ubufOffset = 0;
3464 QRhiGraphicsPipeline *ps = nullptr;
3465 QRhiGraphicsPipeline *depthPostPassPs = nullptr;
3466 e = batch->first;
3467
3468 char *directUpdatePtr = nullptr;
3469 if (batch->ubuf->nativeBuffer().slotCount == 0)
3470 directUpdatePtr = batch->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
3471
3472 while (e) {
3473 gn = e->node;
3474
3475 m_current_model_view_matrix = rootMatrix * *gn->matrix();
3476 m_current_determinant = m_current_model_view_matrix.determinant();
3477
3478 const int viewCount = projectionMatrixCount();
3479 m_current_projection_matrix.resize(sz: viewCount);
3480 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
3481 m_current_projection_matrix[viewIndex] = projectionMatrix(index: viewIndex);
3482
3483 m_current_projection_matrix_native_ndc.resize(sz: projectionMatrixWithNativeNDCCount());
3484 for (int viewIndex = 0; viewIndex < projectionMatrixWithNativeNDCCount(); ++viewIndex)
3485 m_current_projection_matrix_native_ndc[viewIndex] = projectionMatrixWithNativeNDC(index: viewIndex);
3486
3487 if (useDepthBuffer()) {
3488 // this cannot be multiview
3489 m_current_projection_matrix[0](2, 2) = m_zRange;
3490 m_current_projection_matrix[0](2, 3) = calculateElementZOrder(e, zRange: m_zRange);
3491 }
3492
3493 QSGMaterialShader::RenderState renderState = state(dirty: QSGMaterialShader::RenderState::DirtyStates(int(dirty)));
3494 updateMaterialDynamicData(sms, renderState, material, batch, e, ubufOffset, ubufRegionSize: ubufSize, directUpdatePtr);
3495
3496#ifndef QT_NO_DEBUG
3497 if (qsg_test_and_clear_material_failure()) {
3498 qDebug(msg: "QSGMaterial::updateState() triggered an error (unmerged), batch will be skipped:");
3499 qDebug() << " - offending node is" << e->node;
3500 QSGNodeDumper::dump(n: rootNode());
3501 qFatal(msg: "Aborting: scene graph is invalid...");
3502 return false;
3503 }
3504#endif
3505
3506 ubufOffset += aligned(v: ubufSize, byteAlign: m_ubufAlignment);
3507
3508 const QSGGeometry::DrawingMode prevDrawMode = m_gstate.drawMode;
3509 const float prevLineWidth = m_gstate.lineWidth;
3510 m_gstate.drawMode = QSGGeometry::DrawingMode(g->drawingMode());
3511 m_gstate.lineWidth = g->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)
3516 if (!ps || m_gstate.drawMode != prevDrawMode || m_gstate.lineWidth != prevLineWidth || pendingGStatePop) {
3517 if (!ensurePipelineState(e, sms)) {
3518 if (pendingGStatePop)
3519 m_gstate = m_gstateStack.pop();
3520 return false;
3521 }
3522 ps = e->ps;
3523 if (m_renderMode == QSGRendererInterface::RenderMode3D) {
3524 m_gstateStack.push(t: m_gstate);
3525 setStateForDepthPostPass();
3526 ensurePipelineState(e, sms, depthPostPass: true);
3527 m_gstate = m_gstateStack.pop();
3528 depthPostPassPs = e->depthPostPassPs;
3529 }
3530 } else {
3531 e->ps = ps;
3532 if (m_renderMode == QSGRendererInterface::RenderMode3D)
3533 e->depthPostPassPs = depthPostPassPs;
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.
3538 m_currentMaterial = material;
3539
3540 // We only need to push this on the very first iteration...
3541 dirty &= ~QSGMaterialShader::RenderState::DirtyOpacity;
3542
3543 e = e->nextInBatch;
3544 }
3545
3546 if (directUpdatePtr)
3547 batch->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
3548
3549 if (pendingGStatePop)
3550 m_gstate = m_gstateStack.pop();
3551
3552 batch->ubufDataValid = true;
3553
3554 renderBatch->batch = batch;
3555 renderBatch->sms = sms;
3556
3557 return true;
3558}
3559
3560void Renderer::renderUnmergedBatch(PreparedRenderBatch *renderBatch, bool depthPostPass)
3561{
3562 const Batch *batch = renderBatch->batch;
3563 if (!batch->vbo.buf)
3564 return;
3565
3566 Element *e = batch->first;
3567
3568 if (batch->clipState.type & ClipState::StencilClip)
3569 enqueueStencilDraw(batch);
3570
3571 quint32 vOffset = 0;
3572 quint32 iOffset = 0;
3573 QRhiCommandBuffer *cb = renderTarget().cb;
3574
3575 while (e) {
3576 QSGGeometry *g = e->node->geometry();
3577 checkLineWidth(g);
3578 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
3579
3580 setGraphicsPipeline(cb, batch, e, depthPostPass);
3581
3582 const QRhiCommandBuffer::VertexInput vbufBinding(batch->vbo.buf, vOffset);
3583 if (g->indexCount()) {
3584 if (batch->ibo.buf) {
3585 cb->setVertexInput(startBinding: VERTEX_BUFFER_BINDING, bindingCount: 1, bindings: &vbufBinding,
3586 indexBuf: batch->ibo.buf, indexOffset: iOffset,
3587 indexFormat: effectiveIndexSize == sizeof(quint32) ? QRhiCommandBuffer::IndexUInt32
3588 : QRhiCommandBuffer::IndexUInt16);
3589 cb->drawIndexed(indexCount: g->indexCount());
3590 }
3591 } else {
3592 cb->setVertexInput(startBinding: VERTEX_BUFFER_BINDING, bindingCount: 1, bindings: &vbufBinding);
3593 cb->draw(vertexCount: g->vertexCount());
3594 }
3595
3596 vOffset += g->sizeOfVertex() * g->vertexCount();
3597 iOffset += g->indexCount() * effectiveIndexSize;
3598
3599 e = e->nextInBatch;
3600 }
3601}
3602
3603void Renderer::setGraphicsPipeline(QRhiCommandBuffer *cb, const Batch *batch, Element *e, bool depthPostPass)
3604{
3605 cb->setGraphicsPipeline(depthPostPass ? e->depthPostPassPs : e->ps);
3606
3607 if (!m_pstate.viewportSet) {
3608 m_pstate.viewportSet = true;
3609 cb->setViewport(m_pstate.viewport);
3610 }
3611 if (batch->clipState.type & ClipState::ScissorClip) {
3612 Q_ASSERT(e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesScissor));
3613 m_pstate.scissorSet = true;
3614 cb->setScissor(batch->clipState.scissor);
3615 } else {
3616 Q_ASSERT(!e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesScissor));
3617 // Regardless of the ps not using scissor, the scissor may need to be
3618 // reset, depending on the backend. So set the viewport again, which in
3619 // turn also sets the scissor on backends where a scissor rect is
3620 // always-on (Vulkan).
3621 if (m_pstate.scissorSet) {
3622 m_pstate.scissorSet = false;
3623 cb->setViewport(m_pstate.viewport);
3624 }
3625 }
3626 if (batch->clipState.type & ClipState::StencilClip) {
3627 Q_ASSERT(e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesStencilRef));
3628 cb->setStencilRef(batch->clipState.stencilRef);
3629 }
3630 if (!depthPostPass && e->ps->flags().testFlag(flag: QRhiGraphicsPipeline::UsesBlendConstants))
3631 cb->setBlendConstants(batch->blendConstant);
3632
3633 cb->setShaderResources(srb: e->srb);
3634}
3635
3636void Renderer::releaseElement(Element *e, bool inDestructor)
3637{
3638 if (e->isRenderNode) {
3639 delete static_cast<RenderNodeElement *>(e);
3640 } else {
3641 if (e->srb) {
3642 if (!inDestructor) {
3643 if (m_shaderManager->srbPool.size() < m_srbPoolThreshold)
3644 m_shaderManager->srbPool.insert(key: e->srb->serializedLayoutDescription(), value: e->srb);
3645 else
3646 delete e->srb;
3647 } else {
3648 delete e->srb;
3649 }
3650 e->srb = nullptr;
3651 }
3652 m_elementAllocator.release(t: e);
3653 }
3654}
3655
3656void Renderer::deleteRemovedElements()
3657{
3658 if (!m_elementsToDelete.size())
3659 return;
3660
3661 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3662 Element **e = m_opaqueRenderList.data() + i;
3663 if (*e && (*e)->removed)
3664 *e = nullptr;
3665 }
3666 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3667 Element **e = m_alphaRenderList.data() + i;
3668 if (*e && (*e)->removed)
3669 *e = nullptr;
3670 }
3671
3672 for (int i=0; i<m_elementsToDelete.size(); ++i)
3673 releaseElement(e: m_elementsToDelete.at(i));
3674
3675 m_elementsToDelete.reset();
3676}
3677
3678void Renderer::render()
3679{
3680 // Gracefully handle the lack of a render target - some autotests may rely
3681 // on this in odd cases.
3682 if (!renderTarget().rt)
3683 return;
3684
3685 prepareRenderPass(ctx: &m_mainRenderPassContext);
3686 beginRenderPass(ctx: &m_mainRenderPassContext);
3687 recordRenderPass(ctx: &m_mainRenderPassContext);
3688 endRenderPass(ctx: &m_mainRenderPassContext);
3689}
3690
3691// An alternative to render() is to call prepareInline() and renderInline() at
3692// the appropriate times (i.e. outside of a QRhi::beginPass() and then inside,
3693// respectively) These allow rendering within a render pass that is started by
3694// another component. In contrast, render() records a full render pass on its
3695// own.
3696
3697void Renderer::prepareInline()
3698{
3699 prepareRenderPass(ctx: &m_mainRenderPassContext);
3700}
3701
3702void Renderer::renderInline()
3703{
3704 recordRenderPass(ctx: &m_mainRenderPassContext);
3705}
3706
3707void Renderer::prepareRenderPass(RenderPassContext *ctx)
3708{
3709 if (ctx->valid)
3710 qWarning(msg: "prepareRenderPass() called with an already prepared render pass context");
3711
3712 ctx->valid = true;
3713
3714 if (Q_UNLIKELY(debug_dump())) {
3715 qDebug(msg: "\n");
3716 QSGNodeDumper::dump(n: rootNode());
3717 }
3718
3719 ctx->timeRenderLists = 0;
3720 ctx->timePrepareOpaque = 0;
3721 ctx->timePrepareAlpha = 0;
3722 ctx->timeSorting = 0;
3723 ctx->timeUploadOpaque = 0;
3724 ctx->timeUploadAlpha = 0;
3725
3726 if (Q_UNLIKELY(debug_render() || debug_build())) {
3727 QByteArray type("rebuild:");
3728 if (m_rebuild == 0)
3729 type += " none";
3730 if (m_rebuild == FullRebuild)
3731 type += " full";
3732 else {
3733 if (m_rebuild & BuildRenderLists)
3734 type += " renderlists";
3735 else if (m_rebuild & BuildRenderListsForTaggedRoots)
3736 type += " partial";
3737 else if (m_rebuild & BuildBatches)
3738 type += " batches";
3739 }
3740
3741 qDebug() << "Renderer::render()" << this << type;
3742 ctx->timer.start();
3743 }
3744
3745 m_resourceUpdates = m_rhi->nextResourceUpdateBatch();
3746
3747 if (m_rebuild & (BuildRenderLists | BuildRenderListsForTaggedRoots)) {
3748 bool complete = (m_rebuild & BuildRenderLists) != 0;
3749 if (complete)
3750 buildRenderListsFromScratch();
3751 else
3752 buildRenderListsForTaggedRoots();
3753 m_rebuild |= BuildBatches;
3754
3755 if (Q_UNLIKELY(debug_build())) {
3756 qDebug(msg: "Opaque render lists %s:", (complete ? "(complete)" : "(partial)"));
3757 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
3758 Element *e = m_opaqueRenderList.at(i);
3759 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3760 }
3761 qDebug(msg: "Alpha render list %s:", complete ? "(complete)" : "(partial)");
3762 for (int i=0; i<m_alphaRenderList.size(); ++i) {
3763 Element *e = m_alphaRenderList.at(i);
3764 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
3765 }
3766 }
3767 }
3768 if (Q_UNLIKELY(debug_render())) ctx->timeRenderLists = ctx->timer.restart();
3769
3770 for (int i=0; i<m_opaqueBatches.size(); ++i)
3771 m_opaqueBatches.at(i)->cleanupRemovedElements();
3772 for (int i=0; i<m_alphaBatches.size(); ++i)
3773 m_alphaBatches.at(i)->cleanupRemovedElements();
3774 deleteRemovedElements();
3775
3776 cleanupBatches(batches: &m_opaqueBatches);
3777 cleanupBatches(batches: &m_alphaBatches);
3778
3779 if (m_rebuild & BuildBatches) {
3780 prepareOpaqueBatches();
3781 if (Q_UNLIKELY(debug_render())) ctx->timePrepareOpaque = ctx->timer.restart();
3782 prepareAlphaBatches();
3783 if (Q_UNLIKELY(debug_render())) ctx->timePrepareAlpha = ctx->timer.restart();
3784
3785 if (Q_UNLIKELY(debug_build())) {
3786 qDebug(msg: "Opaque Batches:");
3787 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3788 Batch *b = m_opaqueBatches.at(i);
3789 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3790 for (Element *e = b->first; e; e = e->nextInBatch) {
3791 qDebug() << " - element:" << e << " node:" << e->node << e->order;
3792 }
3793 }
3794 qDebug(msg: "Alpha Batches:");
3795 for (int i=0; i<m_alphaBatches.size(); ++i) {
3796 Batch *b = m_alphaBatches.at(i);
3797 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
3798 for (Element *e = b->first; e; e = e->nextInBatch) {
3799 qDebug() << " - element:" << e << e->bounds << " node:" << e->node << " order:" << e->order;
3800 }
3801 }
3802 }
3803 } else {
3804 if (Q_UNLIKELY(debug_render())) ctx->timePrepareOpaque = ctx->timePrepareAlpha = ctx->timer.restart();
3805 }
3806
3807
3808 deleteRemovedElements();
3809
3810 if (m_rebuild != 0) {
3811 // Then sort opaque batches so that we're drawing the batches with the highest
3812 // order first, maximizing the benefit of front-to-back z-ordering.
3813 if (m_opaqueBatches.size())
3814 std::sort(first: &m_opaqueBatches.first(), last: &m_opaqueBatches.last() + 1, comp: qsg_sort_batch_decreasing_order);
3815
3816 // Sort alpha batches back to front so that they render correctly.
3817 if (m_alphaBatches.size())
3818 std::sort(first: &m_alphaBatches.first(), last: &m_alphaBatches.last() + 1, comp: qsg_sort_batch_increasing_order);
3819
3820 m_zRange = m_nextRenderOrder != 0
3821 ? 1.0 / (m_nextRenderOrder)
3822 : 0;
3823 }
3824
3825 if (Q_UNLIKELY(debug_render())) ctx->timeSorting = ctx->timer.restart();
3826
3827 // Set size to 0, nothing is deallocated, they will "grow" again
3828 // as part of uploadBatch.
3829 m_vertexUploadPool.reset();
3830 m_indexUploadPool.reset();
3831
3832 if (Q_UNLIKELY(debug_upload())) qDebug(msg: "Uploading Opaque Batches:");
3833 for (int i=0; i<m_opaqueBatches.size(); ++i) {
3834 Batch *b = m_opaqueBatches.at(i);
3835 uploadBatch(b);
3836 }
3837 if (Q_UNLIKELY(debug_render())) ctx->timeUploadOpaque = ctx->timer.restart();
3838
3839 if (Q_UNLIKELY(debug_upload())) qDebug(msg: "Uploading Alpha Batches:");
3840 for (int i=0; i<m_alphaBatches.size(); ++i) {
3841 Batch *b = m_alphaBatches.at(i);
3842 uploadBatch(b);
3843 }
3844 if (Q_UNLIKELY(debug_render())) ctx->timeUploadAlpha = ctx->timer.restart();
3845
3846 if (Q_UNLIKELY(debug_render())) {
3847 qDebug().nospace() << "Rendering:" << Qt::endl
3848 << " -> Opaque: " << qsg_countNodesInBatches(batches: m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << Qt::endl
3849 << " -> Alpha: " << qsg_countNodesInBatches(batches: m_alphaBatches) << " nodes in " << m_alphaBatches.size() << " batches...";
3850 }
3851
3852 m_current_opacity = 1;
3853 m_currentMaterial = nullptr;
3854 m_currentShader = nullptr;
3855 m_currentProgram = nullptr;
3856 m_currentClipState.reset();
3857
3858 const QRect viewport = viewportRect();
3859
3860 bool renderOpaque = !debug_noopaque();
3861 bool renderAlpha = !debug_noalpha();
3862
3863 m_pstate.viewport =
3864 QRhiViewport(viewport.x(), deviceRect().bottom() - viewport.bottom(), viewport.width(),
3865 viewport.height(), VIEWPORT_MIN_DEPTH, VIEWPORT_MAX_DEPTH);
3866 m_pstate.clearColor = clearColor();
3867 m_pstate.dsClear = QRhiDepthStencilClearValue(1.0f, 0);
3868 m_pstate.viewportSet = false;
3869 m_pstate.scissorSet = false;
3870
3871 m_gstate.depthTest = useDepthBuffer();
3872 m_gstate.depthWrite = useDepthBuffer();
3873 m_gstate.depthFunc = QRhiGraphicsPipeline::Less;
3874 m_gstate.blending = false;
3875
3876 m_gstate.cullMode = QRhiGraphicsPipeline::None;
3877 m_gstate.polygonMode = QRhiGraphicsPipeline::Fill;
3878 m_gstate.colorWrite = QRhiGraphicsPipeline::R
3879 | QRhiGraphicsPipeline::G
3880 | QRhiGraphicsPipeline::B
3881 | QRhiGraphicsPipeline::A;
3882 m_gstate.usesScissor = false;
3883 m_gstate.stencilTest = false;
3884
3885 m_gstate.sampleCount = renderTarget().rt->sampleCount();
3886 m_gstate.multiViewCount = renderTarget().multiViewCount;
3887
3888 ctx->opaqueRenderBatches.clear();
3889 if (Q_LIKELY(renderOpaque)) {
3890 for (int i = 0, ie = m_opaqueBatches.size(); i != ie; ++i) {
3891 Batch *b = m_opaqueBatches.at(i);
3892 PreparedRenderBatch renderBatch;
3893 bool ok;
3894 if (b->merged)
3895 ok = prepareRenderMergedBatch(batch: b, renderBatch: &renderBatch);
3896 else
3897 ok = prepareRenderUnmergedBatch(batch: b, renderBatch: &renderBatch);
3898 if (ok)
3899 ctx->opaqueRenderBatches.append(t: renderBatch);
3900 }
3901 }
3902
3903 m_gstate.blending = true;
3904 // factors never change, always set for premultiplied alpha based blending
3905
3906 // depth test stays enabled (if useDepthBuffer(), that is) but no need
3907 // to write out depth from the transparent (back-to-front) pass
3908 m_gstate.depthWrite = false;
3909
3910 // special case: the 3D plane mode tests against the depth buffer, but does
3911 // not write (and all batches are alpha because this render mode evaluates
3912 // to useDepthBuffer()==false)
3913 if (m_renderMode == QSGRendererInterface::RenderMode3D) {
3914 Q_ASSERT(m_opaqueBatches.isEmpty());
3915 m_gstate.depthTest = true;
3916 }
3917
3918 ctx->alphaRenderBatches.clear();
3919 if (Q_LIKELY(renderAlpha)) {
3920 for (int i = 0, ie = m_alphaBatches.size(); i != ie; ++i) {
3921 Batch *b = m_alphaBatches.at(i);
3922 PreparedRenderBatch renderBatch;
3923 bool ok;
3924 if (b->merged)
3925 ok = prepareRenderMergedBatch(batch: b, renderBatch: &renderBatch);
3926 else if (b->isRenderNode)
3927 ok = prepareRhiRenderNode(batch: b, renderBatch: &renderBatch);
3928 else
3929 ok = prepareRenderUnmergedBatch(batch: b, renderBatch: &renderBatch);
3930 if (ok)
3931 ctx->alphaRenderBatches.append(t: renderBatch);
3932 }
3933 }
3934
3935 m_rebuild = 0;
3936
3937#if defined(QSGBATCHRENDERER_INVALIDATE_WEDGED_NODES)
3938 m_renderOrderRebuildLower = -1;
3939 m_renderOrderRebuildUpper = -1;
3940#endif
3941
3942 if (m_visualizer->mode() != Visualizer::VisualizeNothing)
3943 m_visualizer->prepareVisualize();
3944
3945 renderTarget().cb->resourceUpdate(resourceUpdates: m_resourceUpdates);
3946 m_resourceUpdates = nullptr;
3947}
3948
3949void Renderer::beginRenderPass(RenderPassContext *)
3950{
3951 const QSGRenderTarget &rt(renderTarget());
3952 rt.cb->beginPass(rt: rt.rt, colorClearValue: m_pstate.clearColor, depthStencilClearValue: m_pstate.dsClear, resourceUpdates: nullptr,
3953 // we cannot tell if the application will have
3954 // native rendering thrown in to this pass
3955 // (QQuickWindow::beginExternalCommands()), so
3956 // we have no choice but to set the flag always
3957 // (thus triggering using secondary command
3958 // buffers with Vulkan)
3959 flags: QRhiCommandBuffer::ExternalContent
3960 // We do not use GPU compute at all at the moment, this means we can
3961 // get a small performance gain with OpenGL by declaring this.
3962 | QRhiCommandBuffer::DoNotTrackResourcesForCompute);
3963
3964 if (m_renderPassRecordingCallbacks.start)
3965 m_renderPassRecordingCallbacks.start(m_renderPassRecordingCallbacks.userData);
3966}
3967
3968void Renderer::recordRenderPass(RenderPassContext *ctx)
3969{
3970 // prepareRenderPass and recordRenderPass must always be called together.
3971 // They are separate because beginRenderPass and endRenderPass are optional.
3972 //
3973 // The valid call sequence are therefore:
3974 // prepare, begin, record, end
3975 // or
3976 // prepare, record
3977
3978 if (!ctx->valid)
3979 qWarning(msg: "recordRenderPass() called without a prepared render pass context");
3980
3981 ctx->valid = false;
3982
3983 QRhiCommandBuffer *cb = renderTarget().cb;
3984 cb->debugMarkBegin(QByteArrayLiteral("Qt Quick scene render"));
3985
3986 for (int i = 0, ie = ctx->opaqueRenderBatches.size(); i != ie; ++i) {
3987 if (i == 0)
3988 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick opaque batches"));
3989 PreparedRenderBatch *renderBatch = &ctx->opaqueRenderBatches[i];
3990 if (renderBatch->batch->merged)
3991 renderMergedBatch(renderBatch);
3992 else
3993 renderUnmergedBatch(renderBatch);
3994 }
3995
3996 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
3997 if (i == 0) {
3998 if (m_renderMode == QSGRendererInterface::RenderMode3D)
3999 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D batches"));
4000 else
4001 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick alpha batches"));
4002 }
4003 PreparedRenderBatch *renderBatch = &ctx->alphaRenderBatches[i];
4004 if (renderBatch->batch->merged)
4005 renderMergedBatch(renderBatch);
4006 else if (renderBatch->batch->isRenderNode)
4007 renderRhiRenderNode(batch: renderBatch->batch);
4008 else
4009 renderUnmergedBatch(renderBatch);
4010 }
4011
4012 if (m_renderMode == QSGRendererInterface::RenderMode3D) {
4013 // Depth post-pass to fill up the depth buffer in a way that it
4014 // corresponds to what got rendered to the color buffer in the previous
4015 // (alpha) pass. The previous pass cannot enable depth write due to Z
4016 // fighting. Rather, do it separately in a dedicated color-write-off,
4017 // depth-write-on pass. This enables the 3D content drawn afterwards to
4018 // depth test against the 2D items' rendering.
4019 for (int i = 0, ie = ctx->alphaRenderBatches.size(); i != ie; ++i) {
4020 if (i == 0)
4021 cb->debugMarkMsg(QByteArrayLiteral("Qt Quick 2D-in-3D depth post-pass"));
4022 PreparedRenderBatch *renderBatch = &ctx->alphaRenderBatches[i];
4023 if (renderBatch->batch->merged)
4024 renderMergedBatch(renderBatch, depthPostPass: true);
4025 else if (!renderBatch->batch->isRenderNode) // rendernodes are skipped here for now
4026 renderUnmergedBatch(renderBatch, depthPostPass: true);
4027 }
4028 }
4029
4030 if (m_currentShader)
4031 setActiveRhiShader(program: nullptr, shader: nullptr);
4032
4033 cb->debugMarkEnd();
4034
4035 if (Q_UNLIKELY(debug_render())) {
4036 qDebug(msg: " -> times: build: %d, prepare(opaque/alpha): %d/%d, sorting: %d, upload(opaque/alpha): %d/%d, record rendering: %d",
4037 (int) ctx->timeRenderLists,
4038 (int) ctx->timePrepareOpaque, (int) ctx->timePrepareAlpha,
4039 (int) ctx->timeSorting,
4040 (int) ctx->timeUploadOpaque, (int) ctx->timeUploadAlpha,
4041 (int) ctx->timer.elapsed());
4042 }
4043}
4044
4045void Renderer::endRenderPass(RenderPassContext *)
4046{
4047 if (m_renderPassRecordingCallbacks.end)
4048 m_renderPassRecordingCallbacks.end(m_renderPassRecordingCallbacks.userData);
4049
4050 if (m_visualizer->mode() != Visualizer::VisualizeNothing)
4051 m_visualizer->visualize();
4052
4053 renderTarget().cb->endPass();
4054}
4055
4056struct RenderNodeState : public QSGRenderNode::RenderState
4057{
4058 const QMatrix4x4 *projectionMatrix() const override { return m_projectionMatrix; }
4059 QRect scissorRect() const override { return m_scissorRect; }
4060 bool scissorEnabled() const override { return m_scissorEnabled; }
4061 int stencilValue() const override { return m_stencilValue; }
4062 bool stencilEnabled() const override { return m_stencilEnabled; }
4063 const QRegion *clipRegion() const override { return nullptr; }
4064
4065 const QMatrix4x4 *m_projectionMatrix;
4066 QRect m_scissorRect;
4067 int m_stencilValue;
4068 bool m_scissorEnabled;
4069 bool m_stencilEnabled;
4070};
4071
4072bool Renderer::prepareRhiRenderNode(Batch *batch, PreparedRenderBatch *renderBatch)
4073{
4074 if (Q_UNLIKELY(debug_render()))
4075 qDebug() << " -" << batch << "rendernode";
4076
4077 Q_ASSERT(batch->first->isRenderNode);
4078 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4079
4080 setActiveRhiShader(program: nullptr, shader: nullptr);
4081
4082 QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(node: e->renderNode);
4083 rd->m_clip_list = nullptr;
4084 if (m_renderMode != QSGRendererInterface::RenderMode3D) {
4085 QSGNode *clip = e->renderNode->parent();
4086 while (clip != rootNode()) {
4087 if (clip->type() == QSGNode::ClipNodeType) {
4088 rd->m_clip_list = static_cast<QSGClipNode *>(clip);
4089 break;
4090 }
4091 clip = clip->parent();
4092 }
4093 updateClipState(clipList: rd->m_clip_list, batch);
4094 }
4095
4096 QSGNode *xform = e->renderNode->parent();
4097 QMatrix4x4 matrix;
4098 QSGNode *root = rootNode();
4099 if (e->root) {
4100 matrix = qsg_matrixForRoot(node: e->root);
4101 root = e->root->sgNode;
4102 }
4103 while (xform != root) {
4104 if (xform->type() == QSGNode::TransformNodeType) {
4105 matrix = matrix * static_cast<QSGTransformNode *>(xform)->combinedMatrix();
4106 break;
4107 }
4108 xform = xform->parent();
4109 }
4110 rd->m_localMatrix = matrix;
4111 rd->m_matrix = &rd->m_localMatrix;
4112
4113 QSGNode *opacity = e->renderNode->parent();
4114 rd->m_opacity = 1.0;
4115 while (opacity != rootNode()) {
4116 if (opacity->type() == QSGNode::OpacityNodeType) {
4117 rd->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity();
4118 break;
4119 }
4120 opacity = opacity->parent();
4121 }
4122
4123 rd->m_rt = renderTarget();
4124
4125 const int viewCount = projectionMatrixCount();
4126 rd->m_projectionMatrix.resize(sz: viewCount);
4127 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
4128 rd->m_projectionMatrix[viewIndex] = projectionMatrix(index: viewIndex);
4129
4130 if (useDepthBuffer()) {
4131 // this cannot be multiview
4132 rd->m_projectionMatrix[0](2, 2) = m_zRange;
4133 rd->m_projectionMatrix[0](2, 3) = calculateElementZOrder(e, zRange: m_zRange);
4134 }
4135
4136 e->renderNode->prepare();
4137
4138 renderBatch->batch = batch;
4139 renderBatch->sms = nullptr;
4140
4141 return true;
4142}
4143
4144void Renderer::renderRhiRenderNode(const Batch *batch)
4145{
4146 if (batch->clipState.type & ClipState::StencilClip)
4147 enqueueStencilDraw(batch);
4148
4149 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4150 QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(node: e->renderNode);
4151
4152 RenderNodeState state;
4153 // Expose only the first matrix through the state object, the rest are
4154 // queriable through the QSGRenderNode getters anyway.
4155 state.m_projectionMatrix = &rd->m_projectionMatrix[0];
4156 const std::array<int, 4> scissor = batch->clipState.scissor.scissor();
4157 state.m_scissorRect = QRect(scissor[0], scissor[1], scissor[2], scissor[3]);
4158 state.m_stencilValue = batch->clipState.stencilRef;
4159 state.m_scissorEnabled = batch->clipState.type & ClipState::ScissorClip;
4160 state.m_stencilEnabled = batch->clipState.type & ClipState::StencilClip;
4161
4162 const QSGRenderNode::StateFlags changes = e->renderNode->changedStates();
4163
4164 QRhiCommandBuffer *cb = renderTarget().cb;
4165 const bool needsExternal = !e->renderNode->flags().testFlag(flag: QSGRenderNode::NoExternalRendering);
4166 if (needsExternal)
4167 cb->beginExternal();
4168 e->renderNode->render(state: &state);
4169 if (needsExternal)
4170 cb->endExternal();
4171
4172 rd->m_matrix = nullptr;
4173 rd->m_clip_list = nullptr;
4174
4175 if ((changes & QSGRenderNode::ViewportState)
4176 || (changes & QSGRenderNode::ScissorState))
4177 {
4178 // Reset both flags if either is reported as changed, since with the rhi
4179 // it could be setViewport() that will record the resetting of the scissor.
4180 m_pstate.viewportSet = false;
4181 m_pstate.scissorSet = false;
4182 }
4183
4184 // Do not bother with RenderTargetState. Where applicable, endExternal()
4185 // ensures the correct target is rebound. For others (like Vulkan) it makes
4186 // no sense since render() could not possibly do that on our command buffer
4187 // which is in renderpass recording state.
4188}
4189
4190void Renderer::setVisualizationMode(const QByteArray &mode)
4191{
4192 if (mode.isEmpty())
4193 m_visualizer->setMode(Visualizer::VisualizeNothing);
4194 else if (mode == "clip")
4195 m_visualizer->setMode(Visualizer::VisualizeClipping);
4196 else if (mode == "overdraw")
4197 m_visualizer->setMode(Visualizer::VisualizeOverdraw);
4198 else if (mode == "batches")
4199 m_visualizer->setMode(Visualizer::VisualizeBatches);
4200 else if (mode == "changes")
4201 m_visualizer->setMode(Visualizer::VisualizeChanges);
4202}
4203
4204bool Renderer::hasVisualizationModeWithContinuousUpdate() const
4205{
4206 return m_visualizer->mode() == Visualizer::VisualizeOverdraw;
4207}
4208
4209bool operator==(const GraphicsState &a, const GraphicsState &b) noexcept
4210{
4211 return a.depthTest == b.depthTest
4212 && a.depthWrite == b.depthWrite
4213 && a.depthFunc == b.depthFunc
4214 && a.blending == b.blending
4215 && a.srcColor == b.srcColor
4216 && a.dstColor == b.dstColor
4217 && a.srcAlpha == b.srcAlpha
4218 && a.dstAlpha == b.dstAlpha
4219 && a.opColor == b.opColor
4220 && a.opAlpha == b.opAlpha
4221 && a.colorWrite == b.colorWrite
4222 && a.cullMode == b.cullMode
4223 && a.usesScissor == b.usesScissor
4224 && a.stencilTest == b.stencilTest
4225 && a.sampleCount == b.sampleCount
4226 && a.drawMode == b.drawMode
4227 && a.lineWidth == b.lineWidth
4228 && a.polygonMode == b.polygonMode
4229 && a.multiViewCount == b.multiViewCount;
4230}
4231
4232bool operator!=(const GraphicsState &a, const GraphicsState &b) noexcept
4233{
4234 return !(a == b);
4235}
4236
4237size_t qHash(const GraphicsState &s, size_t seed) noexcept
4238{
4239 // do not bother with all fields
4240 return seed
4241 + s.depthTest * 1000
4242 + s.depthWrite * 100
4243 + s.depthFunc
4244 + s.blending * 10
4245 + s.srcColor
4246 + s.cullMode
4247 + s.usesScissor
4248 + s.stencilTest
4249 + s.sampleCount
4250 + s.multiViewCount;
4251}
4252
4253bool operator==(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) noexcept
4254{
4255 return a.state == b.state
4256 && a.sms->materialShader == b.sms->materialShader
4257 && a.renderTargetDescription == b.renderTargetDescription
4258 && a.srbLayoutDescription == b.srbLayoutDescription;
4259}
4260
4261bool operator!=(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) noexcept
4262{
4263 return !(a == b);
4264}
4265
4266size_t qHash(const GraphicsPipelineStateKey &k, size_t seed) noexcept
4267{
4268 return qHash(s: k.state, seed)
4269 ^ qHash(key: k.sms->materialShader)
4270 ^ k.extra.renderTargetDescriptionHash
4271 ^ k.extra.srbLayoutDescriptionHash;
4272}
4273
4274bool operator==(const ShaderKey &a, const ShaderKey &b) noexcept
4275{
4276 return a.type == b.type
4277 && a.renderMode == b.renderMode
4278 && a.multiViewCount == b.multiViewCount;
4279}
4280
4281bool operator!=(const ShaderKey &a, const ShaderKey &b) noexcept
4282{
4283 return !(a == b);
4284}
4285
4286size_t qHash(const ShaderKey &k, size_t seed) noexcept
4287{
4288 return qHash(t: k.type, seed) ^ int(k.renderMode) ^ k.multiViewCount;
4289}
4290
4291Visualizer::Visualizer(Renderer *renderer)
4292 : m_renderer(renderer),
4293 m_visualizeMode(VisualizeNothing)
4294{
4295}
4296
4297Visualizer::~Visualizer()
4298{
4299}
4300
4301#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded \
4302 | QSGNode::DirtyOpacity \
4303 | QSGNode::DirtyMatrix \
4304 | QSGNode::DirtyNodeRemoved)
4305
4306void Visualizer::visualizeChangesPrepare(Node *n, uint parentChanges)
4307{
4308 uint childDirty = (parentChanges | n->dirtyState) & QSGNODE_DIRTY_PARENT;
4309 uint selfDirty = n->dirtyState | parentChanges;
4310 if (n->type() == QSGNode::GeometryNodeType && selfDirty != 0)
4311 m_visualizeChangeSet.insert(key: n, value: selfDirty);
4312 SHADOWNODE_TRAVERSE(n) {
4313 visualizeChangesPrepare(n: child, parentChanges: childDirty);
4314 }
4315}
4316
4317} // namespace QSGBatchRenderer
4318
4319QT_END_NAMESPACE
4320
4321#include "moc_qsgbatchrenderer_p.cpp"
4322

source code of qtdeclarative/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp