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->stencilClipState.srb); // use something, it just needs to be layout-compatible
2688 ps->setRenderPassDescriptor(renderPassDescriptor());
2689
2690 if (!ps->build()) {
2691 qWarning(msg: "Failed to build stencil clip pipeline");
2692 delete ps;
2693 return nullptr;
2694 }
2695
2696 return ps;
2697}
2698
2699void Renderer::updateClipState(const QSGClipNode *clipList, Batch *batch) // RHI only
2700{
2701 // Note: No use of the clip-related speparate m_current* vars is allowed
2702 // here. All stored in batch->clipState instead. To collect state during
2703 // the prepare steps, m_currentClipState is used. It should not be used in
2704 // the render steps afterwards.
2705
2706 // The stenciling logic is slightly different from the legacy GL path as we
2707 // cannot just randomly clear the stencil buffer. We now put all clip
2708 // shapes into the stencil buffer for all batches in the frame. This means
2709 // that the number of total clips in a scene is reduced (since the stencil
2710 // value cannot exceed 255) but we do not need any clears inbetween.
2711
2712 Q_ASSERT(m_rhi);
2713 batch->stencilClipState.updateStencilBuffer = false;
2714 if (clipList == m_currentClipState.clipList || Q_UNLIKELY(debug_noclip())) {
2715 applyClipStateToGraphicsState();
2716 batch->clipState = m_currentClipState;
2717 return;
2718 }
2719
2720 ClipState::ClipType clipType = ClipState::NoClip;
2721 QRect scissorRect;
2722 QVarLengthArray<const QSGClipNode *, 4> stencilClipNodes;
2723 const QSGClipNode *clip = clipList;
2724
2725 batch->stencilClipState.drawCalls.reset();
2726 int totalVSize = 0;
2727 int totalISize = 0;
2728 int totalUSize = 0;
2729 const int StencilClipUbufSize = 64;
2730
2731 while (clip) {
2732 QMatrix4x4 m = m_current_projection_matrix_native_ndc;
2733 if (clip->matrix())
2734 m *= *clip->matrix();
2735
2736 bool isRectangleWithNoPerspective = clip->isRectangular()
2737 && qFuzzyIsNull(f: m(3, 0)) && qFuzzyIsNull(f: m(3, 1));
2738 bool noRotate = qFuzzyIsNull(f: m(0, 1)) && qFuzzyIsNull(f: m(1, 0));
2739 bool isRotate90 = qFuzzyIsNull(f: m(0, 0)) && qFuzzyIsNull(f: m(1, 1));
2740
2741 if (isRectangleWithNoPerspective && (noRotate || isRotate90)) {
2742 QRectF bbox = clip->clipRect();
2743 qreal invW = 1 / m(3, 3);
2744 qreal fx1, fy1, fx2, fy2;
2745 if (noRotate) {
2746 fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
2747 fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
2748 fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
2749 fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
2750 } else {
2751 Q_ASSERT(isRotate90);
2752 fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
2753 fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
2754 fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
2755 fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
2756 }
2757
2758 if (fx1 > fx2)
2759 qSwap(value1&: fx1, value2&: fx2);
2760 if (fy1 > fy2)
2761 qSwap(value1&: fy1, value2&: fy2);
2762
2763 QRect deviceRect = this->deviceRect();
2764
2765 GLint ix1 = qRound(d: (fx1 + 1) * deviceRect.width() * qreal(0.5));
2766 GLint iy1 = qRound(d: (fy1 + 1) * deviceRect.height() * qreal(0.5));
2767 GLint ix2 = qRound(d: (fx2 + 1) * deviceRect.width() * qreal(0.5));
2768 GLint iy2 = qRound(d: (fy2 + 1) * deviceRect.height() * qreal(0.5));
2769
2770 if (!(clipType & ClipState::ScissorClip)) {
2771 clipType |= ClipState::ScissorClip;
2772 scissorRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2773 } else {
2774 scissorRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
2775 }
2776 } else {
2777 clipType |= ClipState::StencilClip;
2778
2779 const QSGGeometry *g = clip->geometry();
2780 Q_ASSERT(g->attributeCount() > 0);
2781
2782 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2783 // the 4 byte alignment may not actually be needed here
2784 totalVSize = aligned(v: totalVSize, byteAlign: 4) + vertexByteSize;
2785 if (g->indexCount()) {
2786 const int indexByteSize = g->sizeOfIndex() * g->indexCount();
2787 // so no need to worry about NonFourAlignedEffectiveIndexBufferOffset
2788 totalISize = aligned(v: totalISize, byteAlign: 4) + indexByteSize;
2789 }
2790 // ubuf start offsets must be aligned (typically to 256 bytes)
2791 totalUSize = aligned(v: totalUSize, byteAlign: m_ubufAlignment) + StencilClipUbufSize;
2792
2793 stencilClipNodes.append(t: clip);
2794 }
2795
2796 clip = clip->clipList();
2797 }
2798
2799 if (clipType & ClipState::StencilClip) {
2800 bool rebuildVBuf = false;
2801 if (!batch->stencilClipState.vbuf) {
2802 batch->stencilClipState.vbuf = m_rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::VertexBuffer, size: totalVSize);
2803 rebuildVBuf = true;
2804 } else if (batch->stencilClipState.vbuf->size() < totalVSize) {
2805 batch->stencilClipState.vbuf->setSize(totalVSize);
2806 rebuildVBuf = true;
2807 }
2808 if (rebuildVBuf) {
2809 if (!batch->stencilClipState.vbuf->build()) {
2810 qWarning(msg: "Failed to build stencil clip vertex buffer");
2811 delete batch->stencilClipState.vbuf;
2812 batch->stencilClipState.vbuf = nullptr;
2813 return;
2814 }
2815 }
2816
2817 if (totalISize) {
2818 bool rebuildIBuf = false;
2819 if (!batch->stencilClipState.ibuf) {
2820 batch->stencilClipState.ibuf = m_rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::IndexBuffer, size: totalISize);
2821 rebuildIBuf = true;
2822 } else if (batch->stencilClipState.ibuf->size() < totalISize) {
2823 batch->stencilClipState.ibuf->setSize(totalISize);
2824 rebuildIBuf = true;
2825 }
2826 if (rebuildIBuf) {
2827 if (!batch->stencilClipState.ibuf->build()) {
2828 qWarning(msg: "Failed to build stencil clip index buffer");
2829 delete batch->stencilClipState.ibuf;
2830 batch->stencilClipState.ibuf = nullptr;
2831 return;
2832 }
2833 }
2834 }
2835
2836 bool rebuildUBuf = false;
2837 if (!batch->stencilClipState.ubuf) {
2838 batch->stencilClipState.ubuf = m_rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: totalUSize);
2839 rebuildUBuf = true;
2840 } else if (batch->stencilClipState.ubuf->size() < totalUSize) {
2841 batch->stencilClipState.ubuf->setSize(totalUSize);
2842 rebuildUBuf = true;
2843 }
2844 if (rebuildUBuf) {
2845 if (!batch->stencilClipState.ubuf->build()) {
2846 qWarning(msg: "Failed to build stencil clip uniform buffer");
2847 delete batch->stencilClipState.ubuf;
2848 batch->stencilClipState.ubuf = nullptr;
2849 return;
2850 }
2851 }
2852
2853 if (!batch->stencilClipState.srb) {
2854 batch->stencilClipState.srb = m_rhi->newShaderResourceBindings();
2855 const QRhiShaderResourceBinding ubufBinding = QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(
2856 binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: batch->stencilClipState.ubuf, size: StencilClipUbufSize);
2857 batch->stencilClipState.srb->setBindings({ ubufBinding });
2858 if (!batch->stencilClipState.srb->build()) {
2859 qWarning(msg: "Failed to build stencil clip srb");
2860 delete batch->stencilClipState.srb;
2861 batch->stencilClipState.srb = nullptr;
2862 return;
2863 }
2864 }
2865
2866 int vOffset = 0;
2867 int iOffset = 0;
2868 int uOffset = 0;
2869 for (const QSGClipNode *clip : stencilClipNodes) {
2870 const QSGGeometry *g = clip->geometry();
2871 const QSGGeometry::Attribute *a = g->attributes();
2872 StencilClipState::StencilDrawCall drawCall;
2873 const bool firstStencilClipInBatch = batch->stencilClipState.drawCalls.isEmpty();
2874
2875 if (firstStencilClipInBatch) {
2876 m_stencilClipCommon.inputLayout.setBindings({ QRhiVertexInputBinding(g->sizeOfVertex()) });
2877 m_stencilClipCommon.inputLayout.setAttributes({ QRhiVertexInputAttribute(0, 0, qsg_vertexInputFormat(a: *a), 0) });
2878 m_stencilClipCommon.topology = qsg_topology(geomDrawMode: g->drawingMode());
2879 }
2880#ifndef QT_NO_DEBUG
2881 else {
2882 if (qsg_topology(geomDrawMode: g->drawingMode()) != m_stencilClipCommon.topology)
2883 qWarning(msg: "updateClipState: Clip list entries have different primitive topologies, this is not currently supported.");
2884 if (qsg_vertexInputFormat(a: *a) != m_stencilClipCommon.inputLayout.cbeginAttributes()->format())
2885 qWarning(msg: "updateClipState: Clip list entries have different vertex input layouts, this is must not happen.");
2886 }
2887#endif
2888
2889 drawCall.vbufOffset = aligned(v: vOffset, byteAlign: 4);
2890 const int vertexByteSize = g->sizeOfVertex() * g->vertexCount();
2891 vOffset = drawCall.vbufOffset + vertexByteSize;
2892
2893 int indexByteSize = 0;
2894 if (g->indexCount()) {
2895 drawCall.ibufOffset = aligned(v: iOffset, byteAlign: 4);
2896 indexByteSize = g->sizeOfIndex() * g->indexCount();
2897 iOffset = drawCall.ibufOffset + indexByteSize;
2898 }
2899
2900 drawCall.ubufOffset = aligned(v: uOffset, byteAlign: m_ubufAlignment);
2901 uOffset = drawCall.ubufOffset + StencilClipUbufSize;
2902
2903 QMatrix4x4 matrixYUpNDC = m_current_projection_matrix;
2904 if (clip->matrix())
2905 matrixYUpNDC *= *clip->matrix();
2906
2907 m_resourceUpdates->updateDynamicBuffer(buf: batch->stencilClipState.ubuf, offset: drawCall.ubufOffset, size: 64, data: matrixYUpNDC.constData());
2908 m_resourceUpdates->updateDynamicBuffer(buf: batch->stencilClipState.vbuf, offset: drawCall.vbufOffset, size: vertexByteSize, data: g->vertexData());
2909 if (indexByteSize)
2910 m_resourceUpdates->updateDynamicBuffer(buf: batch->stencilClipState.ibuf, offset: drawCall.ibufOffset, size: indexByteSize, data: g->indexData());
2911
2912 // stencil ref goes 1, 1, 2, 3, 4, ..., N for the clips in the first batch,
2913 // then N+1, N+1, N+2, N+3, ... for the next batch,
2914 // and so on.
2915 // Note the different stencilOp for the first and the subsequent clips.
2916 drawCall.stencilRef = firstStencilClipInBatch ? m_currentClipState.stencilRef + 1 : m_currentClipState.stencilRef;
2917 m_currentClipState.stencilRef += 1;
2918
2919 drawCall.vertexCount = g->vertexCount();
2920 drawCall.indexCount = g->indexCount();
2921 drawCall.indexFormat = qsg_indexFormat(geometry: g);
2922 batch->stencilClipState.drawCalls.add(t: drawCall);
2923 }
2924
2925 if (!m_stencilClipCommon.vs.isValid())
2926 m_stencilClipCommon.vs = QSGMaterialRhiShaderPrivate::loadShader(filename: QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.vert.qsb"));
2927
2928 if (!m_stencilClipCommon.fs.isValid())
2929 m_stencilClipCommon.fs = QSGMaterialRhiShaderPrivate::loadShader(filename: QLatin1String(":/qt-project.org/scenegraph/shaders_ng/stencilclip.frag.qsb"));
2930
2931 if (!m_stencilClipCommon.replacePs)
2932 m_stencilClipCommon.replacePs = buildStencilPipeline(batch, firstStencilClipInBatch: true);
2933
2934 if (!m_stencilClipCommon.incrPs)
2935 m_stencilClipCommon.incrPs = buildStencilPipeline(batch, firstStencilClipInBatch: false);
2936
2937 batch->stencilClipState.updateStencilBuffer = true;
2938 }
2939
2940 m_currentClipState.clipList = clipList;
2941 m_currentClipState.type = clipType;
2942 m_currentClipState.scissor = QRhiScissor(scissorRect.x(), scissorRect.y(),
2943 scissorRect.width(), scissorRect.height());
2944
2945 applyClipStateToGraphicsState();
2946 batch->clipState = m_currentClipState;
2947}
2948
2949void Renderer::enqueueStencilDraw(const Batch *batch) // RHI only
2950{
2951 // cliptype stencil + updateStencilBuffer==false means the batch uses
2952 // stenciling but relies on the stencil data generated by a previous batch
2953 // (due to the having the same clip node). Do not enqueue draw calls for
2954 // stencil in this case as the stencil buffer is already up-to-date.
2955 if (!batch->stencilClipState.updateStencilBuffer)
2956 return;
2957
2958 QRhiCommandBuffer *cb = commandBuffer();
2959 const int count = batch->stencilClipState.drawCalls.size();
2960 for (int i = 0; i < count; ++i) {
2961 const StencilClipState::StencilDrawCall &drawCall(batch->stencilClipState.drawCalls.at(i));
2962 QRhiShaderResourceBindings *srb = batch->stencilClipState.srb;
2963 QRhiCommandBuffer::DynamicOffset ubufOffset(0, drawCall.ubufOffset);
2964 if (i == 0) {
2965 cb->setGraphicsPipeline(m_stencilClipCommon.replacePs);
2966 cb->setViewport(m_pstate.viewport);
2967 } else if (i == 1) {
2968 cb->setGraphicsPipeline(m_stencilClipCommon.incrPs);
2969 cb->setViewport(m_pstate.viewport);
2970 }
2971 // else incrPs is already bound
2972 cb->setShaderResources(srb, dynamicOffsetCount: 1, dynamicOffsets: &ubufOffset);
2973 cb->setStencilRef(drawCall.stencilRef);
2974 const QRhiCommandBuffer::VertexInput vbufBinding(batch->stencilClipState.vbuf, drawCall.vbufOffset);
2975 if (drawCall.indexCount) {
2976 cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbufBinding,
2977 indexBuf: batch->stencilClipState.ibuf, indexOffset: drawCall.ibufOffset, indexFormat: drawCall.indexFormat);
2978 cb->drawIndexed(indexCount: drawCall.indexCount);
2979 } else {
2980 cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbufBinding);
2981 cb->draw(vertexCount: drawCall.vertexCount);
2982 }
2983 }
2984}
2985
2986void Renderer::setActiveRhiShader(QSGMaterialRhiShader *program, ShaderManager::Shader *shader) // RHI only
2987{
2988 Q_ASSERT(m_rhi);
2989 m_currentRhiProgram = program;
2990 m_currentShader = shader;
2991 m_currentMaterial = nullptr;
2992}
2993
2994void Renderer::updateLineWidth(QSGGeometry *g) // legacy (GL-only)
2995{
2996 if (g->drawingMode() == GL_LINE_STRIP || g->drawingMode() == GL_LINE_LOOP || g->drawingMode() == GL_LINES)
2997 glLineWidth(width: g->lineWidth());
2998#if !defined(QT_OPENGL_ES_2)
2999 else {
3000 QOpenGLContext *ctx = m_context->openglContext();
3001 if (!ctx->isOpenGLES() && g->drawingMode() == GL_POINTS) {
3002 QOpenGLFunctions_1_0 *gl1funcs = nullptr;
3003 QOpenGLFunctions_3_2_Core *gl3funcs = nullptr;
3004 if (ctx->format().profile() == QSurfaceFormat::CoreProfile)
3005 gl3funcs = ctx->versionFunctions<QOpenGLFunctions_3_2_Core>();
3006 else
3007 gl1funcs = ctx->versionFunctions<QOpenGLFunctions_1_0>();
3008 Q_ASSERT(gl1funcs || gl3funcs);
3009 if (gl1funcs)
3010 gl1funcs->glPointSize(size: g->lineWidth());
3011 else
3012 gl3funcs->glPointSize(size: g->lineWidth());
3013 }
3014 }
3015#endif
3016}
3017
3018void Renderer::renderMergedBatch(const Batch *batch) // legacy (GL-only)
3019{
3020 if (batch->vertexCount == 0 || batch->indexCount == 0)
3021 return;
3022
3023 Element *e = batch->first;
3024 Q_ASSERT(e);
3025
3026#ifndef QT_NO_DEBUG_OUTPUT
3027 if (Q_UNLIKELY(debug_render())) {
3028 QDebug debug = qDebug();
3029 debug << " -"
3030 << batch
3031 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3032 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3033 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3034 << "[ merged]"
3035 << " Nodes:" << QString::fromLatin1(str: "%1").arg(a: qsg_countNodesInBatch(batch), fieldWidth: 4).toLatin1().constData()
3036 << " Vertices:" << QString::fromLatin1(str: "%1").arg(a: batch->vertexCount, fieldWidth: 5).toLatin1().constData()
3037 << " Indices:" << QString::fromLatin1(str: "%1").arg(a: batch->indexCount, fieldWidth: 5).toLatin1().constData()
3038 << " root:" << batch->root;
3039 if (batch->drawSets.size() > 1)
3040 debug << "sets:" << batch->drawSets.size();
3041 if (!batch->isOpaque)
3042 debug << "opacity:" << e->node->inheritedOpacity();
3043 batch->uploadedThisFrame = false;
3044 }
3045#endif
3046
3047 QSGGeometryNode *gn = e->node;
3048
3049 // We always have dirty matrix as all batches are at a unique z range.
3050 QSGMaterialShader::RenderState::DirtyStates dirty = QSGMaterialShader::RenderState::DirtyMatrix;
3051 if (batch->root)
3052 m_current_model_view_matrix = qsg_matrixForRoot(node: batch->root);
3053 else
3054 m_current_model_view_matrix.setToIdentity();
3055 m_current_determinant = m_current_model_view_matrix.determinant();
3056 m_current_projection_matrix = projectionMatrix(); // has potentially been changed by renderUnmergedBatch..
3057
3058 // updateClip() uses m_current_projection_matrix.
3059 updateClip(clipList: gn->clipList(), batch);
3060
3061 glBindBuffer(GL_ARRAY_BUFFER, buffer: batch->vbo.id);
3062
3063 char *indexBase = nullptr;
3064 const Buffer *indexBuf = m_context->separateIndexBuffer() ? &batch->ibo : &batch->vbo;
3065 if (m_context->hasBrokenIndexBufferObjects()) {
3066 indexBase = indexBuf->data;
3067 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer: 0);
3068 } else {
3069 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer: indexBuf->id);
3070 }
3071
3072
3073 QSGMaterial *material = gn->activeMaterial();
3074 ShaderManager::Shader *sms = m_useDepthBuffer ? m_shaderManager->prepareMaterial(material)
3075 : m_shaderManager->prepareMaterialNoRewrite(material);
3076 if (!sms)
3077 return;
3078
3079 Q_ASSERT(sms->programGL.program);
3080 if (m_currentShader != sms)
3081 setActiveShader(program: sms->programGL.program, shader: sms);
3082
3083 m_current_opacity = gn->inheritedOpacity();
3084 if (!qFuzzyCompare(p1: sms->lastOpacity, p2: float(m_current_opacity))) {
3085 dirty |= QSGMaterialShader::RenderState::DirtyOpacity;
3086 sms->lastOpacity = m_current_opacity;
3087 }
3088
3089 sms->programGL.program->updateState(state: state(dirty), newMaterial: material, oldMaterial: m_currentMaterial);
3090
3091#ifndef QT_NO_DEBUG
3092 if (qsg_test_and_clear_material_failure()) {
3093 qDebug(msg: "QSGMaterial::updateState triggered an error (merged), batch will be skipped:");
3094 Element *ee = e;
3095 while (ee) {
3096 qDebug() << " -" << ee->node;
3097 ee = ee->nextInBatch;
3098 }
3099 QSGNodeDumper::dump(n: rootNode());
3100 qFatal(msg: "Aborting: scene graph is invalid...");
3101 }
3102#endif
3103
3104 m_currentMaterial = material;
3105
3106 QSGGeometry *g = gn->geometry();
3107 updateLineWidth(g);
3108 char const *const *attrNames = sms->programGL.program->attributeNames();
3109 for (int i=0; i<batch->drawSets.size(); ++i) {
3110 const DrawSet &draw = batch->drawSets.at(i);
3111 int offset = 0;
3112 for (int j = 0; attrNames[j]; ++j) {
3113 if (!*attrNames[j])
3114 continue;
3115 const QSGGeometry::Attribute &a = g->attributes()[j];
3116 GLboolean normalize = a.type != GL_FLOAT && a.type != GL_DOUBLE;
3117 glVertexAttribPointer(indx: a.position, size: a.tupleSize, type: a.type, normalized: normalize, stride: g->sizeOfVertex(), ptr: (void *) (qintptr) (offset + draw.vertices));
3118 offset += a.tupleSize * size_of_type(type: a.type);
3119 }
3120 if (m_useDepthBuffer)
3121 glVertexAttribPointer(indx: sms->programGL.pos_order, size: 1, GL_FLOAT, normalized: false, stride: 0, ptr: (void *) (qintptr) (draw.zorders));
3122
3123 glDrawElements(mode: g->drawingMode(), count: draw.indexCount, GL_UNSIGNED_SHORT, indices: (void *) (qintptr) (indexBase + draw.indices));
3124 }
3125}
3126
3127void Renderer::renderUnmergedBatch(const Batch *batch) // legacy (GL-only)
3128{
3129 if (batch->vertexCount == 0)
3130 return;
3131
3132 Element *e = batch->first;
3133 Q_ASSERT(e);
3134
3135 if (Q_UNLIKELY(debug_render())) {
3136 qDebug() << " -"
3137 << batch
3138 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3139 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3140 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3141 << "[unmerged]"
3142 << " Nodes:" << QString::fromLatin1(str: "%1").arg(a: qsg_countNodesInBatch(batch), fieldWidth: 4).toLatin1().constData()
3143 << " Vertices:" << QString::fromLatin1(str: "%1").arg(a: batch->vertexCount, fieldWidth: 5).toLatin1().constData()
3144 << " Indices:" << QString::fromLatin1(str: "%1").arg(a: batch->indexCount, fieldWidth: 5).toLatin1().constData()
3145 << " root:" << batch->root;
3146
3147 batch->uploadedThisFrame = false;
3148 }
3149
3150 QSGGeometryNode *gn = e->node;
3151
3152 m_current_projection_matrix = projectionMatrix();
3153 updateClip(clipList: gn->clipList(), batch);
3154
3155 glBindBuffer(GL_ARRAY_BUFFER, buffer: batch->vbo.id);
3156 char *indexBase = nullptr;
3157 const bool separateIndexBuffer = m_context->separateIndexBuffer();
3158 const Buffer *indexBuf = separateIndexBuffer ? &batch->ibo : &batch->vbo;
3159 if (batch->indexCount) {
3160 if (m_context->hasBrokenIndexBufferObjects()) {
3161 indexBase = indexBuf->data;
3162 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer: 0);
3163 } else {
3164 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer: indexBuf->id);
3165 }
3166 }
3167
3168 // We always have dirty matrix as all batches are at a unique z range.
3169 QSGMaterialShader::RenderState::DirtyStates dirty = QSGMaterialShader::RenderState::DirtyMatrix;
3170
3171 QSGMaterial *material = gn->activeMaterial();
3172 ShaderManager::Shader *sms = m_shaderManager->prepareMaterialNoRewrite(material);
3173 if (!sms)
3174 return;
3175
3176 Q_ASSERT(sms->programGL.program);
3177 if (m_currentShader != sms)
3178 setActiveShader(program: sms->programGL.program, shader: sms);
3179
3180 m_current_opacity = gn->inheritedOpacity();
3181 if (sms->lastOpacity != m_current_opacity) {
3182 dirty |= QSGMaterialShader::RenderState::DirtyOpacity;
3183 sms->lastOpacity = m_current_opacity;
3184 }
3185
3186 int vOffset = 0;
3187 char *iOffset = indexBase;
3188 // If a shared buffer is used, 4 byte alignment was done to avoid issues
3189 // while using glDrawElements with both QSGGeometry::UnsignedShortType and
3190 // QSGGeometry::UnsignedIntType. Here, we need to take this into account
3191 // while calculating iOffset value to end up with the correct offset for drawing.
3192 int vertexDataByteSize = batch->vertexCount * gn->geometry()->sizeOfVertex();
3193 vertexDataByteSize = aligned(v: vertexDataByteSize, byteAlign: 4);
3194 if (!separateIndexBuffer)
3195 iOffset += vertexDataByteSize;
3196
3197 QMatrix4x4 rootMatrix = batch->root ? qsg_matrixForRoot(node: batch->root) : QMatrix4x4();
3198
3199 while (e) {
3200 gn = e->node;
3201
3202 m_current_model_view_matrix = rootMatrix * *gn->matrix();
3203 m_current_determinant = m_current_model_view_matrix.determinant();
3204
3205 m_current_projection_matrix = projectionMatrix();
3206 if (m_useDepthBuffer) {
3207 m_current_projection_matrix(2, 2) = m_zRange;
3208 m_current_projection_matrix(2, 3) = 1.0f - e->order * m_zRange;
3209 }
3210
3211 sms->programGL.program->updateState(state: state(dirty), newMaterial: material, oldMaterial: m_currentMaterial);
3212
3213#ifndef QT_NO_DEBUG
3214 if (qsg_test_and_clear_material_failure()) {
3215 qDebug(msg: "QSGMaterial::updateState() triggered an error (unmerged), batch will be skipped:");
3216 qDebug() << " - offending node is" << e->node;
3217 QSGNodeDumper::dump(n: rootNode());
3218 qFatal(msg: "Aborting: scene graph is invalid...");
3219 return;
3220 }
3221#endif
3222
3223 // We don't need to bother with asking each node for its material as they
3224 // are all identical (compare==0) since they are in the same batch.
3225 m_currentMaterial = material;
3226
3227 QSGGeometry *g = gn->geometry();
3228 char const *const *attrNames = sms->programGL.program->attributeNames();
3229 int offset = 0;
3230 for (int j = 0; attrNames[j]; ++j) {
3231 if (!*attrNames[j])
3232 continue;
3233 const QSGGeometry::Attribute &a = g->attributes()[j];
3234 GLboolean normalize = a.type != GL_FLOAT && a.type != GL_DOUBLE;
3235 glVertexAttribPointer(indx: a.position, size: a.tupleSize, type: a.type, normalized: normalize, stride: g->sizeOfVertex(), ptr: (void *) (qintptr) (offset + vOffset));
3236 offset += a.tupleSize * size_of_type(type: a.type);
3237 }
3238
3239 updateLineWidth(g);
3240 if (g->indexCount())
3241 glDrawElements(mode: g->drawingMode(), count: g->indexCount(), type: g->indexType(), indices: iOffset);
3242 else
3243 glDrawArrays(mode: g->drawingMode(), first: 0, count: g->vertexCount());
3244
3245 vOffset += g->sizeOfVertex() * g->vertexCount();
3246 iOffset += g->indexCount() * g->sizeOfIndex();
3247
3248 // We only need to push this on the very first iteration...
3249 dirty &= ~QSGMaterialShader::RenderState::DirtyOpacity;
3250
3251 e = e->nextInBatch;
3252 }
3253}
3254
3255static inline bool needsBlendConstant(QRhiGraphicsPipeline::BlendFactor f)
3256{
3257 return f == QRhiGraphicsPipeline::ConstantColor
3258 || f == QRhiGraphicsPipeline::OneMinusConstantColor
3259 || f == QRhiGraphicsPipeline::ConstantAlpha
3260 || f == QRhiGraphicsPipeline::OneMinusConstantAlpha;
3261}
3262
3263// With QRhi renderBatches() is split to two steps: prepare and render.
3264//
3265// Prepare goes through the batches and elements, and set up a graphics
3266// pipeline, srb, uniform buffer, calculates clipping, based on m_gstate, the
3267// material (shaders), and the batches. This step does not touch the command
3268// buffer or renderpass-related state (m_pstate).
3269//
3270// The render step then starts a renderpass, and goes through all
3271// batches/elements again and records setGraphicsPipeline, drawIndexed, etc. on
3272// the command buffer. The prepare step's accumulated global state like
3273// m_gstate must not be used here. Rather, all data needed for rendering is
3274// available from Batch/Element at this stage. Bookkeeping of state in the
3275// renderpass is done via m_pstate.
3276
3277bool Renderer::ensurePipelineState(Element *e, const ShaderManager::Shader *sms) // RHI only, [prepare step]
3278{
3279 // In unmerged batches the srbs in the elements are all compatible
3280 // layout-wise. Note the key's == and qHash implementations: the rp desc and
3281 // srb are tested for (layout) compatibility, not pointer equality.
3282 const GraphicsPipelineStateKey k { .state: m_gstate, .sms: sms, .compatibleRenderPassDescriptor: renderPassDescriptor(), .layoutCompatibleSrb: e->srb };
3283
3284 // Note: dynamic state (viewport rect, scissor rect, stencil ref, blend
3285 // constant) is never a part of GraphicsState/QRhiGraphicsPipeline.
3286
3287 // See if there is an existing, matching pipeline state object.
3288 auto it = m_shaderManager->pipelineCache.constFind(key: k);
3289 if (it != m_shaderManager->pipelineCache.constEnd()) {
3290 e->ps = *it;
3291 return true;
3292 }
3293
3294 // Build a new one. This is potentially expensive.
3295 QRhiGraphicsPipeline *ps = m_rhi->newGraphicsPipeline();
3296 ps->setShaderStages(first: sms->programRhi.shaderStages.cbegin(), last: sms->programRhi.shaderStages.cend());
3297 ps->setVertexInputLayout(sms->programRhi.inputLayout);
3298 ps->setShaderResourceBindings(e->srb);
3299 ps->setRenderPassDescriptor(renderPassDescriptor());
3300
3301 QRhiGraphicsPipeline::Flags flags;
3302 if (needsBlendConstant(f: m_gstate.srcColor) || needsBlendConstant(f: m_gstate.dstColor))
3303 flags |= QRhiGraphicsPipeline::UsesBlendConstants;
3304 if (m_gstate.usesScissor)
3305 flags |= QRhiGraphicsPipeline::UsesScissor;
3306 if (m_gstate.stencilTest)
3307 flags |= QRhiGraphicsPipeline::UsesStencilRef;
3308
3309 ps->setFlags(flags);
3310 ps->setTopology(qsg_topology(geomDrawMode: m_gstate.drawMode));
3311 ps->setCullMode(m_gstate.cullMode);
3312
3313 QRhiGraphicsPipeline::TargetBlend blend;
3314 blend.colorWrite = m_gstate.colorWrite;
3315 blend.enable = m_gstate.blending;
3316 blend.srcColor = m_gstate.srcColor;
3317 blend.dstColor = m_gstate.dstColor;
3318 ps->setTargetBlends({ blend });
3319
3320 ps->setDepthTest(m_gstate.depthTest);
3321 ps->setDepthWrite(m_gstate.depthWrite);
3322 ps->setDepthOp(m_gstate.depthFunc);
3323
3324 if (m_gstate.stencilTest) {
3325 ps->setStencilTest(true);
3326 QRhiGraphicsPipeline::StencilOpState stencilOp;
3327 stencilOp.compareOp = QRhiGraphicsPipeline::Equal;
3328 stencilOp.failOp = QRhiGraphicsPipeline::Keep;
3329 stencilOp.depthFailOp = QRhiGraphicsPipeline::Keep;
3330 stencilOp.passOp = QRhiGraphicsPipeline::Keep;
3331 ps->setStencilFront(stencilOp);
3332 ps->setStencilBack(stencilOp);
3333 }
3334
3335 ps->setSampleCount(m_gstate.sampleCount);
3336
3337 ps->setLineWidth(m_gstate.lineWidth);
3338
3339 //qDebug("building new ps %p", ps);
3340 if (!ps->build()) {
3341 qWarning(msg: "Failed to build graphics pipeline state");
3342 delete ps;
3343 return false;
3344 }
3345
3346 m_shaderManager->pipelineCache.insert(key: k, value: ps);
3347 e->ps = ps;
3348 return true;
3349}
3350
3351static QRhiSampler *newSampler(QRhi *rhi, const QSGSamplerDescription &desc)
3352{
3353 QRhiSampler::Filter magFilter;
3354 QRhiSampler::Filter minFilter;
3355 QRhiSampler::Filter mipmapMode;
3356 QRhiSampler::AddressMode u;
3357 QRhiSampler::AddressMode v;
3358
3359 switch (desc.filtering) {
3360 case QSGTexture::None:
3361 Q_FALLTHROUGH();
3362 case QSGTexture::Nearest:
3363 magFilter = minFilter = QRhiSampler::Nearest;
3364 break;
3365 case QSGTexture::Linear:
3366 magFilter = minFilter = QRhiSampler::Linear;
3367 break;
3368 default:
3369 Q_UNREACHABLE();
3370 magFilter = minFilter = QRhiSampler::Nearest;
3371 break;
3372 }
3373
3374 switch (desc.mipmapFiltering) {
3375 case QSGTexture::None:
3376 mipmapMode = QRhiSampler::None;
3377 break;
3378 case QSGTexture::Nearest:
3379 mipmapMode = QRhiSampler::Nearest;
3380 break;
3381 case QSGTexture::Linear:
3382 mipmapMode = QRhiSampler::Linear;
3383 break;
3384 default:
3385 Q_UNREACHABLE();
3386 mipmapMode = QRhiSampler::None;
3387 break;
3388 }
3389
3390 switch (desc.horizontalWrap) {
3391 case QSGTexture::Repeat:
3392 u = QRhiSampler::Repeat;
3393 break;
3394 case QSGTexture::ClampToEdge:
3395 u = QRhiSampler::ClampToEdge;
3396 break;
3397 case QSGTexture::MirroredRepeat:
3398 u = QRhiSampler::Mirror;
3399 break;
3400 default:
3401 Q_UNREACHABLE();
3402 u = QRhiSampler::ClampToEdge;
3403 break;
3404 }
3405
3406 switch (desc.verticalWrap) {
3407 case QSGTexture::Repeat:
3408 v = QRhiSampler::Repeat;
3409 break;
3410 case QSGTexture::ClampToEdge:
3411 v = QRhiSampler::ClampToEdge;
3412 break;
3413 case QSGTexture::MirroredRepeat:
3414 v = QRhiSampler::Mirror;
3415 break;
3416 default:
3417 Q_UNREACHABLE();
3418 v = QRhiSampler::ClampToEdge;
3419 break;
3420 }
3421
3422 return rhi->newSampler(magFilter, minFilter, mipmapMode, addressU: u, addressV: v);
3423}
3424
3425QRhiTexture *Renderer::dummyTexture()
3426{
3427 if (!m_dummyTexture) {
3428 m_dummyTexture = m_rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: QSize(64, 64));
3429 if (m_dummyTexture->build()) {
3430 if (m_resourceUpdates) {
3431 QImage img(m_dummyTexture->pixelSize(), QImage::Format_RGBA8888_Premultiplied);
3432 img.fill(pixel: 0);
3433 m_resourceUpdates->uploadTexture(tex: m_dummyTexture, image: img);
3434 }
3435 }
3436 }
3437 return m_dummyTexture;
3438}
3439
3440static void rendererToMaterialGraphicsState(QSGMaterialRhiShader::GraphicsPipelineState *dst,
3441 GraphicsState *src)
3442{
3443 dst->blendEnable = src->blending;
3444
3445 // the enum values should match, sanity check it
3446 Q_ASSERT(int(QSGMaterialRhiShader::GraphicsPipelineState::OneMinusSrc1Alpha) == int(QRhiGraphicsPipeline::OneMinusSrc1Alpha));
3447 Q_ASSERT(int(QSGMaterialRhiShader::GraphicsPipelineState::A) == int(QRhiGraphicsPipeline::A));
3448 Q_ASSERT(int(QSGMaterialRhiShader::GraphicsPipelineState::CullBack) == int(QRhiGraphicsPipeline::Back));
3449
3450 dst->srcColor = QSGMaterialRhiShader::GraphicsPipelineState::BlendFactor(src->srcColor);
3451 dst->dstColor = QSGMaterialRhiShader::GraphicsPipelineState::BlendFactor(src->dstColor);
3452
3453 dst->colorWrite = QSGMaterialRhiShader::GraphicsPipelineState::ColorMask(int(src->colorWrite));
3454
3455 dst->cullMode = QSGMaterialRhiShader::GraphicsPipelineState::CullMode(src->cullMode);
3456}
3457
3458static void materialToRendererGraphicsState(GraphicsState *dst,
3459 QSGMaterialRhiShader::GraphicsPipelineState *src)
3460{
3461 dst->blending = src->blendEnable;
3462 dst->srcColor = QRhiGraphicsPipeline::BlendFactor(src->srcColor);
3463 dst->dstColor = QRhiGraphicsPipeline::BlendFactor(src->dstColor);
3464 dst->colorWrite = QRhiGraphicsPipeline::ColorMask(int(src->colorWrite));
3465 dst->cullMode = QRhiGraphicsPipeline::CullMode(src->cullMode);
3466}
3467
3468void Renderer::updateMaterialDynamicData(ShaderManager::Shader *sms,
3469 QSGMaterialRhiShader::RenderState &renderState,
3470 QSGMaterial *material,
3471 ShaderManager::ShaderResourceBindingList *bindings,
3472 const Batch *batch,
3473 int ubufOffset,
3474 int ubufRegionSize) // RHI only, [prepare step]
3475{
3476 m_current_resource_update_batch = m_resourceUpdates;
3477
3478 QSGMaterialRhiShader *shader = sms->programRhi.program;
3479 QSGMaterialRhiShaderPrivate *pd = QSGMaterialRhiShaderPrivate::get(s: shader);
3480 if (pd->ubufBinding >= 0) {
3481 m_current_uniform_data = &pd->masterUniformData;
3482 const bool changed = shader->updateUniformData(state&: renderState, newMaterial: material, oldMaterial: m_currentMaterial);
3483 m_current_uniform_data = nullptr;
3484
3485 if (changed || !batch->ubufDataValid)
3486 m_resourceUpdates->updateDynamicBuffer(buf: batch->ubuf, offset: ubufOffset, size: ubufRegionSize, data: pd->masterUniformData.constData());
3487
3488 bindings->append(t: QRhiShaderResourceBinding::uniformBuffer(binding: pd->ubufBinding,
3489 stage: pd->ubufStages,
3490 buf: batch->ubuf,
3491 offset: ubufOffset,
3492 size: ubufRegionSize));
3493 }
3494
3495 for (int binding = 0; binding < QSGMaterialRhiShaderPrivate::MAX_SHADER_RESOURCE_BINDINGS; ++binding) {
3496 const QRhiShaderResourceBinding::StageFlags stages = pd->combinedImageSamplerBindings[binding];
3497 if (!stages)
3498 continue;
3499
3500 QSGTexture *prevTex = pd->textureBindingTable[binding];
3501 QSGTexture *t = prevTex;
3502
3503 shader->updateSampledImage(state&: renderState, binding, texture: &t, newMaterial: material, oldMaterial: m_currentMaterial);
3504 if (!t) {
3505 qWarning(msg: "No QSGTexture provided from updateSampledImage(). This is wrong.");
3506 continue;
3507 }
3508
3509 QSGTexturePrivate *td = QSGTexturePrivate::get(t);
3510 // prevTex may be invalid at this point, avoid dereferencing it
3511 if (t != prevTex || td->hasDirtySamplerOptions()) {
3512 // The QSGTexture, and so the sampler parameters, may have changed.
3513 // The rhiTexture is not relevant here.
3514 td->resetDirtySamplerOptions();
3515 pd->textureBindingTable[binding] = t; // does not own
3516 pd->samplerBindingTable[binding] = nullptr;
3517 if (t->anisotropyLevel() != QSGTexture::AnisotropyNone) // ###
3518 qWarning(msg: "QSGTexture anisotropy levels are not currently supported");
3519
3520 const QSGSamplerDescription samplerDesc = QSGSamplerDescription::fromTexture(t);
3521 QRhiSampler *sampler = nullptr;
3522 auto it = m_samplers.constFind(key: samplerDesc);
3523 if (it != m_samplers.constEnd()) {
3524 sampler = *it;
3525 Q_ASSERT(sampler);
3526 } else {
3527 sampler = newSampler(rhi: m_rhi, desc: samplerDesc);
3528 if (!sampler->build()) {
3529 qWarning(msg: "Failed to build sampler");
3530 delete sampler;
3531 continue;
3532 }
3533 m_samplers.insert(key: samplerDesc, value: sampler);
3534 }
3535 pd->samplerBindingTable[binding] = sampler; // does not own
3536 }
3537
3538 if (pd->textureBindingTable[binding] && pd->samplerBindingTable[binding]) {
3539 QRhiTexture *texture = QSGTexturePrivate::get(t: pd->textureBindingTable[binding])->rhiTexture();
3540 // texture may be null if the update above failed for any reason,
3541 // or if the QSGTexture chose to return null intentionally. This is
3542 // valid and we still need to provide something to the shader.
3543 if (!texture)
3544 texture = dummyTexture();
3545 QRhiSampler *sampler = pd->samplerBindingTable[binding];
3546 bindings->append(t: QRhiShaderResourceBinding::sampledTexture(binding,
3547 stage: stages,
3548 tex: texture,
3549 sampler));
3550 }
3551 }
3552
3553#ifndef QT_NO_DEBUG
3554 if (bindings->isEmpty())
3555 qWarning(msg: "No shader resources for material %p, this is odd.", material);
3556#endif
3557}
3558
3559void Renderer::updateMaterialStaticData(ShaderManager::Shader *sms,
3560 QSGMaterialRhiShader::RenderState &renderState,
3561 QSGMaterial *material,
3562 Batch *batch,
3563 bool *gstateChanged) // RHI only, [prepare step]
3564{
3565 QSGMaterialRhiShader *shader = sms->programRhi.program;
3566 *gstateChanged = false;
3567 if (shader->flags().testFlag(flag: QSGMaterialRhiShader::UpdatesGraphicsPipelineState)) {
3568 // generate the public mini-state from m_gstate, invoke the material,
3569 // write the changes, if any, back to m_gstate, together with a way to
3570 // roll those back.
3571 QSGMaterialRhiShader::GraphicsPipelineState shaderPs;
3572 rendererToMaterialGraphicsState(dst: &shaderPs, src: &m_gstate);
3573 const bool changed = shader->updateGraphicsPipelineState(state&: renderState, ps: &shaderPs, newMaterial: material, oldMaterial: m_currentMaterial);
3574 if (changed) {
3575 m_gstateStack.push(t: m_gstate);
3576 materialToRendererGraphicsState(dst: &m_gstate, src: &shaderPs);
3577 if (needsBlendConstant(f: m_gstate.srcColor) || needsBlendConstant(f: m_gstate.dstColor))
3578 batch->blendConstant = shaderPs.blendConstant;
3579 *gstateChanged = true;
3580 }
3581 }
3582}
3583
3584bool Renderer::prepareRenderMergedBatch(Batch *batch, PreparedRenderBatch *renderBatch) // split prepare-render (RHI only)
3585{
3586 if (batch->vertexCount == 0 || batch->indexCount == 0)
3587 return false;
3588
3589 Element *e = batch->first;
3590 Q_ASSERT(e);
3591
3592#ifndef QT_NO_DEBUG_OUTPUT
3593 if (Q_UNLIKELY(debug_render())) {
3594 QDebug debug = qDebug();
3595 debug << " -"
3596 << batch
3597 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3598 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3599 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3600 << "[ merged]"
3601 << " Nodes:" << QString::fromLatin1(str: "%1").arg(a: qsg_countNodesInBatch(batch), fieldWidth: 4).toLatin1().constData()
3602 << " Vertices:" << QString::fromLatin1(str: "%1").arg(a: batch->vertexCount, fieldWidth: 5).toLatin1().constData()
3603 << " Indices:" << QString::fromLatin1(str: "%1").arg(a: batch->indexCount, fieldWidth: 5).toLatin1().constData()
3604 << " root:" << batch->root;
3605 if (batch->drawSets.size() > 1)
3606 debug << "sets:" << batch->drawSets.size();
3607 if (!batch->isOpaque)
3608 debug << "opacity:" << e->node->inheritedOpacity();
3609 batch->uploadedThisFrame = false;
3610 }
3611#endif
3612
3613 QSGGeometryNode *gn = e->node;
3614
3615 // We always have dirty matrix as all batches are at a unique z range.
3616 QSGMaterialShader::RenderState::DirtyStates dirty = QSGMaterialShader::RenderState::DirtyMatrix;
3617 if (batch->root)
3618 m_current_model_view_matrix = qsg_matrixForRoot(node: batch->root);
3619 else
3620 m_current_model_view_matrix.setToIdentity();
3621 m_current_determinant = m_current_model_view_matrix.determinant();
3622 m_current_projection_matrix = projectionMatrix();
3623 m_current_projection_matrix_native_ndc = projectionMatrixWithNativeNDC();
3624
3625 QSGMaterial *material = gn->activeMaterial();
3626 updateClipState(clipList: gn->clipList(), batch);
3627
3628 const QSGGeometry *g = gn->geometry();
3629 ShaderManager::Shader *sms = m_useDepthBuffer ? m_shaderManager->prepareMaterial(material, enableRhiShaders: true, geometry: g)
3630 : m_shaderManager->prepareMaterialNoRewrite(material, enableRhiShaders: true, geometry: g);
3631 if (!sms)
3632 return false;
3633
3634 Q_ASSERT(sms->programRhi.program);
3635 if (m_currentShader != sms)
3636 setActiveRhiShader(program: sms->programRhi.program, shader: sms);
3637
3638 m_current_opacity = gn->inheritedOpacity();
3639 if (!qFuzzyCompare(p1: sms->lastOpacity, p2: float(m_current_opacity))) {
3640 dirty |= QSGMaterialShader::RenderState::DirtyOpacity;
3641 sms->lastOpacity = m_current_opacity;
3642 }
3643
3644 QSGMaterialRhiShaderPrivate *pd = QSGMaterialRhiShaderPrivate::get(s: sms->programRhi.program);
3645 const int ubufSize = pd->masterUniformData.size();
3646 if (pd->ubufBinding >= 0) {
3647 bool ubufRebuild = false;
3648 if (!batch->ubuf) {
3649 batch->ubuf = m_rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: ubufSize);
3650 ubufRebuild = true;
3651 } else {
3652 if (batch->ubuf->size() < ubufSize) {
3653 batch->ubuf->setSize(ubufSize);
3654 ubufRebuild = true;
3655 }
3656 }
3657 if (ubufRebuild) {
3658 batch->ubufDataValid = false;
3659 if (!batch->ubuf->build()) {
3660 qWarning(msg: "Failed to build uniform buffer of size %d bytes", ubufSize);
3661 delete batch->ubuf;
3662 batch->ubuf = nullptr;
3663 return false;
3664 }
3665 }
3666 }
3667
3668 QSGMaterialRhiShader::RenderState renderState = rhiState(dirty: QSGMaterialRhiShader::RenderState::DirtyStates(int(dirty)));
3669
3670 bool pendingGStatePop = false;
3671 updateMaterialStaticData(sms, renderState, material, batch, gstateChanged: &pendingGStatePop);
3672
3673 ShaderManager::ShaderResourceBindingList bindings;
3674 updateMaterialDynamicData(sms, renderState, material, bindings: &bindings, batch, ubufOffset: 0, ubufRegionSize: ubufSize);
3675
3676#ifndef QT_NO_DEBUG
3677 if (qsg_test_and_clear_material_failure()) {
3678 qDebug(msg: "QSGMaterial::updateState triggered an error (merged), batch will be skipped:");
3679 Element *ee = e;
3680 while (ee) {
3681 qDebug() << " -" << ee->node;
3682 ee = ee->nextInBatch;
3683 }
3684 QSGNodeDumper::dump(n: rootNode());
3685 qFatal(msg: "Aborting: scene graph is invalid...");
3686 }
3687#endif
3688
3689 e->srb = m_shaderManager->srb(bindings);
3690
3691 m_gstate.drawMode = QSGGeometry::DrawingMode(g->drawingMode());
3692 m_gstate.lineWidth = g->lineWidth();
3693
3694 const bool hasPipeline = ensurePipelineState(e, sms);
3695
3696 if (pendingGStatePop)
3697 m_gstate = m_gstateStack.pop();
3698
3699 if (!hasPipeline)
3700 return false;
3701
3702 batch->ubufDataValid = true;
3703
3704 m_currentMaterial = material;
3705
3706 renderBatch->batch = batch;
3707 renderBatch->sms = sms;
3708
3709 return true;
3710}
3711
3712void Renderer::checkLineWidth(QSGGeometry *g)
3713{
3714 if (g->drawingMode() == QSGGeometry::DrawLines || g->drawingMode() == QSGGeometry::DrawLineLoop
3715 || g->drawingMode() == QSGGeometry::DrawLineStrip)
3716 {
3717 if (g->lineWidth() != 1.0f) {
3718 static bool checkedWideLineSupport = false;
3719 if (!checkedWideLineSupport) {
3720 checkedWideLineSupport = true;
3721 if (!m_rhi->isFeatureSupported(feature: QRhi::WideLines))
3722 qWarning(msg: "Line widths other than 1 are not supported by the graphics API");
3723 }
3724 }
3725 } else if (g->drawingMode() == QSGGeometry::DrawPoints) {
3726 if (g->lineWidth() != 1.0f) {
3727 static bool warnedPointSize = false;
3728 if (!warnedPointSize) {
3729 warnedPointSize = true;
3730 qWarning(msg: "Point size is not controllable by QSGGeometry. "
3731 "Set gl_PointSize from the vertex shader instead.");
3732 }
3733 }
3734 }
3735}
3736
3737void Renderer::renderMergedBatch(PreparedRenderBatch *renderBatch) // split prepare-render (RHI only)
3738{
3739 const Batch *batch = renderBatch->batch;
3740 Element *e = batch->first;
3741 QSGGeometryNode *gn = e->node;
3742 QSGGeometry *g = gn->geometry();
3743 checkLineWidth(g);
3744
3745 if (batch->clipState.type & ClipState::StencilClip)
3746 enqueueStencilDraw(batch);
3747
3748 QRhiCommandBuffer *cb = commandBuffer();
3749 setGraphicsPipeline(cb, batch, e);
3750
3751 for (int i = 0, ie = batch->drawSets.size(); i != ie; ++i) {
3752 const DrawSet &draw = batch->drawSets.at(i);
3753 const QRhiCommandBuffer::VertexInput vbufBindings[] = {
3754 { batch->vbo.buf, quint32(draw.vertices) },
3755 { batch->vbo.buf, quint32(draw.zorders) }
3756 };
3757 cb->setVertexInput(startBinding: VERTEX_BUFFER_BINDING, bindingCount: m_useDepthBuffer ? 2 : 1, bindings: vbufBindings,
3758 indexBuf: batch->ibo.buf, indexOffset: draw.indices,
3759 indexFormat: m_uint32IndexForRhi ? QRhiCommandBuffer::IndexUInt32 : QRhiCommandBuffer::IndexUInt16);
3760 cb->drawIndexed(indexCount: draw.indexCount);
3761 }
3762}
3763
3764bool Renderer::prepareRenderUnmergedBatch(Batch *batch, PreparedRenderBatch *renderBatch) // split prepare-render (RHI only)
3765{
3766 if (batch->vertexCount == 0)
3767 return false;
3768
3769 Element *e = batch->first;
3770 Q_ASSERT(e);
3771
3772 if (Q_UNLIKELY(debug_render())) {
3773 qDebug() << " -"
3774 << batch
3775 << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
3776 << (e->node->clipList() ? "[ clip]" : "[noclip]")
3777 << (batch->isOpaque ? "[opaque]" : "[ alpha]")
3778 << "[unmerged]"
3779 << " Nodes:" << QString::fromLatin1(str: "%1").arg(a: qsg_countNodesInBatch(batch), fieldWidth: 4).toLatin1().constData()
3780 << " Vertices:" << QString::fromLatin1(str: "%1").arg(a: batch->vertexCount, fieldWidth: 5).toLatin1().constData()
3781 << " Indices:" << QString::fromLatin1(str: "%1").arg(a: batch->indexCount, fieldWidth: 5).toLatin1().constData()
3782 << " root:" << batch->root;
3783
3784 batch->uploadedThisFrame = false;
3785 }
3786
3787 m_current_projection_matrix = projectionMatrix();
3788 m_current_projection_matrix_native_ndc = projectionMatrixWithNativeNDC();
3789
3790 QSGGeometryNode *gn = e->node;
3791 updateClipState(clipList: gn->clipList(), batch);
3792
3793 // We always have dirty matrix as all batches are at a unique z range.
3794 QSGMaterialShader::RenderState::DirtyStates dirty = QSGMaterialShader::RenderState::DirtyMatrix;
3795
3796 // The vertex attributes are assumed to be the same for all elements in the
3797 // unmerged batch since the material (and so the shaders) is the same.
3798 QSGGeometry *g = gn->geometry();
3799 QSGMaterial *material = gn->activeMaterial();
3800 ShaderManager::Shader *sms = m_shaderManager->prepareMaterialNoRewrite(material, enableRhiShaders: m_rhi, geometry: g);
3801 if (!sms)
3802 return false;
3803
3804 Q_ASSERT(sms->programRhi.program);
3805 if (m_currentShader != sms)
3806 setActiveRhiShader(program: sms->programRhi.program, shader: sms);
3807
3808 m_current_opacity = gn->inheritedOpacity();
3809 if (sms->lastOpacity != m_current_opacity) {
3810 dirty |= QSGMaterialShader::RenderState::DirtyOpacity;
3811 sms->lastOpacity = m_current_opacity;
3812 }
3813
3814 QMatrix4x4 rootMatrix = batch->root ? qsg_matrixForRoot(node: batch->root) : QMatrix4x4();
3815
3816 QSGMaterialRhiShaderPrivate *pd = QSGMaterialRhiShaderPrivate::get(s: sms->programRhi.program);
3817 const int ubufSize = pd->masterUniformData.size();
3818 if (pd->ubufBinding >= 0) {
3819 int totalUBufSize = 0;
3820 while (e) {
3821 totalUBufSize += aligned(v: ubufSize, byteAlign: m_ubufAlignment);
3822 e = e->nextInBatch;
3823 }
3824 bool ubufRebuild = false;
3825 if (!batch->ubuf) {
3826 batch->ubuf = m_rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: totalUBufSize);
3827 ubufRebuild = true;
3828 } else {
3829 if (batch->ubuf->size() < totalUBufSize) {
3830 batch->ubuf->setSize(totalUBufSize);
3831 ubufRebuild = true;
3832 }
3833 }
3834 if (ubufRebuild) {
3835 batch->ubufDataValid = false;
3836 if (!batch->ubuf->build()) {
3837 qWarning(msg: "Failed to build uniform buffer of size %d bytes", totalUBufSize);
3838 delete batch->ubuf;
3839 batch->ubuf = nullptr;
3840 return false;
3841 }
3842 }
3843 }
3844
3845 QSGMaterialRhiShader::RenderState renderState = rhiState(dirty: QSGMaterialRhiShader::RenderState::DirtyStates(int(dirty)));
3846 bool pendingGStatePop = false;
3847 updateMaterialStaticData(sms, renderState,
3848 material, batch, gstateChanged: &pendingGStatePop);
3849
3850 int ubufOffset = 0;
3851 QRhiGraphicsPipeline *ps = nullptr;
3852 e = batch->first;
3853 while (e) {
3854 gn = e->node;
3855
3856 m_current_model_view_matrix = rootMatrix * *gn->matrix();
3857 m_current_determinant = m_current_model_view_matrix.determinant();
3858
3859 m_current_projection_matrix = projectionMatrix();
3860 m_current_projection_matrix_native_ndc = projectionMatrixWithNativeNDC();
3861 if (m_useDepthBuffer) {
3862 m_current_projection_matrix(2, 2) = m_zRange;
3863 m_current_projection_matrix(2, 3) = 1.0f - e->order * m_zRange;
3864 }
3865
3866 QSGMaterialRhiShader::RenderState renderState = rhiState(dirty: QSGMaterialRhiShader::RenderState::DirtyStates(int(dirty)));
3867 ShaderManager::ShaderResourceBindingList bindings;
3868 updateMaterialDynamicData(sms, renderState,
3869 material, bindings: &bindings, batch, ubufOffset, ubufRegionSize: ubufSize);
3870
3871#ifndef QT_NO_DEBUG
3872 if (qsg_test_and_clear_material_failure()) {
3873 qDebug(msg: "QSGMaterial::updateState() triggered an error (unmerged), batch will be skipped:");
3874 qDebug() << " - offending node is" << e->node;
3875 QSGNodeDumper::dump(n: rootNode());
3876 qFatal(msg: "Aborting: scene graph is invalid...");
3877 return false;
3878 }
3879#endif
3880
3881 e->srb = m_shaderManager->srb(bindings);
3882
3883 ubufOffset += aligned(v: ubufSize, byteAlign: m_ubufAlignment);
3884
3885 const QSGGeometry::DrawingMode prevDrawMode = m_gstate.drawMode;
3886 const float prevLineWidth = m_gstate.lineWidth;
3887 m_gstate.drawMode = QSGGeometry::DrawingMode(g->drawingMode());
3888 m_gstate.lineWidth = g->lineWidth();
3889
3890 // Do not bother even looking up the ps if the topology has not changed
3891 // since everything else is the same for all elements in the batch.
3892 // (except if the material modified blend state)
3893 if (!ps || m_gstate.drawMode != prevDrawMode || m_gstate.lineWidth != prevLineWidth || pendingGStatePop) {
3894 if (!ensurePipelineState(e, sms)) {
3895 if (pendingGStatePop)
3896 m_gstate = m_gstateStack.pop();
3897 return false;
3898 }
3899 ps = e->ps;
3900 } else {
3901 e->ps = ps;
3902 }
3903
3904 // We don't need to bother with asking each node for its material as they
3905 // are all identical (compare==0) since they are in the same batch.
3906 m_currentMaterial = material;
3907
3908 // We only need to push this on the very first iteration...
3909 dirty &= ~QSGMaterialShader::RenderState::DirtyOpacity;
3910
3911 e = e->nextInBatch;
3912 }
3913
3914 if (pendingGStatePop)
3915 m_gstate = m_gstateStack.pop();
3916
3917 batch->ubufDataValid = true;
3918
3919 renderBatch->batch = batch;
3920 renderBatch->sms = sms;
3921
3922 return true;
3923}
3924
3925void Renderer::renderUnmergedBatch(PreparedRenderBatch *renderBatch) // split prepare-render (RHI only)
3926{
3927 const Batch *batch = renderBatch->batch;
3928 Element *e = batch->first;
3929 QSGGeometryNode *gn = e->node;
3930
3931 if (batch->clipState.type & ClipState::StencilClip)
3932 enqueueStencilDraw(batch);
3933
3934 int vOffset = 0;
3935 int iOffset = 0;
3936 QRhiCommandBuffer *cb = commandBuffer();
3937
3938 while (e) {
3939 gn = e->node;
3940 QSGGeometry *g = gn->geometry();
3941 checkLineWidth(g);
3942 const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : g->sizeOfIndex();
3943
3944 setGraphicsPipeline(cb, batch, e);
3945
3946 const QRhiCommandBuffer::VertexInput vbufBinding(batch->vbo.buf, vOffset);
3947 if (g->indexCount()) {
3948 cb->setVertexInput(startBinding: VERTEX_BUFFER_BINDING, bindingCount: 1, bindings: &vbufBinding,
3949 indexBuf: batch->ibo.buf, indexOffset: iOffset,
3950 indexFormat: effectiveIndexSize == sizeof(quint32) ? QRhiCommandBuffer::IndexUInt32
3951 : QRhiCommandBuffer::IndexUInt16);
3952 cb->drawIndexed(indexCount: g->indexCount());
3953 } else {
3954 cb->setVertexInput(startBinding: VERTEX_BUFFER_BINDING, bindingCount: 1, bindings: &vbufBinding);
3955 cb->draw(vertexCount: g->vertexCount());
3956 }
3957
3958 vOffset += g->sizeOfVertex() * g->vertexCount();
3959 iOffset += g->indexCount() * effectiveIndexSize;
3960
3961 e = e->nextInBatch;
3962 }
3963}
3964
3965void Renderer::setGraphicsPipeline(QRhiCommandBuffer *cb, const Batch *batch, Element *e) // RHI only, [render step]
3966{
3967 cb->setGraphicsPipeline(e->ps);
3968
3969 if (!m_pstate.viewportSet) {
3970 m_pstate.viewportSet = true;
3971 cb->setViewport(m_pstate.viewport);
3972 }
3973 if (batch->clipState.type & ClipState::ScissorClip) {
3974 Q_ASSERT(e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesScissor));
3975 m_pstate.scissorSet = true;
3976 cb->setScissor(batch->clipState.scissor);
3977 } else {
3978 Q_ASSERT(!e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesScissor));
3979 // Regardless of the ps not using scissor, the scissor may need to be
3980 // reset, depending on the backend. So set the viewport again, which in
3981 // turn also sets the scissor on backends where a scissor rect is
3982 // always-on (Vulkan).
3983 if (m_pstate.scissorSet) {
3984 m_pstate.scissorSet = false;
3985 cb->setViewport(m_pstate.viewport);
3986 }
3987 }
3988 if (batch->clipState.type & ClipState::StencilClip) {
3989 Q_ASSERT(e->ps->flags().testFlag(QRhiGraphicsPipeline::UsesStencilRef));
3990 cb->setStencilRef(batch->clipState.stencilRef);
3991 }
3992 if (e->ps->flags().testFlag(flag: QRhiGraphicsPipeline::UsesBlendConstants))
3993 cb->setBlendConstants(batch->blendConstant);
3994
3995 cb->setShaderResources(srb: e->srb);
3996}
3997
3998void Renderer::renderBatches()
3999{
4000 if (Q_UNLIKELY(debug_render())) {
4001 qDebug().nospace() << "Rendering:" << Qt::endl
4002 << " -> Opaque: " << qsg_countNodesInBatches(batches: m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << Qt::endl
4003 << " -> Alpha: " << qsg_countNodesInBatches(batches: m_alphaBatches) << " nodes in " << m_alphaBatches.size() << " batches...";
4004 }
4005
4006 m_current_opacity = 1;
4007 m_currentMaterial = nullptr;
4008 m_currentShader = nullptr;
4009 m_currentProgram = nullptr;
4010 m_currentRhiProgram = nullptr;
4011 m_currentClip = nullptr;
4012 m_currentClipState.reset();
4013
4014 const QRect viewport = viewportRect();
4015
4016 bool renderOpaque = !debug_noopaque();
4017 bool renderAlpha = !debug_noalpha();
4018
4019 if (!m_rhi) {
4020 // legacy, GL-only path
4021
4022 glViewport(x: viewport.x(), y: deviceRect().bottom() - viewport.bottom(), width: viewport.width(), height: viewport.height());
4023 glClearColor(red: clearColor().redF(), green: clearColor().greenF(), blue: clearColor().blueF(), alpha: clearColor().alphaF());
4024
4025 if (m_useDepthBuffer) {
4026 glClearDepthf(depth: 1); // calls glClearDepth() under the hood for desktop OpenGL
4027 }
4028 glColorMask(red: true, green: true, blue: true, alpha: true);
4029 glDisable(GL_SCISSOR_TEST);
4030
4031 bindable()->clear(mode: clearMode());
4032
4033 if (m_renderPassRecordingCallbacks.start)
4034 m_renderPassRecordingCallbacks.start(m_renderPassRecordingCallbacks.userData);
4035
4036 if (m_useDepthBuffer) {
4037 glEnable(GL_DEPTH_TEST);
4038 glDepthFunc(GL_LESS);
4039 glDepthMask(flag: true);
4040 glDisable(GL_BLEND);
4041 } else {
4042 glDisable(GL_DEPTH_TEST);
4043 glDepthMask(flag: false);
4044 }
4045 glDisable(GL_CULL_FACE);
4046 glColorMask(red: true, green: true, blue: true, alpha: true);
4047 glDisable(GL_SCISSOR_TEST);
4048 glDisable(GL_STENCIL_TEST);
4049
4050 if (Q_LIKELY(renderOpaque)) {
4051 for (int i=0; i<m_opaqueBatches.size(); ++i) {
4052 Batch *b = m_opaqueBatches.at(i);
4053 if (b->merged)
4054 renderMergedBatch(batch: b);
4055 else
4056 renderUnmergedBatch(batch: b);
4057 }
4058 }
4059
4060 glEnable(GL_BLEND);
4061 if (m_useDepthBuffer)
4062 glDepthMask(flag: false);
4063 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
4064
4065 if (Q_LIKELY(renderAlpha)) {
4066 for (int i=0; i<m_alphaBatches.size(); ++i) {
4067 Batch *b = m_alphaBatches.at(i);
4068 if (b->merged) {
4069 renderMergedBatch(batch: b);
4070 } else if (b->isRenderNode) {
4071 m_current_projection_matrix = projectionMatrix();
4072 renderRenderNode(batch: b);
4073 } else {
4074 renderUnmergedBatch(batch: b);
4075 }
4076 }
4077 }
4078
4079 if (m_currentShader)
4080 setActiveShader(program: nullptr, shader: nullptr);
4081
4082 updateStencilClip(clip: nullptr);
4083
4084 glBindBuffer(GL_ARRAY_BUFFER, buffer: 0);
4085 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer: 0);
4086 glDepthMask(flag: true);
4087
4088 if (m_renderPassRecordingCallbacks.end)
4089 m_renderPassRecordingCallbacks.end(m_renderPassRecordingCallbacks.userData);
4090
4091 } else {
4092 // RHI path
4093
4094 m_pstate.viewport = QRhiViewport(viewport.x(), deviceRect().bottom() - viewport.bottom(), viewport.width(), viewport.height());
4095 m_pstate.clearColor = clearColor();
4096 m_pstate.dsClear = QRhiDepthStencilClearValue(1.0f, 0);
4097 m_pstate.viewportSet = false;
4098 m_pstate.scissorSet = false;
4099
4100 m_gstate.depthTest = m_useDepthBuffer;
4101 m_gstate.depthWrite = m_useDepthBuffer;
4102 m_gstate.depthFunc = QRhiGraphicsPipeline::Less;
4103 m_gstate.blending = false;
4104
4105 m_gstate.cullMode = QRhiGraphicsPipeline::None;
4106 m_gstate.colorWrite = QRhiGraphicsPipeline::R
4107 | QRhiGraphicsPipeline::G
4108 | QRhiGraphicsPipeline::B
4109 | QRhiGraphicsPipeline::A;
4110 m_gstate.usesScissor = false;
4111 m_gstate.stencilTest = false;
4112
4113 m_gstate.sampleCount = renderTarget()->sampleCount();
4114
4115 QVarLengthArray<PreparedRenderBatch, 64> opaqueRenderBatches;
4116 if (Q_LIKELY(renderOpaque)) {
4117 for (int i = 0, ie = m_opaqueBatches.size(); i != ie; ++i) {
4118 Batch *b = m_opaqueBatches.at(i);
4119 PreparedRenderBatch renderBatch;
4120 bool ok;
4121 if (b->merged)
4122 ok = prepareRenderMergedBatch(batch: b, renderBatch: &renderBatch);
4123 else
4124 ok = prepareRenderUnmergedBatch(batch: b, renderBatch: &renderBatch);
4125 if (ok)
4126 opaqueRenderBatches.append(t: renderBatch);
4127 }
4128 }
4129
4130 m_gstate.blending = true;
4131 // factors never change, always set for premultiplied alpha based blending
4132
4133 // depth test stays enabled (if m_useDepthBuffer, that is) but no need
4134 // to write out depth from the transparent (back-to-front) pass
4135 m_gstate.depthWrite = false;
4136
4137 QVarLengthArray<PreparedRenderBatch, 64> alphaRenderBatches;
4138 if (Q_LIKELY(renderAlpha)) {
4139 for (int i = 0, ie = m_alphaBatches.size(); i != ie; ++i) {
4140 Batch *b = m_alphaBatches.at(i);
4141 PreparedRenderBatch renderBatch;
4142 bool ok;
4143 if (b->merged)
4144 ok = prepareRenderMergedBatch(batch: b, renderBatch: &renderBatch);
4145 else if (b->isRenderNode)
4146 ok = prepareRhiRenderNode(batch: b, renderBatch: &renderBatch);
4147 else
4148 ok = prepareRenderUnmergedBatch(batch: b, renderBatch: &renderBatch);
4149 if (ok)
4150 alphaRenderBatches.append(t: renderBatch);
4151 }
4152 }
4153
4154 if (m_visualizer->mode() != Visualizer::VisualizeNothing)
4155 m_visualizer->prepareVisualize();
4156
4157 QRhiCommandBuffer *cb = commandBuffer();
4158 cb->beginPass(rt: renderTarget(), colorClearValue: m_pstate.clearColor, depthStencilClearValue: m_pstate.dsClear, resourceUpdates: m_resourceUpdates);
4159 m_resourceUpdates = nullptr;
4160
4161 if (m_renderPassRecordingCallbacks.start)
4162 m_renderPassRecordingCallbacks.start(m_renderPassRecordingCallbacks.userData);
4163
4164 for (int i = 0, ie = opaqueRenderBatches.count(); i != ie; ++i) {
4165 PreparedRenderBatch *renderBatch = &opaqueRenderBatches[i];
4166 if (renderBatch->batch->merged)
4167 renderMergedBatch(renderBatch);
4168 else
4169 renderUnmergedBatch(renderBatch);
4170 }
4171
4172 for (int i = 0, ie = alphaRenderBatches.count(); i != ie; ++i) {
4173 PreparedRenderBatch *renderBatch = &alphaRenderBatches[i];
4174 if (renderBatch->batch->merged)
4175 renderMergedBatch(renderBatch);
4176 else if (renderBatch->batch->isRenderNode)
4177 renderRhiRenderNode(batch: renderBatch->batch);
4178 else
4179 renderUnmergedBatch(renderBatch);
4180 }
4181
4182 if (m_currentShader)
4183 setActiveRhiShader(program: nullptr, shader: nullptr);
4184
4185 if (m_renderPassRecordingCallbacks.end)
4186 m_renderPassRecordingCallbacks.end(m_renderPassRecordingCallbacks.userData);
4187
4188 if (m_visualizer->mode() == Visualizer::VisualizeNothing)
4189 cb->endPass();
4190 }
4191}
4192
4193void Renderer::deleteRemovedElements()
4194{
4195 if (!m_elementsToDelete.size())
4196 return;
4197
4198 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
4199 Element **e = m_opaqueRenderList.data() + i;
4200 if (*e && (*e)->removed)
4201 *e = nullptr;
4202 }
4203 for (int i=0; i<m_alphaRenderList.size(); ++i) {
4204 Element **e = m_alphaRenderList.data() + i;
4205 if (*e && (*e)->removed)
4206 *e = nullptr;
4207 }
4208
4209 for (int i=0; i<m_elementsToDelete.size(); ++i) {
4210 Element *e = m_elementsToDelete.at(i);
4211 if (e->isRenderNode)
4212 delete static_cast<RenderNodeElement *>(e);
4213 else
4214 m_elementAllocator.release(t: e);
4215 }
4216 m_elementsToDelete.reset();
4217}
4218
4219void Renderer::render()
4220{
4221 if (Q_UNLIKELY(debug_dump())) {
4222 qDebug(msg: "\n");
4223 QSGNodeDumper::dump(n: rootNode());
4224 }
4225
4226 QElapsedTimer timer;
4227 quint64 timeRenderLists = 0;
4228 quint64 timePrepareOpaque = 0;
4229 quint64 timePrepareAlpha = 0;
4230 quint64 timeSorting = 0;
4231 quint64 timeUploadOpaque = 0;
4232 quint64 timeUploadAlpha = 0;
4233
4234 if (Q_UNLIKELY(debug_render() || debug_build())) {
4235 QByteArray type("rebuild:");
4236 if (m_rebuild == 0)
4237 type += " none";
4238 if (m_rebuild == FullRebuild)
4239 type += " full";
4240 else {
4241 if (m_rebuild & BuildRenderLists)
4242 type += " renderlists";
4243 else if (m_rebuild & BuildRenderListsForTaggedRoots)
4244 type += " partial";
4245 else if (m_rebuild & BuildBatches)
4246 type += " batches";
4247 }
4248
4249 qDebug() << "Renderer::render()" << this << type;
4250 timer.start();
4251 }
4252
4253 if (!m_rhi) {
4254 Q_ASSERT(m_context->openglContext() == QOpenGLContext::currentContext());
4255 if (m_vao)
4256 m_vao->bind();
4257 } else {
4258 m_resourceUpdates = m_rhi->nextResourceUpdateBatch();
4259 }
4260
4261 if (m_rebuild & (BuildRenderLists | BuildRenderListsForTaggedRoots)) {
4262 bool complete = (m_rebuild & BuildRenderLists) != 0;
4263 if (complete)
4264 buildRenderListsFromScratch();
4265 else
4266 buildRenderListsForTaggedRoots();
4267 m_rebuild |= BuildBatches;
4268
4269 if (Q_UNLIKELY(debug_build())) {
4270 qDebug(msg: "Opaque render lists %s:", (complete ? "(complete)" : "(partial)"));
4271 for (int i=0; i<m_opaqueRenderList.size(); ++i) {
4272 Element *e = m_opaqueRenderList.at(i);
4273 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
4274 }
4275 qDebug(msg: "Alpha render list %s:", complete ? "(complete)" : "(partial)");
4276 for (int i=0; i<m_alphaRenderList.size(); ++i) {
4277 Element *e = m_alphaRenderList.at(i);
4278 qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
4279 }
4280 }
4281 }
4282 if (Q_UNLIKELY(debug_render())) timeRenderLists = timer.restart();
4283
4284 for (int i=0; i<m_opaqueBatches.size(); ++i)
4285 m_opaqueBatches.at(i)->cleanupRemovedElements();
4286 for (int i=0; i<m_alphaBatches.size(); ++i)
4287 m_alphaBatches.at(i)->cleanupRemovedElements();
4288 deleteRemovedElements();
4289
4290 cleanupBatches(batches: &m_opaqueBatches);
4291 cleanupBatches(batches: &m_alphaBatches);
4292
4293 if (m_rebuild & BuildBatches) {
4294 prepareOpaqueBatches();
4295 if (Q_UNLIKELY(debug_render())) timePrepareOpaque = timer.restart();
4296 prepareAlphaBatches();
4297 if (Q_UNLIKELY(debug_render())) timePrepareAlpha = timer.restart();
4298
4299 if (Q_UNLIKELY(debug_build())) {
4300 qDebug(msg: "Opaque Batches:");
4301 for (int i=0; i<m_opaqueBatches.size(); ++i) {
4302 Batch *b = m_opaqueBatches.at(i);
4303 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
4304 for (Element *e = b->first; e; e = e->nextInBatch) {
4305 qDebug() << " - element:" << e << " node:" << e->node << e->order;
4306 }
4307 }
4308 qDebug(msg: "Alpha Batches:");
4309 for (int i=0; i<m_alphaBatches.size(); ++i) {
4310 Batch *b = m_alphaBatches.at(i);
4311 qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
4312 for (Element *e = b->first; e; e = e->nextInBatch) {
4313 qDebug() << " - element:" << e << e->bounds << " node:" << e->node << " order:" << e->order;
4314 }
4315 }
4316 }
4317 } else {
4318 if (Q_UNLIKELY(debug_render())) timePrepareOpaque = timePrepareAlpha = timer.restart();
4319 }
4320
4321
4322 deleteRemovedElements();
4323
4324 if (m_rebuild != 0) {
4325 // Then sort opaque batches so that we're drawing the batches with the highest
4326 // order first, maximizing the benefit of front-to-back z-ordering.
4327 if (m_opaqueBatches.size())
4328 std::sort(first: &m_opaqueBatches.first(), last: &m_opaqueBatches.last() + 1, comp: qsg_sort_batch_decreasing_order);
4329
4330 // Sort alpha batches back to front so that they render correctly.
4331 if (m_alphaBatches.size())
4332 std::sort(first: &m_alphaBatches.first(), last: &m_alphaBatches.last() + 1, comp: qsg_sort_batch_increasing_order);
4333
4334 m_zRange = m_nextRenderOrder != 0
4335 ? 1.0 / (m_nextRenderOrder)
4336 : 0;
4337 }
4338
4339 if (Q_UNLIKELY(debug_render())) timeSorting = timer.restart();
4340
4341 int largestVBO = 0;
4342 int largestIBO = 0;
4343
4344 if (Q_UNLIKELY(debug_upload())) qDebug(msg: "Uploading Opaque Batches:");
4345 for (int i=0; i<m_opaqueBatches.size(); ++i) {
4346 Batch *b = m_opaqueBatches.at(i);
4347 largestVBO = qMax(a: b->vbo.size, b: largestVBO);
4348 largestIBO = qMax(a: b->ibo.size, b: largestIBO);
4349 uploadBatch(b);
4350 }
4351 if (Q_UNLIKELY(debug_render())) timeUploadOpaque = timer.restart();
4352
4353
4354 if (Q_UNLIKELY(debug_upload())) qDebug(msg: "Uploading Alpha Batches:");
4355 for (int i=0; i<m_alphaBatches.size(); ++i) {
4356 Batch *b = m_alphaBatches.at(i);
4357 uploadBatch(b);
4358 largestVBO = qMax(a: b->vbo.size, b: largestVBO);
4359 largestIBO = qMax(a: b->ibo.size, b: largestIBO);
4360 }
4361 if (Q_UNLIKELY(debug_render())) timeUploadAlpha = timer.restart();
4362
4363 if (largestVBO * 2 < m_vertexUploadPool.size())
4364 m_vertexUploadPool.resize(size: largestVBO * 2);
4365 if (m_context->separateIndexBuffer() && largestIBO * 2 < m_indexUploadPool.size())
4366 m_indexUploadPool.resize(size: largestIBO * 2);
4367
4368 renderBatches();
4369
4370 if (Q_UNLIKELY(debug_render())) {
4371 qDebug(msg: " -> times: build: %d, prepare(opaque/alpha): %d/%d, sorting: %d, upload(opaque/alpha): %d/%d, render: %d",
4372 (int) timeRenderLists,
4373 (int) timePrepareOpaque, (int) timePrepareAlpha,
4374 (int) timeSorting,
4375 (int) timeUploadOpaque, (int) timeUploadAlpha,
4376 (int) timer.elapsed());
4377 }
4378
4379 m_rebuild = 0;
4380 m_renderOrderRebuildLower = -1;
4381 m_renderOrderRebuildUpper = -1;
4382
4383 if (m_visualizer->mode() != Visualizer::VisualizeNothing)
4384 m_visualizer->visualize();
4385
4386 if (!m_rhi) {
4387 if (m_vao)
4388 m_vao->release();
4389 } else {
4390 if (m_visualizer->mode() != Visualizer::VisualizeNothing)
4391 commandBuffer()->endPass();
4392
4393 if (m_resourceUpdates) {
4394 m_resourceUpdates->release();
4395 m_resourceUpdates = nullptr;
4396 }
4397 }
4398}
4399
4400struct RenderNodeState : public QSGRenderNode::RenderState
4401{
4402 const QMatrix4x4 *projectionMatrix() const override { return m_projectionMatrix; }
4403 QRect scissorRect() const override { return m_scissorRect; }
4404 bool scissorEnabled() const override { return m_scissorEnabled; }
4405 int stencilValue() const override { return m_stencilValue; }
4406 bool stencilEnabled() const override { return m_stencilEnabled; }
4407 const QRegion *clipRegion() const override { return nullptr; }
4408
4409 const QMatrix4x4 *m_projectionMatrix;
4410 QRect m_scissorRect;
4411 int m_stencilValue;
4412 bool m_scissorEnabled;
4413 bool m_stencilEnabled;
4414};
4415
4416void Renderer::renderRenderNode(Batch *batch) // legacy (GL-only)
4417{
4418 if (Q_UNLIKELY(debug_render()))
4419 qDebug() << " -" << batch << "rendernode";
4420
4421 Q_ASSERT(batch->first->isRenderNode);
4422 RenderNodeElement *e = (RenderNodeElement *) batch->first;
4423
4424 setActiveShader(program: nullptr, shader: nullptr);
4425
4426 QSGNode *clip = e->renderNode->parent();
4427 QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(node: e->renderNode);
4428 rd->m_clip_list = nullptr;
4429 while (clip != rootNode()) {
4430 if (clip->type() == QSGNode::ClipNodeType) {
4431 rd->m_clip_list = static_cast<QSGClipNode *>(clip);
4432 break;
4433 }
4434 clip = clip->parent();
4435 }
4436
4437 updateClip(clipList: rd->m_clip_list, batch);
4438
4439 QMatrix4x4 pm = projectionMatrix();
4440 if (m_useDepthBuffer) {
4441 pm(2, 2) = m_zRange;
4442 pm(2, 3) = 1.0f - e->order * m_zRange;
4443 }
4444
4445 RenderNodeState state;
4446 state.m_projectionMatrix = &pm;
4447 state.m_scissorEnabled = m_currentClipType & ClipState::ScissorClip;
4448 state.m_stencilEnabled = m_currentClipType & ClipState::StencilClip;
4449 state.m_scissorRect = m_currentScissorRect;
4450 state.m_stencilValue = m_currentStencilValue;
4451
4452 QSGNode *xform = e->renderNode->parent();
4453 QMatrix4x4 matrix;
4454 QSGNode *root = rootNode();
4455 if (e->root) {
4456 matrix = qsg_matrixForRoot(node: e->root);
4457 root = e->root->sgNode;
4458 }
4459 while (xform != root) {
4460 if (xform->type() == QSGNode::TransformNodeType) {
4461 matrix = matrix * static_cast<QSGTransformNode *>(xform)->combinedMatrix();
4462 break;
4463 }
4464 xform = xform->parent();
4465 }
4466 rd->m_matrix = &matrix;
4467
4468 QSGNode *opacity = e->renderNode->parent();
4469 rd->m_opacity = 1.0;
4470 while (opacity != rootNode()) {
4471 if (opacity->type() == QSGNode::OpacityNodeType) {
4472 rd->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity();
4473 break;
4474 }
4475 opacity = opacity->parent();
4476 }
4477
4478 // having DepthAwareRendering leaves depth test on in the alpha pass
4479 const bool depthTestWasEnabled = m_useDepthBuffer;
4480
4481 glDisable(GL_STENCIL_TEST);
4482 glDisable(GL_SCISSOR_TEST);
4483 glDisable(GL_DEPTH_TEST);
4484 glBindBuffer(GL_ARRAY_BUFFER, buffer: 0);
4485 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer: 0);
4486
4487 QSGRenderNode::StateFlags changes = e->renderNode->changedStates();
4488
4489 GLuint prevFbo = 0;
4490 if (changes & QSGRenderNode::RenderTargetState)
4491 glGetIntegerv(GL_FRAMEBUFFER_BINDING, params: (GLint *) &prevFbo);
4492
4493 e->renderNode->render(state: &state);
4494
4495 rd->m_matrix = nullptr;
4496 rd->m_clip_list = nullptr;
4497
4498 if (changes & QSGRenderNode::ViewportState) {
4499 QRect r = viewportRect();
4500 glViewport(x: r.x(), y: deviceRect().bottom() - r.bottom(), width: r.width(), height: r.height());
4501 }
4502
4503 if (changes & QSGRenderNode::StencilState) {
4504 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
4505 glStencilMask(mask: 0xff);
4506 glDisable(GL_STENCIL_TEST);
4507 }
4508
4509 if (changes & (QSGRenderNode::StencilState | QSGRenderNode::ScissorState)) {
4510 glDisable(GL_SCISSOR_TEST);
4511 m_currentClip = nullptr;
4512 m_currentClipType = ClipState::NoClip;
4513 }
4514
4515 if (depthTestWasEnabled)
4516 glEnable(GL_DEPTH_TEST);
4517 else if (changes & QSGRenderNode::DepthState)
4518 glDisable(GL_DEPTH_TEST);
4519
4520 if (changes & QSGRenderNode::ColorState)
4521 bindable()->reactivate();
4522
4523 if (changes & QSGRenderNode::BlendState) {
4524 glEnable(GL_BLEND);
4525 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
4526 }
4527
4528 if (changes & QSGRenderNode::CullState) {
4529 glFrontFace(mode: isMirrored() ? GL_CW : GL_CCW);
4530 glDisable(GL_CULL_FACE);
4531 }
4532
4533 if (changes & QSGRenderNode::RenderTargetState)
4534 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: prevFbo);
4535}
4536
4537bool Renderer::prepareRhiRenderNode(Batch *batch, PreparedRenderBatch *renderBatch) // split prepare-render (RHI only)
4538{
4539 if (Q_UNLIKELY(debug_render()))
4540 qDebug() << " -" << batch << "rendernode";
4541
4542 Q_ASSERT(batch->first->isRenderNode);
4543 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4544
4545 setActiveRhiShader(program: nullptr, shader: nullptr);
4546
4547 QSGNode *clip = e->renderNode->parent();
4548 QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(node: e->renderNode);
4549 rd->m_clip_list = nullptr;
4550 while (clip != rootNode()) {
4551 if (clip->type() == QSGNode::ClipNodeType) {
4552 rd->m_clip_list = static_cast<QSGClipNode *>(clip);
4553 break;
4554 }
4555 clip = clip->parent();
4556 }
4557
4558 updateClipState(clipList: rd->m_clip_list, batch);
4559
4560 QSGNode *xform = e->renderNode->parent();
4561 QMatrix4x4 matrix;
4562 QSGNode *root = rootNode();
4563 if (e->root) {
4564 matrix = qsg_matrixForRoot(node: e->root);
4565 root = e->root->sgNode;
4566 }
4567 while (xform != root) {
4568 if (xform->type() == QSGNode::TransformNodeType) {
4569 matrix = matrix * static_cast<QSGTransformNode *>(xform)->combinedMatrix();
4570 break;
4571 }
4572 xform = xform->parent();
4573 }
4574 rd->m_matrix = &matrix;
4575
4576 QSGNode *opacity = e->renderNode->parent();
4577 rd->m_opacity = 1.0;
4578 while (opacity != rootNode()) {
4579 if (opacity->type() == QSGNode::OpacityNodeType) {
4580 rd->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity();
4581 break;
4582 }
4583 opacity = opacity->parent();
4584 }
4585
4586 if (rd->m_prepareCallback)
4587 rd->m_prepareCallback();
4588
4589 renderBatch->batch = batch;
4590 renderBatch->sms = nullptr;
4591
4592 return true;
4593}
4594
4595void Renderer::renderRhiRenderNode(const Batch *batch) // split prepare-render (RHI only)
4596{
4597 if (batch->clipState.type & ClipState::StencilClip)
4598 enqueueStencilDraw(batch);
4599
4600 RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
4601 QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(node: e->renderNode);
4602
4603 QMatrix4x4 pm = projectionMatrix();
4604 if (m_useDepthBuffer) {
4605 pm(2, 2) = m_zRange;
4606 pm(2, 3) = 1.0f - e->order * m_zRange;
4607 }
4608
4609 RenderNodeState state;
4610 state.m_projectionMatrix = &pm;
4611 const std::array<int, 4> scissor = batch->clipState.scissor.scissor();
4612 state.m_scissorRect = QRect(scissor[0], scissor[1], scissor[2], scissor[3]);
4613 state.m_stencilValue = batch->clipState.stencilRef;
4614 state.m_scissorEnabled = batch->clipState.type & ClipState::ScissorClip;
4615 state.m_stencilEnabled = batch->clipState.type & ClipState::StencilClip;
4616
4617 const QSGRenderNode::StateFlags changes = e->renderNode->changedStates();
4618
4619 QRhiCommandBuffer *cb = commandBuffer();
4620 const bool needsExternal = rd->m_needsExternalRendering;
4621 if (needsExternal)
4622 cb->beginExternal();
4623 e->renderNode->render(state: &state);
4624 if (needsExternal)
4625 cb->endExternal();
4626
4627 rd->m_matrix = nullptr;
4628 rd->m_clip_list = nullptr;
4629
4630 if ((changes & QSGRenderNode::ViewportState)
4631 || (changes & QSGRenderNode::ScissorState))
4632 {
4633 // Reset both flags if either is reported as changed, since with the rhi
4634 // it could be setViewport() that will record the resetting of the scissor.
4635 m_pstate.viewportSet = false;
4636 m_pstate.scissorSet = false;
4637 }
4638
4639 // Do not bother with RenderTargetState. Where applicable, endExternal()
4640 // ensures the correct target is rebound. For others (like Vulkan) it makes
4641 // no sense since render() could not possibly do that on our command buffer
4642 // which is in renderpass recording state.
4643}
4644
4645void Renderer::setCustomRenderMode(const QByteArray &mode)
4646{
4647 if (mode.isEmpty())
4648 m_visualizer->setMode(Visualizer::VisualizeNothing);
4649 else if (mode == "clip")
4650 m_visualizer->setMode(Visualizer::VisualizeClipping);
4651 else if (mode == "overdraw")
4652 m_visualizer->setMode(Visualizer::VisualizeOverdraw);
4653 else if (mode == "batches")
4654 m_visualizer->setMode(Visualizer::VisualizeBatches);
4655 else if (mode == "changes")
4656 m_visualizer->setMode(Visualizer::VisualizeChanges);
4657}
4658
4659bool Renderer::hasCustomRenderModeWithContinuousUpdate() const
4660{
4661 return m_visualizer->mode() == Visualizer::VisualizeOverdraw;
4662}
4663
4664bool operator==(const GraphicsState &a, const GraphicsState &b) Q_DECL_NOTHROW
4665{
4666 return a.depthTest == b.depthTest
4667 && a.depthWrite == b.depthWrite
4668 && a.depthFunc == b.depthFunc
4669 && a.blending == b.blending
4670 && a.srcColor == b.srcColor
4671 && a.dstColor == b.dstColor
4672 && a.colorWrite == b.colorWrite
4673 && a.cullMode == b.cullMode
4674 && a.usesScissor == b.usesScissor
4675 && a.stencilTest == b.stencilTest
4676 && a.sampleCount == b.sampleCount
4677 && a.drawMode == b.drawMode
4678 && a.lineWidth == b.lineWidth;
4679}
4680
4681bool operator!=(const GraphicsState &a, const GraphicsState &b) Q_DECL_NOTHROW
4682{
4683 return !(a == b);
4684}
4685
4686uint qHash(const GraphicsState &s, uint seed) Q_DECL_NOTHROW
4687{
4688 // do not bother with all fields
4689 return seed
4690 + s.depthTest * 1000
4691 + s.depthWrite * 100
4692 + s.depthFunc
4693 + s.blending * 10
4694 + s.srcColor
4695 + s.cullMode
4696 + s.usesScissor
4697 + s.stencilTest
4698 + s.sampleCount;
4699}
4700
4701bool operator==(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) Q_DECL_NOTHROW
4702{
4703 return a.state == b.state
4704 && a.sms->programRhi.program == b.sms->programRhi.program
4705 && a.compatibleRenderPassDescriptor->isCompatible(other: b.compatibleRenderPassDescriptor)
4706 && a.layoutCompatibleSrb->isLayoutCompatible(other: b.layoutCompatibleSrb);
4707}
4708
4709bool operator!=(const GraphicsPipelineStateKey &a, const GraphicsPipelineStateKey &b) Q_DECL_NOTHROW
4710{
4711 return !(a == b);
4712}
4713
4714uint qHash(const GraphicsPipelineStateKey &k, uint seed) Q_DECL_NOTHROW
4715{
4716 // no srb and rp included due to their special comparison semantics and lack of hash keys
4717 return qHash(s: k.state, seed) + qHash(t: k.sms->programRhi.program, seed);
4718}
4719
4720Visualizer::Visualizer(Renderer *renderer)
4721 : m_renderer(renderer),
4722 m_visualizeMode(VisualizeNothing)
4723{
4724}
4725
4726Visualizer::~Visualizer()
4727{
4728}
4729
4730#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded \
4731 | QSGNode::DirtyOpacity \
4732 | QSGNode::DirtyMatrix \
4733 | QSGNode::DirtyNodeRemoved)
4734
4735void Visualizer::visualizeChangesPrepare(Node *n, uint parentChanges)
4736{
4737 uint childDirty = (parentChanges | n->dirtyState) & QSGNODE_DIRTY_PARENT;
4738 uint selfDirty = n->dirtyState | parentChanges;
4739 if (n->type() == QSGNode::GeometryNodeType && selfDirty != 0)
4740 m_visualizeChangeSet.insert(key: n, value: selfDirty);
4741 SHADOWNODE_TRAVERSE(n) {
4742 visualizeChangesPrepare(n: child, parentChanges: childDirty);
4743 }
4744}
4745
4746} // namespace QSGBatchRenderer
4747
4748QT_END_NAMESPACE
4749
4750#include "moc_qsgbatchrenderer_p.cpp"
4751

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