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 | |
73 | QT_BEGIN_NAMESPACE |
74 | |
75 | #ifndef QT_NO_DEBUG |
76 | Q_QUICK_PRIVATE_EXPORT bool qsg_test_and_clear_material_failure(); |
77 | #endif |
78 | |
79 | extern QByteArray qsgShaderRewriter_insertZAttributes(const char *input, QSurfaceFormat::OpenGLContextProfile profile); |
80 | |
81 | int qt_sg_envInt(const char *name, int defaultValue); |
82 | |
83 | namespace 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; } |
89 | DECLARE_DEBUG_VAR(render) |
90 | DECLARE_DEBUG_VAR(build) |
91 | DECLARE_DEBUG_VAR(change) |
92 | DECLARE_DEBUG_VAR(upload) |
93 | DECLARE_DEBUG_VAR(roots) |
94 | DECLARE_DEBUG_VAR(dump) |
95 | DECLARE_DEBUG_VAR(noalpha) |
96 | DECLARE_DEBUG_VAR(noopaque) |
97 | DECLARE_DEBUG_VAR(noclip) |
98 | #undef DECLARE_DEBUG_VAR |
99 | |
100 | static 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 | |
105 | static 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 | |
124 | bool qsg_sort_element_increasing_order(Element *a, Element *b) { return a->order < b->order; } |
125 | bool qsg_sort_element_decreasing_order(Element *a, Element *b) { return a->order > b->order; } |
126 | bool qsg_sort_batch_is_valid(Batch *a, Batch *b) { return a->first && !b->first; } |
127 | bool qsg_sort_batch_increasing_order(Batch *a, Batch *b) { return a->first->order < b->first->order; } |
128 | bool qsg_sort_batch_decreasing_order(Batch *a, Batch *b) { return a->first->order > b->first->order; } |
129 | |
130 | QSGMaterial::Flag QSGMaterial_FullMatrix = (QSGMaterial::Flag) (QSGMaterial::RequiresFullMatrix & ~QSGMaterial::RequiresFullMatrixExceptTranslate); |
131 | |
132 | struct 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 | |
142 | const float OPAQUE_LIMIT = 0.999f; |
143 | |
144 | const uint DYNAMIC_VERTEX_INDEX_BUFFER_THRESHOLD = 4; |
145 | const int VERTEX_BUFFER_BINDING = 0; |
146 | const int ZORDER_BUFFER_BINDING = VERTEX_BUFFER_BINDING + 1; |
147 | |
148 | static inline uint aligned(uint v, uint byteAlign) |
149 | { |
150 | return (v + byteAlign - 1) & ~(byteAlign - 1); |
151 | } |
152 | |
153 | QRhiVertexInputAttribute::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("Unsupported attribute type 0x%x with %d components" , a.type, a.tupleSize); |
178 | Q_UNREACHABLE(); |
179 | return QRhiVertexInputAttribute::Float; |
180 | } |
181 | |
182 | static 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("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(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(a.position)) { |
198 | qWarning("Vertex input %d is present in material but not in shader. This is wrong." , |
199 | a.position); |
200 | } |
201 | inputAttributes.append(QRhiVertexInputAttribute(VERTEX_BUFFER_BINDING, a.position, qsg_vertexInputFormat(a), offset)); |
202 | offset += a.tupleSize * size_of_type(a.type); |
203 | } |
204 | if (batchable) { |
205 | inputAttributes.append(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(QRhiVertexInputBinding(geometry->sizeOfVertex())); |
212 | if (batchable) |
213 | inputBindings.append(QRhiVertexInputBinding(sizeof(float))); |
214 | |
215 | QRhiVertexInputLayout inputLayout; |
216 | inputLayout.setBindings(inputBindings.cbegin(), inputBindings.cend()); |
217 | inputLayout.setAttributes(inputAttributes.cbegin(), inputAttributes.cend()); |
218 | |
219 | return inputLayout; |
220 | } |
221 | |
222 | QRhiCommandBuffer::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 | |
237 | QRhiGraphicsPipeline::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("Primitive topology 0x%x not supported" , geomDrawMode); |
258 | break; |
259 | } |
260 | return topology; |
261 | } |
262 | |
263 | ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material, bool enableRhiShaders, const QSGGeometry *geometry) |
264 | { |
265 | QSGMaterialType *type = material->type(); |
266 | Shader *shader = rewrittenShaders.value(type, 0); |
267 | if (shader) |
268 | return shader; |
269 | |
270 | if (enableRhiShaders && !material->flags().testFlag(QSGMaterial::SupportsRhiShader)) { |
271 | qWarning("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(QSGMaterial::RhiShaderWanted, true); |
283 | QSGMaterialRhiShader *s = static_cast<QSGMaterialRhiShader *>(material->createShader()); |
284 | material->setFlag(QSGMaterial::RhiShaderWanted, false); |
285 | context->initializeRhiShader(s, QShader::BatchableVertexShader); |
286 | shader->programRhi.program = s; |
287 | shader->programRhi.inputLayout = calculateVertexInputLayout(s, geometry, true); |
288 | QSGMaterialRhiShaderPrivate *sD = QSGMaterialRhiShaderPrivate::get(s); |
289 | shader->programRhi.shaderStages = { |
290 | { QRhiGraphicsShaderStage::Vertex, sD->shader(QShader::VertexStage), QShader::BatchableVertexShader }, |
291 | { QRhiGraphicsShaderStage::Fragment, sD->shader(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(attr[i], i); |
303 | } |
304 | p->bindAttributeLocation("_qt_order" , i); |
305 | context->compileShader(s, material, qsgShaderRewriter_insertZAttributes(s->vertexShader(), profile), nullptr); |
306 | context->initializeShader(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 | |
326 | ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material, bool enableRhiShaders, const QSGGeometry *geometry) |
327 | { |
328 | QSGMaterialType *type = material->type(); |
329 | Shader *shader = stockShaders.value(type, 0); |
330 | if (shader) |
331 | return shader; |
332 | |
333 | if (enableRhiShaders && !material->flags().testFlag(QSGMaterial::SupportsRhiShader)) { |
334 | qWarning("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(QSGMaterial::RhiShaderWanted, true); |
346 | QSGMaterialRhiShader *s = static_cast<QSGMaterialRhiShader *>(material->createShader()); |
347 | material->setFlag(QSGMaterial::RhiShaderWanted, false); |
348 | context->initializeRhiShader(s, QShader::StandardShader); |
349 | shader->programRhi.program = s; |
350 | shader->programRhi.inputLayout = calculateVertexInputLayout(s, geometry, false); |
351 | QSGMaterialRhiShaderPrivate *sD = QSGMaterialRhiShaderPrivate::get(s); |
352 | shader->programRhi.shaderStages = { |
353 | { QRhiGraphicsShaderStage::Vertex, sD->shader(QShader::VertexStage) }, |
354 | { QRhiGraphicsShaderStage::Fragment, sD->shader(QShader::FragmentStage) } |
355 | }; |
356 | } else { |
357 | QSGMaterialShader *s = material->createShader(); |
358 | context->compileShader(s, material); |
359 | context->initializeShader(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 | |
375 | void ShaderManager::invalidated() |
376 | { |
377 | qDeleteAll(stockShaders); |
378 | stockShaders.clear(); |
379 | qDeleteAll(rewrittenShaders); |
380 | rewrittenShaders.clear(); |
381 | delete blitProgram; |
382 | blitProgram = nullptr; |
383 | |
384 | qDeleteAll(srbCache); |
385 | srbCache.clear(); |
386 | |
387 | qDeleteAll(pipelineCache); |
388 | pipelineCache.clear(); |
389 | } |
390 | |
391 | void 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 | |
409 | QRhiShaderResourceBindings *ShaderManager::srb(const ShaderResourceBindingList &bindings) |
410 | { |
411 | auto it = srbCache.constFind(bindings); |
412 | if (it != srbCache.constEnd()) |
413 | return *it; |
414 | |
415 | QRhiShaderResourceBindings *srb = context->rhi()->newShaderResourceBindings(); |
416 | srb->setBindings(bindings.cbegin(), bindings.cend()); |
417 | if (srb->build()) { |
418 | srbCache.insert(bindings, srb); |
419 | } else { |
420 | qWarning("Failed to build srb" ); |
421 | delete srb; |
422 | srb = nullptr; |
423 | } |
424 | return srb; |
425 | } |
426 | |
427 | void qsg_dumpShadowRoots(BatchRootInfo *i, int indent) |
428 | { |
429 | static int = 0; |
430 | ++extraIndent; |
431 | |
432 | QByteArray ind(indent + extraIndent + 10, ' '); |
433 | |
434 | if (!i) { |
435 | qDebug("%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((*it)->rootInfo(), indent); |
442 | } |
443 | } |
444 | |
445 | --extraIndent; |
446 | } |
447 | |
448 | void 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(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(child); |
468 | |
469 | --indent; |
470 | #else |
471 | Q_UNUSED(n) |
472 | #endif |
473 | } |
474 | |
475 | Updater::Updater(Renderer *r) |
476 | : renderer(r) |
477 | , m_roots(32) |
478 | , m_rootMatrices(8) |
479 | { |
480 | m_roots.add(0); |
481 | m_combined_matrix_stack.add(&m_identityMatrix); |
482 | m_rootMatrices.add(m_identityMatrix); |
483 | |
484 | Q_ASSERT(sizeof(QMatrix4x4_Accessor) == sizeof(QMatrix4x4)); |
485 | } |
486 | |
487 | void 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(n, 0); |
496 | Q_ASSERT(sn); |
497 | |
498 | if (Q_UNLIKELY(debug_roots())) |
499 | qsg_dumpShadowRoots(sn); |
500 | |
501 | if (Q_UNLIKELY(debug_build())) { |
502 | qDebug("Updater::updateStates()" ); |
503 | if (sn->dirtyState & (QSGNode::DirtyNodeAdded << 16)) |
504 | qDebug(" - nodes have been added" ); |
505 | if (sn->dirtyState & (QSGNode::DirtyMatrix << 16)) |
506 | qDebug(" - transforms have changed" ); |
507 | if (sn->dirtyState & (QSGNode::DirtyOpacity << 16)) |
508 | qDebug(" - opacity has changed" ); |
509 | if (uint(sn->dirtyState) & uint(QSGNode::DirtyForceUpdate << 16)) |
510 | qDebug(" - forceupdate" ); |
511 | } |
512 | |
513 | if (Q_UNLIKELY(renderer->m_visualizer->mode() == Visualizer::VisualizeChanges)) |
514 | renderer->m_visualizer->visualizeChangesPrepare(sn); |
515 | |
516 | visitNode(sn); |
517 | } |
518 | |
519 | void 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(child); |
551 | break; |
552 | } |
553 | |
554 | m_added = count; |
555 | m_force_update = force; |
556 | n->dirtyState = {}; |
557 | } |
558 | |
559 | void Updater::visitClipNode(Node *n) |
560 | { |
561 | ClipBatchRootInfo * = n->clipInfo(); |
562 | |
563 | QSGClipNode *cn = static_cast<QSGClipNode *>(n->sgNode); |
564 | |
565 | if (m_roots.last() && m_added > 0) |
566 | renderer->registerBatchRoot(n, m_roots.last()); |
567 | |
568 | cn->setRendererClipList(m_current_clip); |
569 | m_current_clip = cn; |
570 | m_roots << n; |
571 | m_rootMatrices.add(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(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 | |
584 | void 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(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(child); |
601 | --m_opacityChange; |
602 | } else { |
603 | if (m_added > 0) |
604 | n->isOpaque = on->opacity() > OPAQUE_LIMIT; |
605 | SHADOWNODE_TRAVERSE(n) visitNode(child); |
606 | } |
607 | |
608 | m_opacity_stack.pop_back(); |
609 | } |
610 | |
611 | void 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(n, 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(n); |
629 | for (QSet<Node *>::const_iterator it = info->subRoots.constBegin(); |
630 | it != info->subRoots.constEnd(); ++it) { |
631 | updateRootTransforms(*it, n, tn->combinedMatrix()); |
632 | } |
633 | return; |
634 | } |
635 | |
636 | n->becameBatchRoot = false; |
637 | |
638 | m_combined_matrix_stack.add(&m_identityMatrix); |
639 | m_roots.add(n); |
640 | m_rootMatrices.add(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(&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(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 | |
667 | void 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(*gn->matrix()); |
679 | |
680 | if (e->root) { |
681 | BatchRootInfo *info = renderer->batchRootInfo(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(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(*gn->matrix()); |
702 | } |
703 | if (m_opacityChange) { |
704 | Element *e = n->element(); |
705 | if (e->batch) |
706 | renderer->invalidateBatchAndOverlappingRenderOrders(e->batch); |
707 | } |
708 | } |
709 | |
710 | SHADOWNODE_TRAVERSE(n) visitNode(child); |
711 | } |
712 | |
713 | void 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(*it, node, m); |
737 | } |
738 | } |
739 | |
740 | int 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(attr.type); |
749 | } |
750 | return -1; |
751 | } |
752 | |
753 | |
754 | void Rect::map(const QMatrix4x4 &matrix) |
755 | { |
756 | const float *m = matrix.constData(); |
757 | if (QMatrix4x4_Accessor::isScale(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(tl.x, br.x); |
764 | if (tl.y > br.y) |
765 | qSwap(tl.y, br.y); |
766 | } else { |
767 | Pt mtl = tl; |
768 | Pt mtr = { br.x, tl.y }; |
769 | Pt mbl = { tl.x, br.y }; |
770 | Pt mbr = br; |
771 | |
772 | mtl.map(matrix); |
773 | mtr.map(matrix); |
774 | mbl.map(matrix); |
775 | mbr.map(matrix); |
776 | |
777 | set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); |
778 | (*this) |= mtl; |
779 | (*this) |= mtr; |
780 | (*this) |= mbl; |
781 | (*this) |= mbr; |
782 | } |
783 | } |
784 | |
785 | void 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(-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX); |
795 | return; |
796 | } |
797 | |
798 | bounds.set(FLT_MAX, FLT_MAX, -FLT_MAX, -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(*node->matrix()); |
805 | |
806 | if (!qt_is_finite(bounds.tl.x) || bounds.tl.x == FLT_MAX) |
807 | bounds.tl.x = -FLT_MAX; |
808 | if (!qt_is_finite(bounds.tl.y) || bounds.tl.y == FLT_MAX) |
809 | bounds.tl.y = -FLT_MAX; |
810 | if (!qt_is_finite(bounds.br.x) || bounds.br.x == -FLT_MAX) |
811 | bounds.br.x = FLT_MAX; |
812 | if (!qt_is_finite(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 | |
821 | BatchCompatibility 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(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 | */ |
845 | bool 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 | |
860 | void 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 | */ |
889 | void 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 | |
905 | bool 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 | */ |
932 | bool Batch::isSafeToBatch() const { |
933 | Element *e = first; |
934 | while (e) { |
935 | if (e->boundsOutsideFloatRange) |
936 | return false; |
937 | if (!QMatrix4x4_Accessor::is2DSafe(*e->node->matrix())) |
938 | return false; |
939 | e = e->nextInBatch; |
940 | } |
941 | return true; |
942 | } |
943 | |
944 | static 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 | |
955 | static 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(batches.at(i)); |
960 | } |
961 | return sum; |
962 | } |
963 | |
964 | Renderer::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(QRhi::NonFourAlignedEffectiveIndexBufferOffset); |
997 | if (qEnvironmentVariableIntValue("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" ), 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(ctx, SIGNAL(invalidated()), 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("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("QSG_RENDERER_BATCH_NODE_THRESHOLD" , 64); |
1029 | m_batchVertexThreshold = qt_sg_envInt("QSG_RENDERER_BATCH_VERTEX_THRESHOLD" , 1024); |
1030 | |
1031 | if (Q_UNLIKELY(debug_build() || debug_render())) { |
1032 | qDebug("Batch thresholds: nodes: %d vertices: %d" , |
1033 | m_batchNodeThreshold, m_batchVertexThreshold); |
1034 | qDebug("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("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 | |
1053 | static 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(1, &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(buffer->data); |
1069 | } |
1070 | |
1071 | static void qsg_wipeBatch(Batch *batch, QOpenGLFunctions *funcs, bool separateIndexBuffer) |
1072 | { |
1073 | qsg_wipeBuffer(&batch->vbo, funcs); |
1074 | if (separateIndexBuffer) |
1075 | qsg_wipeBuffer(&batch->ibo, funcs); |
1076 | delete batch->ubuf; |
1077 | batch->stencilClipState.reset(); |
1078 | delete batch; |
1079 | } |
1080 | |
1081 | Renderer::~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(m_opaqueBatches.at(i), this, separateIndexBuffer); |
1088 | for (int i = 0; i < m_alphaBatches.size(); ++i) |
1089 | qsg_wipeBatch(m_alphaBatches.at(i), this, separateIndexBuffer); |
1090 | for (int i = 0; i < m_batchPool.size(); ++i) |
1091 | qsg_wipeBatch(m_batchPool.at(i), this, separateIndexBuffer); |
1092 | } |
1093 | |
1094 | for (Node *n : qAsConst(m_nodes)) |
1095 | m_nodeAllocator.release(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(e); |
1104 | } |
1105 | |
1106 | destroyGraphicsResources(); |
1107 | |
1108 | delete m_visualizer; |
1109 | } |
1110 | |
1111 | void 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(m_samplers); |
1120 | m_stencilClipCommon.reset(); |
1121 | delete m_dummyTexture; |
1122 | m_visualizer->releaseResources(); |
1123 | } |
1124 | |
1125 | void 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 | |
1138 | void Renderer::invalidateAndRecycleBatch(Batch *b) |
1139 | { |
1140 | b->invalidate(); |
1141 | for (int i=0; i<m_batchPool.size(); ++i) |
1142 | if (b == m_batchPool.at(i)) |
1143 | return; |
1144 | m_batchPool.add(b); |
1145 | } |
1146 | |
1147 | /* The code here does a CPU-side allocation which might seem like a performance issue |
1148 | * compared to using glMapBuffer or glMapBufferRange which would give me back |
1149 | * potentially GPU allocated memory and saving me one deep-copy, but... |
1150 | * |
1151 | * Because we do a lot of CPU-side transformations, we need random-access memory |
1152 | * and the memory returned from glMapBuffer/glMapBufferRange is typically |
1153 | * uncached and thus very slow for our purposes. |
1154 | * |
1155 | * ref: http://www.opengl.org/wiki/Buffer_Object |
1156 | */ |
1157 | void Renderer::map(Buffer *buffer, int byteSize, bool isIndexBuf) |
1158 | { |
1159 | if (!m_context->hasBrokenIndexBufferObjects() && m_visualizer->mode() == Visualizer::VisualizeNothing) { |
1160 | // Common case, use a shared memory pool for uploading vertex data to avoid |
1161 | // excessive reevaluation |
1162 | QDataBuffer<char> &pool = m_context->separateIndexBuffer() && isIndexBuf |
1163 | ? m_indexUploadPool : m_vertexUploadPool; |
1164 | if (byteSize > pool.size()) |
1165 | pool.resize(byteSize); |
1166 | buffer->data = pool.data(); |
1167 | } else if (buffer->size != byteSize) { |
1168 | free(buffer->data); |
1169 | buffer->data = (char *) malloc(byteSize); |
1170 | Q_CHECK_PTR(buffer->data); |
1171 | } |
1172 | buffer->size = byteSize; |
1173 | } |
1174 | |
1175 | void Renderer::unmap(Buffer *buffer, bool isIndexBuf) |
1176 | { |
1177 | if (m_rhi) { |
1178 | // Batches are pooled and reused which means the QRhiBuffer will be |
1179 | // still valid in a recycled Batch. We only hit the newBuffer() path |
1180 | // for brand new Batches. |
1181 | if (!buffer->buf) { |
1182 | buffer->buf = m_rhi->newBuffer(QRhiBuffer::Immutable, |
1183 | isIndexBuf ? QRhiBuffer::IndexBuffer : QRhiBuffer::VertexBuffer, |
1184 | buffer->size); |
1185 | if (!buffer->buf->build()) |
1186 | qWarning("Failed to build vertex/index buffer of size %d" , buffer->size); |
1187 | // else |
1188 | // qDebug("created rhibuf %p size %d", buffer->buf, buffer->size); |
1189 | } else { |
1190 | bool needsRebuild = false; |
1191 | if (buffer->buf->size() < buffer->size) { |
1192 | buffer->buf->setSize(buffer->size); |
1193 | needsRebuild = true; |
1194 | } |
1195 | if (buffer->buf->type() != QRhiBuffer::Dynamic |
1196 | && buffer->nonDynamicChangeCount > DYNAMIC_VERTEX_INDEX_BUFFER_THRESHOLD) |
1197 | { |
1198 | buffer->buf->setType(QRhiBuffer::Dynamic); |
1199 | buffer->nonDynamicChangeCount = 0; |
1200 | needsRebuild = true; |
1201 | } |
1202 | if (needsRebuild) { |
1203 | //qDebug("rebuilding rhibuf %p size %d type Dynamic", buffer->buf, buffer->size); |
1204 | buffer->buf->build(); |
1205 | } |
1206 | } |
1207 | if (buffer->buf->type() != QRhiBuffer::Dynamic) { |
1208 | m_resourceUpdates->uploadStaticBuffer(buffer->buf, |
1209 | QByteArray::fromRawData(buffer->data, buffer->size)); |
1210 | buffer->nonDynamicChangeCount += 1; |
1211 | } else { |
1212 | m_resourceUpdates->updateDynamicBuffer(buffer->buf, 0, buffer->size, |
1213 | QByteArray::fromRawData(buffer->data, buffer->size)); |
1214 | } |
1215 | if (m_visualizer->mode() == Visualizer::VisualizeNothing) |
1216 | buffer->data = nullptr; |
1217 | } else { |
1218 | if (buffer->id == 0) |
1219 | glGenBuffers(1, &buffer->id); |
1220 | GLenum target = isIndexBuf ? GL_ELEMENT_ARRAY_BUFFER : GL_ARRAY_BUFFER; |
1221 | glBindBuffer(target, buffer->id); |
1222 | glBufferData(target, buffer->size, buffer->data, m_bufferStrategy); |
1223 | if (!m_context->hasBrokenIndexBufferObjects() && m_visualizer->mode() == Visualizer::VisualizeNothing) |
1224 | buffer->data = nullptr; |
1225 | } |
1226 | } |
1227 | |
1228 | BatchRootInfo *Renderer::batchRootInfo(Node *node) |
1229 | { |
1230 | BatchRootInfo *info = node->rootInfo(); |
1231 | if (!info) { |
1232 | if (node->type() == QSGNode::ClipNodeType) |
1233 | info = new ClipBatchRootInfo; |
1234 | else { |
1235 | Q_ASSERT(node->type() == QSGNode::TransformNodeType); |
1236 | info = new BatchRootInfo; |
1237 | } |
1238 | node->data = info; |
1239 | } |
1240 | return info; |
1241 | } |
1242 | |
1243 | void Renderer::removeBatchRootFromParent(Node *childRoot) |
1244 | { |
1245 | BatchRootInfo *childInfo = batchRootInfo(childRoot); |
1246 | if (!childInfo->parentRoot) |
1247 | return; |
1248 | BatchRootInfo *parentInfo = batchRootInfo(childInfo->parentRoot); |
1249 | |
1250 | Q_ASSERT(parentInfo->subRoots.contains(childRoot)); |
1251 | parentInfo->subRoots.remove(childRoot); |
1252 | childInfo->parentRoot = nullptr; |
1253 | } |
1254 | |
1255 | void Renderer::registerBatchRoot(Node *subRoot, Node *parentRoot) |
1256 | { |
1257 | BatchRootInfo *subInfo = batchRootInfo(subRoot); |
1258 | BatchRootInfo *parentInfo = batchRootInfo(parentRoot); |
1259 | subInfo->parentRoot = parentRoot; |
1260 | parentInfo->subRoots << subRoot; |
1261 | } |
1262 | |
1263 | bool Renderer::changeBatchRoot(Node *node, Node *root) |
1264 | { |
1265 | BatchRootInfo *subInfo = batchRootInfo(node); |
1266 | if (subInfo->parentRoot == root) |
1267 | return false; |
1268 | if (subInfo->parentRoot) { |
1269 | BatchRootInfo *oldRootInfo = batchRootInfo(subInfo->parentRoot); |
1270 | oldRootInfo->subRoots.remove(node); |
1271 | } |
1272 | BatchRootInfo *newRootInfo = batchRootInfo(root); |
1273 | newRootInfo->subRoots << node; |
1274 | subInfo->parentRoot = root; |
1275 | return true; |
1276 | } |
1277 | |
1278 | void Renderer::nodeChangedBatchRoot(Node *node, Node *root) |
1279 | { |
1280 | if (node->type() == QSGNode::ClipNodeType || node->isBatchRoot) { |
1281 | // When we reach a batchroot, we only need to update it. Its subtree |
1282 | // is relative to that root, so no need to recurse further. |
1283 | changeBatchRoot(node, root); |
1284 | return; |
1285 | } else if (node->type() == QSGNode::GeometryNodeType) { |
1286 | // Only need to change the root as nodeChanged anyway flags a full update. |
1287 | Element *e = node->element(); |
1288 | if (e) { |
1289 | e->root = root; |
1290 | e->boundsComputed = false; |
1291 | } |
1292 | } else if (node->type() == QSGNode::RenderNodeType) { |
1293 | RenderNodeElement *e = node->renderNodeElement(); |
1294 | if (e) |
1295 | e->root = root; |
1296 | } |
1297 | |
1298 | SHADOWNODE_TRAVERSE(node) |
1299 | nodeChangedBatchRoot(child, root); |
1300 | } |
1301 | |
1302 | void Renderer::nodeWasTransformed(Node *node, int *vertexCount) |
1303 | { |
1304 | if (node->type() == QSGNode::GeometryNodeType) { |
1305 | QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node->sgNode); |
1306 | *vertexCount += gn->geometry()->vertexCount(); |
1307 | Element *e = node->element(); |
1308 | if (e) { |
1309 | e->boundsComputed = false; |
1310 | if (e->batch) { |
1311 | if (!e->batch->isOpaque) { |
1312 | invalidateBatchAndOverlappingRenderOrders(e->batch); |
1313 | } else if (e->batch->merged) { |
1314 | e->batch->needsUpload = true; |
1315 | } |
1316 | } |
1317 | } |
1318 | } |
1319 | |
1320 | SHADOWNODE_TRAVERSE(node) |
1321 | nodeWasTransformed(child, vertexCount); |
1322 | } |
1323 | |
1324 | void Renderer::nodeWasAdded(QSGNode *node, Node *shadowParent) |
1325 | { |
1326 | Q_ASSERT(!m_nodes.contains(node)); |
1327 | if (node->isSubtreeBlocked()) |
1328 | return; |
1329 | |
1330 | Node *snode = m_nodeAllocator.allocate(); |
1331 | snode->sgNode = node; |
1332 | m_nodes.insert(node, snode); |
1333 | if (shadowParent) |
1334 | shadowParent->append(snode); |
1335 | |
1336 | if (node->type() == QSGNode::GeometryNodeType) { |
1337 | snode->data = m_elementAllocator.allocate(); |
1338 | snode->element()->setNode(static_cast<QSGGeometryNode *>(node)); |
1339 | |
1340 | } else if (node->type() == QSGNode::ClipNodeType) { |
1341 | snode->data = new ClipBatchRootInfo; |
1342 | m_rebuild |= FullRebuild; |
1343 | |
1344 | } else if (node->type() == QSGNode::RenderNodeType) { |
1345 | QSGRenderNode *rn = static_cast<QSGRenderNode *>(node); |
1346 | RenderNodeElement *e = new RenderNodeElement(rn); |
1347 | snode->data = e; |
1348 | Q_ASSERT(!m_renderNodeElements.contains(rn)); |
1349 | m_renderNodeElements.insert(e->renderNode, e); |
1350 | if (!rn->flags().testFlag(QSGRenderNode::DepthAwareRendering)) |
1351 | m_useDepthBuffer = false; |
1352 | m_rebuild |= FullRebuild; |
1353 | } |
1354 | |
1355 | QSGNODE_TRAVERSE(node) |
1356 | nodeWasAdded(child, snode); |
1357 | } |
1358 | |
1359 | void Renderer::nodeWasRemoved(Node *node) |
1360 | { |
1361 | // Prefix traversal as removeBatchRootFromParent below removes nodes |
1362 | // in a bottom-up manner. Note that we *cannot* use SHADOWNODE_TRAVERSE |
1363 | // here, because we delete 'child' (when recursed, down below), so we'd |
1364 | // have a use-after-free. |
1365 | { |
1366 | Node *child = node->firstChild(); |
1367 | while (child) { |
1368 | // Remove (and delete) child |
1369 | node->remove(child); |
1370 | nodeWasRemoved(child); |
1371 | child = node->firstChild(); |
1372 | } |
1373 | } |
1374 | |
1375 | if (node->type() == QSGNode::GeometryNodeType) { |
1376 | Element *e = node->element(); |
1377 | if (e) { |
1378 | e->removed = true; |
1379 | m_elementsToDelete.add(e); |
1380 | e->node = nullptr; |
1381 | if (e->root) { |
1382 | BatchRootInfo *info = batchRootInfo(e->root); |
1383 | info->availableOrders++; |
1384 | } |
1385 | if (e->batch) { |
1386 | e->batch->needsUpload = true; |
1387 | e->batch->needsPurge = true; |
1388 | } |
1389 | |
1390 | } |
1391 | |
1392 | } else if (node->type() == QSGNode::ClipNodeType) { |
1393 | removeBatchRootFromParent(node); |
1394 | delete node->clipInfo(); |
1395 | m_rebuild |= FullRebuild; |
1396 | m_taggedRoots.remove(node); |
1397 | |
1398 | } else if (node->isBatchRoot) { |
1399 | removeBatchRootFromParent(node); |
1400 | delete node->rootInfo(); |
1401 | m_rebuild |= FullRebuild; |
1402 | m_taggedRoots.remove(node); |
1403 | |
1404 | } else if (node->type() == QSGNode::RenderNodeType) { |
1405 | RenderNodeElement *e = m_renderNodeElements.take(static_cast<QSGRenderNode *>(node->sgNode)); |
1406 | if (e) { |
1407 | e->removed = true; |
1408 | m_elementsToDelete.add(e); |
1409 | if (m_renderNodeElements.isEmpty()) { |
1410 | static const bool useDepth = qEnvironmentVariableIsEmpty("QSG_NO_DEPTH_BUFFER" ); |
1411 | if (m_rhi) |
1412 | m_useDepthBuffer = useDepth; |
1413 | else |
1414 | m_useDepthBuffer = useDepth && m_context->openglContext()->format().depthBufferSize() > 0; |
1415 | } |
1416 | |
1417 | if (e->batch != nullptr) |
1418 | e->batch->needsPurge = true; |
1419 | } |
1420 | } |
1421 | |
1422 | Q_ASSERT(m_nodes.contains(node->sgNode)); |
1423 | |
1424 | m_nodeAllocator.release(m_nodes.take(node->sgNode)); |
1425 | } |
1426 | |
1427 | void Renderer::turnNodeIntoBatchRoot(Node *node) |
1428 | { |
1429 | if (Q_UNLIKELY(debug_change())) qDebug(" - new batch root" ); |
1430 | m_rebuild |= FullRebuild; |
1431 | node->isBatchRoot = true; |
1432 | node->becameBatchRoot = true; |
1433 | |
1434 | Node *p = node->parent(); |
1435 | while (p) { |
1436 | if (p->type() == QSGNode::ClipNodeType || p->isBatchRoot) { |
1437 | registerBatchRoot(node, p); |
1438 | break; |
1439 | } |
1440 | p = p->parent(); |
1441 | } |
1442 | |
1443 | SHADOWNODE_TRAVERSE(node) |
1444 | nodeChangedBatchRoot(child, node); |
1445 | } |
1446 | |
1447 | |
1448 | void Renderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state) |
1449 | { |
1450 | #ifndef QT_NO_DEBUG_OUTPUT |
1451 | if (Q_UNLIKELY(debug_change())) { |
1452 | QDebug debug = qDebug(); |
1453 | debug << "dirty:" ; |
1454 | if (state & QSGNode::DirtyGeometry) |
1455 | debug << "Geometry" ; |
1456 | if (state & QSGNode::DirtyMaterial) |
1457 | debug << "Material" ; |
1458 | if (state & QSGNode::DirtyMatrix) |
1459 | debug << "Matrix" ; |
1460 | if (state & QSGNode::DirtyNodeAdded) |
1461 | debug << "Added" ; |
1462 | if (state & QSGNode::DirtyNodeRemoved) |
1463 | debug << "Removed" ; |
1464 | if (state & QSGNode::DirtyOpacity) |
1465 | debug << "Opacity" ; |
1466 | if (state & QSGNode::DirtySubtreeBlocked) |
1467 | debug << "SubtreeBlocked" ; |
1468 | if (state & QSGNode::DirtyForceUpdate) |
1469 | debug << "ForceUpdate" ; |
1470 | |
1471 | // when removed, some parts of the node could already have been destroyed |
1472 | // so don't debug it out. |
1473 | if (state & QSGNode::DirtyNodeRemoved) |
1474 | debug << (void *) node << node->type(); |
1475 | else |
1476 | debug << node; |
1477 | } |
1478 | #endif |
1479 | // As this function calls nodeChanged recursively, we do it at the top |
1480 | // to avoid that any of the others are processed twice. |
1481 | if (state & QSGNode::DirtySubtreeBlocked) { |
1482 | Node *sn = m_nodes.value(node); |
1483 | |
1484 | // Force a batch rebuild if this includes an opacity change |
1485 | if (state & QSGNode::DirtyOpacity) |
1486 | m_rebuild |= FullRebuild; |
1487 | |
1488 | bool blocked = node->isSubtreeBlocked(); |
1489 | if (blocked && sn) { |
1490 | nodeChanged(node, QSGNode::DirtyNodeRemoved); |
1491 | Q_ASSERT(m_nodes.value(node) == 0); |
1492 | } else if (!blocked && !sn) { |
1493 | nodeChanged(node, QSGNode::DirtyNodeAdded); |
1494 | } |
1495 | return; |
1496 | } |
1497 | |
1498 | if (state & QSGNode::DirtyNodeAdded) { |
1499 | if (nodeUpdater()->isNodeBlocked(node, rootNode())) { |
1500 | QSGRenderer::nodeChanged(node, state); |
1501 | return; |
1502 | } |
1503 | if (node == rootNode()) |
1504 | nodeWasAdded(node, nullptr); |
1505 | else |
1506 | nodeWasAdded(node, m_nodes.value(node->parent())); |
1507 | } |
1508 | |
1509 | // Mark this node dirty in the shadow tree. |
1510 | Node *shadowNode = m_nodes.value(node); |
1511 | |
1512 | // Blocked subtrees won't have shadow nodes, so we can safely abort |
1513 | // here.. |
1514 | if (!shadowNode) { |
1515 | QSGRenderer::nodeChanged(node, state); |
1516 | return; |
1517 | } |
1518 | |
1519 | shadowNode->dirtyState |= state; |
1520 | |
1521 | if (state & QSGNode::DirtyMatrix && !shadowNode->isBatchRoot) { |
1522 | Q_ASSERT(node->type() == QSGNode::TransformNodeType); |
1523 | if (node->m_subtreeRenderableCount > m_batchNodeThreshold) { |
1524 | turnNodeIntoBatchRoot(shadowNode); |
1525 | } else { |
1526 | int vertices = 0; |
1527 | nodeWasTransformed(shadowNode, &vertices); |
1528 | if (vertices > m_batchVertexThreshold) { |
1529 | turnNodeIntoBatchRoot(shadowNode); |
1530 | } |
1531 | } |
1532 | } |
1533 | |
1534 | if (state & QSGNode::DirtyGeometry && node->type() == QSGNode::GeometryNodeType) { |
1535 | QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node); |
1536 | Element *e = shadowNode->element(); |
1537 | if (e) { |
1538 | e->boundsComputed = false; |
1539 | Batch *b = e->batch; |
1540 | if (b) { |
1541 | if (!e->batch->geometryWasChanged(gn) || !e->batch->isOpaque) { |
1542 | invalidateBatchAndOverlappingRenderOrders(e->batch); |
1543 | } else { |
1544 | b->needsUpload = true; |
1545 | } |
1546 | } |
1547 | } |
1548 | } |
1549 | |
1550 | if (state & QSGNode::DirtyMaterial && node->type() == QSGNode::GeometryNodeType) { |
1551 | Element *e = shadowNode->element(); |
1552 | if (e) { |
1553 | bool blended = hasMaterialWithBlending(static_cast<QSGGeometryNode *>(node)); |
1554 | if (e->isMaterialBlended != blended) { |
1555 | m_rebuild |= Renderer::FullRebuild; |
1556 | e->isMaterialBlended = blended; |
1557 | } else if (e->batch) { |
1558 | if (e->batch->isMaterialCompatible(e) == BatchBreaksOnCompare) |
1559 | invalidateBatchAndOverlappingRenderOrders(e->batch); |
1560 | } else { |
1561 | m_rebuild |= Renderer::BuildBatches; |
1562 | } |
1563 | } |
1564 | } |
1565 | |
1566 | // Mark the shadow tree dirty all the way back to the root... |
1567 | QSGNode::DirtyState dirtyChain = state & (QSGNode::DirtyNodeAdded |
1568 | | QSGNode::DirtyOpacity |
1569 | | QSGNode::DirtyMatrix |
1570 | | QSGNode::DirtySubtreeBlocked |
1571 | | QSGNode::DirtyForceUpdate); |
1572 | if (dirtyChain != 0) { |
1573 | dirtyChain = QSGNode::DirtyState(dirtyChain << 16); |
1574 | Node *sn = shadowNode->parent(); |
1575 | while (sn) { |
1576 | sn->dirtyState |= dirtyChain; |
1577 | sn = sn->parent(); |
1578 | } |
1579 | } |
1580 | |
1581 | // Delete happens at the very end because it deletes the shadownode. |
1582 | if (state & QSGNode::DirtyNodeRemoved) { |
1583 | Node *parent = shadowNode->parent(); |
1584 | if (parent) |
1585 | parent->remove(shadowNode); |
1586 | nodeWasRemoved(shadowNode); |
1587 | Q_ASSERT(m_nodes.value(node) == 0); |
1588 | } |
1589 | |
1590 | QSGRenderer::nodeChanged(node, state); |
1591 | } |
1592 | |
1593 | /* |
1594 | * Traverses the tree and builds two list of geometry nodes. One for |
1595 | * the opaque and one for the translucent. These are populated |
1596 | * in the order they should visually appear in, meaning first |
1597 | * to the back and last to the front. |
1598 | * |
1599 | * We split opaque and translucent as we can perform different |
1600 | * types of reordering / batching strategies on them, depending |
1601 | * |
1602 | * Note: It would be tempting to use the shadow nodes instead of the QSGNodes |
1603 | * for traversal to avoid hash lookups, but the order of the children |
1604 | * is important and they are not preserved in the shadow tree, so we must |
1605 | * use the actual QSGNode tree. |
1606 | */ |
1607 | void Renderer::buildRenderLists(QSGNode *node) |
1608 | { |
1609 | if (node->isSubtreeBlocked()) |
1610 | return; |
1611 | |
1612 | Node *shadowNode = m_nodes.value(node); |
1613 | Q_ASSERT(shadowNode); |
1614 | |
1615 | if (node->type() == QSGNode::GeometryNodeType) { |
1616 | QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node); |
1617 | |
1618 | Element *e = shadowNode->element(); |
1619 | Q_ASSERT(e); |
1620 | |
1621 | bool opaque = gn->inheritedOpacity() > OPAQUE_LIMIT && !(gn->activeMaterial()->flags() & QSGMaterial::Blending); |
1622 | if (opaque && m_useDepthBuffer) |
1623 | m_opaqueRenderList << e; |
1624 | else |
1625 | m_alphaRenderList << e; |
1626 | |
1627 | e->order = ++m_nextRenderOrder; |
1628 | // Used while rebuilding partial roots. |
1629 | if (m_partialRebuild) |
1630 | e->orphaned = false; |
1631 | |
1632 | } else if (node->type() == QSGNode::ClipNodeType || shadowNode->isBatchRoot) { |
1633 | Q_ASSERT(m_nodes.contains(node)); |
1634 | BatchRootInfo *info = batchRootInfo(shadowNode); |
1635 | if (node == m_partialRebuildRoot) { |
1636 | m_nextRenderOrder = info->firstOrder; |
1637 | QSGNODE_TRAVERSE(node) |
1638 | buildRenderLists(child); |
1639 | m_nextRenderOrder = info->lastOrder + 1; |
1640 | } else { |
1641 | int currentOrder = m_nextRenderOrder; |
1642 | QSGNODE_TRAVERSE(node) |
1643 | buildRenderLists(child); |
1644 | int padding = (m_nextRenderOrder - currentOrder) >> 2; |
1645 | info->firstOrder = currentOrder; |
1646 | info->availableOrders = padding; |
1647 | info->lastOrder = m_nextRenderOrder + padding; |
1648 | m_nextRenderOrder = info->lastOrder; |
1649 | } |
1650 | return; |
1651 | } else if (node->type() == QSGNode::RenderNodeType) { |
1652 | RenderNodeElement *e = shadowNode->renderNodeElement(); |
1653 | m_alphaRenderList << e; |
1654 | e->order = ++m_nextRenderOrder; |
1655 | Q_ASSERT(e); |
1656 | } |
1657 | |
1658 | QSGNODE_TRAVERSE(node) |
1659 | buildRenderLists(child); |
1660 | } |
1661 | |
1662 | void Renderer::tagSubRoots(Node *node) |
1663 | { |
1664 | BatchRootInfo *i = batchRootInfo(node); |
1665 | m_taggedRoots << node; |
1666 | for (QSet<Node *>::const_iterator it = i->subRoots.constBegin(); |
1667 | it != i->subRoots.constEnd(); ++it) { |
1668 | tagSubRoots(*it); |
1669 | } |
1670 | } |
1671 | |
1672 | static void qsg_addOrphanedElements(QDataBuffer<Element *> &orphans, const QDataBuffer<Element *> &renderList) |
1673 | { |
1674 | orphans.reset(); |
1675 | for (int i=0; i<renderList.size(); ++i) { |
1676 | Element *e = renderList.at(i); |
1677 | if (e && !e->removed) { |
1678 | e->orphaned = true; |
1679 | orphans.add(e); |
1680 | } |
1681 | } |
1682 | } |
1683 | |
1684 | static void qsg_addBackOrphanedElements(QDataBuffer<Element *> &orphans, QDataBuffer<Element *> &renderList) |
1685 | { |
1686 | for (int i=0; i<orphans.size(); ++i) { |
1687 | Element *e = orphans.at(i); |
1688 | if (e->orphaned) |
1689 | renderList.add(e); |
1690 | } |
1691 | orphans.reset(); |
1692 | } |
1693 | |
1694 | /* |
1695 | * To rebuild the tagged roots, we start by putting all subroots of tagged |
1696 | * roots into the list of tagged roots. This is to make the rest of the |
1697 | * algorithm simpler. |
1698 | * |
1699 | * Second, we invalidate all batches which belong to tagged roots, which now |
1700 | * includes the entire subtree under a given root |
1701 | * |
1702 | * Then we call buildRenderLists for all tagged subroots which do not have |
1703 | * parents which are tagged, aka, we traverse only the topmosts roots. |
1704 | * |
1705 | * Then we sort the render lists based on their render order, to restore the |
1706 | * right order for rendering. |
1707 | */ |
1708 | void Renderer::buildRenderListsForTaggedRoots() |
1709 | { |
1710 | // Flag any element that is currently in the render lists, but which |
1711 | // is not in a batch. This happens when we have a partial rebuild |
1712 | // in one sub tree while we have a BuildBatches change in another |
1713 | // isolated subtree. So that batch-building takes into account |
1714 | // these "orphaned" nodes, we flag them now. The ones under tagged |
1715 | // roots will be cleared again. The remaining ones are added into the |
1716 | // render lists so that they contain all visual nodes after the |
1717 | // function completes. |
1718 | qsg_addOrphanedElements(m_tmpOpaqueElements, m_opaqueRenderList); |
1719 | qsg_addOrphanedElements(m_tmpAlphaElements, m_alphaRenderList); |
1720 | |
1721 | // Take a copy now, as we will be adding to this while traversing.. |
1722 | QSet<Node *> roots = m_taggedRoots; |
1723 | for (QSet<Node *>::const_iterator it = roots.constBegin(); |
1724 | it != roots.constEnd(); ++it) { |
1725 | tagSubRoots(*it); |
1726 | } |
1727 | |
1728 | for (int i=0; i<m_opaqueBatches.size(); ++i) { |
1729 | Batch *b = m_opaqueBatches.at(i); |
1730 | if (m_taggedRoots.contains(b->root)) |
1731 | invalidateAndRecycleBatch(b); |
1732 | |
1733 | } |
1734 | for (int i=0; i<m_alphaBatches.size(); ++i) { |
1735 | Batch *b = m_alphaBatches.at(i); |
1736 | if (m_taggedRoots.contains(b->root)) |
1737 | invalidateAndRecycleBatch(b); |
1738 | } |
1739 | |
1740 | m_opaqueRenderList.reset(); |
1741 | m_alphaRenderList.reset(); |
1742 | int maxRenderOrder = m_nextRenderOrder; |
1743 | m_partialRebuild = true; |
1744 | // Traverse each root, assigning it |
1745 | for (QSet<Node *>::const_iterator it = m_taggedRoots.constBegin(); |
1746 | it != m_taggedRoots.constEnd(); ++it) { |
1747 | Node *root = *it; |
1748 | BatchRootInfo *i = batchRootInfo(root); |
1749 | if ((!i->parentRoot || !m_taggedRoots.contains(i->parentRoot)) |
1750 | && !nodeUpdater()->isNodeBlocked(root->sgNode, rootNode())) { |
1751 | m_nextRenderOrder = i->firstOrder; |
1752 | m_partialRebuildRoot = root->sgNode; |
1753 | buildRenderLists(root->sgNode); |
1754 | } |
1755 | } |
1756 | m_partialRebuild = false; |
1757 | m_partialRebuildRoot = nullptr; |
1758 | m_taggedRoots.clear(); |
1759 | m_nextRenderOrder = qMax(m_nextRenderOrder, maxRenderOrder); |
1760 | |
1761 | // Add orphaned elements back into the list and then sort it.. |
1762 | qsg_addBackOrphanedElements(m_tmpOpaqueElements, m_opaqueRenderList); |
1763 | qsg_addBackOrphanedElements(m_tmpAlphaElements, m_alphaRenderList); |
1764 | |
1765 | if (m_opaqueRenderList.size()) |
1766 | std::sort(&m_opaqueRenderList.first(), &m_opaqueRenderList.last() + 1, qsg_sort_element_decreasing_order); |
1767 | if (m_alphaRenderList.size()) |
1768 | std::sort(&m_alphaRenderList.first(), &m_alphaRenderList.last() + 1, qsg_sort_element_increasing_order); |
1769 | |
1770 | } |
1771 | |
1772 | void Renderer::buildRenderListsFromScratch() |
1773 | { |
1774 | m_opaqueRenderList.reset(); |
1775 | m_alphaRenderList.reset(); |
1776 | |
1777 | for (int i=0; i<m_opaqueBatches.size(); ++i) |
1778 | invalidateAndRecycleBatch(m_opaqueBatches.at(i)); |
1779 | for (int i=0; i<m_alphaBatches.size(); ++i) |
1780 | invalidateAndRecycleBatch(m_alphaBatches.at(i)); |
1781 | m_opaqueBatches.reset(); |
1782 | m_alphaBatches.reset(); |
1783 | |
1784 | m_nextRenderOrder = 0; |
1785 | |
1786 | buildRenderLists(rootNode()); |
1787 | } |
1788 | |
1789 | void Renderer::invalidateBatchAndOverlappingRenderOrders(Batch *batch) |
1790 | { |
1791 | Q_ASSERT(batch); |
1792 | Q_ASSERT(batch->first); |
1793 | |
1794 | if (m_renderOrderRebuildLower < 0 || batch->first->order < m_renderOrderRebuildLower) |
1795 | m_renderOrderRebuildLower = batch->first->order; |
1796 | if (m_renderOrderRebuildUpper < 0 || batch->lastOrderInBatch > m_renderOrderRebuildUpper) |
1797 | m_renderOrderRebuildUpper = batch->lastOrderInBatch; |
1798 | |
1799 | batch->invalidate(); |
1800 | |
1801 | for (int i=0; i<m_alphaBatches.size(); ++i) { |
1802 | Batch *b = m_alphaBatches.at(i); |
1803 | if (b->first) { |
1804 | int bf = b->first->order; |
1805 | int bl = b->lastOrderInBatch; |
1806 | if (bl > m_renderOrderRebuildLower && bf < m_renderOrderRebuildUpper) |
1807 | b->invalidate(); |
1808 | } |
1809 | } |
1810 | |
1811 | m_rebuild |= BuildBatches; |
1812 | } |
1813 | |
1814 | /* Clean up batches by making it a consecutive list of "valid" |
1815 | * batches and moving all invalidated batches to the batches pool. |
1816 | */ |
1817 | void Renderer::cleanupBatches(QDataBuffer<Batch *> *batches) { |
1818 | if (batches->size()) { |
1819 | std::stable_sort(&batches->first(), &batches->last() + 1, qsg_sort_batch_is_valid); |
1820 | int count = 0; |
1821 | while (count < batches->size() && batches->at(count)->first) |
1822 | ++count; |
1823 | for (int i=count; i<batches->size(); ++i) |
1824 | invalidateAndRecycleBatch(batches->at(i)); |
1825 | batches->resize(count); |
1826 | } |
1827 | } |
1828 | |
1829 | void Renderer::prepareOpaqueBatches() |
1830 | { |
1831 | for (int i=m_opaqueRenderList.size() - 1; i >= 0; --i) { |
1832 | Element *ei = m_opaqueRenderList.at(i); |
1833 | if (!ei || ei->batch || ei->node->geometry()->vertexCount() == 0) |
1834 | continue; |
1835 | Batch *batch = newBatch(); |
1836 | batch->first = ei; |
1837 | batch->root = ei->root; |
1838 | batch->isOpaque = true; |
1839 | batch->needsUpload = true; |
1840 | batch->positionAttribute = qsg_positionAttribute(ei->node->geometry()); |
1841 | |
1842 | m_opaqueBatches.add(batch); |
1843 | |
1844 | ei->batch = batch; |
1845 | Element *next = ei; |
1846 | |
1847 | QSGGeometryNode *gni = ei->node; |
1848 | |
1849 | for (int j = i - 1; j >= 0; --j) { |
1850 | Element *ej = m_opaqueRenderList.at(j); |
1851 | if (!ej) |
1852 | continue; |
1853 | if (ej->root != ei->root) |
1854 | break; |
1855 | if (ej->batch || ej->node->geometry()->vertexCount() == 0) |
1856 | continue; |
1857 | |
1858 | QSGGeometryNode *gnj = ej->node; |
1859 | |
1860 | if (gni->clipList() == gnj->clipList() |
1861 | && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode() |
1862 | && (gni->geometry()->drawingMode() != QSGGeometry::DrawLines || gni->geometry()->lineWidth() == gnj->geometry()->lineWidth()) |
1863 | && gni->geometry()->attributes() == gnj->geometry()->attributes() |
1864 | && gni->inheritedOpacity() == gnj->inheritedOpacity() |
1865 | && gni->activeMaterial()->type() == gnj->activeMaterial()->type() |
1866 | && gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) { |
1867 | ej->batch = batch; |
1868 | next->nextInBatch = ej; |
1869 | next = ej; |
1870 | } |
1871 | } |
1872 | |
1873 | batch->lastOrderInBatch = next->order; |
1874 | } |
1875 | } |
1876 | |
1877 | bool Renderer::checkOverlap(int first, int last, const Rect &bounds) |
1878 | { |
1879 | for (int i=first; i<=last; ++i) { |
1880 | Element *e = m_alphaRenderList.at(i); |
1881 | if (!e || e->batch) |
1882 | continue; |
1883 | Q_ASSERT(e->boundsComputed); |
1884 | if (e->bounds.intersects(bounds)) |
1885 | return true; |
1886 | } |
1887 | return false; |
1888 | } |
1889 | |
1890 | /* |
1891 | * |
1892 | * To avoid the O(n^2) checkOverlap check in most cases, we have the |
1893 | * overlapBounds which is the union of all bounding rects to check overlap |
1894 | * for. We know that if it does not overlap, then none of the individual |
1895 | * ones will either. For the typical list case, this results in no calls |
1896 | * to checkOverlap what-so-ever. This also ensures that when all consecutive |
1897 | * items are matching (such as a table of text), we don't build up an |
1898 | * overlap bounds and thus do not require full overlap checks. |
1899 | */ |
1900 | |
1901 | void Renderer::prepareAlphaBatches() |
1902 | { |
1903 | for (int i=0; i<m_alphaRenderList.size(); ++i) { |
1904 | Element *e = m_alphaRenderList.at(i); |
1905 | if (!e || e->isRenderNode) |
1906 | continue; |
1907 | Q_ASSERT(!e->removed); |
1908 | e->ensureBoundsValid(); |
1909 | } |
1910 | |
1911 | for (int i=0; i<m_alphaRenderList.size(); ++i) { |
1912 | Element *ei = m_alphaRenderList.at(i); |
1913 | if (!ei || ei->batch) |
1914 | continue; |
1915 | |
1916 | if (ei->isRenderNode) { |
1917 | Batch *rnb = newBatch(); |
1918 | rnb->first = ei; |
1919 | rnb->root = ei->root; |
1920 | rnb->isOpaque = false; |
1921 | rnb->isRenderNode = true; |
1922 | ei->batch = rnb; |
1923 | m_alphaBatches.add(rnb); |
1924 | continue; |
1925 | } |
1926 | |
1927 | if (ei->node->geometry()->vertexCount() == 0) |
1928 | continue; |
1929 | |
1930 | Batch *batch = newBatch(); |
1931 | batch->first = ei; |
1932 | batch->root = ei->root; |
1933 | batch->isOpaque = false; |
1934 | batch->needsUpload = true; |
1935 | m_alphaBatches.add(batch); |
1936 | ei->batch = batch; |
1937 | |
1938 | QSGGeometryNode *gni = ei->node; |
1939 | batch->positionAttribute = qsg_positionAttribute(gni->geometry()); |
1940 | |
1941 | Rect overlapBounds; |
1942 | overlapBounds.set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); |
1943 | |
1944 | Element *next = ei; |
1945 | |
1946 | for (int j = i + 1; j < m_alphaRenderList.size(); ++j) { |
1947 | Element *ej = m_alphaRenderList.at(j); |
1948 | if (!ej) |
1949 | continue; |
1950 | if (ej->root != ei->root || ej->isRenderNode) |
1951 | break; |
1952 | if (ej->batch) |
1953 | continue; |
1954 | |
1955 | QSGGeometryNode *gnj = ej->node; |
1956 | if (gnj->geometry()->vertexCount() == 0) |
1957 | continue; |
1958 | |
1959 | if (gni->clipList() == gnj->clipList() |
1960 | && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode() |
1961 | && (gni->geometry()->drawingMode() != QSGGeometry::DrawLines || gni->geometry()->lineWidth() == gnj->geometry()->lineWidth()) |
1962 | && gni->geometry()->attributes() == gnj->geometry()->attributes() |
1963 | && gni->inheritedOpacity() == gnj->inheritedOpacity() |
1964 | && gni->activeMaterial()->type() == gnj->activeMaterial()->type() |
1965 | && gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) { |
1966 | if (!overlapBounds.intersects(ej->bounds) || !checkOverlap(i+1, j - 1, ej->bounds)) { |
1967 | ej->batch = batch; |
1968 | next->nextInBatch = ej; |
1969 | next = ej; |
1970 | } else { |
1971 | /* When we come across a compatible element which hits an overlap, we |
1972 | * need to stop the batch right away. We cannot add more elements |
1973 | * to the current batch as they will be rendered before the batch that the |
1974 | * current 'ej' will be added to. |
1975 | */ |
1976 | break; |
1977 | } |
1978 | } else { |
1979 | overlapBounds |= ej->bounds; |
1980 | } |
1981 | } |
1982 | |
1983 | batch->lastOrderInBatch = next->order; |
1984 | } |
1985 | |
1986 | |
1987 | } |
1988 | |
1989 | static inline int qsg_fixIndexCount(int iCount, int drawMode) |
1990 | { |
1991 | switch (drawMode) { |
1992 | case QSGGeometry::DrawTriangleStrip: |
1993 | // Merged triangle strips need to contain degenerate triangles at the beginning and end. |
1994 | // One could save 2 uploaded ushorts here by ditching the padding for the front of the |
1995 | // first and the end of the last, but for simplicity, we simply don't care. |
1996 | // Those extra triangles will be skipped while drawing to preserve the strip's parity |
1997 | // anyhow. |
1998 | return iCount + 2; |
1999 | case QSGGeometry::DrawLines: |
2000 | // For lines we drop the last vertex if the number of vertices is uneven. |
2001 | return iCount - (iCount % 2); |
2002 | case QSGGeometry::DrawTriangles: |
2003 | // For triangles we drop trailing vertices until the result is divisible by 3. |
2004 | return iCount - (iCount % 3); |
2005 | default: |
2006 | return iCount; |
2007 | } |
2008 | } |
2009 | |
2010 | /* These parameters warrant some explanation... |
2011 | * |
2012 | * vaOffset: The byte offset into the vertex data to the location of the |
2013 | * 2D float point vertex attributes. |
2014 | * |
2015 | * vertexData: destination where the geometry's vertex data should go |
2016 | * |
2017 | * zData: destination of geometries injected Z positioning |
2018 | * |
2019 | * indexData: destination of the indices for this element |
2020 | * |
2021 | * iBase: The starting index for this element in the batch |
2022 | */ |
2023 | |
2024 | void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, void *iBasePtr, int *indexCount) |
2025 | { |
2026 | if (Q_UNLIKELY(debug_upload())) qDebug() << " - uploading element:" << e << e->node << (void *) *vertexData << (qintptr) (*zData - *vertexData) << (qintptr) (*indexData - *vertexData); |
2027 | QSGGeometry *g = e->node->geometry(); |
2028 | |
2029 | const QMatrix4x4 &localx = *e->node->matrix(); |
2030 | |
2031 | const int vCount = g->vertexCount(); |
2032 | const int vSize = g->sizeOfVertex(); |
2033 | memcpy(*vertexData, g->vertexData(), vSize * vCount); |
2034 | |
2035 | // apply vertex transform.. |
2036 | char *vdata = *vertexData + vaOffset; |
2037 | if (((const QMatrix4x4_Accessor &) localx).flagBits == 1) { |
2038 | for (int i=0; i<vCount; ++i) { |
2039 | Pt *p = (Pt *) vdata; |
2040 | p->x += ((const QMatrix4x4_Accessor &) localx).m[3][0]; |
2041 | p->y += ((const QMatrix4x4_Accessor &) localx).m[3][1]; |
2042 | vdata += vSize; |
2043 | } |
2044 | } else if (((const QMatrix4x4_Accessor &) localx).flagBits > 1) { |
2045 | for (int i=0; i<vCount; ++i) { |
2046 | ((Pt *) vdata)->map(localx); |
2047 | vdata += vSize; |
2048 | } |
2049 | } |
2050 | |
2051 | if (m_useDepthBuffer) { |
2052 | float *vzorder = (float *) *zData; |
2053 | float zorder = 1.0f - e->order * m_zRange; |
2054 | for (int i=0; i<vCount; ++i) |
2055 | vzorder[i] = zorder; |
2056 | *zData += vCount * sizeof(float); |
2057 | } |
2058 | |
2059 | int iCount = g->indexCount(); |
2060 | if (m_uint32IndexForRhi) { |
2061 | // can only happen when using the rhi |
2062 | quint32 *iBase = (quint32 *) iBasePtr; |
2063 | quint32 *indices = (quint32 *) *indexData; |
2064 | if (iCount == 0) { |
2065 | iCount = vCount; |
2066 | if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) |
2067 | *indices++ = *iBase; |
2068 | else |
2069 | iCount = qsg_fixIndexCount(iCount, g->drawingMode()); |
2070 | |
2071 | for (int i=0; i<iCount; ++i) |
2072 | indices[i] = *iBase + i; |
2073 | } else { |
2074 | // source index data in QSGGeometry is always ushort (we would not merge otherwise) |
2075 | const quint16 *srcIndices = g->indexDataAsUShort(); |
2076 | if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) |
2077 | *indices++ = *iBase + srcIndices[0]; |
2078 | else |
2079 | iCount = qsg_fixIndexCount(iCount, g->drawingMode()); |
2080 | |
2081 | for (int i=0; i<iCount; ++i) |
2082 | indices[i] = *iBase + srcIndices[i]; |
2083 | } |
2084 | if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) { |
2085 | indices[iCount] = indices[iCount - 1]; |
2086 | iCount += 2; |
2087 | } |
2088 | *iBase += vCount; |
2089 | } else { |
2090 | // normally batching is only done for ushort index data |
2091 | quint16 *iBase = (quint16 *) iBasePtr; |
2092 | quint16 *indices = (quint16 *) *indexData; |
2093 | if (iCount == 0) { |
2094 | iCount = vCount; |
2095 | if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) |
2096 | *indices++ = *iBase; |
2097 | else |
2098 | iCount = qsg_fixIndexCount(iCount, g->drawingMode()); |
2099 | |
2100 | for (int i=0; i<iCount; ++i) |
2101 | indices[i] = *iBase + i; |
2102 | } else { |
2103 | const quint16 *srcIndices = g->indexDataAsUShort(); |
2104 | if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) |
2105 | *indices++ = *iBase + srcIndices[0]; |
2106 | else |
2107 | iCount = qsg_fixIndexCount(iCount, g->drawingMode()); |
2108 | |
2109 | for (int i=0; i<iCount; ++i) |
2110 | indices[i] = *iBase + srcIndices[i]; |
2111 | } |
2112 | if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) { |
2113 | indices[iCount] = indices[iCount - 1]; |
2114 | iCount += 2; |
2115 | } |
2116 | *iBase += vCount; |
2117 | } |
2118 | |
2119 | *vertexData += vCount * vSize; |
2120 | *indexData += iCount * mergedIndexElemSize(); |
2121 | *indexCount += iCount; |
2122 | } |
2123 | |
2124 | QMatrix4x4 qsg_matrixForRoot(Node *node) |
2125 | { |
2126 | if (node->type() == QSGNode::TransformNodeType) |
2127 | return static_cast<QSGTransformNode *>(node->sgNode)->combinedMatrix(); |
2128 | Q_ASSERT(node->type() == QSGNode::ClipNodeType); |
2129 | QSGClipNode *c = static_cast<QSGClipNode *>(node->sgNode); |
2130 | return *c->matrix(); |
2131 | } |
2132 | |
2133 | void Renderer::uploadBatch(Batch *b) |
2134 | { |
2135 | // Early out if nothing has changed in this batch.. |
2136 | if (!b->needsUpload) { |
2137 | if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "already uploaded..." ; |
2138 | return; |
2139 | } |
2140 | |
2141 | if (!b->first) { |
2142 | if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch:" << b << "is invalid..." ; |
2143 | return; |
2144 | } |
2145 | |
2146 | if (b->isRenderNode) { |
2147 | if (Q_UNLIKELY(debug_upload())) qDebug() << " Batch: " << b << "is a render node..." ; |
2148 | return; |
2149 | } |
2150 | |
2151 | // Figure out if we can merge or not, if not, then just render the batch as is.. |
2152 | Q_ASSERT(b->first); |
2153 | Q_ASSERT(b->first->node); |
2154 | |
2155 | QSGGeometryNode *gn = b->first->node; |
2156 | QSGGeometry *g = gn->geometry(); |
2157 | QSGMaterial::Flags flags = gn->activeMaterial()->flags(); |
2158 | bool canMerge = (g->drawingMode() == QSGGeometry::DrawTriangles || g->drawingMode() == QSGGeometry::DrawTriangleStrip || |
2159 | g->drawingMode() == QSGGeometry::DrawLines || g->drawingMode() == QSGGeometry::DrawPoints) |
2160 | && b->positionAttribute >= 0 |
2161 | && g->indexType() == QSGGeometry::UnsignedShortType |
2162 | && (flags & (QSGMaterial::CustomCompileStep | QSGMaterial_FullMatrix)) == 0 |
2163 | && ((flags & QSGMaterial::RequiresFullMatrixExceptTranslate) == 0 || b->isTranslateOnlyToRoot()) |
2164 | && b->isSafeToBatch(); |
2165 | |
2166 | b->merged = canMerge; |
2167 | |
2168 | // Figure out how much memory we need... |
2169 | b->vertexCount = 0; |
2170 | b->indexCount = 0; |
2171 | int unmergedIndexSize = 0; |
2172 | Element *e = b->first; |
2173 | |
2174 | while (e) { |
2175 | QSGGeometry *eg = e->node->geometry(); |
2176 | b->vertexCount += eg->vertexCount(); |
2177 | int iCount = eg->indexCount(); |
2178 | if (b->merged) { |
2179 | if (iCount == 0) |
2180 | iCount = eg->vertexCount(); |
2181 | iCount = qsg_fixIndexCount(iCount, g->drawingMode()); |
2182 | } else { |
2183 | const int effectiveIndexSize = m_uint32IndexForRhi ? sizeof(quint32) : eg->sizeOfIndex(); |
2184 | unmergedIndexSize += iCount * effectiveIndexSize; |
2185 | } |
2186 | b->indexCount += iCount; |
2187 | e = e->nextInBatch; |
2188 | } |
2189 | |
2190 | // Abort if there are no vertices in this batch.. We abort this late as |
2191 | // this is a broken usecase which we do not care to optimize for... |
2192 | if (b->vertexCount == 0 || (b->merged && b->indexCount == 0)) |
2193 | return; |
2194 | |
2195 | /* Allocate memory for this batch. Merged batches are divided into three separate blocks |
2196 | 1. Vertex data for all elements, as they were in the QSGGeometry object, but |
2197 | with the tranform relative to this batch's root applied. The vertex data |
2198 | is otherwise unmodified. |
2199 | 2. Z data for all elements, derived from each elements "render order". |
2200 | This is present for merged data only. |
2201 | 3. Indices for all elements, as they were in the QSGGeometry object, but |
2202 | adjusted so that each index matches its. |
2203 | And for TRIANGLE_STRIPs, we need to insert degenerate between each |
2204 | primitive. These are unsigned shorts for merged and arbitrary for |
2205 | non-merged. |
2206 | */ |
2207 | int bufferSize = b->vertexCount * g->sizeOfVertex(); |
2208 | int ibufferSize = 0; |
2209 | if (b->merged) { |
2210 | ibufferSize = b->indexCount * mergedIndexElemSize(); |
2211 | if (m_useDepthBuffer) |
2212 | bufferSize += b->vertexCount * sizeof(float); |
2213 | } else { |
2214 | ibufferSize = unmergedIndexSize; |
2215 | } |
2216 | |
2217 | const bool separateIndexBuffer = m_context->separateIndexBuffer(); |
2218 | if (separateIndexBuffer) |
2219 | map(&b->ibo, ibufferSize, true); |
2220 | else |
2221 | bufferSize += ibufferSize; |
2222 | map(&b->vbo, bufferSize); |
2223 | |
2224 | if (Q_UNLIKELY(debug_upload())) qDebug() << " - batch" << b << " first:" << b->first << " root:" |
2225 | << b->root << " merged:" << b->merged << " positionAttribute" << b->positionAttribute |
2226 | << " vbo:" << b->vbo.id << ":" << b->vbo.size; |
2227 | |
2228 | if (b->merged) { |
2229 | char *vertexData = b->vbo.data; |
2230 | char *zData = vertexData + b->vertexCount * g->sizeOfVertex(); |
2231 | char *indexData = separateIndexBuffer |
2232 | ? b->ibo.data |
2233 | : zData + (int(m_useDepthBuffer) * b->vertexCount * sizeof(float)); |
2234 | |
2235 | quint16 iOffset16 = 0; |
2236 | quint32 iOffset32 = 0; |
2237 | e = b->first; |
2238 | uint verticesInSet = 0; |
2239 | // Start a new set already after 65534 vertices because 0xFFFF may be |
2240 | // used for an always-on primitive restart with some apis (adapt for |
2241 | // uint32 indices as appropriate). |
2242 | const uint verticesInSetLimit = m_uint32IndexForRhi ? 0xfffffffe : 0xfffe; |
2243 | int indicesInSet = 0; |
2244 | b->drawSets.reset(); |
2245 | int drawSetIndices = separateIndexBuffer ? 0 : indexData - vertexData; |
2246 | const char *indexBase = separateIndexBuffer ? b->ibo.data : b->vbo.data; |
2247 | b->drawSets << DrawSet(0, zData - vertexData, drawSetIndices); |
2248 | while (e) { |
2249 | verticesInSet += e->node->geometry()->vertexCount(); |
2250 | if (verticesInSet > verticesInSetLimit) { |
2251 | b->drawSets.last().indexCount = indicesInSet; |
2252 | if (g->drawingMode() == QSGGeometry::DrawTriangleStrip) { |
2253 | b->drawSets.last().indices += 1 * mergedIndexElemSize(); |
2254 | b->drawSets.last().indexCount -= 2; |
2255 | } |
2256 | drawSetIndices = indexData - indexBase; |
2257 | b->drawSets << DrawSet(vertexData - b->vbo.data, |
2258 | zData - b->vbo. |
---|