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 QtDeclarative 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 ****************************************************************************/
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
96 QSGBindableFboId::QSGBindableFboId(GLuint id)
102 void QSGBindableFboId::bind() const
104 QOpenGLContext::currentContext()->functions()->glBindFramebuffer(GL_FRAMEBUFFER, m_id);
109 \brief The renderer class is the abstract baseclass use for rendering the
112 The renderer is not tied to any particular surface. It expects a context to
113 be current and will render into that surface according to how the device rect,
114 viewport rect and projection transformation are set up.
116 Rendering is a sequence of steps initiated by calling renderScene(). This will
117 effectively draw the scene graph starting at the root node. The QSGNode::preprocess()
118 function will be called for all the nodes in the graph, followed by an update
119 pass which updates all matrices, opacity, clip states and similar in the graph.
120 Because the update pass is called after preprocess, it is safe to modify the graph
121 during preprocess. To run a custom update pass over the graph, install a custom
122 QSGNodeUpdater using setNodeUpdater(). Once all the graphs dirty states are updated,
123 the virtual render() function is called.
125 The render() function is implemented by QSGRenderer subclasses to render the graph
126 in the most optimal way for a given hardware.
128 The renderer can make use of stencil, depth and color buffers in addition to the
135 QSGRenderer::QSGRenderer(QSGContext *context)
137 , m_clear_color(Qt::transparent)
138 , m_clear_mode(ClearColorBuffer | ClearDepthBuffer)
139 , m_current_opacity(1)
144 , m_changed_emitted(false)
146 , m_is_rendering(false)
147 , m_vertex_buffer_bound(false)
148 , m_index_buffer_bound(false)
150 initializeGLFunctions();
154 QSGRenderer::~QSGRenderer()
157 delete m_node_updater;
161 Returns the scene graph context for this renderer.
166 QSGContext *QSGRenderer::context()
175 Returns the node updater that this renderer uses to update states in the
178 If no updater is specified a default one is constructed.
181 QSGNodeUpdater *QSGRenderer::nodeUpdater() const
184 const_cast<QSGRenderer *>(this)->m_node_updater = new QSGNodeUpdater();
185 return m_node_updater;
190 Sets the node updater that this renderer uses to update states in the
193 This will delete and override any existing node updater
195 void QSGRenderer::setNodeUpdater(QSGNodeUpdater *updater)
198 delete m_node_updater;
199 m_node_updater = updater;
203 void QSGRenderer::setRootNode(QSGRootNode *node)
205 if (m_root_node == node)
208 m_root_node->m_renderers.removeOne(this);
209 nodeChanged(m_root_node, QSGNode::DirtyNodeRemoved);
213 Q_ASSERT(!m_root_node->m_renderers.contains(this));
214 m_root_node->m_renderers << this;
215 nodeChanged(m_root_node, QSGNode::DirtyNodeAdded);
220 void QSGRenderer::renderScene()
222 class B : public QSGBindable
225 void bind() const { QOpenGLFramebufferObject::bindDefault(); }
230 void QSGRenderer::renderScene(const QSGBindable &bindable)
235 m_is_rendering = true;
238 #ifdef QSG_RENDERER_TIMING
239 if (qsg_render_timing)
245 m_bindable = &bindable;
249 #ifdef QSG_RENDERER_TIMING
250 if (qsg_render_timing)
251 bindTime = frameTimer.elapsed();
255 // Sanity check that attribute registers are disabled
258 glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &count);
260 for (int i=0; i<count; ++i) {
261 glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled);
263 qWarning("QSGRenderer: attribute %d is enabled, this can lead to memory corruption and crashes.", i);
270 #ifdef QSG_RENDERER_TIMING
271 if (qsg_render_timing)
272 renderTime = frameTimer.elapsed();
275 glDisable(GL_SCISSOR_TEST);
276 m_is_rendering = false;
277 m_changed_emitted = false;
280 if (m_vertex_buffer_bound) {
281 glBindBuffer(GL_ARRAY_BUFFER, 0);
282 m_vertex_buffer_bound = false;
285 if (m_index_buffer_bound) {
286 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
287 m_index_buffer_bound = false;
290 #ifdef QSG_RENDERER_TIMING
291 if (qsg_render_timing) {
292 printf(" - Breakdown of frametime: preprocess=%d, updates=%d, binding=%d, render=%d, total=%d\n",
294 updatePassTime - preprocessTime,
295 bindTime - updatePassTime,
296 renderTime - bindTime,
302 void QSGRenderer::setProjectionMatrixToDeviceRect()
304 setProjectionMatrixToRect(m_device_rect);
307 void QSGRenderer::setProjectionMatrixToRect(const QRectF &rect)
310 matrix.ortho(rect.x(),
311 rect.x() + rect.width(),
312 rect.y() + rect.height(),
316 setProjectionMatrix(matrix);
319 void QSGRenderer::setProjectionMatrix(const QMatrix4x4 &matrix)
321 m_projection_matrix = matrix;
322 // Mirrored relative to the usual Qt coordinate system with origin in the top left corner.
323 m_mirrored = matrix(0, 0) * matrix(1, 1) - matrix(0, 1) * matrix(1, 0) > 0;
326 void QSGRenderer::setClearColor(const QColor &color)
328 m_clear_color = color;
332 Updates internal data structures and emits the sceneGraphChanged() signal.
334 If \a flags contains the QSGNode::DirtyNodeRemoved flag, the node might be
335 in the process of being destroyed. It is then not safe to downcast the node
339 void QSGRenderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state)
341 if (state & QSGNode::DirtyNodeAdded)
342 addNodesToPreprocess(node);
343 if (state & QSGNode::DirtyNodeRemoved)
344 removeNodesToPreprocess(node);
345 if (state & QSGNode::DirtyUsePreprocess) {
346 if (node->flags() & QSGNode::UsePreprocess)
347 m_nodes_to_preprocess.insert(node);
349 m_nodes_to_preprocess.remove(node);
352 if (!m_changed_emitted && !m_is_rendering) {
353 // Premature overoptimization to avoid excessive signal emissions
354 m_changed_emitted = true;
355 emit sceneGraphChanged();
359 void QSGRenderer::materialChanged(QSGGeometryNode *, QSGMaterial *, QSGMaterial *)
363 void QSGRenderer::preprocess()
365 Q_ASSERT(m_root_node);
367 // We need to take a copy here, in case any of the preprocess calls deletes a node that
368 // is in the preprocess list and thus, changes the m_nodes_to_preprocess behind our backs
369 // For the default case, when this does not happen, the cost is neglishible.
370 QSet<QSGNode *> items = m_nodes_to_preprocess;
372 for (QSet<QSGNode *>::const_iterator it = items.constBegin();
373 it != items.constEnd(); ++it) {
375 if (!nodeUpdater()->isNodeBlocked(n, m_root_node)) {
380 #ifdef QSG_RENDERER_TIMING
381 if (qsg_render_timing)
382 preprocessTime = frameTimer.elapsed();
385 nodeUpdater()->setToplevelOpacity(context()->renderAlpha());
386 nodeUpdater()->updateStates(m_root_node);
388 #ifdef QSG_RENDERER_TIMING
389 if (qsg_render_timing)
390 updatePassTime = frameTimer.elapsed();
395 void QSGRenderer::addNodesToPreprocess(QSGNode *node)
397 for (QSGNode *c = node->firstChild(); c; c = c->nextSibling())
398 addNodesToPreprocess(c);
399 if (node->flags() & QSGNode::UsePreprocess)
400 m_nodes_to_preprocess.insert(node);
403 void QSGRenderer::removeNodesToPreprocess(QSGNode *node)
405 for (QSGNode *c = node->firstChild(); c; c = c->nextSibling())
406 removeNodesToPreprocess(c);
407 if (node->flags() & QSGNode::UsePreprocess)
408 m_nodes_to_preprocess.remove(node);
413 Convenience function to set up the stencil buffer for clipping based on \a clip.
415 If the clip is a pixel aligned rectangle, this function will use glScissor instead
419 QSGRenderer::ClipType QSGRenderer::updateStencilClip(const QSGClipNode *clip)
422 glDisable(GL_STENCIL_TEST);
423 glDisable(GL_SCISSOR_TEST);
427 bool stencilEnabled = false;
428 bool scissorEnabled = false;
430 glDisable(GL_SCISSOR_TEST);
435 QMatrix4x4 m = m_current_projection_matrix;
437 m *= *clip->matrix();
439 // TODO: Check for multisampling and pixel grid alignment.
440 bool isRectangleWithNoPerspective = clip->isRectangular()
441 && qFuzzyIsNull(m(3, 0)) && qFuzzyIsNull(m(3, 1));
442 bool noRotate = qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(1, 0));
443 bool isRotate90 = qFuzzyIsNull(m(0, 0)) && qFuzzyIsNull(m(1, 1));
445 if (isRectangleWithNoPerspective && (noRotate || isRotate90)) {
446 QRectF bbox = clip->clipRect();
447 qreal invW = 1 / m(3, 3);
448 qreal fx1, fy1, fx2, fy2;
450 fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
451 fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
452 fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
453 fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
455 Q_ASSERT(isRotate90);
456 fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
457 fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
458 fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
459 fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
467 GLint ix1 = qRound((fx1 + 1) * m_device_rect.width() * qreal(0.5));
468 GLint iy1 = qRound((fy1 + 1) * m_device_rect.height() * qreal(0.5));
469 GLint ix2 = qRound((fx2 + 1) * m_device_rect.width() * qreal(0.5));
470 GLint iy2 = qRound((fy2 + 1) * m_device_rect.height() * qreal(0.5));
472 if (!scissorEnabled) {
473 clipRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
474 glEnable(GL_SCISSOR_TEST);
475 scissorEnabled = true;
477 clipRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
480 glScissor(clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());
482 if (!stencilEnabled) {
483 if (!m_clip_program.isLinked()) {
484 m_clip_program.addShaderFromSourceCode(QOpenGLShader::Vertex,
485 "attribute highp vec4 vCoord; \n"
486 "uniform highp mat4 matrix; \n"
488 " gl_Position = matrix * vCoord; \n"
490 m_clip_program.addShaderFromSourceCode(QOpenGLShader::Fragment,
492 " gl_FragColor = vec4(0.81, 0.83, 0.12, 1.0); \n" // Trolltech green ftw!
494 m_clip_program.bindAttributeLocation("vCoord", 0);
495 m_clip_program.link();
496 m_clip_matrix_id = m_clip_program.uniformLocation("matrix");
499 glStencilMask(0xff); // write mask
501 glClear(GL_STENCIL_BUFFER_BIT);
502 glEnable(GL_STENCIL_TEST);
503 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
504 glDepthMask(GL_FALSE);
506 m_clip_program.bind();
507 m_clip_program.enableAttributeArray(0);
509 stencilEnabled = true;
512 glStencilFunc(GL_EQUAL, clipDepth, 0xff); // stencil test, ref, test mask
513 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); // stencil fail, z fail, z pass
515 const QSGGeometry *g = clip->geometry();
516 Q_ASSERT(g->attributeCount() > 0);
517 const QSGGeometry::Attribute *a = g->attributes();
518 glVertexAttribPointer(0, a->tupleSize, a->type, GL_FALSE, g->sizeOfVertex(), g->vertexData());
520 m_clip_program.setUniformValue(m_clip_matrix_id, m);
521 if (g->indexCount()) {
522 glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData());
524 glDrawArrays(g->drawingMode(), 0, g->vertexCount());
530 clip = clip->clipList();
533 if (stencilEnabled) {
534 m_clip_program.disableAttributeArray(0);
535 glStencilFunc(GL_EQUAL, clipDepth, 0xff); // stencil test, ref, test mask
536 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // stencil fail, z fail, z pass
537 glStencilMask(0); // write mask
538 bindable()->reactivate();
540 glDisable(GL_STENCIL_TEST);
544 glDisable(GL_SCISSOR_TEST);
546 return stencilEnabled ? StencilClip : ScissorClip;
551 static inline int size_of_type(GLenum type)
553 static int sizes[] = {
555 sizeof(unsigned char),
557 sizeof(unsigned short),
559 sizeof(unsigned int),
566 Q_ASSERT(type >= GL_BYTE && type <= 0x140A); // the value of GL_DOUBLE
567 return sizes[type - GL_BYTE];
571 class QSGRendererVBOGeometryData : public QSGGeometryData
574 QSGRendererVBOGeometryData()
580 ~QSGRendererVBOGeometryData()
582 QOpenGLContext *ctx = QOpenGLContext::currentContext();
585 QOpenGLFunctions *func = ctx->functions();
587 func->glDeleteBuffers(1, &vertexBuffer);
589 func->glDeleteBuffers(1, &indexBuffer);
595 static QSGRendererVBOGeometryData *get(const QSGGeometry *g) {
596 QSGRendererVBOGeometryData *gd = static_cast<QSGRendererVBOGeometryData *>(QSGGeometryData::data(g));
598 gd = new QSGRendererVBOGeometryData;
599 QSGGeometryData::install(g, gd);
606 static inline GLenum qt_drawTypeForPattern(QSGGeometry::DataPattern p)
608 Q_ASSERT(p > 0 && p <= 3);
609 static GLenum drawTypes[] = { 0,
619 Issues the GL draw call for the geometry \a g using the material \a shader.
621 The function assumes that attributes have been bound and set up prior
627 void QSGRenderer::draw(const QSGMaterialShader *shader, const QSGGeometry *g)
629 // ### remove before final release...
630 static bool use_vbo = !QGuiApplication::arguments().contains(QLatin1String("--no-vbo"));
632 const void *vertexData;
633 int vertexByteSize = g->vertexCount() * g->sizeOfVertex();
634 if (use_vbo && g->vertexDataPattern() != QSGGeometry::AlwaysUploadPattern && vertexByteSize > 1024) {
636 // The base pointer for a VBO is 0
639 bool updateData = QSGGeometryData::hasDirtyVertexData(g);
640 QSGRendererVBOGeometryData *gd = QSGRendererVBOGeometryData::get(g);
641 if (!gd->vertexBuffer) {
642 glGenBuffers(1, &gd->vertexBuffer);
646 glBindBuffer(GL_ARRAY_BUFFER, gd->vertexBuffer);
647 m_vertex_buffer_bound = true;
650 glBufferData(GL_ARRAY_BUFFER, vertexByteSize, g->vertexData(),
651 qt_drawTypeForPattern(g->vertexDataPattern()));
652 QSGGeometryData::clearDirtyVertexData(g);
656 if (m_vertex_buffer_bound) {
657 glBindBuffer(GL_ARRAY_BUFFER, 0);
658 m_vertex_buffer_bound = false;
660 vertexData = g->vertexData();
663 // Bind the vertices to attributes...
664 char const *const *attrNames = shader->attributeNames();
666 for (int j = 0; attrNames[j]; ++j) {
669 Q_ASSERT_X(j < g->attributeCount(), "QSGRenderer::bindGeometry()", "Geometry lacks attribute required by material");
670 const QSGGeometry::Attribute &a = g->attributes()[j];
671 Q_ASSERT_X(j == a.position, "QSGRenderer::bindGeometry()", "Geometry does not have continuous attribute positions");
673 #if defined(QT_OPENGL_ES_2)
674 GLboolean normalize = a.type != GL_FLOAT;
676 GLboolean normalize = a.type != GL_FLOAT && a.type != GL_DOUBLE;
678 glVertexAttribPointer(a.position, a.tupleSize, a.type, normalize, g->sizeOfVertex(), (char *) vertexData + offset);
679 offset += a.tupleSize * size_of_type(a.type);
682 // Set up the indices...
683 const void *indexData;
684 if (use_vbo && g->indexDataPattern() != QSGGeometry::AlwaysUploadPattern && g->indexCount() > 512) {
686 // Base pointer for a VBO is 0
689 bool updateData = QSGGeometryData::hasDirtyIndexData(g);
690 QSGRendererVBOGeometryData *gd = QSGRendererVBOGeometryData::get(g);
691 if (!gd->indexBuffer) {
692 glGenBuffers(1, &gd->indexBuffer);
696 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gd->indexBuffer);
697 m_index_buffer_bound = true;
700 glBufferData(GL_ELEMENT_ARRAY_BUFFER,
701 g->indexCount() * g->sizeOfIndex(),
703 qt_drawTypeForPattern(g->indexDataPattern()));
704 QSGGeometryData::clearDirtyIndexData(g);
708 if (m_index_buffer_bound) {
709 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
710 m_index_buffer_bound = false;
712 indexData = g->indexData();
715 // Set the line width if applicable
716 if (g->drawingMode() == GL_LINES || g->drawingMode() == GL_LINE_STRIP || g->drawingMode() == GL_LINE_LOOP) {
717 glLineWidth(g->lineWidth());
721 if (g->indexCount()) {
722 glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), indexData);
724 glDrawArrays(g->drawingMode(), 0, g->vertexCount());
727 // We leave buffers bound for now... They will be reset by bind on next draw() or
728 // set back to 0 if next draw is not using VBOs
734 \brief The QSGNodeDumper class provides a way of dumping a scene grahp to the console.
736 This class is solely for debugging purposes.
741 void QSGNodeDumper::dump(QSGNode *n)
747 void QSGNodeDumper::visitNode(QSGNode *n)
749 qDebug() << QString(m_indent * 2, QLatin1Char(' ')) << n;
750 QSGNodeVisitor::visitNode(n);
753 void QSGNodeDumper::visitChildren(QSGNode *n)
756 QSGNodeVisitor::visitChildren(n);