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