1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
5** Copyright (C) 2016 Robin Burchell <robin.burchell@viroteck.net>
6** Contact: https://www.qt.io/licensing/
7**
8** This file is part of the QtQuick module of the Qt Toolkit.
9**
10** $QT_BEGIN_LICENSE:LGPL$
11** Commercial License Usage
12** Licensees holding valid commercial Qt licenses may use this file in
13** accordance with the commercial license agreement provided with the
14** Software or, alternatively, in accordance with the terms contained in
15** a written agreement between you and The Qt Company. For licensing terms
16** and conditions see https://www.qt.io/terms-conditions. For further
17** information use the contact form at https://www.qt.io/contact-us.
18**
19** GNU Lesser General Public License Usage
20** Alternatively, this file may be used under the terms of the GNU Lesser
21** General Public License version 3 as published by the Free Software
22** Foundation and appearing in the file LICENSE.LGPL3 included in the
23** packaging of this file. Please review the following information to
24** ensure the GNU Lesser General Public License version 3 requirements
25** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
26**
27** GNU General Public License Usage
28** Alternatively, this file may be used under the terms of the GNU
29** General Public License version 2.0 or (at your option) the GNU General
30** Public license version 3 or any later version approved by the KDE Free
31** Qt Foundation. The licenses are as published by the Free Software
32** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
33** included in the packaging of this file. Please review the following
34** information to ensure the GNU General Public License requirements will
35** be met: https://www.gnu.org/licenses/gpl-2.0.html and
36** https://www.gnu.org/licenses/gpl-3.0.html.
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qsgbatchrenderer_p.h"
43#include <private/qsgshadersourcebuilder_p.h>
44
45#include <QQuickWindow>
46
47#include <qmath.h>
48
49#include <QtCore/QElapsedTimer>
50#include <QtCore/QtNumeric>
51
52#include <QtGui/QGuiApplication>
53#include <QtGui/QOpenGLFramebufferObject>
54#include <QtGui/QOpenGLVertexArrayObject>
55#include <QtGui/QOpenGLFunctions_1_0>
56#include <QtGui/QOpenGLFunctions_3_2_Core>
57
58#include <private/qnumeric_p.h>
59#include <private/qquickprofiler_p.h>
60#include "qsgmaterialrhishader_p.h"
61
62#include "qsgopenglvisualizer_p.h"
63#include "qsgrhivisualizer_p.h"
64
65#include <qtquick_tracepoints_p.h>
66
67#include <algorithm>
68
69#ifndef GL_DOUBLE
70 #define GL_DOUBLE 0x140A
71#endif
72
73QT_BEGIN_NAMESPACE
74
75#ifndef QT_NO_DEBUG
76Q_QUICK_PRIVATE_EXPORT bool qsg_test_and_clear_material_failure();
77#endif
78
79extern QByteArray qsgShaderRewriter_insertZAttributes(const char *input, QSurfaceFormat::OpenGLContextProfile profile);
80
81int qt_sg_envInt(const char *name, int defaultValue);
82
83namespace QSGBatchRenderer
84{
85
86#define DECLARE_DEBUG_VAR(variable) \
87 static bool debug_ ## variable() \
88 { static bool value = qgetenv("QSG_RENDERER_DEBUG").contains(QT_STRINGIFY(variable)); return value; }
89DECLARE_DEBUG_VAR(render)
90DECLARE_DEBUG_VAR(build)
91DECLARE_DEBUG_VAR(change)
92DECLARE_DEBUG_VAR(upload)
93DECLARE_DEBUG_VAR(roots)
94DECLARE_DEBUG_VAR(dump)
95DECLARE_DEBUG_VAR(noalpha)
96DECLARE_DEBUG_VAR(noopaque)
97DECLARE_DEBUG_VAR(noclip)
98#undef DECLARE_DEBUG_VAR
99
100static QElapsedTimer qsg_renderer_timer;
101
102#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling())
103#define SHADOWNODE_TRAVERSE(NODE) for (Node *child = NODE->firstChild(); child; child = child->sibling())
104
105static inline int size_of_type(GLenum type)
106{
107 static int sizes[] = {
108 sizeof(char),
109 sizeof(unsigned char),
110 sizeof(short),
111 sizeof(unsigned short),
112 sizeof(int),
113 sizeof(unsigned int),
114 sizeof(float),
115 2,
116 3,
117 4,
118 sizeof(double)
119 };
120 Q_ASSERT(type >= QSGGeometry::ByteType && type <= QSGGeometry::DoubleType);
121 return sizes[type - QSGGeometry::ByteType];
122}
123
124bool qsg_sort_element_increasing_order(Element *a, Element *b) { return a->order < b->order; }
125bool qsg_sort_element_decreasing_order(Element *a, Element *b) { return a->order > b->order; }
126bool qsg_sort_batch_is_valid(Batch *a, Batch *b) { return a->first && !b->first; }
127bool qsg_sort_batch_increasing_order(Batch *a, Batch *b) { return a->first->order < b->first->order; }
128bool qsg_sort_batch_decreasing_order(Batch *a, Batch *b) { return a->first->order > b->first->order; }
129
130QSGMaterial::Flag QSGMaterial_FullMatrix = (QSGMaterial::Flag) (QSGMaterial::RequiresFullMatrix & ~QSGMaterial::RequiresFullMatrixExceptTranslate);
131
132struct QMatrix4x4_Accessor
133{
134 float m[4][4];
135 int flagBits;
136
137 static bool isTranslate(const QMatrix4x4 &m) { return ((const QMatrix4x4_Accessor &) m).flagBits <= 0x1; }
138 static bool isScale(const QMatrix4x4 &m) { return ((const QMatrix4x4_Accessor &) m).flagBits <= 0x2; }
139 static bool is2DSafe(const QMatrix4x4 &m) { return ((const QMatrix4x4_Accessor &) m).flagBits < 0x8; }
140};
141
142const float OPAQUE_LIMIT = 0.999f;
143
144const uint DYNAMIC_VERTEX_INDEX_BUFFER_THRESHOLD = 4;
145const int VERTEX_BUFFER_BINDING = 0;
146const int ZORDER_BUFFER_BINDING = VERTEX_BUFFER_BINDING + 1;
147
148static inline uint aligned(uint v, uint byteAlign)
149{
150 return (v + byteAlign - 1) & ~(byteAlign - 1);
151}
152
153QRhiVertexInputAttribute::Format qsg_vertexInputFormat(const QSGGeometry::Attribute &a)
154{
155 switch (a.type) {
156 case QSGGeometry::FloatType:
157 if (a.tupleSize == 4)
158 return QRhiVertexInputAttribute::Float4;
159 if (a.tupleSize == 3)
160 return QRhiVertexInputAttribute::Float3;
161 if (a.tupleSize == 2)
162 return QRhiVertexInputAttribute::Float2;
163 if (a.tupleSize == 1)
164 return QRhiVertexInputAttribute::Float;
165 break;
166 case QSGGeometry::UnsignedByteType:
167 if (a.tupleSize == 4)
168 return QRhiVertexInputAttribute::UNormByte4;
169 if (a.tupleSize == 2)
170 return QRhiVertexInputAttribute::UNormByte2;
171 if (a.tupleSize == 1)
172 return QRhiVertexInputAttribute::UNormByte;
173 break;
174 default:
175 break;
176 }
177 qWarning(msg: "Unsupported attribute type 0x%x with %d components", a.type, a.tupleSize);
178 Q_UNREACHABLE();
179 return QRhiVertexInputAttribute::Float;
180}
181
182static QRhiVertexInputLayout calculateVertexInputLayout(const QSGMaterialRhiShader *s, const QSGGeometry *geometry, bool batchable)
183{
184 Q_ASSERT(geometry);
185 const QSGMaterialRhiShaderPrivate *sd = QSGMaterialRhiShaderPrivate::get(s);
186 if (!sd->vertexShader) {
187 qWarning(msg: "No vertex shader in QSGMaterialRhiShader %p", s);
188 return QRhiVertexInputLayout();
189 }
190
191 const int attrCount = geometry->attributeCount();
192 QVarLengthArray<QRhiVertexInputAttribute, 8> inputAttributes;
193 inputAttributes.reserve(size: attrCount + 1);
194 int offset = 0;
195 for (int i = 0; i < attrCount; ++i) {
196 const QSGGeometry::Attribute &a = geometry->attributes()[i];
197 if (!sd->vertexShader->vertexInputLocations.contains(t: a.position)) {
198 qWarning(msg: "Vertex input %d is present in material but not in shader. This is wrong.",
199 a.position);
200 }
201 inputAttributes.append(t: QRhiVertexInputAttribute(VERTEX_BUFFER_BINDING, a.position, qsg_vertexInputFormat(a), offset));
202 offset += a.tupleSize * size_of_type(type: a.type);
203 }
204 if (batchable) {
205 inputAttributes.append(t: QRhiVertexInputAttribute(ZORDER_BUFFER_BINDING, sd->vertexShader->qt_order_attrib_location,
206 QRhiVertexInputAttribute::Float, 0));
207 }
208
209 Q_ASSERT(VERTEX_BUFFER_BINDING == 0 && ZORDER_BUFFER_BINDING == 1); // not very flexible
210 QVarLengthArray<QRhiVertexInputBinding, 2> inputBindings;
211 inputBindings.append(t: QRhiVertexInputBinding(geometry->sizeOfVertex()));
212 if (batchable)
213 inputBindings.append(t: QRhiVertexInputBinding(sizeof(float)));
214
215 QRhiVertexInputLayout inputLayout;
216 inputLayout.setBindings(first: inputBindings.cbegin(), last: inputBindings.cend());
217 inputLayout.setAttributes(first: inputAttributes.cbegin(), last: inputAttributes.cend());
218
219 return inputLayout;
220}
221
222QRhiCommandBuffer::IndexFormat qsg_indexFormat(const QSGGeometry *geometry)
223{
224 switch (geometry->indexType()) {
225 case QSGGeometry::UnsignedShortType:
226 return QRhiCommandBuffer::IndexUInt16;
227 break;
228 case QSGGeometry::UnsignedIntType:
229 return QRhiCommandBuffer::IndexUInt32;
230 break;
231 default:
232 Q_UNREACHABLE();
233 return QRhiCommandBuffer::IndexUInt16;
234 }
235}
236
237QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode)
238{
239 QRhiGraphicsPipeline::Topology topology = QRhiGraphicsPipeline::Triangles;
240 switch (geomDrawMode) {
241 case QSGGeometry::DrawPoints:
242 topology = QRhiGraphicsPipeline::Points;
243 break;
244 case QSGGeometry::DrawLines:
245 topology = QRhiGraphicsPipeline::Lines;
246 break;
247 case QSGGeometry::DrawLineStrip:
248 topology = QRhiGraphicsPipeline::LineStrip;
249 break;
250 case QSGGeometry::DrawTriangles:
251 topology = QRhiGraphicsPipeline::Triangles;
252 break;
253 case QSGGeometry::DrawTriangleStrip:
254 topology = QRhiGraphicsPipeline::TriangleStrip;
255 break;
256 default:
257 qWarning(msg: "Primitive topology 0x%x not supported", geomDrawMode);
258 break;
259 }
260 return topology;
261}
262
263ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material, bool enableRhiShaders, const QSGGeometry *geometry)
264{
265 QSGMaterialType *type = material->type();
266 Shader *shader = rewrittenShaders.value(key: type, defaultValue: 0);
267 if (shader)
268 return shader;
269
270 if (enableRhiShaders && !material->flags().testFlag(flag: QSGMaterial::SupportsRhiShader)) {
271 qWarning(msg: "The material failed to provide a working QShader pack");
272 return nullptr;
273 }
274
275 Q_TRACE_SCOPE(QSG_prepareMaterial);
276 if (QSG_LOG_TIME_COMPILATION().isDebugEnabled())
277 qsg_renderer_timer.start();
278 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphContextFrame);
279
280 shader = new Shader;
281 if (enableRhiShaders) {
282 material->setFlag(flags: QSGMaterial::RhiShaderWanted, on: true);
283 QSGMaterialRhiShader *s = static_cast<QSGMaterialRhiShader *>(material->createShader());
284 material->setFlag(flags: QSGMaterial::RhiShaderWanted, on: false);
285 context->initializeRhiShader(shader: s, shaderVariant: QShader::BatchableVertexShader);
286 shader->programRhi.program = s;
287 shader->programRhi.inputLayout = calculateVertexInputLayout(s, geometry, batchable: true);
288 QSGMaterialRhiShaderPrivate *sD = QSGMaterialRhiShaderPrivate::get(s);
289 shader->programRhi.shaderStages = {
290 { QRhiGraphicsShaderStage::Vertex, sD->shader(stage: QShader::VertexStage), QShader::BatchableVertexShader },
291 { QRhiGraphicsShaderStage::Fragment, sD->shader(stage: QShader::FragmentStage) }
292 };
293 } else {
294 QSGMaterialShader *s = material->createShader();
295 QOpenGLContext *ctx = context->openglContext();
296 QSurfaceFormat::OpenGLContextProfile profile = ctx->format().profile();
297 QOpenGLShaderProgram *p = s->program();
298 char const *const *attr = s->attributeNames();
299 int i;
300 for (i = 0; attr[i]; ++i) {
301 if (*attr[i])
302 p->bindAttributeLocation(name: attr[i], location: i);
303 }
304 p->bindAttributeLocation(name: "_qt_order", location: i);
305 context->compileShader(shader: s, material, vertexCode: qsgShaderRewriter_insertZAttributes(input: s->vertexShader(), profile), fragmentCode: nullptr);
306 context->initializeShader(shader: s);
307 if (!p->isLinked()) {
308 delete shader;
309 return nullptr;
310 }
311 shader->programGL.program = s;
312 shader->programGL.pos_order = i;
313 }
314
315 shader->lastOpacity = 0;
316
317 qCDebug(QSG_LOG_TIME_COMPILATION, "material shaders prepared in %dms", (int) qsg_renderer_timer.elapsed());
318
319 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphContextFrame,
320 QQuickProfiler::SceneGraphContextMaterialCompile);
321
322 rewrittenShaders[type] = shader;
323 return shader;
324}
325
326ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material, bool enableRhiShaders, const QSGGeometry *geometry)
327{
328 QSGMaterialType *type = material->type();
329 Shader *shader = stockShaders.value(key: type, defaultValue: 0);
330 if (shader)
331 return shader;
332
333 if (enableRhiShaders && !material->flags().testFlag(flag: QSGMaterial::SupportsRhiShader)) {
334 qWarning(msg: "The material failed to provide a working QShader pack");
335 return nullptr;
336 }
337
338 Q_TRACE_SCOPE(QSG_prepareMaterial);
339 if (QSG_LOG_TIME_COMPILATION().isDebugEnabled())
340 qsg_renderer_timer.start();
341 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphContextFrame);
342
343 shader = new Shader;
344 if (enableRhiShaders) {
345 material->setFlag(flags: QSGMaterial::RhiShaderWanted, on: true);
346 QSGMaterialRhiShader *s = static_cast<QSGMaterialRhiShader *>(material->createShader());
347 material->setFlag(flags: QSGMaterial::RhiShaderWanted, on: false);
348 context->initializeRhiShader(shader: s, shaderVariant: QShader::StandardShader);
349 shader->programRhi.program = s;
350 shader->programRhi.inputLayout = calculateVertexInputLayout(s, geometry, batchable: false);
351 QSGMaterialRhiShaderPrivate *sD = QSGMaterialRhiShaderPrivate::get(s);
352 shader->programRhi.shaderStages = {
353 { QRhiGraphicsShaderStage::Vertex, sD->shader(stage: QShader::VertexStage) },
354 { QRhiGraphicsShaderStage::Fragment, sD->shader(stage: QShader::FragmentStage) }
355 };
356 } else {
357 QSGMaterialShader *s = material->createShader();
358 context->compileShader(shader: s, material);
359 context->initializeShader(shader: s);
360 shader->programGL.program = s;
361 shader->programGL.pos_order = -1;
362 }
363
364 shader->lastOpacity = 0;
365
366 stockShaders[type] = shader;
367
368 qCDebug(QSG_LOG_TIME_COMPILATION, "shader compiled in %dms (no rewrite)", (int) qsg_renderer_timer.elapsed());
369
370 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphContextFrame,
371 QQuickProfiler::SceneGraphContextMaterialCompile);
372 return shader;
373}
374
375void ShaderManager::invalidated()
376{
377 qDeleteAll(c: stockShaders);
378 stockShaders.clear();
379 qDeleteAll(c: rewrittenShaders);
380 rewrittenShaders.clear();
381 delete blitProgram;
382 blitProgram = nullptr;
383
384 qDeleteAll(c: srbCache);
385 srbCache.clear();
386
387 qDeleteAll(c: pipelineCache);
388 pipelineCache.clear();
389}
390
391void ShaderManager::clearCachedRendererData()
392{
393 for (ShaderManager::Shader *sms : stockShaders) {
394 QSGMaterialRhiShader *s = sms->programRhi.program;
395 if (s) {
396 QSGMaterialRhiShaderPrivate *sd = QSGMaterialRhiShaderPrivate::get(s);
397 sd->clearCachedRendererData();
398 }
399 }
400 for (ShaderManager::Shader *sms : rewrittenShaders) {
401 QSGMaterialRhiShader *s = sms->programRhi.program;
402 if (s) {
403 QSGMaterialRhiShaderPrivate *sd = QSGMaterialRhiShaderPrivate::get(s);
404 sd->clearCachedRendererData();
405 }
406 }
407}
408
409QRhiShaderResourceBindings *ShaderManager::srb(const ShaderResourceBindingList &bindings)
410{
411 auto it = srbCache.constFind(key: bindings);
412 if (it != srbCache.constEnd())
413 return *it;
414
415 QRhiShaderResourceBindings *srb = context->rhi()->newShaderResourceBindings();
416 srb->setBindings(first: bindings.cbegin(), last: bindings.cend());
417 if (srb->build()) {
418 srbCache.insert(key: bindings, value: srb);
419 } else {
420 qWarning(msg: "Failed to build srb");
421 delete srb;
422 srb = nullptr;
423 }
424 return srb;
425}
426
427void qsg_dumpShadowRoots(BatchRootInfo *i, int indent)
428{
429 static int extraIndent = 0;
430 ++extraIndent;
431
432 QByteArray ind(indent + extraIndent + 10, ' ');
433
434 if (!i) {
435 qDebug(msg: "%s - no info", ind.constData());
436 } else {
437 qDebug() << ind.constData() << "- parent:" << i->parentRoot << "orders" << i->firstOrder << "->" << i->lastOrder << ", avail:" << i->availableOrders;
438 for (QSet<Node *>::const_iterator it = i->subRoots.constBegin();
439 it != i->subRoots.constEnd(); ++it) {
440 qDebug() << ind.constData() << "-" << *it;
441 qsg_dumpShadowRoots(i: (*it)->rootInfo(), indent);
442 }
443 }
444
445 --extraIndent;
446}
447
448void qsg_dumpShadowRoots(Node *n)
449{
450#ifndef QT_NO_DEBUG_OUTPUT
451 static int indent = 0;
452 ++indent;
453
454 QByteArray ind(indent, ' ');
455
456 if (n->type() == QSGNode::ClipNodeType || n->isBatchRoot) {
457 qDebug() << ind.constData() << "[X]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
458 qsg_dumpShadowRoots(i: n->rootInfo(), indent);
459 } else {
460 QDebug d = qDebug();
461 d << ind.constData() << "[ ]" << n->sgNode << Qt::hex << uint(n->sgNode->flags());
462 if (n->type() == QSGNode::GeometryNodeType)
463 d << "order" << Qt::dec << n->element()->order;
464 }
465
466 SHADOWNODE_TRAVERSE(n)
467 qsg_dumpShadowRoots(n: child);
468
469 --indent;
470#else
471 Q_UNUSED(n)
472#endif
473}
474
475Updater::Updater(Renderer *r)
476 : renderer(r)
477 , m_roots(32)
478 , m_rootMatrices(8)
479{
480 m_roots.add(t: 0);
481 m_combined_matrix_stack.add(t: &m_identityMatrix);
482 m_rootMatrices.add(t: m_identityMatrix);
483
484 Q_ASSERT(sizeof(QMatrix4x4_Accessor) == sizeof(QMatrix4x4));
485}
486
487void Updater::updateStates(QSGNode *n)
488{
489 m_current_clip = nullptr;
490
491 m_added = 0;
492 m_transformChange = 0;
493 m_opacityChange = 0;
494
495 Node *sn = renderer->m_nodes.value(key: n, defaultValue: 0);
496 Q_ASSERT(sn);
497
498 if (Q_UNLIKELY(debug_roots()))
499 qsg_dumpShadowRoots(n: sn);
500
501 if (Q_UNLIKELY(debug_build())) {
502 qDebug(msg: "Updater::updateStates()");
503 if (sn->dirtyState & (QSGNode::DirtyNodeAdded << 16))
504 qDebug(msg: " - nodes have been added");
505 if (sn->dirtyState & (QSGNode::DirtyMatrix << 16))
506 qDebug(msg: " - transforms have changed");
507 if (sn->dirtyState & (QSGNode::DirtyOpacity << 16))
508 qDebug(msg: " - opacity has changed");
509 if (uint(sn->dirtyState) & uint(QSGNode::DirtyForceUpdate << 16))
510 qDebug(msg: " - forceupdate");
511 }
512
513 if (Q_UNLIKELY(renderer->m_visualizer->mode() == Visualizer::VisualizeChanges))
514 renderer->m_visualizer->visualizeChangesPrepare(n: sn);
515
516 visitNode(n: sn);
517}
518
519void Updater::visitNode(Node *n)
520{
521 if (m_added == 0 && n->dirtyState == 0 && m_force_update == 0 && m_transformChange == 0 && m_opacityChange == 0)
522 return;
523
524 int count = m_added;
525 if (n->dirtyState & QSGNode::DirtyNodeAdded)
526 ++m_added;
527
528 int force = m_force_update;
529 if (n->dirtyState & QSGNode::DirtyForceUpdate)
530 ++m_force_update;
531
532 switch (n->type()) {
533 case QSGNode::OpacityNodeType:
534 visitOpacityNode(n);
535 break;
536 case QSGNode::TransformNodeType:
537 visitTransformNode(n);
538 break;
539 case QSGNode::GeometryNodeType:
540 visitGeometryNode(n);
541 break;
542 case QSGNode::ClipNodeType:
543 visitClipNode(n);
544 break;
545 case QSGNode::RenderNodeType:
546 if (m_added)
547 n->renderNodeElement()->root = m_roots.last();
548 Q_FALLTHROUGH(); // to visit children
549 default:
550 SHADOWNODE_TRAVERSE(n) visitNode(n: child);
551 break;
552 }
553
554 m_added = count;
555 m_force_update = force;
556 n->dirtyState = {};
557}
558
559void Updater::visitClipNode(Node *n)
560{
561 ClipBatchRootInfo *extra = n->clipInfo();
562
563 QSGClipNode *cn = static_cast<QSGClipNode *>(n->sgNode);
564
565 if (m_roots.last() && m_added > 0)
566 renderer->registerBatchRoot(childRoot: n, parentRoot: m_roots.last());
567
568 cn->setRendererClipList(m_current_clip);
569 m_current_clip = cn;
570 m_roots << n;
571 m_rootMatrices.add(t: m_rootMatrices.last() * *m_combined_matrix_stack.last());
572 extra->matrix = m_rootMatrices.last();
573 cn->setRendererMatrix(&extra->matrix);
574 m_combined_matrix_stack << &m_identityMatrix;
575
576 SHADOWNODE_TRAVERSE(n) visitNode(n: child);
577
578 m_current_clip = cn->clipList();
579 m_rootMatrices.pop_back();
580 m_combined_matrix_stack.pop_back();
581 m_roots.pop_back();
582}
583
584void Updater::visitOpacityNode(Node *n)
585{
586 QSGOpacityNode *on = static_cast<QSGOpacityNode *>(n->sgNode);
587
588 qreal combined = m_opacity_stack.last() * on->opacity();
589 on->setCombinedOpacity(combined);
590 m_opacity_stack.add(t: combined);
591
592 if (m_added == 0 && n->dirtyState & QSGNode::DirtyOpacity) {
593 bool was = n->isOpaque;
594 bool is = on->opacity() > OPAQUE_LIMIT;
595 if (was != is) {
596 renderer->m_rebuild = Renderer::FullRebuild;
597 n->isOpaque = is;
598 }
599 ++m_opacityChange;
600 SHADOWNODE_TRAVERSE(n) visitNode(n: child);
601 --m_opacityChange;
602 } else {
603 if (m_added > 0)
604 n->isOpaque = on->opacity() > OPAQUE_LIMIT;
605 SHADOWNODE_TRAVERSE(n) visitNode(n: child);
606 }
607
608 m_opacity_stack.pop_back();
609}
610
611void Updater::visitTransformNode(Node *n)
612{
613 bool popMatrixStack = false;
614 bool popRootStack = false;
615 bool dirty = n->dirtyState & QSGNode::DirtyMatrix;
616
617 QSGTransformNode *tn = static_cast<QSGTransformNode *>(n->sgNode);
618
619 if (n->isBatchRoot) {
620 if (m_added > 0 && m_roots.last())
621 renderer->registerBatchRoot(childRoot: n, parentRoot: m_roots.last());
622 tn->setCombinedMatrix(m_rootMatrices.last() * *m_combined_matrix_stack.last() * tn->matrix());
623
624 // The only change in this subtree is ourselves and we are a batch root, so
625 // only update subroots and return, saving tons of child-processing (flickable-panning)
626
627 if (!n->becameBatchRoot && m_added == 0 && m_force_update == 0 && m_opacityChange == 0 && dirty && (n->dirtyState & ~QSGNode::DirtyMatrix) == 0) {
628 BatchRootInfo *info = renderer->batchRootInfo(node: n);
629 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
630 it != info->subRoots.constEnd(); ++it) {
631 updateRootTransforms(n: *it, root: n, combined: tn->combinedMatrix());
632 }
633 return;
634 }
635
636 n->becameBatchRoot = false;
637
638 m_combined_matrix_stack.add(t: &m_identityMatrix);
639 m_roots.add(t: n);
640 m_rootMatrices.add(t: tn->combinedMatrix());
641
642 popMatrixStack = true;
643 popRootStack = true;
644 } else if (!tn->matrix().isIdentity()) {
645 tn->setCombinedMatrix(*m_combined_matrix_stack.last() * tn->matrix());
646 m_combined_matrix_stack.add(t: &tn->combinedMatrix());
647 popMatrixStack = true;
648 } else {
649 tn->setCombinedMatrix(*m_combined_matrix_stack.last());
650 }
651
652 if (dirty)
653 ++m_transformChange;
654
655 SHADOWNODE_TRAVERSE(n) visitNode(n: child);
656
657 if (dirty)
658 --m_transformChange;
659 if (popMatrixStack)
660 m_combined_matrix_stack.pop_back();
661 if (popRootStack) {
662 m_roots.pop_back();
663 m_rootMatrices.pop_back();
664 }
665}
666
667void Updater::visitGeometryNode(Node *n)
668{
669 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode);
670
671 gn->setRendererMatrix(m_combined_matrix_stack.last());
672 gn->setRendererClipList(m_current_clip);
673 gn->setInheritedOpacity(m_opacity_stack.last());
674
675 if (m_added) {
676 Element *e = n->element();
677 e->root = m_roots.last();
678 e->translateOnlyToRoot = QMatrix4x4_Accessor::isTranslate(m: *gn->matrix());
679
680 if (e->root) {
681 BatchRootInfo *info = renderer->batchRootInfo(node: e->root);
682 while (info != nullptr) {
683 info->availableOrders--;
684 if (info->availableOrders < 0) {
685 renderer->m_rebuild |= Renderer::BuildRenderLists;
686 } else {
687 renderer->m_rebuild |= Renderer::BuildRenderListsForTaggedRoots;
688 renderer->m_taggedRoots << e->root;
689 }
690 if (info->parentRoot != nullptr)
691 info = renderer->batchRootInfo(node: info->parentRoot);
692 else
693 info = nullptr;
694 }
695 } else {
696 renderer->m_rebuild |= Renderer::FullRebuild;
697 }
698 } else {
699 if (m_transformChange) {
700 Element *e = n->element();
701 e->translateOnlyToRoot = QMatrix4x4_Accessor::isTranslate(m: *gn->matrix());
702 }
703 if (m_opacityChange) {
704 Element *e = n->element();
705 if (e->batch)
706 renderer->invalidateBatchAndOverlappingRenderOrders(batch: e->batch);
707 }
708 }
709
710 SHADOWNODE_TRAVERSE(n) visitNode(n: child);
711}
712
713void Updater::updateRootTransforms(Node *node, Node *root, const QMatrix4x4 &combined)
714{
715 BatchRootInfo *info = renderer->batchRootInfo(node);
716 QMatrix4x4 m;
717 Node *n = node;
718
719 while (n != root) {
720 if (n->type() == QSGNode::TransformNodeType)
721 m = static_cast<QSGTransformNode *>(n->sgNode)->matrix() * m;
722 n = n->parent();
723 }
724
725 m = combined * m;
726
727 if (node->type() == QSGNode::ClipNodeType) {
728 static_cast<ClipBatchRootInfo *>(info)->matrix = m;
729 } else {
730 Q_ASSERT(node->type() == QSGNode::TransformNodeType);
731 static_cast<QSGTransformNode *>(node->sgNode)->setCombinedMatrix(m);
732 }
733
734 for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
735 it != info->subRoots.constEnd(); ++it) {
736 updateRootTransforms(node: *it, root: node, combined: m);
737 }
738}
739
740int qsg_positionAttribute(QSGGeometry *g)
741{
742 int vaOffset = 0;
743 for (int a=0; a<g->attributeCount(); ++a) {
744 const QSGGeometry::Attribute &attr = g->attributes()[a];
745 if (attr.isVertexCoordinate && attr.tupleSize == 2 && attr.type == QSGGeometry::FloatType) {
746 return vaOffset;
747 }
748 vaOffset += attr.tupleSize * size_of_type(type: attr.type);
749 }
750 return -1;
751}
752
753
754void Rect::map(const QMatrix4x4 &matrix)
755{
756 const float *m = matrix.constData();
757 if (QMatrix4x4_Accessor::isScale(m: matrix)) {
758 tl.x = tl.x * m[0] + m[12];
759 tl.y = tl.y * m[5] + m[13];
760 br.x = br.x * m[0] + m[12];
761 br.y = br.y * m[5] + m[13];
762 if (tl.x > br.x)
763 qSwap(value1&: tl.x, value2&: br.x);
764 if (tl.y > br.y)
765 qSwap(value1&: tl.y, value2&: br.y);
766 } else {
767 Pt mtl = tl;
768 Pt mtr = { .x: br.x, .y: tl.y };
769 Pt mbl = { .x: tl.x, .y: br.y };
770 Pt mbr = br;
771
772 mtl.map(mat: matrix);
773 mtr.map(mat: matrix);
774 mbl.map(mat: matrix);
775 mbr.map(mat: matrix);
776
777 set(FLT_MAX, FLT_MAX, right: -FLT_MAX, bottom: -FLT_MAX);
778 (*this) |= mtl;
779 (*this) |= mtr;
780 (*this) |= mbl;
781 (*this) |= mbr;
782 }
783}
784
785void Element::computeBounds()
786{
787 Q_ASSERT(!boundsComputed);
788 boundsComputed = true;
789
790 QSGGeometry *g = node->geometry();
791 int offset = qsg_positionAttribute(g);
792 if (offset == -1) {
793 // No position attribute means overlaps with everything..
794 bounds.set(left: -FLT_MAX, top: -FLT_MAX, FLT_MAX, FLT_MAX);
795 return;
796 }
797
798 bounds.set(FLT_MAX, FLT_MAX, right: -FLT_MAX, bottom: -FLT_MAX);
799 char *vd = (char *) g->vertexData() + offset;
800 for (int i=0; i<g->vertexCount(); ++i) {
801 bounds |= *(Pt *) vd;
802 vd += g->sizeOfVertex();
803 }
804 bounds.map(matrix: *node->matrix());
805
806 if (!qt_is_finite(f: bounds.tl.x) || bounds.tl.x == FLT_MAX)
807 bounds.tl.x = -FLT_MAX;
808 if (!qt_is_finite(f: bounds.tl.y) || bounds.tl.y == FLT_MAX)
809 bounds.tl.y = -FLT_MAX;
810 if (!qt_is_finite(f: bounds.br.x) || bounds.br.x == -FLT_MAX)
811 bounds.br.x = FLT_MAX;
812 if (!qt_is_finite(f: bounds.br.y) || bounds.br.y == -FLT_MAX)
813 bounds.br.y = FLT_MAX;
814
815 Q_ASSERT(bounds.tl.x <= bounds.br.x);
816 Q_ASSERT(bounds.tl.y <= bounds.br.y);
817
818 boundsOutsideFloatRange = bounds.isOutsideFloatRange();
819}
820
821BatchCompatibility Batch::isMaterialCompatible(Element *e) const
822{
823 Element *n = first;
824 // Skip to the first node other than e which has not been removed
825 while (n && (n == e || n->removed))
826 n = n->nextInBatch;
827
828 // Only 'e' in this batch, so a material change doesn't change anything as long as
829 // its blending is still in sync with this batch...
830 if (!n)
831 return BatchIsCompatible;
832
833 QSGMaterial *m = e->node->activeMaterial();
834 QSGMaterial *nm = n->node->activeMaterial();
835 return (nm->type() == m->type() && nm->compare(other: m) == 0)
836 ? BatchIsCompatible
837 : BatchBreaksOnCompare;
838}
839
840/*
841 * Marks this batch as dirty or in the case where the geometry node has
842 * changed to be incompatible with this batch, return false so that
843 * the caller can mark the entire sg for a full rebuild...
844 */
845bool Batch::geometryWasChanged(QSGGeometryNode *gn)
846{
847 Element *e = first;
848 Q_ASSERT_X(e, "Batch::geometryWasChanged", "Batch is expected to 'valid' at this time");
849 // 'gn' is the first node in the batch, compare against the next one.
850 while (e && (e->node == gn || e->removed))
851 e = e->nextInBatch;
852 if (!e || e->node->geometry()->attributes() == gn->geometry()->attributes()) {
853 needsUpload = true;
854 return true;
855 } else {
856 return false;
857 }
858}
859
860void Batch::cleanupRemovedElements()
861{
862 if (!needsPurge)
863 return;
864
865 // remove from front of batch..
866 while (first && first->removed) {
867 first = first->nextInBatch;
868 }
869
870 // Then continue and remove other nodes further out in the batch..
871 if (first) {
872 Element *e = first;
873 while (e->nextInBatch) {
874 if (e->nextInBatch->removed)
875 e->nextInBatch = e->nextInBatch->nextInBatch;
876 else
877 e = e->nextInBatch;
878
879 }
880 }
881
882 needsPurge = false;
883}
884
885/*
886 * Iterates through all geometry nodes in this batch and unsets their batch,
887 * thus forcing them to be rebuilt
888 */
889void Batch::invalidate()
890{
891 // If doing removal here is a performance issue, we might add a "hasRemoved" bit to
892 // the batch to do an early out..
893 cleanupRemovedElements();
894 Element *e = first;
895 first = nullptr;
896 root = nullptr;
897 while (e) {
898 e->batch = nullptr;
899 Element *n = e->nextInBatch;
900 e->nextInBatch = nullptr;
901 e = n;
902 }
903}
904
905bool Batch::isTranslateOnlyToRoot() const {
906 bool only = true;
907 Element *e = first;
908 while (e && only) {
909 only &= e->translateOnlyToRoot;
910 e = e->nextInBatch;
911 }
912 return only;
913}
914
915/*
916 * Iterates through all the nodes in the batch and returns true if the
917 * nodes are all safe to batch. There are two separate criteria:
918 *
919 * - The matrix is such that the z component of the result is of no
920 * consequence.
921 *
922 * - The bounds are inside the stable floating point range. This applies
923 * to desktop only where we in this case can trigger a fallback to
924 * unmerged in which case we pass the geometry straight through and
925 * just apply the matrix.
926 *
927 * NOTE: This also means a slight performance impact for geometries which
928 * are defined to be outside the stable floating point range and still
929 * use single precision float, but given that this implicitly fixes
930 * huge lists and tables, it is worth it.
931 */
932bool Batch::isSafeToBatch() const {
933 Element *e = first;
934 while (e) {
935 if (e->boundsOutsideFloatRange)
936 return false;
937 if (!QMatrix4x4_Accessor::is2DSafe(m: *e->node->matrix()))
938 return false;
939 e = e->nextInBatch;
940 }
941 return true;
942}
943
944static int qsg_countNodesInBatch(const Batch *batch)
945{
946 int sum = 0;
947 Element *e = batch->first;
948 while (e) {
949 ++sum;
950 e = e->nextInBatch;
951 }
952 return sum;
953}
954
955static int qsg_countNodesInBatches(const QDataBuffer<Batch *> &batches)
956{
957 int sum = 0;
958 for (int i=0; i<batches.size(); ++i) {
959 sum += qsg_countNodesInBatch(batch: batches.at(i));
960 }
961 return sum;
962}
963
964Renderer::Renderer(QSGDefaultRenderContext *ctx)
965 : QSGRenderer(ctx)
966 , m_context(ctx)
967 , m_opaqueRenderList(64)
968 , m_alphaRenderList(64)
969 , m_nextRenderOrder(0)
970 , m_partialRebuild(false)
971 , m_partialRebuildRoot(nullptr)
972 , m_useDepthBuffer(true)
973 , m_opaqueBatches(16)
974 , m_alphaBatches(16)
975 , m_batchPool(16)
976 , m_elementsToDelete(64)
977 , m_tmpAlphaElements(16)
978 , m_tmpOpaqueElements(16)
979 , m_rebuild(FullRebuild)
980 , m_zRange(0)
981 , m_renderOrderRebuildLower(-1)
982 , m_renderOrderRebuildUpper(-1)
983 , m_currentMaterial(nullptr)
984 , m_currentShader(nullptr)
985 , m_currentStencilValue(0)
986 , m_clipMatrixId(0)
987 , m_currentClip(nullptr)
988 , m_currentClipType(ClipState::NoClip)
989 , m_vertexUploadPool(256)
990 , m_indexUploadPool(64)
991 , m_vao(nullptr)
992{
993 m_rhi = m_context->rhi();
994 if (m_rhi) {
995 m_ubufAlignment = m_rhi->ubufAlignment();
996 m_uint32IndexForRhi = !m_rhi->isFeatureSupported(feature: QRhi::NonFourAlignedEffectiveIndexBufferOffset);
997 if (qEnvironmentVariableIntValue(varName: "QSG_RHI_UINT32_INDEX"))
998 m_uint32IndexForRhi = true;
999 m_visualizer = new RhiVisualizer(this);
1000 } else {
1001 initializeOpenGLFunctions();
1002 m_uint32IndexForRhi = false;
1003 m_visualizer = new OpenGLVisualizer(this);
1004 }
1005
1006 setNodeUpdater(new Updater(this));
1007
1008 // The shader manager is shared between renderers (think for example Item
1009 // layers that create a new Renderer each) with the same rendercontext
1010 // (i.e. QRhi or QOpenGLContext).
1011 m_shaderManager = ctx->findChild<ShaderManager *>(QStringLiteral("__qt_ShaderManager"), options: Qt::FindDirectChildrenOnly);
1012 if (!m_shaderManager) {
1013 m_shaderManager = new ShaderManager(ctx);
1014 m_shaderManager->setObjectName(QStringLiteral("__qt_ShaderManager"));
1015 m_shaderManager->setParent(ctx);
1016 QObject::connect(sender: ctx, SIGNAL(invalidated()), receiver: m_shaderManager, SLOT(invalidated()), Qt::DirectConnection);
1017 }
1018
1019 m_bufferStrategy = GL_STATIC_DRAW;
1020 if (Q_UNLIKELY(qEnvironmentVariableIsSet("QSG_RENDERER_BUFFER_STRATEGY"))) {
1021 const QByteArray strategy = qgetenv(varName: "QSG_RENDERER_BUFFER_STRATEGY");
1022 if (strategy == "dynamic")
1023 m_bufferStrategy = GL_DYNAMIC_DRAW;
1024 else if (strategy == "stream")
1025 m_bufferStrategy = GL_STREAM_DRAW;
1026 }
1027
1028 m_batchNodeThreshold = qt_sg_envInt(name: "QSG_RENDERER_BATCH_NODE_THRESHOLD", defaultValue: 64);
1029 m_batchVertexThreshold = qt_sg_envInt(name: "QSG_RENDERER_BATCH_VERTEX_THRESHOLD", defaultValue: 1024);
1030
1031 if (Q_UNLIKELY(debug_build() || debug_render())) {
1032 qDebug(msg: "Batch thresholds: nodes: %d vertices: %d",
1033 m_batchNodeThreshold, m_batchVertexThreshold);
1034 qDebug(msg: "Using buffer strategy: %s",
1035 (m_bufferStrategy == GL_STATIC_DRAW
1036 ? "static" : (m_bufferStrategy == GL_DYNAMIC_DRAW ? "dynamic" : "stream")));
1037 }
1038
1039 static const bool useDepth = qEnvironmentVariableIsEmpty(varName: "QSG_NO_DEPTH_BUFFER");
1040 if (!m_rhi) {
1041 // If rendering with an OpenGL Core profile context, we need to create a VAO
1042 // to hold our vertex specification state.
1043 if (m_context->openglContext()->format().profile() == QSurfaceFormat::CoreProfile) {
1044 m_vao = new QOpenGLVertexArrayObject(this);
1045 m_vao->create();
1046 }
1047 m_useDepthBuffer = useDepth && ctx->openglContext()->format().depthBufferSize() > 0;
1048 } else {
1049 m_useDepthBuffer = useDepth;
1050 }
1051}
1052
1053static void qsg_wipeBuffer(Buffer *buffer, QOpenGLFunctions *funcs)
1054{
1055 if (buffer->buf) {
1056 //qDebug("releasing rhibuf %p", buffer->buf);
1057 delete buffer->buf;
1058 }
1059
1060 if (buffer->id)
1061 funcs->glDeleteBuffers(n: 1, buffers: &buffer->id);
1062
1063 // The free here is ok because we're in one of two situations.
1064 // 1. We're using the upload pool in which case unmap will have set the
1065 // data pointer to 0 and calling free on 0 is ok.
1066 // 2. We're using dedicated buffers because of visualization or IBO workaround
1067 // and the data something we malloced and must be freed.
1068 free(ptr: buffer->data);
1069}
1070
1071static void qsg_wipeBatch(Batch *batch, QOpenGLFunctions *funcs, bool separateIndexBuffer)
1072{
1073 qsg_wipeBuffer(buffer: &batch->vbo, funcs);
1074 if (separateIndexBuffer)
1075 qsg_wipeBuffer(buffer: &batch->ibo, funcs);
1076 delete batch->ubuf;
1077 batch->stencilClipState.reset();
1078 delete batch;
1079}
1080
1081Renderer::~Renderer()
1082{
1083 if (m_rhi || QOpenGLContext::currentContext()) {
1084 // Clean up batches and buffers
1085 const bool separateIndexBuffer = m_context->separateIndexBuffer();
1086 for (int i = 0; i < m_opaqueBatches.size(); ++i)
1087 qsg_wipeBatch(batch: m_opaqueBatches.at(i), funcs: this, separateIndexBuffer);
1088 for (int i = 0; i < m_alphaBatches.size(); ++i)
1089 qsg_wipeBatch(batch: m_alphaBatches.at(i), funcs: this, separateIndexBuffer);
1090 for (int i = 0; i < m_batchPool.size(); ++i)
1091 qsg_wipeBatch(batch: m_batchPool.at(i), funcs: this, separateIndexBuffer);
1092 }
1093
1094 for (Node *n : qAsConst(t&: m_nodes))
1095 m_nodeAllocator.release(t: n);
1096
1097 // Remaining elements...
1098 for (int i=0; i<m_elementsToDelete.size(); ++i) {
1099 Element *e = m_elementsToDelete.at(i);
1100 if (e->isRenderNode)
1101 delete static_cast<RenderNodeElement *>(e);
1102 else
1103 m_elementAllocator.release(t: e);
1104 }
1105
1106 destroyGraphicsResources();
1107
1108 delete m_visualizer;
1109}
1110
1111void Renderer::destroyGraphicsResources()
1112{
1113 // If this is from the dtor, then the shader manager and its already
1114 // prepared shaders will stay around for other renderers -> the cached data
1115 // in the rhi shaders have to be purged as it may refer to samplers we
1116 // are going to destroy.
1117 m_shaderManager->clearCachedRendererData();
1118
1119 qDeleteAll(c: m_samplers);
1120 m_stencilClipCommon.reset();
1121 delete m_dummyTexture;
1122 m_visualizer->releaseResources();
1123}
1124
1125void Renderer::releaseCachedResources()
1126{
1127 m_shaderManager->invalidated();
1128
1129 destroyGraphicsResources();
1130
1131 m_samplers.clear();
1132 m_dummyTexture = nullptr;
1133
1134 if (m_rhi)
1135 m_rhi->releaseCachedResources();
1136
1137 m_vertexUploadPool.resize(size: 0);
1138 m_indexUploadPool.resize(size: 0);
1139}
1140
1141void Renderer::invalidateAndRecycleBatch(Batch *b)
1142{
1143 b->invalidate();
1144 for (int i=0; i<m_batchPool.size(); ++i)
1145 if (b == m_batchPool.at(i))
1146 return;
1147 m_batchPool.add(t: b);
1148}
1149
1150/* The code here does a CPU-side allocation which might seem like a performance issue
1151 * compared to using glMapBuffer or glMapBufferRange which would give me back
1152 * potentially GPU allocated memory and saving me one deep-copy, but...
1153 *
1154 * Because we do a lot of CPU-side transformations, we need random-access memory
1155 * and the memory returned from glMapBuffer/glMapBufferRange is typically
1156 * uncached and thus very slow for our purposes.
1157 *
1158 * ref: http://www.opengl.org/wiki/Buffer_Object
1159 */
1160void Renderer::map(Buffer *buffer, int byteSize, bool isIndexBuf)
1161{
1162 if (!m_context->hasBrokenIndexBufferObjects() && m_visualizer->mode() == Visualizer::VisualizeNothing) {
1163 // Common case, use a shared memory pool for uploading vertex data to avoid
1164 // excessive reevaluation
1165 QDataBuffer<char> &pool = m_context->separateIndexBuffer() && isIndexBuf
1166 ? m_indexUploadPool : m_vertexUploadPool;
1167 if (byteSize > pool.size())
1168 pool.resize(size: byteSize);
1169 buffer->data = pool.data();
1170 } else if (buffer->size != byteSize) {
1171 free(ptr: buffer->data);
1172 buffer->data = (char *) malloc(size: byteSize);
1173 Q_CHECK_PTR(buffer->data);
1174 }
1175 buffer->size = byteSize;
1176}
1177
1178void Renderer::unmap(Buffer *buffer, bool isIndexBuf)
1179{
1180 if (m_rhi) {
1181 // Batches are pooled and reused which means the QRhiBuffer will be
1182 // still valid in a recycled Batch. We only hit the newBuffer() path
1183 // for brand new Batches.
1184 if (!buffer->buf) {
1185 buffer->buf = m_rhi->newBuffer(type: QRhiBuffer::Immutable,
1186 usage: isIndexBuf ? QRhiBuffer::IndexBuffer : QRhiBuffer::VertexBuffer,
1187 size: buffer->size);
1188 if (!buffer->buf->build())
1189 qWarning(msg: "Failed to build vertex/index buffer of size %d", buffer->size);
1190// else
1191// qDebug("created rhibuf %p size %d", buffer->buf, buffer->size);
1192 } else {
1193 bool needsRebuild = false;
1194 if (buffer->buf->size() < buffer->size) {
1195 buffer->buf->setSize(buffer->size);
1196 needsRebuild = true;
1197 }
1198 if (buffer->buf->type() != QRhiBuffer::Dynamic
1199 && buffer->nonDynamicChangeCount > DYNAMIC_VERTEX_INDEX_BUFFER_THRESHOLD)
1200 {
1201 buffer->buf->setType(QRhiBuffer::Dynamic);
1202 buffer->nonDynamicChangeCount = 0;
1203 needsRebuild = true;
1204 }
1205 if (needsRebuild) {
1206 //qDebug("rebuilding rhibuf %p size %d type Dynamic", buffer->buf, buffer->size);
1207 buffer->buf->build();
1208 }
1209 }
1210 if (buffer->buf->type() != QRhiBuffer::Dynamic) {
1211 m_resourceUpdates->uploadStaticBuffer(buf: buffer->buf,
1212 data: QByteArray::fromRawData(buffer->data, size: buffer->size));
1213 buffer->nonDynamicChangeCount += 1;
1214 } else {
1215 m_resourceUpdates->updateDynamicBuffer(buf: buffer->buf, offset: 0, size: buffer->size,
1216 data: QByteArray::fromRawData(buffer->data, size: buffer->size));
1217 }
1218 if (m_visualizer->mode() == Visualizer::VisualizeNothing)
1219 buffer->data = nullptr;
1220 } else {
1221 if (buffer->id == 0)
1222 glGenBuffers(n: 1, buffers: &buffer->id);
1223 GLenum target = isIndexBuf ? GL_ELEMENT_ARRAY_BUFFER : GL_ARRAY_BUFFER;
1224 glBindBuffer(target, buffer: buffer->id);
1225 glBufferData(target, size: buffer->size, data: buffer->data, usage: m_bufferStrategy);
1226 if (!m_context->hasBrokenIndexBufferObjects() && m_visualizer->mode() == Visualizer::VisualizeNothing)
1227 buffer->data = nullptr;
1228 }
1229}
1230
1231BatchRootInfo *Renderer::batchRootInfo(Node *node)
1232{
1233 BatchRootInfo *info = node->rootInfo();
1234 if (!info) {
1235 if (node->type() == QSGNode::ClipNodeType)
1236 info = new ClipBatchRootInfo;
1237 else {
1238 Q_ASSERT(node->type() == QSGNode::TransformNodeType);
1239 info = new BatchRootInfo;
1240 }
1241 node->data = info;
1242 }
1243 return info;
1244}
1245
1246void Renderer::removeBatchRootFromParent(Node *childRoot)
1247{
1248 BatchRootInfo *childInfo = batchRootInfo(node: childRoot);
1249 if (!childInfo->parentRoot)
1250 return;
1251 BatchRootInfo *parentInfo = batchRootInfo(node: childInfo->parentRoot);
1252
1253 Q_ASSERT(parentInfo->subRoots.contains(childRoot));
1254 parentInfo->subRoots.remove(value: childRoot);
1255 childInfo->parentRoot = nullptr;
1256}
1257
1258void Renderer::registerBatchRoot(Node *subRoot, Node *parentRoot)
1259{
1260 BatchRootInfo *subInfo = batchRootInfo(node: subRoot);
1261 BatchRootInfo *parentInfo = batchRootInfo(node: parentRoot);
1262 subInfo->parentRoot = parentRoot;
1263 parentInfo->subRoots << subRoot;
1264}
1265
1266bool Renderer::changeBatchRoot(Node *node, Node *root)
1267{
1268 BatchRootInfo *subInfo = batchRootInfo(node);
1269 if (subInfo->parentRoot == root)
1270 return false;
1271 if (subInfo->parentRoot) {
1272 BatchRootInfo *oldRootInfo = batchRootInfo(node: subInfo->parentRoot);
1273 oldRootInfo->subRoots.remove(value: node);
1274 }
1275 BatchRootInfo *newRootInfo = batchRootInfo(node: root);
1276 newRootInfo->subRoots << node;
1277 subInfo->parentRoot = root;
1278 return true;
1279}
1280
1281void Renderer::nodeChangedBatchRoot(Node *node, Node *root)
1282{
1283 if (node->type() == QSGNode::ClipNodeType || node->isBatchRoot) {
1284 // When we reach a batchroot, we only need to update it. Its subtree
1285 // is relative to that root, so no need to recurse further.
1286 changeBatchRoot(node, root);
1287 return;
1288 } else if (node->type() == QSGNode::GeometryNodeType) {
1289 // Only need to change the root as nodeChanged anyway flags a full update.
1290 Element *e = node->element();
1291 if (e) {
1292 e->root = root;
1293 e->boundsComputed = false;
1294 }
1295 } else if (node->type() == QSGNode::RenderNodeType) {
1296 RenderNodeElement *e = node->renderNodeElement();
1297 if (e)
1298 e->root = root;
1299 }
1300
1301 SHADOWNODE_TRAVERSE(node)
1302 nodeChangedBatchRoot(node: child, root);
1303}
1304
1305void Renderer::nodeWasTransformed(Node *node, int *vertexCount)
1306{
1307 if (node->type() == QSGNode::GeometryNodeType) {
1308 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node->sgNode);
1309 *vertexCount += gn->geometry()->vertexCount();
1310 Element *e = node->element();
1311 if (e) {
1312 e->boundsComputed = false;
1313 if (e->batch) {
1314 if (!e->batch->isOpaque) {
1315 invalidateBatchAndOverlappingRenderOrders(batch: e->batch);
1316 } else if (e->batch->merged) {
1317 e->batch->needsUpload = true;
1318 }
1319 }
1320 }
1321 }
1322
1323 SHADOWNODE_TRAVERSE(node)
1324 nodeWasTransformed(node: child, vertexCount);
1325}
1326
1327void Renderer::nodeWasAdded(QSGNode *node, Node *shadowParent)
1328{
1329 Q_ASSERT(!m_nodes.contains(node));
1330 if (node->isSubtreeBlocked())
1331 return;
1332
1333 Node *snode = m_nodeAllocator.allocate();
1334 snode->sgNode = node;
1335 m_nodes.insert(key: node, value: snode);
1336 if (shadowParent)
1337 shadowParent->append(child: snode);
1338
1339 if (node->type() == QSGNode::GeometryNodeType) {
1340 snode->data = m_elementAllocator.allocate();
1341 snode->element()->setNode(static_cast<QSGGeometryNode *>(node));
1342
1343 } else if (node->type() == QSGNode::ClipNodeType) {
1344 snode->data = new ClipBatchRootInfo;
1345 m_rebuild |= FullRebuild;
1346
1347 } else if (node->type() == QSGNode::RenderNodeType) {
1348 QSGRenderNode *rn = static_cast<QSGRenderNode *>(node);
1349 RenderNodeElement *e = new RenderNodeElement(rn);
1350 snode->data = e;
1351 Q_ASSERT(!m_renderNodeElements.contains(rn));
1352 m_renderNodeElements.insert(key: e->renderNode, value: e);
1353 if (!rn->flags().testFlag(flag: QSGRenderNode::DepthAwareRendering))
1354 m_useDepthBuffer = false;
1355 m_rebuild |= FullRebuild;
1356 }
1357
1358 QSGNODE_TRAVERSE(node)
1359 nodeWasAdded(node: child, shadowParent: snode);
1360}
1361
1362void Renderer::nodeWasRemoved(Node *node)
1363{
1364 // Prefix traversal as removeBatchRootFromParent below removes nodes
1365 // in a bottom-up manner. Note that we *cannot* use SHADOWNODE_TRAVERSE
1366 // here, because we delete 'child' (when recursed, down below), so we'd
1367 // have a use-after-free.
1368 {
1369 Node *child = node->firstChild();
1370 while (child) {
1371 // Remove (and delete) child
1372 node->remove(child);
1373 nodeWasRemoved(node: child);
1374 child = node->firstChild();
1375 }
1376 }
1377
1378 if (node->type() == QSGNode::GeometryNodeType) {
1379 Element *e = node->element();
1380 if (e) {
1381 e->removed = true;
1382 m_elementsToDelete.add(t: e);
1383 e->node = nullptr;
1384 if (e->root) {
1385 BatchRootInfo *info = batchRootInfo(node: e->root);
1386 info->availableOrders++;
1387 }
1388 if (e->batch) {
1389 e->batch->needsUpload = true;
1390 e->batch->needsPurge = true;
1391 }
1392
1393 }
1394
1395 } else if (node->type() == QSGNode::ClipNodeType) {
1396 removeBatchRootFromParent(childRoot: node);
1397 delete node->clipInfo();
1398 m_rebuild |= FullRebuild;
1399 m_taggedRoots.remove(value: node);
1400
1401 } else if (node->isBatchRoot) {
1402 removeBatchRootFromParent(childRoot: node);
1403 delete node->rootInfo();
1404 m_rebuild |= FullRebuild;
1405 m_taggedRoots.remove(value: node);
1406
1407 } else if (node->type() == QSGNode::RenderNodeType) {
1408 RenderNodeElement *e = m_renderNodeElements.take(key: static_cast<QSGRenderNode *>(node->sgNode));
1409 if (e) {
1410 e->removed = true;
1411 m_elementsToDelete.add(t: e);
1412 if (m_renderNodeElements.isEmpty()) {
1413 static const bool useDepth = qEnvironmentVariableIsEmpty(varName: "QSG_NO_DEPTH_BUFFER");
1414 if (m_rhi)
1415 m_useDepthBuffer = useDepth;
1416 else
1417 m_useDepthBuffer = useDepth && m_context->openglContext()->format().depthBufferSize() > 0;
1418 }
1419
1420 if (e->batch != nullptr)
1421 e->batch->needsPurge = true;
1422 }
1423 }
1424
1425 Q_ASSERT(m_nodes.contains(node->sgNode));
1426
1427 m_nodeAllocator.release(t: m_nodes.take(key: node->sgNode));
1428}
1429
1430void Renderer::turnNodeIntoBatchRoot(Node *node)
1431{
1432 if (Q_UNLIKELY(debug_change())) qDebug(msg: " - new batch root");
1433 m_rebuild |= FullRebuild;
1434 node->isBatchRoot = true;
1435 node->becameBatchRoot = true;
1436
1437 Node *p = node->parent();
1438 while (p) {
1439 if (p->type() == QSGNode::ClipNodeType || p->isBatchRoot) {
1440 registerBatchRoot(subRoot: node, parentRoot: p);
1441 break;
1442 }
1443 p = p->parent();
1444 }
1445
1446 SHADOWNODE_TRAVERSE(node)
1447 nodeChangedBatchRoot(node: child, root: node);
1448}
1449
1450
1451void Renderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state)
1452{
1453#ifndef QT_NO_DEBUG_OUTPUT
1454 if (Q_UNLIKELY(debug_change())) {
1455 QDebug debug = qDebug();
1456 debug << "dirty:";
1457 if (state & QSGNode::DirtyGeometry)
1458 debug << "Geometry";
1459 if (state & QSGNode::DirtyMaterial)
1460 debug << "Material";
1461 if (state & QSGNode::DirtyMatrix)
1462 debug << "Matrix";
1463 if (state & QSGNode::DirtyNodeAdded)
1464 debug << "Added";
1465 if (state & QSGNode::DirtyNodeRemoved)
1466 debug << "Removed";
1467 if (state & QSGNode::DirtyOpacity)
1468 debug << "Opacity";
1469 if (state & QSGNode::DirtySubtreeBlocked)
1470 debug << "SubtreeBlocked";
1471 if (state & QSGNode::DirtyForceUpdate)
1472 debug << "ForceUpdate";
1473
1474 // when removed, some parts of the node could already have been destroyed
1475 // so don't debug it out.
1476 if (state & QSGNode::DirtyNodeRemoved)
1477 debug << (void *) node << node->type();
1478 else
1479 debug << node;
1480 }
1481#endif
1482 // As this function calls nodeChanged recursively, we do it at the top
1483 // to avoid that any of the others are processed twice.
1484 if (state & QSGNode::DirtySubtreeBlocked) {
1485 Node *sn = m_nodes.value(key: node);
1486
1487 // Force a batch rebuild if this includes an opacity change
1488 if (state & QSGNode::DirtyOpacity)
1489 m_rebuild |= FullRebuild;
1490
1491 bool blocked = node->isSubtreeBlocked();
1492 if (blocked && sn) {
1493 nodeChanged(node, state: QSGNode::DirtyNodeRemoved);
1494 Q_ASSERT(m_nodes.value(node) == 0);
1495 } else if (!blocked && !sn) {
1496 nodeChanged(node, state: QSGNode::DirtyNodeAdded);
1497 }
1498 return;
1499 }
1500
1501 if (state & QSGNode::DirtyNodeAdded) {
1502 if (nodeUpdater()->isNodeBlocked(n: node, root: rootNode())) {
1503 QSGRenderer::nodeChanged(node, state);
1504 return;
1505 }
1506 if (node == rootNode())
1507 nodeWasAdded(node, shadowParent: nullptr);
1508 else
1509 nodeWasAdded(node, shadowParent: m_nodes.value(key: node->parent()));
1510 }
1511
1512 // Mark this node dirty in the shadow tree.
1513 Node *shadowNode = m_nodes.value(key: node);
1514
1515 // Blocked subtrees won't have shadow nodes, so we can safely abort
1516 // here..
1517 if (!shadowNode) {
1518 QSGRenderer::nodeChanged(node, state);
1519 return;
1520 }
1521
1522 shadowNode->dirtyState |= state;
1523
1524 if (state & QSGNode::DirtyMatrix && !shadowNode->isBatchRoot) {
1525 Q_ASSERT(node->type() == QSGNode::TransformNodeType);
1526 if (node->m_subtreeRenderableCount > m_batchNodeThreshold) {
1527 turnNodeIntoBatchRoot(node: shadowNode);
1528 } else {
1529 int vertices = 0;
1530 nodeWasTransformed(node: shadowNode, vertexCount: &vertices);
1531 if (vertices > m_batchVertexThreshold) {
1532 turnNodeIntoBatchRoot(node: shadowNode);
1533 }
1534 }
1535 }
1536
1537 if (state & QSGNode::DirtyGeometry && node->type() == QSGNode::GeometryNodeType) {
1538 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1539 Element *e = shadowNode->element();
1540 if (e) {
1541 e->boundsComputed = false;
1542 Batch *b = e->batch;
1543 if (b) {
1544 if (!e->batch->geometryWasChanged(gn) || !e->batch->isOpaque) {
1545 invalidateBatchAndOverlappingRenderOrders(batch: e->batch);
1546 } else {
1547 b->needsUpload = true;
1548 }
1549 }
1550 }
1551 }
1552
1553 if (state & QSGNode::DirtyMaterial && node->type() == QSGNode::GeometryNodeType) {
1554 Element *e = shadowNode->element();
1555 if (e) {
1556 bool blended = hasMaterialWithBlending(n: static_cast<QSGGeometryNode *>(node));
1557 if (e->isMaterialBlended != blended) {
1558 m_rebuild |= Renderer::FullRebuild;
1559 e->isMaterialBlended = blended;
1560 } else if (e->batch) {
1561 if (e->batch->isMaterialCompatible(e) == BatchBreaksOnCompare)
1562 invalidateBatchAndOverlappingRenderOrders(batch: e->batch);
1563 } else {
1564 m_rebuild |= Renderer::BuildBatches;
1565 }
1566 }
1567 }
1568
1569 // Mark the shadow tree dirty all the way back to the root...
1570 QSGNode::DirtyState dirtyChain = state & (QSGNode::DirtyNodeAdded
1571 | QSGNode::DirtyOpacity
1572 | QSGNode::DirtyMatrix
1573 | QSGNode::DirtySubtreeBlocked
1574 | QSGNode::DirtyForceUpdate);
1575 if (dirtyChain != 0) {
1576 dirtyChain = QSGNode::DirtyState(dirtyChain << 16);
1577 Node *sn = shadowNode->parent();
1578 while (sn) {
1579 sn->dirtyState |= dirtyChain;
1580 sn = sn->parent();
1581 }
1582 }
1583
1584 // Delete happens at the very end because it deletes the shadownode.
1585 if (state & QSGNode::DirtyNodeRemoved) {
1586 Node *parent = shadowNode->parent();
1587 if (parent)
1588 parent->remove(child: shadowNode);
1589 nodeWasRemoved(node: shadowNode);
1590 Q_ASSERT(m_nodes.value(node) == 0);
1591 }
1592
1593 QSGRenderer::nodeChanged(node, state);
1594}
1595
1596/*
1597 * Traverses the tree and builds two list of geometry nodes. One for
1598 * the opaque and one for the translucent. These are populated
1599 * in the order they should visually appear in, meaning first
1600 * to the back and last to the front.
1601 *
1602 * We split opaque and translucent as we can perform different
1603 * types of reordering / batching strategies on them, depending
1604 *
1605 * Note: It would be tempting to use the shadow nodes instead of the QSGNodes
1606 * for traversal to avoid hash lookups, but the order of the children
1607 * is important and they are not preserved in the shadow tree, so we must
1608 * use the actual QSGNode tree.
1609 */
1610void Renderer::buildRenderLists(QSGNode *node)
1611{
1612 if (node->isSubtreeBlocked())
1613 return;
1614
1615 Node *shadowNode = m_nodes.value(key: node);
1616 Q_ASSERT(shadowNode);
1617
1618 if (node->type() == QSGNode::GeometryNodeType) {
1619 QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
1620
1621 Element *e = shadowNode->element();
1622 Q_ASSERT(e);
1623
1624 bool opaque = gn->inheritedOpacity() > OPAQUE_LIMIT && !(gn->activeMaterial()->flags() & QSGMaterial::Blending);
1625 if (opaque && m_useDepthBuffer)
1626 m_opaqueRenderList << e;
1627 else
1628 m_alphaRenderList << e;
1629
1630 e->order = ++m_nextRenderOrder;
1631 // Used while rebuilding partial roots.
1632 if (m_partialRebuild)
1633 e->orphaned = false;
1634
1635 } else if (node->type() == QSGNode::ClipNodeType || shadowNode->isBatchRoot) {
1636 Q_ASSERT(m_nodes.contains(node));
1637 BatchRootInfo *info = batchRootInfo(node: shadowNode);
1638 if (node == m_partialRebuildRoot) {
1639 m_nextRenderOrder = info->firstOrder;
1640 QSGNODE_TRAVERSE(node)
1641 buildRenderLists(node: child);
1642 m_nextRenderOrder = info->lastOrder + 1;
1643 } else {
1644 int currentOrder = m_nextRenderOrder;
1645 QSGNODE_TRAVERSE(node)
1646 buildRenderLists(node: child);
1647 int padding = (m_nextRenderOrder - currentOrder) >> 2;
1648 info->firstOrder = currentOrder;
1649 info->availableOrders = padding;
1650 info->lastOrder = m_nextRenderOrder + padding;
1651 m_nextRenderOrder = info->lastOrder;
1652 }
1653 return;
1654 } else if (node->type() == QSGNode::RenderNodeType) {
1655 RenderNodeElement *e = shadowNode->renderNodeElement();
1656 m_alphaRenderList << e;
1657 e->order = ++m_nextRenderOrder;
1658 Q_ASSERT(e);
1659 }
1660
1661 QSGNODE_TRAVERSE(node)
1662 buildRenderLists(node: child);
1663}
1664
1665void Renderer::tagSubRoots(Node *node)
1666{
1667 BatchRootInfo *i = batchRootInfo(node);
1668 m_taggedRoots << node;
1669 for (QSet<Node *>::const_iterator it = i->subRoots.constBegin();
1670 it != i->subRoots.constEnd(); ++it) {
1671 tagSubRoots(node: *it);
1672 }
1673}
1674
1675static void qsg_addOrphanedElements(QDataBuffer<Element *> &orphans, const QDataBuffer<Element *> &renderList)
1676{
1677 orphans.reset();
1678 for (int i=0; i<renderList.size(); ++i) {
1679 Element *e = renderList.at(i);
1680 if (e && !e->removed) {
1681 e->orphaned = true;
1682 orphans.add(t: e);
1683 }
1684 }
1685}
1686
1687static void qsg_addBackOrphanedElements(QDataBuffer<Element *> &orphans, QDataBuffer<Element *> &renderList)
1688{
1689 for (int i=0; i<orphans.size(); ++i) {
1690 Element *e = orphans.at(i);
1691 if (e->orphaned)
1692 renderList.add(t: e);
1693 }
1694 orphans.reset();
1695}
1696
1697/*
1698 * To rebuild the tagged roots, we start by putting all subroots of tagged
1699 * roots into the list of tagged roots. This is to make the rest of the
1700 * algorithm simpler.
1701 *
1702 * Second, we invalidate all batches which belong to tagged roots, which now
1703 * includes the entire subtree under a given root
1704 *
1705 * Then we call buildRenderLists for all tagged subroots which do not have
1706 * parents which are tagged, aka, we traverse only the topmosts roots.
1707 *
1708 * Then we sort the render lists based on their render order, to restore the
1709 * right order for rendering.
1710 */
1711void Renderer::buildRenderListsForTaggedRoots()
1712{
1713 // Flag any element that is currently in the render lists, but which
1714 // is not in a batch. This happens when we have a partial rebuild
1715 // in one sub tree while we have a BuildBatches change in another
1716 // isolated subtree. So that batch-building takes into account
1717 // these "orphaned" nodes, we flag them now. The ones under tagged
1718 // roots will be cleared again. The remaining ones are added into the
1719 // render lists so that they contain all visual nodes after the
1720 // function completes.
1721 qsg_addOrphanedElements(orphans&: m_tmpOpaqueElements, renderList: m_opaqueRenderList);
1722 qsg_addOrphanedElements(orphans&: m_tmpAlphaElements, renderList: m_alphaRenderList);
1723
1724 // Take a copy now, as we will be adding to this while traversing..
1725 QSet<Node *> roots = m_taggedRoots;
1726 for (QSet<Node *>::const_iterator it = roots.constBegin();
1727 it != roots.constEnd(); ++it) {
1728 tagSubRoots(node: *it);
1729 }
1730
1731 for (int i=0; i<m_opaqueBatches.size(); ++i) {
1732 Batch *b = m_opaqueBatches.at(i);
1733 if (m_taggedRoots.contains(value: b->root))
1734 invalidateAndRecycleBatch(b);
1735
1736 }
1737 for (int i=0; i<m_alphaBatches.size(); ++i) {
1738 Batch *b = m_alphaBatches.at(i);
1739 if (m_taggedRoots.contains(value: b->root))
1740 invalidateAndRecycleBatch(b);
1741 }
1742
1743 m_opaqueRenderList.reset();
1744 m_alphaRenderList.reset();
1745 int maxRenderOrder = m_nextRenderOrder;
1746 m_partialRebuild = true;
1747 // Traverse each root, assigning it
1748 for (QSet<Node *>::const_iterator it = m_taggedRoots.constBegin();
1749 it != m_taggedRoots.constEnd(); ++it) {
1750 Node *root = *it;
1751 BatchRootInfo *i = batchRootInfo(node: root);
1752 if ((!i->parentRoot || !m_taggedRoots.contains(value: i->parentRoot))
1753 && !nodeUpdater()->isNodeBlocked(n: root->sgNode, root: rootNode())) {
1754 m_nextRenderOrder = i->firstOrder;
1755 m_partialRebuildRoot = root->sgNode;
1756 buildRenderLists(node: root->sgNode);
1757 }
1758 }
1759 m_partialRebuild = false;
1760 m_partialRebuildRoot = nullptr;
1761 m_taggedRoots.clear();
1762 m_nextRenderOrder = qMax(a: m_nextRenderOrder, b: maxRenderOrder);
1763
1764 // Add orphaned elements back into the list and then sort it..
1765 qsg_addBackOrphanedElements(orphans&: m_tmpOpaqueElements, renderList&: m_opaqueRenderList);
1766 qsg_addBackOrphanedElements(orphans&: m_tmpAlphaElements, renderList&: m_alphaRenderList);
1767
1768 if (m_opaqueRenderList.size())
1769 std::sort(first: &m_opaqueRenderList.first(), last: &m_opaqueRenderList.last() + 1, comp: qsg_sort_element_decreasing_order);
1770 if (m_alphaRenderList.size())
1771 std::sort(first: &m_alphaRenderList.first(), last: &m_alphaRenderList.last() + 1, comp: qsg_sort_element_increasing_order);
1772
1773}
1774
1775void Renderer::buildRenderListsFromScratch()
1776{
1777 m_opaqueRenderList.reset();
1778 m_alphaRenderList.reset();
1779
1780 for (int i=0; i<m_opaqueBatches.size(); ++i)
1781 invalidateAndRecycleBatch(b: m_opaqueBatches.at(i));
1782 for (int i=0; i<m_alphaBatches.size(); ++i)
1783 invalidateAndRecycleBatch(b: m_alphaBatches.at(i));
1784 m_opaqueBatches.reset();
1785 m_alphaBatches.reset();
1786
1787 m_nextRenderOrder = 0;
1788
1789 buildRenderLists(node: rootNode());
1790}
1791
1792void Renderer::invalidateBatchAndOverlappingRenderOrders(Batch *batch)
1793{
1794 Q_ASSERT(batch);
1795 Q_ASSERT(batch->first);
1796
1797 if (m_renderOrderRebuildLower < 0 || batch->first->order < m_renderOrderRebuildLower)
1798 m_renderOrderRebuildLower = batch->first->order;
1799 if (m_renderOrderRebuildUpper < 0 || batch->lastOrderInBatch > m_renderOrderRebuildUpper)
1800 m_renderOrderRebuildUpper = batch->lastOrderInBatch;
1801
1802 batch->invalidate();
1803
1804 for (int i=0; i<m_alphaBatches.size(); ++i) {
1805 Batch *b = m_alphaBatches.at(i);
1806 if (b->first) {
1807 int bf = b->first->order;
1808 int bl = b->lastOrderInBatch;
1809 if (bl > m_renderOrderRebuildLower && bf < m_renderOrderRebuildUpper)
1810 b->invalidate();
1811 }
1812 }
1813
1814 m_rebuild |= BuildBatches;
1815}
1816
1817/* Clean up batches by making it a consecutive list of "valid"
1818 * batches and moving all invalidated batches to the batches pool.
1819 */
1820void Renderer::cleanupBatches(QDataBuffer<Batch *> *batches) {
1821 if (batches->size()) {
1822 std::stable_sort(first: &batches->first(), last: &batches->last() + 1, comp: qsg_sort_batch_is_valid);
1823 int count = 0;
1824 while (count < batches->size() && batches->at(i: count)->first)
1825 ++count;
1826 for (int i=count; i<batches->size(); ++i)
1827 invalidateAndRecycleBatch(b: batches->at(i));
1828 batches->resize(size: count);
1829 }
1830}
1831
1832void Renderer::prepareOpaqueBatches()
1833{
1834 for (int i=m_opaqueRenderList.size() - 1; i >= 0; --i) {
1835 Element *ei = m_opaqueRenderList.at(i);
1836 if (!ei || ei->batch || ei->node->geometry()->vertexCount() == 0)
1837 continue;
1838 Batch *batch = newBatch();
1839 batch->first = ei;
1840 batch->root = ei->root;
1841 batch->isOpaque = true;
1842 batch->needsUpload = true;
1843 batch->positionAttribute = qsg_positionAttribute(g: ei->node->geometry());
1844
1845 m_opaqueBatches.add(t: batch);
1846
1847 ei->batch = batch;
1848 Element *next = ei;
1849
1850 QSGGeometryNode *gni = ei->node;
1851
1852 for (int j = i - 1; j >= 0; --j) {
1853 Element *ej = m_opaqueRenderList.at(i: j);
1854 if (!ej)
1855 continue;
1856 if (ej->root != ei->root)
1857 break;
1858 if (ej->batch || ej->node->geometry()->vertexCount() == 0)
1859 continue;
1860
1861 QSGGeometryNode *gnj = ej->node;
1862
1863 if (gni->clipList() == gnj->clipList()
1864 && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode()
1865 && (gni->geometry()->drawingMode() != QSGGeometry::DrawLines || gni->geometry()->lineWidth() == gnj->geometry()->lineWidth())
1866 && gni->geometry()->attributes() == gnj->geometry()->attributes()
1867 && gni->inheritedOpacity() == gnj->inheritedOpacity()
1868 && gni->activeMaterial()->type() == gnj->activeMaterial()->type()
1869 && gni->activeMaterial()->compare(other: gnj->activeMaterial()) == 0) {
1870 ej->batch = batch;
1871 next->nextInBatch = ej;
1872 next = ej;
1873 }
1874 }
1875
1876 batch->lastOrderInBatch = next->order;
1877 }
1878}
1879
1880bool Renderer::checkOverlap(int first, int last, const Rect &bounds)
1881{
1882 for (int i=first; i<=last; ++i) {
1883 Element *e = m_alphaRenderList.at(i);
1884 if (!e)
1885 continue;
1886 Q_ASSERT(e->boundsComputed);
1887 if (e->bounds.intersects(r: bounds))
1888 return true;
1889 }
1890 return false;
1891}
1892
1893/*
1894 *
1895 * To avoid the O(n^2) checkOverlap check in most cases, we have the
1896 * overlapBounds which is the union of all bounding rects to check overlap
1897 * for. We know that if it does not overlap, then none of the individual
1898 * ones will either. For the typical list case, this results in no calls
1899 * to checkOverlap what-so-ever. This also ensures that when all consecutive
1900 * items are matching (such as a table of text), we don't build up an
1901 * overlap bounds and thus do not require full overlap checks.
1902 */
1903
1904void Renderer::prepareAlphaBatches()
1905{
1906 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1907 Element *e = m_alphaRenderList.at(i);
1908 if (!e || e->isRenderNode)
1909 continue;
1910 Q_ASSERT(!e->removed);
1911 e->ensureBoundsValid();
1912 }
1913
1914 for (int i=0; i<m_alphaRenderList.size(); ++i) {
1915 Element *ei = m_alphaRenderList.at(i);
1916 if (!ei || ei->batch)
1917 continue;
1918
1919 if (ei->isRenderNode) {
1920 Batch *rnb = newBatch();
1921 rnb->first = ei;
1922 rnb->root = ei->root;
1923 rnb->isOpaque = false;
1924 rnb->isRenderNode = true;
1925 ei->batch = rnb;
1926 m_alphaBatches.add(t: rnb);
1927 continue;
1928 }
1929
1930 if (ei->node->geometry()->vertexCount() == 0)
1931 continue;
1932
1933 Batch *batch = newBatch();
1934 batch->first = ei;
1935 batch->root = ei->root;
1936 batch->isOpaque = false;
1937 batch->needsUpload = true;
1938 m_alphaBatches.add(t: batch);
1939 ei->batch = batch;
1940
1941 QSGGeometryNode *gni = ei->node;
1942 batch->positionAttribute = qsg_positionAttribute(g: gni->geometry());
1943
1944 Rect overlapBounds;
1945 overlapBounds.set(FLT_MAX, FLT_MAX, right: -FLT_MAX, bottom: -FLT_MAX);
1946
1947 Element *next = ei;
1948
1949 for (int j = i + 1; j < m_alphaRenderList.size(); ++j) {
1950 Element *ej = m_alphaRenderList.at(i: j);
1951 if (!ej)
1952 continue;
1953 if (ej->root != ei->root || ej->isRenderNode)
1954 break;
1955 if (ej->batch) {
1956 overlapBounds |= ej->bounds;
1957 continue;
1958 }
1959
1960 QSGGeometryNode *gnj = ej->node;
1961 if (gnj->geometry()->vertexCount() == 0)
1962 continue;
1963
1964 if (gni->clipList() == gnj->clipList()
1965 && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode()
1966 && (gni->geometry()->drawingMode() != QSGGeometry::DrawLines
1967 || (gni->geometry()->lineWidth() == gnj->geometry()->lineWidth()
1968 // Must not do overlap checks when the line width is not 1,
1969 // we have no knowledge how such lines are rasterized.
1970 && gni->geometry()->lineWidth() == 1.0f))
1971 && gni->geometry()->attributes() == gnj->geometry()->attributes()
1972 && gni->inheritedOpacity() == gnj->inheritedOpacity()
1973 && gni->activeMaterial()->type() == gnj->activeMaterial()->type()
1974 && gni->activeMaterial()->compare(other: gnj->activeMaterial()) == 0) {
1975 if (!overlapBounds.intersects(r: ej->bounds) || !checkOverlap(first: i+1, last: j - 1, bounds: ej->bounds)) {
1976 ej->batch = batch;
1977 next->nextInBatch = ej;
1978 next = ej;
1979 } else {
1980 /* When we come across a compatible element which hits an overlap, we
1981 * need to stop the batch right away. We cannot add more elements
1982 * to the current batch as they will be rendered before the batch that the
1983 * current 'ej' will be added to.
1984 */
1985 break;
1986 }
1987 } else {
1988 overlapBounds |= ej->bounds;
1989 }
1990 }
1991
1992 batch->lastOrderInBatch = next->order;
1993 }
1994
1995
1996}
1997
1998static inline int qsg_fixIndexCount(int iCount, int drawMode)
1999{
2000 switch (drawMode) {
2001 case QSGGeometry::DrawTriangleStrip:
2002 // Merged triangle strips need to contain degenerate triangles at the beginning and end.
2003 // One could save 2 uploaded ushorts here by ditching the padding for the front of the
2004 // first and the end of the last, but for simplicity, we simply don't care.
2005 // Those extra triangles will be skipped while drawing to preserve the strip's parity
2006 // anyhow.
2007 return iCount + 2;
2008 case QSGGeometry::DrawLines:
2009 // For lines we drop the last vertex if the number of vertices is uneven.
2010 return iCount - (iCount % 2);
2011 case QSGGeometry::DrawTriangles:
2012 // For triangles we drop trailing vertices until the result is divisible by 3.
2013 return iCount - (iCount % 3);
2014 default:
2015 return iCount;
2016 }
2017}
2018
2019/* These parameters warrant some explanation...
2020 *
2021 * vaOffset: The byte offset into the vertex data to the location of the
2022 * 2D float point vertex attributes.
2023 *
2024 * vertexData: destination where the geometry's vertex data should go
2025 *
2026 * zData: destination of geometries injected Z positioning
2027 *
2028 * indexData: destination of the indices for this element
2029 *
2030 * iBase: The starting index for this element in the batch
2031 */
2032
2033void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, void *iBasePtr, int *indexCount)
2034{
2035 if (Q_UNLIKELY(debug_upload())) qDebug() << " - uploading element:" << e << e->node << (void *) *vertexData << (qintptr) (*zData - *vertexData) << (qintptr) (*indexData - *vertexData);
2036 QSGGeometry *g = e->node->geometry();
2037
2038 const QMatrix4x4 &localx = *e->node->matrix();
2039
2040 const int vCount = g->vertexCount();
2041 const int vSize = g->sizeOfVertex();
2042 memcpy(dest: *vertexData, src: g->vertexData(), n: vSize * vCount);
2043
2044 // apply vertex transform..
2045 char *vdata = *vertexData + vaOffset;
2046 if (((const QMatrix4x4_Accessor &) localx).flagBits == 1) {
2047 for (int i=0; i<vCount; ++i) {
2048 Pt *p = (Pt *) vdata;
2049 p->x += ((const QMatrix4x4_Accessor &) localx).m[3][0];
2050 p->y += ((const QMatrix4x4_Accessor &) localx).m[3][1];
2051 vdata += vSize;
2052 }
2053 } else if (((const QMatrix4x4_Accessor &) localx).flagBits > 1) {
2054 for (int i=0; i<vCount; ++i) {
2055 ((Pt *) vdata)->map(mat: localx);
2056 vdata += vSize;
2057 }
2058 }
2059
2060 if (m_useDepthBuffer) {
2061 float *vzorder = (float *) *zData;
2062 float zorder = 1.0f - e->order * m_zRange;
2063 for (int i=0; i<vCount; ++i)
2064 vzorder[i] = zorder;
2065 *zData += vCount * sizeof(float);
2066 }
2067
2068 int iCount = g->indexCount();
2069 if (m_uint32IndexForRhi) {
2070 // can only happen when using the rhi
2071 quint32 *iBase = (quint32 *) iBasePtr;
2072 quint32 *indices = (quint32 *) *indexData;
2073 if (iCount == 0) {
2074 iCount = vCount;
2075 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
2076 *indices++ = *iBase;
2077 else
2078 iCount = qsg_fixIndexCount(iCount, drawMode: g->drawingMode());
2079
2080 for (int i=0; i<iCount; ++i)
2081 indices[i] = *iBase + i;
2082 } else {
2083 // source index data in QSGGeometry is always ushort (we would not merge otherwise)
2084 const quint16 *srcIndices = g->indexDataAsUShort();
2085 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
2086 *indices++ = *iBase + srcIndices[0];
2087 else
2088 iCount = qsg_fixIndexCount(iCount, drawMode: g->drawingMode());
2089
2090 for (int i=0; i<iCount; ++i)
2091 indices[i] = *iBase + srcIndices[i];
2092 }
2093 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
2094 indices[iCount] = indices[iCount - 1];
2095 iCount += 2;
2096 }
2097 *iBase += vCount;
2098 } else {
2099 // normally batching is only done for ushort index data
2100 quint16 *iBase = (quint16 *) iBasePtr;
2101 quint16 *indices = (quint16 *) *indexData;
2102 if (iCount == 0) {
2103 iCount = vCount;
2104 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
2105 *indices++ = *iBase;
2106 else
2107 iCount = qsg_fixIndexCount(iCount, drawMode: g->drawingMode());
2108
2109 for (int i=0; i<iCount; ++i)
2110 indices[i] = *iBase + i;
2111 } else {
2112 const quint16 *srcIndices = g->indexDataAsUShort();
2113 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip)
2114 *indices++ = *iBase + srcIndices[0];
2115 else
2116 iCount = qsg_fixIndexCount(iCount, drawMode: g->drawingMode());
2117
2118 for (int i=0; i<iCount; ++i)
2119 indices[i] = *iBase + srcIndices[i];
2120 }
2121 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
2122 indices[iCount] = indices[iCount - 1];
2123 iCount += 2;
2124 }
2125 *iBase += vCount;
2126 }
2127
2128 *vertexData += vCount * vSize;
2129 *indexData += iCount * mergedIndexElemSize();
2130 *indexCount += iCount;
2131}
2132
2133QMatrix4x4 qsg_matrixForRoot(Node *node)
2134{
2135 if (node->type() == QSGNode::TransformNodeType)
2136 return static_cast<QSGTransformNode *>(node->sgNode)->combinedMatrix();
2137 Q_ASSERT(node->type() == QSGNode::ClipNodeType);
2138 QSGClipNode *c = static_cast<QSGClipNode *>(node->sgNode);
2139 return *c->matrix();
2140}
2141
2142void Renderer::uploadBatch(Batch *b)
2143{
2144 // Early out if nothing has changed in this batch..
2145 if (!b->needsUpload) {
2146 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "already uploaded...";
2147 return;
2148 }
2149
2150 if (!b->first) {
2151 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "is invalid...";
2152 return;
2153 }
2154
2155 if (b->isRenderNode) {
2156 if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch: " << b << "is a render node...";
2157 return;
2158 }
2159
2160 // Figure out if we can merge or not, if not, then just render the batch as is..
2161 Q_ASSERT(b->first);
2162 Q_ASSERT(b->first->node);
2163
2164 QSGGeometryNode *gn = b->first->node;
2165 QSGGeometry *g = gn->geometry();
2166 QSGMaterial::Flags flags = gn->activeMaterial()->flags();
2167 bool canMerge = (g->drawingMode() == QSGGeometry::DrawTriangles || g->drawingMode() == QSGGeometry::DrawTriangleStrip ||
2168 g->drawingMode() == QSGGeometry::DrawLines || g->drawingMode() == QSGGeometry::DrawPoints)
2169 && b->positionAttribute >= 0
2170 && g->indexType() == QSGGeometry::UnsignedShortType
2171 && (flags & (QSGMaterial::CustomCompileStep | QSGMaterial_FullMatrix)) == 0
2172 && ((flags & QSGMaterial::RequiresFullMatrixExceptTranslate) == 0 || b->isTranslateOnlyToRoot())
2173 && b->isSafeToBatch();
2174
2175 b->merged = canMerge;
2176
2177 // Figure out how much memory we need...
2178 b->vertexCount = 0;
2179 b->indexCount = 0;
2180 int unmergedIndexSize = 0;
2181 Element *e = b->first;
2182
2183 while (e) {
2184 QSGGeometry *eg = e->node->geometry();
2185 b->vertexCount += eg->vertexCount();
2186 int iCount = eg->indexCount();
2187 if (b->merged) {
2188 if (iCount == 0)
2189 iCount = eg->vertexCount();
2190 iCount = qsg_fixIndexCount(iCount, drawMode: g->drawingMode());
2191 } else {
2192 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : eg->sizeOfIndex();
2193 unmergedIndexSize += iCount * effectiveIndexSize;
2194 }
2195 b->indexCount += iCount;
2196 e = e->nextInBatch;
2197 }
2198
2199 // Abort if there are no vertices in this batch.. We abort this late as
2200 // this is a broken usecase which we do not care to optimize for...
2201 if (b->vertexCount == 0 || (b->merged && b->indexCount == 0))
2202 return;
2203
2204 /* Allocate memory for this batch. Merged batches are divided into three separate blocks
2205 1. Vertex data for all elements, as they were in the QSGGeometry object, but
2206 with the tranform relative to this batch's root applied. The vertex data
2207 is otherwise unmodified.
2208 2. Z data for all elements, derived from each elements "render order".
2209 This is present for merged data only.
2210 3. Indices for all elements, as they were in the QSGGeometry object, but
2211 adjusted so that each index matches its.
2212 And for TRIANGLE_STRIPs, we need to insert degenerate between each
2213 primitive. These are unsigned shorts for merged and arbitrary for
2214 non-merged.
2215 */
2216 int bufferSize = b->vertexCount * g->sizeOfVertex();
2217 int ibufferSize = 0;
2218 // At this point, we need to check if the vertices byte size is 4 byte aligned or not.
2219 // If an unaligned value is used in a shared buffer with indices, it causes problems with
2220 // glDrawElements. We need to do a 4 byte alignment so that it can work with both
2221 // QSGGeometry::UnsignedShortType and QSGGeometry::UnsignedIntType
2222 int paddingBytes = 0;
2223 if (!m_context->separateIndexBuffer()) {
2224 paddingBytes = aligned(v: bufferSize, byteAlign: 4) - bufferSize;
2225 bufferSize += paddingBytes;
2226 }
2227 if (b->merged) {
2228 ibufferSize = b->indexCount * mergedIndexElemSize();
2229 if (m_useDepthBuffer)
2230 bufferSize += b->vertexCount * sizeof(float);
2231 } else {
2232 ibufferSize = unmergedIndexSize;
2233 }
2234
2235 const bool separateIndexBuffer = m_context->separateIndexBuffer();
2236 if (separateIndexBuffer)
2237 map(buffer: &b->ibo, byteSize: ibufferSize, isIndexBuf: true);
2238 else
2239 bufferSize += ibufferSize;
2240 map(buffer: &b->vbo, byteSize: bufferSize);
2241
2242 if (Q_UNLIKELY(debug_upload())) qDebug() << " - batch" << b << " first:" << b->first << " root:"
2243 << b->root << " merged:" << b->merged << " positionAttribute" << b->positionAttribute
2244 << " vbo:" << b->vbo.id << ":" << b->vbo.size;
2245
2246 if (b->merged) {
2247 char *vertexData = b->vbo.data;
2248 char *zData = vertexData + b->vertexCount * g->sizeOfVertex();
2249 char *indexData = separateIndexBuffer
2250 ? b->ibo.data
2251 : zData + (int(m_useDepthBuffer) * b->vertexCount * sizeof(float)) + paddingBytes;
2252
2253 quint16 iOffset16 = 0;
2254 quint32 iOffset32 = 0;
2255 e = b->first;
2256 uint verticesInSet = 0;
2257 // Start a new set already after 65534 vertices because 0xFFFF may be
2258 // used for an always-on primitive restart with some apis (adapt for
2259 // uint32 indices as appropriate).
2260 const uint verticesInSetLimit = m_uint32IndexForRhi ? 0xfffffffe : 0xfffe;
2261 int indicesInSet = 0;
2262 b->drawSets.reset();
2263 int drawSetIndices = separateIndexBuffer ? 0 : indexData - vertexData;
2264 const char *indexBase = separateIndexBuffer ? b->ibo.data : b->vbo.data;
2265 b->drawSets << DrawSet(0, zData - vertexData, drawSetIndices);
2266 while (e) {
2267 verticesInSet += e->node->geometry()->vertexCount();
2268 if (verticesInSet > verticesInSetLimit) {
2269 b->drawSets.last().indexCount = indicesInSet;
2270 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
2271 b->drawSets.last().indices += 1 * mergedIndexElemSize();
2272 b->drawSets.last().indexCount -= 2;
2273 }
2274 drawSetIndices = indexData - indexBase;
2275 b->drawSets << DrawSet(vertexData - b->vbo.data,
2276 zData - b->vbo.data,
2277 drawSetIndices);
2278 iOffset16 = 0;
2279 iOffset32 = 0;
2280 verticesInSet = e->node->geometry()->vertexCount();
2281 indicesInSet = 0;
2282 }
2283 void *iBasePtr = &iOffset16;
2284 if (m_uint32IndexForRhi)
2285 iBasePtr = &iOffset32;
2286 uploadMergedElement(e, vaOffset: b->positionAttribute, vertexData: &vertexData, zData: &zData, indexData: &indexData, iBasePtr, indexCount: &indicesInSet);
2287 e = e->nextInBatch;
2288 }
2289 b->drawSets.last().indexCount = indicesInSet;
2290 // We skip the very first and very last degenerate triangles since they aren't needed
2291 // and the first one would reverse the vertex ordering of the merged strips.
2292 if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) {
2293 b->drawSets.last().indices += 1 * mergedIndexElemSize();
2294 b->drawSets.last().indexCount -= 2;
2295 }
2296 } else {
2297 char *vboData = b->vbo.data;
2298 char *iboData = separateIndexBuffer ? b->ibo.data
2299 : vboData + b->vertexCount * g->sizeOfVertex() + paddingBytes;
2300 Element *e = b->first;
2301 while (e) {
2302 QSGGeometry *g = e->node->geometry();
2303 int vbs = g->vertexCount() * g->sizeOfVertex();
2304 memcpy(dest: vboData, src: g->vertexData(), n: vbs);
2305 vboData = vboData + vbs;
2306 const int indexCount = g->indexCount();
2307 if (indexCount) {
2308 if (!m_rhi) {
2309 int ibs = g->indexCount() * g->sizeOfIndex();
2310 memcpy(dest: iboData, src: g->indexData(), n: ibs);
2311 iboData += ibs;
2312 } else {
2313 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
2314 const int ibs = indexCount * effectiveIndexSize;
2315 if (g->sizeOfIndex() == effectiveIndexSize) {
2316 memcpy(dest: iboData, src: g->indexData(), n: ibs);
2317 } else {
2318 if (g->sizeOfIndex() == sizeof(quint16) && effectiveIndexSize == sizeof(quint32)) {
2319 quint16 *src = g->indexDataAsUShort();
2320 quint32 *dst = (quint32 *) iboData;
2321 for (int i = 0; i < indexCount; ++i)
2322 dst[i] = src[i];
2323 } else {
2324 Q_ASSERT_X(false, "uploadBatch (unmerged)", "uint index with ushort effective index - cannot happen");
2325 }
2326 }
2327 iboData += ibs;
2328 }
2329 }
2330 e = e->nextInBatch;
2331 }
2332 }
2333#ifndef QT_NO_DEBUG_OUTPUT
2334 if (Q_UNLIKELY(debug_upload())) {
2335 const char *vd = b->vbo.data;
2336 qDebug() << " -- Vertex Data, count:" << b->vertexCount << " - " << g->sizeOfVertex() << "bytes/vertex";
2337 for (int i=0; i<b->vertexCount; ++i) {
2338 QDebug dump = qDebug().nospace();
2339 dump << " --- " << i << ": ";
2340 int offset = 0;
2341 for (int a=0; a<g->attributeCount(); ++a) {
2342 const QSGGeometry::Attribute &attr = g->attributes()[a];
2343 dump << attr.position << ":(" << attr.tupleSize << ",";
2344 if (attr.type == QSGGeometry::FloatType) {
2345 dump << "float ";
2346 if (attr.isVertexCoordinate)
2347 dump << "* ";
2348 for (int t=0; t<attr.tupleSize; ++t)
2349 dump << *(const float *)(vd + offset + t * sizeof(float)) << " ";
2350 } else if (attr.type == QSGGeometry::UnsignedByteType) {
2351 dump << "ubyte ";
2352 for (int t=0; t<attr.tupleSize; ++t)
2353 dump << *(const unsigned char *)(vd + offset + t * sizeof(unsigned char)) << " ";
2354 }
2355 dump << ") ";
2356 offset += attr.tupleSize * size_of_type(type: attr.type);
2357 }
2358 if (b->merged && m_useDepthBuffer) {
2359 float zorder = ((float*)(b->vbo.data + b->vertexCount * g->sizeOfVertex()))[i];
2360 dump << " Z:(" << zorder << ")";
2361 }
2362 vd += g->sizeOfVertex();
2363 }
2364
2365 if (!b->drawSets.isEmpty()) {
2366 if (m_uint32IndexForRhi) {
2367 const quint32 *id = (const quint32 *)(separateIndexBuffer
2368 ? b->ibo.data
2369 : b->vbo.data + b->drawSets.at(i: 0).indices);
2370 {
2371 QDebug iDump = qDebug();
2372 iDump << " -- Index Data, count:" << b->indexCount;
2373 for (int i=0; i<b->indexCount; ++i) {
2374 if ((i % 24) == 0)
2375 iDump << Qt::endl << " --- ";
2376 iDump << id[i];
2377 }
2378 }
2379 } else {
2380 const quint16 *id = (const quint16 *)(separateIndexBuffer
2381 ? b->ibo.data
2382 : b->vbo.data + b->drawSets.at(i: 0).indices);
2383 {
2384 QDebug iDump = qDebug();
2385 iDump << " -- Index Data, count:" << b->indexCount;
2386 for (int i=0; i<b->indexCount; ++i) {
2387 if ((i % 24) == 0)
2388 iDump << Qt::endl << " --- ";
2389 iDump << id[i];
2390 }
2391 }
2392 }
2393
2394 for (int i=0; i<b->drawSets.size(); ++i) {
2395 const DrawSet &s = b->drawSets.at(i);
2396 qDebug() << " -- DrawSet: indexCount:" << s.indexCount << " vertices:" << s.vertices << " z:" << s.zorders << " indices:" << s.indices;
2397 }
2398 }
2399 }
2400#endif // QT_NO_DEBUG_OUTPUT
2401
2402 unmap(buffer: &b->vbo);
2403 if (separateIndexBuffer)
2404 unmap(buffer: &b->ibo, isIndexBuf: true);
2405
2406 if (Q_UNLIKELY(debug_upload())) qDebug() << " --- vertex/index buffers unmapped, batch upload completed...";
2407
2408 b->needsUpload = false;
2409
2410 if (Q_UNLIKELY(debug_render()))
2411 b->uploadedThisFrame = true;
2412}
2413
2414/*!
2415 * Convenience function to set up the stencil buffer for clipping based on \a clip.
2416 *
2417 * If the clip is a pixel aligned rectangle, this function will use glScissor instead
2418 * of stencil.
2419 */
2420ClipState::ClipType Renderer::updateStencilClip(const QSGClipNode *clip)
2421{
2422 if (!clip) {
2423 glDisable(GL_STENCIL_TEST);
2424 glDisable(GL_SCISSOR_TEST);
2425 return ClipState::NoClip;
2426 }
2427
2428 ClipState::ClipType clipType = ClipState::NoClip;
2429 GLuint vbo = 0;
2430 int vboSize = 0;
2431
2432 bool useVBO = false;
2433 QOpenGLContext *ctx = m_context->openglContext();
2434 QSurfaceFormat::OpenGLContextProfile profile = ctx->format().profile();
2435
2436 if (!ctx->isOpenGLES() && profile == QSurfaceFormat::CoreProfile) {
2437 // VBO are more expensive, so only use them if we must.
2438 useVBO = true;
2439 }
2440
2441 glDisable(GL_SCISSOR_TEST);
2442
2443 m_currentStencilValue = 0;
2444 m_currentScissorRect = QRect();
2445 while (clip) {
2446 QMatrix4x4 m = m_current_projection_matrix;
2447 if (clip->matrix())
2448 m *= *clip->matrix();
2449
2450 // TODO: Check for multisampling and pixel grid alignment.
2451 bool isRectangleWithNoPerspective = clip->isRectangular()
2452 && qFuzzyIsNull(f: m(3, 0)) && qFuzzyIsNull(f: m(3, 1));
2453 bool noRotate = qFuzzyIsNull(f: m(0, 1)) && qFuzzyIsNull(f: m(1, 0));
2454 bool isRotate90 = qFuzzyIsNull(f: m(0, 0)) && qFuzzyIsNull(f: m(1, 1));
2455
2456 if (isRectangleWithNoPerspective && (noRotate || isRotate90)) {
2457 QRectF bbox = clip->clipRect();
2458 qreal invW = 1 / m(3, 3);
2459 qreal fx1, fy1, fx2, fy2;
2460 if (noRotate) {
2461 fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
2462 fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
2463 fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
2464 fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
2465 } else {
2466 Q_ASSERT(isRotate90);
2467 fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
2468 fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
2469 fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
2470 fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
2471 }
2472
2473 if (fx1 > fx2)
2474 qSwap(value1&: fx1, value2&: fx2);
2475 if (fy1 > fy2)
2476 qSwap(value1&: fy1, value2&: fy2);
2477
2478 QRect deviceRect = this->deviceRect();
2479
2480 GLint ix1 = qRound(d: (fx1 + 1) * deviceRect.width() * qreal(0.5));
2481 GLint iy1 = qRound(d: (fy1 + 1) * deviceRect.height() * qreal(0.5));
2482 GLint ix2 = qRound(d: (fx2 + 1) * deviceRect.width() * qreal(0.5));
2483 GLint iy2 = qRound(d: (fy2 + 1) * deviceRect.height() * qreal(0.5));
2484
2485 if (!(clipType & ClipState::ScissorClip)) {
2486 m_currentScissorRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2487 glEnable(GL_SCISSOR_TEST);
2488 clipType |= ClipState::ScissorClip;
2489 } else {
2490 m_currentScissorRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2491 }
2492 glScissor(x: m_currentScissorRect.x(), y: m_currentScissorRect.y(),
2493 width: m_currentScissorRect.width(), height: m_currentScissorRect.height());
2494 } else {
2495 if (!(clipType & ClipState::StencilClip)) {
2496 if (!m_clipProgram.isLinked()) {
2497 QSGShaderSourceBuilder::initializeProgramFromFiles(
2498 program: &m_clipProgram,
2499 QStringLiteral(":/qt-project.org/scenegraph/shaders/stencilclip.vert"),
2500 QStringLiteral(":/qt-project.org/scenegraph/shaders/stencilclip.frag"));
2501 m_clipProgram.bindAttributeLocation(name: "vCoord", location: 0);
2502 m_clipProgram.link();
2503 m_clipMatrixId = m_clipProgram.uniformLocation(name: "matrix");
2504 }
2505
2506 glClearStencil(s: 0);
2507 glClear(GL_STENCIL_BUFFER_BIT);
2508 glEnable(GL_STENCIL_TEST);
2509 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
2510 glDepthMask(GL_FALSE);
2511
2512 m_clipProgram.bind();
2513 m_clipProgram.enableAttributeArray(location: 0);
2514
2515 clipType |= ClipState::StencilClip;
2516 }
2517
2518 glStencilFunc(GL_EQUAL, ref: m_currentStencilValue, mask: 0xff); // stencil test, ref, test mask
2519 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); // stencil fail, z fail, z pass
2520
2521 const QSGGeometry *g = clip->geometry();
2522 Q_ASSERT(g->attributeCount() > 0);
2523 const QSGGeometry::Attribute *a = g->attributes();
2524
2525 const GLvoid *pointer;
2526 if (!useVBO) {
2527 pointer = g->vertexData();
2528 } else {
2529 if (!vbo)
2530 glGenBuffers(n: 1, buffers: &vbo);
2531
2532 glBindBuffer(GL_ARRAY_BUFFER, buffer: vbo);
2533
2534 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2535 if (vboSize < vertexByteSize) {
2536 vboSize = vertexByteSize;
2537 glBufferData(GL_ARRAY_BUFFER, size: vertexByteSize, data: g->vertexData(), GL_STATIC_DRAW);
2538 } else {
2539 glBufferSubData(GL_ARRAY_BUFFER, offset: 0, size: vertexByteSize, data: g->vertexData());
2540 }
2541
2542 pointer = nullptr;
2543 }
2544
2545 glVertexAttribPointer(indx: 0, size: a->tupleSize, type: a->type, GL_FALSE, stride: g->sizeOfVertex(), ptr: pointer);
2546
2547 m_clipProgram.setUniformValue(location: m_clipMatrixId, value: m);
2548 if (g->indexCount()) {
2549 glDrawElements(mode: g->drawingMode(), count: g->indexCount(), type: g->indexType(), indices: g->indexData());
2550 } else {
2551 glDrawArrays(mode: g->drawingMode(), first: 0, count: g->vertexCount());
2552 }
2553
2554 if (useVBO)
2555 glBindBuffer(GL_ARRAY_BUFFER, buffer: 0);
2556
2557 ++m_currentStencilValue;
2558 }
2559
2560 clip = clip->clipList();
2561 }
2562
2563 if (vbo)
2564 glDeleteBuffers(n: 1, buffers: &vbo);
2565
2566 if (clipType & ClipState::StencilClip) {
2567 m_clipProgram.disableAttributeArray(location: 0);
2568 glStencilFunc(GL_EQUAL, ref: m_currentStencilValue, mask: 0xff); // stencil test, ref, test mask
2569 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // stencil fail, z fail, z pass
2570 bindable()->reactivate();
2571 } else {
2572 glDisable(GL_STENCIL_TEST);
2573 }
2574
2575 return clipType;
2576}
2577
2578void Renderer::updateClip(const QSGClipNode *clipList, const Batch *batch) // legacy (GL-only)
2579{
2580 if (clipList != m_currentClip && Q_LIKELY(!debug_noclip())) {
2581 m_currentClip = clipList;
2582 // updateClip sets another program, so force-reactivate our own
2583 if (m_currentShader)
2584 setActiveShader(program: nullptr, shader: nullptr);
2585 glBindBuffer(GL_ARRAY_BUFFER, buffer: 0);
2586 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer: 0);
2587 if (batch->isOpaque)
2588 glDisable(GL_DEPTH_TEST);
2589 m_currentClipType = updateStencilClip(clip: m_currentClip);
2590 if (batch->isOpaque) {
2591 glEnable(GL_DEPTH_TEST);
2592 if (m_currentClipType & ClipState::StencilClip)
2593 glDepthMask(flag: true);
2594 }
2595 }
2596}
2597
2598/*!
2599 * Look at the attribute arrays and potentially the injected z attribute to figure out
2600 * which vertex attribute arrays need to be enabled and not. Then update the current
2601 * Shader and current QSGMaterialShader.
2602 */
2603void Renderer::setActiveShader(QSGMaterialShader *program, ShaderManager::Shader *shader) // legacy (GL-only)
2604{
2605 Q_ASSERT(!m_rhi);
2606 const char * const *c = m_currentProgram ? m_currentProgram->attributeNames() : nullptr;
2607 const char * const *n = program ? program->attributeNames() : nullptr;
2608
2609 int cza = m_currentShader ? m_currentShader->programGL.pos_order : -1;
2610 int nza = shader ? shader->programGL.pos_order : -1;
2611
2612 int i = 0;
2613 while (c || n) {
2614
2615 bool was = c;
2616 if (cza == i) {
2617 was = true;
2618 c = nullptr;
2619 } else if (c && !c[i]) { // end of the attribute array names
2620 c = nullptr;
2621 was = false;
2622 }
2623
2624 bool is = n;
2625 if (nza == i) {
2626 is = true;
2627 n = nullptr;
2628 } else if (n && !n[i]) {
2629 n = nullptr;
2630 is = false;
2631 }
2632
2633 if (is && !was)
2634 glEnableVertexAttribArray(index: i);
2635 else if (was && !is)
2636 glDisableVertexAttribArray(index: i);
2637
2638 ++i;
2639 }
2640
2641 if (m_currentProgram)
2642 m_currentProgram->deactivate();
2643 m_currentProgram = program;
2644 m_currentShader = shader;
2645 m_currentMaterial = nullptr;
2646 if (m_currentProgram) {
2647 m_currentProgram->program()->bind();
2648 m_currentProgram->activate();
2649 }
2650}
2651
2652void Renderer::applyClipStateToGraphicsState() // RHI only
2653{
2654 m_gstate.usesScissor = (m_currentClipState.type & ClipState::ScissorClip);
2655 m_gstate.stencilTest = (m_currentClipState.type & ClipState::StencilClip);
2656}
2657
2658QRhiGraphicsPipeline *Renderer::buildStencilPipeline(const Batch *batch, bool firstStencilClipInBatch)
2659{
2660 QRhiGraphicsPipeline *ps = m_rhi->newGraphicsPipeline();
2661 ps->setFlags(QRhiGraphicsPipeline::UsesStencilRef);
2662 QRhiGraphicsPipeline::TargetBlend blend;
2663 blend.colorWrite = {};
2664 ps->setTargetBlends({ blend });
2665 ps->setSampleCount(renderTarget()->sampleCount());
2666 ps->setStencilTest(true);
2667 QRhiGraphicsPipeline::StencilOpState stencilOp;
2668 if (firstStencilClipInBatch) {
2669 stencilOp.compareOp = QRhiGraphicsPipeline::Always;
2670 stencilOp.failOp = QRhiGraphicsPipeline::Keep;
2671 stencilOp.depthFailOp = QRhiGraphicsPipeline::Keep;
2672 stencilOp.passOp = QRhiGraphicsPipeline::Replace;
2673 } else {
2674 stencilOp.compareOp = QRhiGraphicsPipeline::Equal;
2675 stencilOp.failOp = QRhiGraphicsPipeline::Keep;
2676 stencilOp.depthFailOp = QRhiGraphicsPipeline::Keep;
2677 stencilOp.passOp = QRhiGraphicsPipeline::IncrementAndClamp;
2678 }
2679 ps->setStencilFront(stencilOp);
2680 ps->setStencilBack(stencilOp);
2681
2682 ps->setTopology(m_stencilClipCommon.topology);
2683
2684 ps->setShaderStages({ QRhiGraphicsShaderStage(QRhiGraphicsShaderStage::Vertex, m_stencilClipCommon.vs),
2685 QRhiGraphicsShaderStage(QRhiGraphicsShaderStage::Fragment, m_stencilClipCommon.fs) });
2686 ps->setVertexInputLayout(m_stencilClipCommon.inputLayout);
2687 ps->setShaderResourceBindings(batch->