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

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