1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
43 #define GL_GLEXT_PROTOTYPES
45 #include "qsgdefaultrenderer_p.h"
46 #include "qsgmaterial.h"
48 #include <QtCore/qvarlengtharray.h>
49 #include <QtGui/qguiapplication.h>
50 #include <QtCore/qpair.h>
51 #include <QtCore/QElapsedTimer>
53 //#define FORCE_NO_REORDER
55 // #define RENDERER_DEBUG
57 #define DEBUG_THRESHOLD 0
58 QElapsedTimer debugTimer;
60 int geometryNodesDrawn;
65 static bool nodeLessThan(QSGNode *nodeA, QSGNode *nodeB)
67 if (nodeA->type() != nodeB->type())
68 return nodeA->type() < nodeB->type();
69 if (nodeA->type() != QSGNode::GeometryNodeType)
71 QSGGeometryNode *a = static_cast<QSGGeometryNode *>(nodeA);
72 QSGGeometryNode *b = static_cast<QSGGeometryNode *>(nodeA);
75 if (a->clipList() != b->clipList())
76 return a->clipList() < b->clipList();
78 // Sort by material definition
79 QSGMaterialType *aDef = a->material()->type();
80 QSGMaterialType *bDef = b->material()->type();
85 // Sort by material state
86 int cmp = a->material()->compare(b->material());
90 return a->matrix() < b->matrix();
93 static bool nodeLessThanWithRenderOrder(QSGNode *nodeA, QSGNode *nodeB)
95 if (nodeA->type() != nodeB->type())
96 return nodeA->type() < nodeB->type();
97 if (nodeA->type() != QSGNode::GeometryNodeType)
99 QSGGeometryNode *a = static_cast<QSGGeometryNode *>(nodeA);
100 QSGGeometryNode *b = static_cast<QSGGeometryNode *>(nodeA);
103 if (a->clipList() != b->clipList())
104 return a->clipList() < b->clipList();
106 // Sort by material definition
107 QSGMaterialType *aDef = a->material()->type();
108 QSGMaterialType *bDef = b->material()->type();
110 if (!(a->material()->flags() & QSGMaterial::Blending)) {
111 int aOrder = a->renderOrder();
112 int bOrder = b->renderOrder();
113 if (aOrder != bOrder)
114 return aOrder > bOrder;
120 // Sort by material state
121 int cmp = a->material()->compare(b->material());
125 return a->matrix() < b->matrix();
129 QSGDefaultRenderer::IndexNodePair::IndexNodePair(int i, QSGNode *node)
130 : QPair<int, QSGNode *>(i, node)
134 bool QSGDefaultRenderer::IndexNodePair::operator < (const QSGDefaultRenderer::IndexNodePair &other) const
136 return nodeLessThan(second, other.second);
140 QSGDefaultRenderer::IndexNodePairHeap::IndexNodePairHeap()
145 void QSGDefaultRenderer::IndexNodePairHeap::insert(const QSGDefaultRenderer::IndexNodePair &x)
149 while (i != 0 && v.at(i) < v.at(parent(i))) {
150 qSwap(v.at(parent(i)), v.at(i));
155 QSGDefaultRenderer::IndexNodePair QSGDefaultRenderer::IndexNodePairHeap::pop()
157 IndexNodePair x = top();
159 qSwap(v.first(), v.last());
162 while (left(i) < v.size()) {
164 if (right(i) < v.size() && v.at(right(i)) < v.at(low))
166 if (!(v.at(low) < v.at(i)))
168 qSwap(v.at(i), v.at(low));
175 QSGDefaultRenderer::QSGDefaultRenderer(QSGContext *context)
176 : QSGRenderer(context)
178 , m_transparentNodes(64)
181 , m_rebuild_lists(false)
182 , m_needs_sorting(false)
183 , m_sort_front_to_back(false)
184 , m_render_node_added(false)
185 , m_currentRenderOrder(1)
187 QStringList args = qApp->arguments();
188 #if defined(QML_RUNTIME_TESTING)
189 m_render_opaque_nodes = !args.contains(QLatin1String("--no-opaque-nodes"));
190 m_render_alpha_nodes = !args.contains(QLatin1String("--no-alpha-nodes"));
194 void QSGDefaultRenderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state)
196 QSGRenderer::nodeChanged(node, state);
198 const quint32 rebuildBits = QSGNode::DirtyNodeAdded | QSGNode::DirtyNodeRemoved
199 | QSGNode::DirtyMaterial | QSGNode::DirtyOpacity
200 | QSGNode::DirtyForceUpdate | QSGNode::DirtyChildrenDoNotOverlap;
202 if (state & rebuildBits)
203 m_rebuild_lists = true;
205 if (state & (rebuildBits | QSGNode::DirtyClipList))
206 m_needs_sorting = true;
209 void QSGDefaultRenderer::render()
211 #if defined (QML_RUNTIME_TESTING)
212 static bool dumpTree = qApp->arguments().contains(QLatin1String("--dump-tree"));
215 QSGNodeDumper::dump(rootNode());
219 #ifdef RENDERER_DEBUG
220 debugTimer.invalidate();
222 geometryNodesDrawn = 0;
226 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
229 glFrontFace(isMirrored() ? GL_CW : GL_CCW);
230 glDisable(GL_CULL_FACE);
232 glEnable(GL_DEPTH_TEST);
234 glDepthFunc(GL_GREATER);
235 #if defined(QT_OPENGL_ES)
241 glDisable(GL_SCISSOR_TEST);
242 glClearColor(m_clear_color.redF(), m_clear_color.greenF(), m_clear_color.blueF(), m_clear_color.alphaF());
244 #ifdef RENDERER_DEBUG
245 int debugtimeSetup = debugTimer.elapsed();
248 bindable()->clear(clearMode());
250 #ifdef RENDERER_DEBUG
251 int debugtimeClear = debugTimer.elapsed();
254 QRect r = viewportRect();
255 glViewport(r.x(), deviceRect().bottom() - r.bottom(), r.width(), r.height());
256 m_current_projection_matrix = projectionMatrix();
257 m_current_model_view_matrix.setToIdentity();
260 glDisable(GL_STENCIL_TEST);
262 m_currentMaterial = 0;
263 m_currentProgram = 0;
266 if (m_rebuild_lists) {
267 m_opaqueNodes.reset();
268 m_transparentNodes.reset();
269 m_renderGroups.reset();
270 m_currentRenderOrder = 1;
271 buildLists(rootNode());
272 m_rebuild_lists = false;
273 m_render_node_added = false;
274 RenderGroup group = { m_opaqueNodes.size(), m_transparentNodes.size() };
275 m_renderGroups.add(group);
278 #ifdef RENDERER_DEBUG
279 int debugtimeLists = debugTimer.elapsed();
282 if (m_needs_sorting) {
283 if (!m_opaqueNodes.isEmpty()) {
284 bool (*lessThan)(QSGNode *, QSGNode *);
285 lessThan = m_sort_front_to_back ? nodeLessThanWithRenderOrder : nodeLessThan;
287 for (int i = 0; i < m_renderGroups.size(); ++i) {
288 int end = m_renderGroups.at(i).opaqueEnd;
290 qSort(&m_opaqueNodes.first() + start, &m_opaqueNodes.first() + end, lessThan);
294 m_needs_sorting = false;
297 #ifdef RENDERER_DEBUG
298 int debugtimeSorting = debugTimer.elapsed();
301 m_renderOrderMatrix.setToIdentity();
302 m_renderOrderMatrix.scale(1, 1, qreal(1) / m_currentRenderOrder);
305 int transparentStart = 0;
306 for (int i = 0; i < m_renderGroups.size(); ++i) {
307 int opaqueEnd = m_renderGroups.at(i).opaqueEnd;
308 int transparentEnd = m_renderGroups.at(i).transparentEnd;
312 #ifdef QML_RUNTIME_TESTING
313 if (m_render_opaque_nodes)
316 #if defined (QML_RUNTIME_TESTING)
318 qDebug() << "Opaque Nodes:";
320 if (opaqueEnd != opaqueStart)
321 renderNodes(&m_opaqueNodes.first() + opaqueStart, opaqueEnd - opaqueStart);
326 #ifdef QML_RUNTIME_TESTING
327 if (m_render_alpha_nodes)
330 #if defined (QML_RUNTIME_TESTING)
332 qDebug() << "Alpha Nodes:";
334 if (transparentEnd != transparentStart)
335 renderNodes(&m_transparentNodes.first() + transparentStart, transparentEnd - transparentStart);
338 opaqueStart = opaqueEnd;
339 transparentStart = transparentEnd;
342 #ifdef RENDERER_DEBUG
343 int debugtimeRender = debugTimer.elapsed();
346 if (m_currentProgram)
347 m_currentProgram->deactivate();
349 #ifdef RENDERER_DEBUG
350 if (debugTimer.elapsed() > DEBUG_THRESHOLD) {
351 printf(" --- Renderer breakdown:\n"
352 " - setup=%d, clear=%d, building=%d, sorting=%d, render=%d\n"
353 " - material changes: total=%d\n"
354 " - geometry nodes: total=%d\n",
356 debugtimeClear - debugtimeSetup,
357 debugtimeLists - debugtimeClear,
358 debugtimeSorting - debugtimeLists,
359 debugtimeRender - debugtimeSorting,
367 void QSGDefaultRenderer::setSortFrontToBackEnabled(bool sort)
369 printf("setting sorting to... %d\n", sort);
370 m_sort_front_to_back = sort;
373 bool QSGDefaultRenderer::isSortFrontToBackEnabled() const
375 return m_sort_front_to_back;
378 void QSGDefaultRenderer::buildLists(QSGNode *node)
380 if (node->isSubtreeBlocked())
383 if (node->type() == QSGNode::GeometryNodeType) {
384 QSGGeometryNode *geomNode = static_cast<QSGGeometryNode *>(node);
385 qreal opacity = geomNode->inheritedOpacity();
386 QSGMaterial *m = geomNode->activeMaterial();
388 #ifdef FORCE_NO_REORDER
391 if ((m->flags() & QSGMaterial::Blending) || opacity < 1) {
393 geomNode->setRenderOrder(m_currentRenderOrder - 1);
394 m_transparentNodes.add(geomNode);
396 if (m_render_node_added) {
397 // Start new group of nodes so that this opaque node is render on top of the
399 RenderGroup group = { m_opaqueNodes.size(), m_transparentNodes.size() };
400 m_renderGroups.add(group);
401 m_render_node_added = false;
403 geomNode->setRenderOrder(m_currentRenderOrder);
404 m_opaqueNodes.add(geomNode);
405 m_currentRenderOrder += 2;
407 } else if (node->type() == QSGNode::RenderNodeType) {
408 QSGRenderNode *renderNode = static_cast<QSGRenderNode *>(node);
409 m_transparentNodes.add(renderNode);
410 m_render_node_added = true;
413 if (!node->firstChild())
416 #ifdef FORCE_NO_REORDER
417 static bool reorder = false;
419 static bool reorder = qApp->arguments().contains(QLatin1String("--reorder"));
422 if (reorder && node->firstChild() != node->lastChild() && (node->flags() & QSGNode::ChildrenDoNotOverlap)) {
423 QVarLengthArray<int, 16> beginIndices;
424 QVarLengthArray<int, 16> endIndices;
425 int baseCount = m_transparentNodes.size();
426 int baseGroupCount = m_renderGroups.size();
428 for (QSGNode *c = node->firstChild(); c; c = c->nextSibling()) {
429 beginIndices.append(m_transparentNodes.size());
431 endIndices.append(m_transparentNodes.size());
435 int childNodeCount = m_transparentNodes.size() - baseCount;
436 // Don't reorder if new render groups were added.
437 if (m_renderGroups.size() == baseGroupCount && childNodeCount) {
439 m_tempNodes.reserve(childNodeCount);
440 while (childNodeCount) {
441 for (int i = 0; i < count; ++i) {
442 if (beginIndices[i] != endIndices[i])
443 m_heap.insert(IndexNodePair(i, m_transparentNodes.at(beginIndices[i]++)));
445 while (!m_heap.isEmpty()) {
446 IndexNodePair pair = m_heap.pop();
447 m_tempNodes.add(pair.second);
450 if (beginIndices[i] != endIndices[i] && !nodeLessThan(m_transparentNodes.at(beginIndices[i]), pair.second))
451 m_heap.insert(IndexNodePair(i, m_transparentNodes.at(beginIndices[i]++)));
454 Q_ASSERT(m_tempNodes.size() == m_transparentNodes.size() - baseCount);
456 qMemCopy(&m_transparentNodes.at(baseCount), &m_tempNodes.at(0), m_tempNodes.size() * sizeof(QSGGeometryNode *));
459 for (QSGNode *c = node->firstChild(); c; c = c->nextSibling())
464 void QSGDefaultRenderer::renderNodes(QSGNode *const *nodes, int count)
466 const float scale = 1.0f / m_currentRenderOrder;
467 int currentRenderOrder = 0x80000000;
468 ClipType currentClipType = NoClip;
469 QMatrix4x4 projection = projectionMatrix();
470 m_current_projection_matrix.setColumn(2, scale * projection.column(2));
472 //int clipChangeCount = 0;
473 //int programChangeCount = 0;
474 //int materialChangeCount = 0;
476 for (int i = 0; i < count; ++i) {
477 if (nodes[i]->type() == QSGNode::RenderNodeType) {
478 QSGRenderNode *renderNode = static_cast<QSGRenderNode *>(nodes[i]);
480 if (m_currentProgram)
481 m_currentProgram->deactivate();
482 m_currentMaterial = 0;
483 m_currentProgram = 0;
485 currentRenderOrder = 0x80000000;
487 bool changeClip = renderNode->clipList() != m_currentClip;
488 // The clip function relies on there not being any depth testing..
489 glDisable(GL_DEPTH_TEST);
491 currentClipType = updateStencilClip(renderNode->clipList());
492 m_currentClip = renderNode->clipList();
497 glBindBuffer(GL_ARRAY_BUFFER, 0);
498 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
500 QSGRenderNode::RenderState state;
501 state.projectionMatrix = &projection;
502 state.scissorEnabled = currentClipType & ScissorClip;
503 state.stencilEnabled = currentClipType & StencilClip;
504 state.scissorRect = m_current_scissor_rect;
505 state.stencilValue = m_current_stencil_value;
507 renderNode->render(state);
509 QSGRenderNode::StateFlags changes = renderNode->changedStates();
510 if (changes & QSGRenderNode::ViewportState) {
511 QRect r = viewportRect();
512 glViewport(r.x(), deviceRect().bottom() - r.bottom(), r.width(), r.height());
514 if (changes & QSGRenderNode::StencilState) {
515 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
517 glDisable(GL_STENCIL_TEST);
519 if (changes & (QSGRenderNode::StencilState | QSGRenderNode::ScissorState)) {
520 glDisable(GL_SCISSOR_TEST);
522 currentClipType = NoClip;
524 if (changes & QSGRenderNode::DepthState) {
525 #if defined(QT_OPENGL_ES)
530 if (m_clear_mode & QSGRenderer::ClearDepthBuffer) {
532 glClear(GL_DEPTH_BUFFER_BIT);
535 glDepthFunc(GL_GREATER);
537 if (changes & QSGRenderNode::ColorState)
538 bindable()->reactivate();
539 if (changes & QSGRenderNode::BlendState) {
541 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
543 if (changes & QSGRenderNode::CullState) {
544 glFrontFace(isMirrored() ? GL_CW : GL_CCW);
545 glDisable(GL_CULL_FACE);
548 glEnable(GL_DEPTH_TEST);
550 m_current_model_view_matrix.setToIdentity();
551 m_current_determinant = 1;
552 } else if (nodes[i]->type() == QSGNode::GeometryNodeType) {
553 QSGGeometryNode *geomNode = static_cast<QSGGeometryNode *>(nodes[i]);
555 QSGMaterialShader::RenderState::DirtyStates updates;
557 #if defined (QML_RUNTIME_TESTING)
558 static bool dumpTree = qApp->arguments().contains(QLatin1String("--dump-tree"));
560 qDebug() << geomNode;
563 bool changeMatrix = m_currentMatrix != geomNode->matrix();
566 m_currentMatrix = geomNode->matrix();
568 m_current_model_view_matrix = *m_currentMatrix;
570 m_current_model_view_matrix.setToIdentity();
571 m_current_determinant = m_current_model_view_matrix.determinant();
572 updates |= QSGMaterialShader::RenderState::DirtyMatrix;
575 bool changeOpacity = m_current_opacity != geomNode->inheritedOpacity();
577 updates |= QSGMaterialShader::RenderState::DirtyOpacity;
578 m_current_opacity = geomNode->inheritedOpacity();
581 Q_ASSERT(geomNode->activeMaterial());
583 QSGMaterial *material = geomNode->activeMaterial();
584 QSGMaterialShader *program = m_context->prepareMaterial(material);
585 Q_ASSERT(program->program()->isLinked());
587 bool changeClip = geomNode->clipList() != m_currentClip;
589 // The clip function relies on there not being any depth testing..
590 glDisable(GL_DEPTH_TEST);
591 currentClipType = updateStencilClip(geomNode->clipList());
592 glEnable(GL_DEPTH_TEST);
593 m_currentClip = geomNode->clipList();
594 #ifdef FORCE_NO_REORDER
597 glDepthMask((material->flags() & QSGMaterial::Blending) == 0 && m_current_opacity == 1);
602 bool changeProgram = (changeClip && (currentClipType & StencilClip)) || m_currentProgram != program;
604 if (m_currentProgram)
605 m_currentProgram->deactivate();
606 m_currentProgram = program;
607 m_currentProgram->activate();
608 //++programChangeCount;
609 updates |= (QSGMaterialShader::RenderState::DirtyMatrix | QSGMaterialShader::RenderState::DirtyOpacity);
611 #ifdef RENDERER_DEBUG
616 bool changeRenderOrder = currentRenderOrder != geomNode->renderOrder();
617 if (changeRenderOrder) {
618 currentRenderOrder = geomNode->renderOrder();
619 m_current_projection_matrix.setColumn(3, projection.column(3)
621 * m_current_projection_matrix.column(2));
622 updates |= QSGMaterialShader::RenderState::DirtyMatrix;
625 if (changeProgram || m_currentMaterial != material) {
626 program->updateState(state(updates), material, changeProgram ? 0 : m_currentMaterial);
627 m_currentMaterial = material;
628 //++materialChangeCount;
631 //glDepthRange((geomNode->renderOrder() + 0.1) * scale, (geomNode->renderOrder() + 0.9) * scale);
633 const QSGGeometry *g = geomNode->geometry();
636 #ifdef RENDERER_DEBUG
637 geometryNodesDrawn++;
641 //qDebug("Clip: %i, shader program: %i, material: %i times changed while drawing %s items",
642 // clipChangeCount, programChangeCount, materialChangeCount,
643 // &list == &m_transparentNodes ? "transparent" : "opaque");