1 /****************************************************************************
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qsgrenderer_p.h"
44 #include "qsgmaterial.h"
45 #include "qsgnodeupdater_p.h"
46 #include "qsggeometry_p.h"
48 #include "private/qsgadaptationlayer_p.h"
50 #include <QOpenGLShaderProgram>
51 #include <qopenglframebufferobject.h>
52 #include <QtGui/qguiapplication.h>
54 #include <qdatetime.h>
58 //#define RENDERER_DEBUG
59 //#define QT_GL_NO_SCISSOR_TEST
63 #define QSG_RENDERER_TIMING
64 #ifdef QSG_RENDERER_TIMING
65 static bool qsg_render_timing = !qgetenv("QML_RENDERER_TIMING").isEmpty();
66 static QTime frameTimer;
67 static int preprocessTime;
68 static int updatePassTime;
71 void QSGBindable::clear(QSGRenderer::ClearMode mode) const
74 if (mode & QSGRenderer::ClearColorBuffer) bits |= GL_COLOR_BUFFER_BIT;
75 if (mode & QSGRenderer::ClearDepthBuffer) bits |= GL_DEPTH_BUFFER_BIT;
76 if (mode & QSGRenderer::ClearStencilBuffer) bits |= GL_STENCIL_BUFFER_BIT;
80 // Reactivate the color buffer after switching to the stencil.
81 void QSGBindable::reactivate() const
83 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
86 QSGBindableFbo::QSGBindableFbo(QOpenGLFramebufferObject *fbo) : m_fbo(fbo)
91 void QSGBindableFbo::bind() const
98 \brief The renderer class is the abstract baseclass use for rendering the
101 The renderer is not tied to any particular surface. It expects a context to
102 be current and will render into that surface according to how the device rect,
103 viewport rect and projection transformation are set up.
105 Rendering is a sequence of steps initiated by calling renderScene(). This will
106 effectively draw the scene graph starting at the root node. The QSGNode::preprocess()
107 function will be called for all the nodes in the graph, followed by an update
108 pass which updates all matrices, opacity, clip states and similar in the graph.
109 Because the update pass is called after preprocess, it is safe to modify the graph
110 during preprocess. To run a custom update pass over the graph, install a custom
111 QSGNodeUpdater using setNodeUpdater(). Once all the graphs dirty states are updated,
112 the virtual render() function is called.
114 The render() function is implemented by QSGRenderer subclasses to render the graph
115 in the most optimal way for a given hardware.
117 The renderer can make use of stencil, depth and color buffers in addition to the
124 QSGRenderer::QSGRenderer(QSGContext *context)
126 , m_clear_color(Qt::transparent)
127 , m_clear_mode(ClearColorBuffer | ClearDepthBuffer)
128 , m_current_opacity(1)
133 , m_changed_emitted(false)
135 , m_is_rendering(false)
136 , m_vertex_buffer_bound(false)
137 , m_index_buffer_bound(false)
139 initializeGLFunctions();
143 QSGRenderer::~QSGRenderer()
146 delete m_node_updater;
150 Returns the scene graph context for this renderer.
155 QSGContext *QSGRenderer::context()
164 Returns the node updater that this renderer uses to update states in the
167 If no updater is specified a default one is constructed.
170 QSGNodeUpdater *QSGRenderer::nodeUpdater() const
173 const_cast<QSGRenderer *>(this)->m_node_updater = new QSGNodeUpdater();
174 return m_node_updater;
179 Sets the node updater that this renderer uses to update states in the
182 This will delete and override any existing node updater
184 void QSGRenderer::setNodeUpdater(QSGNodeUpdater *updater)
187 delete m_node_updater;
188 m_node_updater = updater;
192 void QSGRenderer::setRootNode(QSGRootNode *node)
194 if (m_root_node == node)
197 m_root_node->m_renderers.removeOne(this);
198 nodeChanged(m_root_node, QSGNode::DirtyNodeRemoved);
202 Q_ASSERT(!m_root_node->m_renderers.contains(this));
203 m_root_node->m_renderers << this;
204 nodeChanged(m_root_node, QSGNode::DirtyNodeAdded);
209 void QSGRenderer::renderScene()
211 class B : public QSGBindable
214 void bind() const { QOpenGLFramebufferObject::bindDefault(); }
219 void QSGRenderer::renderScene(const QSGBindable &bindable)
224 m_is_rendering = true;
227 #ifdef QSG_RENDERER_TIMING
228 if (qsg_render_timing)
234 m_bindable = &bindable;
238 #ifdef QSG_RENDERER_TIMING
239 if (qsg_render_timing)
240 bindTime = frameTimer.elapsed();
244 // Sanity check that attribute registers are disabled
247 glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &count);
249 for (int i=0; i<count; ++i) {
250 glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled);
252 qWarning("QSGRenderer: attribute %d is enabled, this can lead to memory corruption and crashes.", i);
259 #ifdef QSG_RENDERER_TIMING
260 if (qsg_render_timing)
261 renderTime = frameTimer.elapsed();
264 glDisable(GL_SCISSOR_TEST);
265 m_is_rendering = false;
266 m_changed_emitted = false;
269 if (m_vertex_buffer_bound) {
270 glBindBuffer(GL_ARRAY_BUFFER, 0);
271 m_vertex_buffer_bound = false;
274 if (m_index_buffer_bound) {
275 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
276 m_index_buffer_bound = false;
279 #ifdef QSG_RENDERER_TIMING
280 if (qsg_render_timing) {
281 printf(" - Breakdown of frametime: preprocess=%d, updates=%d, binding=%d, render=%d, total=%d\n",
283 updatePassTime - preprocessTime,
284 bindTime - updatePassTime,
285 renderTime - bindTime,
291 void QSGRenderer::setProjectionMatrixToDeviceRect()
293 setProjectionMatrixToRect(m_device_rect);
296 void QSGRenderer::setProjectionMatrixToRect(const QRectF &rect)
299 matrix.ortho(rect.x(),
300 rect.x() + rect.width(),
301 rect.y() + rect.height(),
305 setProjectionMatrix(matrix);
308 void QSGRenderer::setProjectionMatrix(const QMatrix4x4 &matrix)
310 m_projection_matrix = matrix;
311 // Mirrored relative to the usual Qt coordinate system with origin in the top left corner.
312 m_mirrored = matrix(0, 0) * matrix(1, 1) - matrix(0, 1) * matrix(1, 0) > 0;
315 void QSGRenderer::setClearColor(const QColor &color)
317 m_clear_color = color;
321 Updates internal data structures and emits the sceneGraphChanged() signal.
323 If \a flags contains the QSGNode::DirtyNodeRemoved flag, the node might be
324 in the process of being destroyed. It is then not safe to downcast the node
328 void QSGRenderer::nodeChanged(QSGNode *node, QSGNode::DirtyFlags flags)
333 if (flags & QSGNode::DirtyNodeAdded)
334 addNodesToPreprocess(node);
335 if (flags & QSGNode::DirtyNodeRemoved)
336 removeNodesToPreprocess(node);
338 if (!m_changed_emitted && !m_is_rendering) {
339 // Premature overoptimization to avoid excessive signal emissions
340 m_changed_emitted = true;
341 emit sceneGraphChanged();
345 void QSGRenderer::materialChanged(QSGGeometryNode *, QSGMaterial *, QSGMaterial *)
349 void QSGRenderer::preprocess()
351 Q_ASSERT(m_root_node);
353 // We need to take a copy here, in case any of the preprocess calls deletes a node that
354 // is in the preprocess list and thus, changes the m_nodes_to_preprocess behind our backs
355 // For the default case, when this does not happen, the cost is neglishible.
356 QSet<QSGNode *> items = m_nodes_to_preprocess;
358 for (QSet<QSGNode *>::const_iterator it = items.constBegin();
359 it != items.constEnd(); ++it) {
361 if (!nodeUpdater()->isNodeBlocked(n, m_root_node)) {
366 #ifdef QSG_RENDERER_TIMING
367 if (qsg_render_timing)
368 preprocessTime = frameTimer.elapsed();
371 nodeUpdater()->setToplevelOpacity(context()->renderAlpha());
372 nodeUpdater()->updateStates(m_root_node);
374 #ifdef QSG_RENDERER_TIMING
375 if (qsg_render_timing)
376 updatePassTime = frameTimer.elapsed();
381 void QSGRenderer::addNodesToPreprocess(QSGNode *node)
383 for (QSGNode *c = node->firstChild(); c; c = c->nextSibling())
384 addNodesToPreprocess(c);
385 if (node->flags() & QSGNode::UsePreprocess)
386 m_nodes_to_preprocess.insert(node);
389 void QSGRenderer::removeNodesToPreprocess(QSGNode *node)
391 for (QSGNode *c = node->firstChild(); c; c = c->nextSibling())
392 removeNodesToPreprocess(c);
393 if (node->flags() & QSGNode::UsePreprocess)
394 m_nodes_to_preprocess.remove(node);
399 Convenience function to set up the stencil buffer for clipping based on \a clip.
401 If the clip is a pixel aligned rectangle, this function will use glScissor instead
405 QSGRenderer::ClipType QSGRenderer::updateStencilClip(const QSGClipNode *clip)
408 glDisable(GL_STENCIL_TEST);
409 glDisable(GL_SCISSOR_TEST);
413 bool stencilEnabled = false;
414 bool scissorEnabled = false;
416 glDisable(GL_SCISSOR_TEST);
421 QMatrix4x4 m = m_current_projection_matrix;
423 m *= *clip->matrix();
425 // TODO: Check for multisampling and pixel grid alignment.
426 bool isRectangleWithNoPerspective = clip->isRectangular()
427 && qFuzzyIsNull(m(3, 0)) && qFuzzyIsNull(m(3, 1));
428 bool noRotate = qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(1, 0));
429 bool isRotate90 = qFuzzyIsNull(m(0, 0)) && qFuzzyIsNull(m(1, 1));
431 if (isRectangleWithNoPerspective && (noRotate || isRotate90)) {
432 QRectF bbox = clip->clipRect();
433 qreal invW = 1 / m(3, 3);
434 qreal fx1, fy1, fx2, fy2;
436 fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
437 fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
438 fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
439 fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
441 Q_ASSERT(isRotate90);
442 fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
443 fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
444 fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
445 fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
453 GLint ix1 = qRound((fx1 + 1) * m_device_rect.width() * qreal(0.5));
454 GLint iy1 = qRound((fy1 + 1) * m_device_rect.height() * qreal(0.5));
455 GLint ix2 = qRound((fx2 + 1) * m_device_rect.width() * qreal(0.5));
456 GLint iy2 = qRound((fy2 + 1) * m_device_rect.height() * qreal(0.5));
458 if (!scissorEnabled) {
459 clipRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
460 glEnable(GL_SCISSOR_TEST);
461 scissorEnabled = true;
463 clipRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
466 glScissor(clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());
468 if (!stencilEnabled) {
469 if (!m_clip_program.isLinked()) {
470 m_clip_program.addShaderFromSourceCode(QOpenGLShader::Vertex,
471 "attribute highp vec4 vCoord; \n"
472 "uniform highp mat4 matrix; \n"
474 " gl_Position = matrix * vCoord; \n"
476 m_clip_program.addShaderFromSourceCode(QOpenGLShader::Fragment,
478 " gl_FragColor = vec4(0.81, 0.83, 0.12, 1.0); \n" // Trolltech green ftw!
480 m_clip_program.bindAttributeLocation("vCoord", 0);
481 m_clip_program.link();
482 m_clip_matrix_id = m_clip_program.uniformLocation("matrix");
485 glStencilMask(0xff); // write mask
487 glClear(GL_STENCIL_BUFFER_BIT);
488 glEnable(GL_STENCIL_TEST);
489 glDisable(GL_DEPTH_TEST);
490 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
491 glDepthMask(GL_FALSE);
493 m_clip_program.bind();
494 m_clip_program.enableAttributeArray(0);
496 stencilEnabled = true;
499 glStencilFunc(GL_EQUAL, clipDepth, 0xff); // stencil test, ref, test mask
500 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); // stencil fail, z fail, z pass
502 const QSGGeometry *g = clip->geometry();
503 Q_ASSERT(g->attributeCount() > 0);
504 const QSGGeometry::Attribute *a = g->attributes();
505 glVertexAttribPointer(0, a->tupleSize, a->type, GL_FALSE, g->sizeOfVertex(), g->vertexData());
507 m_clip_program.setUniformValue(m_clip_matrix_id, m);
508 if (g->indexCount()) {
509 glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData());
511 glDrawArrays(g->drawingMode(), 0, g->vertexCount());
517 clip = clip->clipList();
520 if (stencilEnabled) {
521 m_clip_program.disableAttributeArray(0);
522 glEnable(GL_DEPTH_TEST);
523 glStencilFunc(GL_EQUAL, clipDepth, 0xff); // stencil test, ref, test mask
524 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // stencil fail, z fail, z pass
525 glStencilMask(0); // write mask
526 bindable()->reactivate();
527 //glDepthMask(GL_TRUE); // must be reset correctly by caller.
529 glDisable(GL_STENCIL_TEST);
533 glDisable(GL_SCISSOR_TEST);
535 return stencilEnabled ? StencilClip : ScissorClip;
540 static inline int size_of_type(GLenum type)
542 static int sizes[] = {
544 sizeof(unsigned char),
546 sizeof(unsigned short),
548 sizeof(unsigned int),
555 Q_ASSERT(type >= GL_BYTE && type <= 0x140A); // the value of GL_DOUBLE
556 return sizes[type - GL_BYTE];
560 class QSGRendererVBOGeometryData : public QSGGeometryData
563 QSGRendererVBOGeometryData()
569 ~QSGRendererVBOGeometryData()
571 QOpenGLFunctions *func = QOpenGLContext::currentContext()->functions();
573 func->glDeleteBuffers(1, &vertexBuffer);
575 func->glDeleteBuffers(1, &indexBuffer);
581 static QSGRendererVBOGeometryData *get(const QSGGeometry *g) {
582 QSGRendererVBOGeometryData *gd = static_cast<QSGRendererVBOGeometryData *>(QSGGeometryData::data(g));
584 gd = new QSGRendererVBOGeometryData;
585 QSGGeometryData::install(g, gd);
592 static inline GLenum qt_drawTypeForPattern(QSGGeometry::DataPattern p)
594 Q_ASSERT(p > 0 && p <= 3);
595 static GLenum drawTypes[] = { 0,
605 Issues the GL draw call for the geometry \a g using the material \a shader.
607 The function assumes that attributes have been bound and set up prior
613 void QSGRenderer::draw(const QSGMaterialShader *shader, const QSGGeometry *g)
615 // ### remove before final release...
616 static bool use_vbo = !QGuiApplication::arguments().contains(QLatin1String("--no-vbo"));
618 const void *vertexData;
619 int vertexByteSize = g->vertexCount() * g->sizeOfVertex();
620 if (use_vbo && g->vertexDataPattern() != QSGGeometry::AlwaysUploadPattern && vertexByteSize > 1024) {
622 // The base pointer for a VBO is 0
625 bool updateData = QSGGeometryData::hasDirtyVertexData(g);
626 QSGRendererVBOGeometryData *gd = QSGRendererVBOGeometryData::get(g);
627 if (!gd->vertexBuffer) {
628 glGenBuffers(1, &gd->vertexBuffer);
632 glBindBuffer(GL_ARRAY_BUFFER, gd->vertexBuffer);
633 m_vertex_buffer_bound = true;
636 glBufferData(GL_ARRAY_BUFFER, vertexByteSize, g->vertexData(),
637 qt_drawTypeForPattern(g->vertexDataPattern()));
638 QSGGeometryData::clearDirtyVertexData(g);
642 if (m_vertex_buffer_bound) {
643 glBindBuffer(GL_ARRAY_BUFFER, 0);
644 m_vertex_buffer_bound = false;
646 vertexData = g->vertexData();
649 // Bind the vertices to attributes...
650 char const *const *attrNames = shader->attributeNames();
652 for (int j = 0; attrNames[j]; ++j) {
655 Q_ASSERT_X(j < g->attributeCount(), "QSGRenderer::bindGeometry()", "Geometry lacks attribute required by material");
656 const QSGGeometry::Attribute &a = g->attributes()[j];
657 Q_ASSERT_X(j == a.position, "QSGRenderer::bindGeometry()", "Geometry does not have continuous attribute positions");
659 #if defined(QT_OPENGL_ES_2)
660 GLboolean normalize = a.type != GL_FLOAT;
662 GLboolean normalize = a.type != GL_FLOAT && a.type != GL_DOUBLE;
664 glVertexAttribPointer(a.position, a.tupleSize, a.type, normalize, g->sizeOfVertex(), (char *) vertexData + offset);
665 offset += a.tupleSize * size_of_type(a.type);
668 // Set up the indices...
669 const void *indexData;
670 if (use_vbo && g->indexDataPattern() != QSGGeometry::AlwaysUploadPattern && g->indexCount() > 512) {
672 // Base pointer for a VBO is 0
675 bool updateData = QSGGeometryData::hasDirtyIndexData(g);
676 QSGRendererVBOGeometryData *gd = QSGRendererVBOGeometryData::get(g);
677 if (!gd->indexBuffer) {
678 glGenBuffers(1, &gd->indexBuffer);
682 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gd->indexBuffer);
683 m_index_buffer_bound = true;
686 glBufferData(GL_ELEMENT_ARRAY_BUFFER,
687 g->indexCount() * g->sizeOfIndex(),
689 qt_drawTypeForPattern(g->indexDataPattern()));
690 QSGGeometryData::clearDirtyIndexData(g);
694 if (m_index_buffer_bound) {
695 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
696 m_index_buffer_bound = false;
698 indexData = g->indexData();
703 if (g->indexCount()) {
704 glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), indexData);
706 glDrawArrays(g->drawingMode(), 0, g->vertexCount());
709 // We leave buffers bound for now... They will be reset by bind on next draw() or
710 // set back to 0 if next draw is not using VBOs
716 \brief The QSGNodeDumper class provides a way of dumping a scene grahp to the console.
718 This class is solely for debugging purposes.
723 void QSGNodeDumper::dump(QSGNode *n)
729 void QSGNodeDumper::visitNode(QSGNode *n)
731 qDebug() << QString(m_indent * 2, QLatin1Char(' ')) << n;
732 QSGNodeVisitor::visitNode(n);
735 void QSGNodeDumper::visitChildren(QSGNode *n)
738 QSGNodeVisitor::visitChildren(n);