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
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::DirtyState state)
330 if (state & QSGNode::DirtyNodeAdded)
331 addNodesToPreprocess(node);
332 if (state & QSGNode::DirtyNodeRemoved)
333 removeNodesToPreprocess(node);
334 if (state & QSGNode::DirtyUsePreprocess) {
335 if (node->flags() & QSGNode::UsePreprocess)
336 m_nodes_to_preprocess.insert(node);
338 m_nodes_to_preprocess.remove(node);
341 if (!m_changed_emitted && !m_is_rendering) {
342 // Premature overoptimization to avoid excessive signal emissions
343 m_changed_emitted = true;
344 emit sceneGraphChanged();
348 void QSGRenderer::materialChanged(QSGGeometryNode *, QSGMaterial *, QSGMaterial *)
352 void QSGRenderer::preprocess()
354 Q_ASSERT(m_root_node);
356 // We need to take a copy here, in case any of the preprocess calls deletes a node that
357 // is in the preprocess list and thus, changes the m_nodes_to_preprocess behind our backs
358 // For the default case, when this does not happen, the cost is neglishible.
359 QSet<QSGNode *> items = m_nodes_to_preprocess;
361 for (QSet<QSGNode *>::const_iterator it = items.constBegin();
362 it != items.constEnd(); ++it) {
364 if (!nodeUpdater()->isNodeBlocked(n, m_root_node)) {
369 #ifdef QSG_RENDERER_TIMING
370 if (qsg_render_timing)
371 preprocessTime = frameTimer.elapsed();
374 nodeUpdater()->setToplevelOpacity(context()->renderAlpha());
375 nodeUpdater()->updateStates(m_root_node);
377 #ifdef QSG_RENDERER_TIMING
378 if (qsg_render_timing)
379 updatePassTime = frameTimer.elapsed();
384 void QSGRenderer::addNodesToPreprocess(QSGNode *node)
386 for (QSGNode *c = node->firstChild(); c; c = c->nextSibling())
387 addNodesToPreprocess(c);
388 if (node->flags() & QSGNode::UsePreprocess)
389 m_nodes_to_preprocess.insert(node);
392 void QSGRenderer::removeNodesToPreprocess(QSGNode *node)
394 for (QSGNode *c = node->firstChild(); c; c = c->nextSibling())
395 removeNodesToPreprocess(c);
396 if (node->flags() & QSGNode::UsePreprocess)
397 m_nodes_to_preprocess.remove(node);
402 Convenience function to set up the stencil buffer for clipping based on \a clip.
404 If the clip is a pixel aligned rectangle, this function will use glScissor instead
408 QSGRenderer::ClipType QSGRenderer::updateStencilClip(const QSGClipNode *clip)
411 glDisable(GL_STENCIL_TEST);
412 glDisable(GL_SCISSOR_TEST);
416 bool stencilEnabled = false;
417 bool scissorEnabled = false;
419 glDisable(GL_SCISSOR_TEST);
424 QMatrix4x4 m = m_current_projection_matrix;
426 m *= *clip->matrix();
428 // TODO: Check for multisampling and pixel grid alignment.
429 bool isRectangleWithNoPerspective = clip->isRectangular()
430 && qFuzzyIsNull(m(3, 0)) && qFuzzyIsNull(m(3, 1));
431 bool noRotate = qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(1, 0));
432 bool isRotate90 = qFuzzyIsNull(m(0, 0)) && qFuzzyIsNull(m(1, 1));
434 if (isRectangleWithNoPerspective && (noRotate || isRotate90)) {
435 QRectF bbox = clip->clipRect();
436 qreal invW = 1 / m(3, 3);
437 qreal fx1, fy1, fx2, fy2;
439 fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
440 fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
441 fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
442 fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
444 Q_ASSERT(isRotate90);
445 fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
446 fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
447 fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
448 fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
456 GLint ix1 = qRound((fx1 + 1) * m_device_rect.width() * qreal(0.5));
457 GLint iy1 = qRound((fy1 + 1) * m_device_rect.height() * qreal(0.5));
458 GLint ix2 = qRound((fx2 + 1) * m_device_rect.width() * qreal(0.5));
459 GLint iy2 = qRound((fy2 + 1) * m_device_rect.height() * qreal(0.5));
461 if (!scissorEnabled) {
462 clipRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
463 glEnable(GL_SCISSOR_TEST);
464 scissorEnabled = true;
466 clipRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
469 glScissor(clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());
471 if (!stencilEnabled) {
472 if (!m_clip_program.isLinked()) {
473 m_clip_program.addShaderFromSourceCode(QOpenGLShader::Vertex,
474 "attribute highp vec4 vCoord; \n"
475 "uniform highp mat4 matrix; \n"
477 " gl_Position = matrix * vCoord; \n"
479 m_clip_program.addShaderFromSourceCode(QOpenGLShader::Fragment,
481 " gl_FragColor = vec4(0.81, 0.83, 0.12, 1.0); \n" // Trolltech green ftw!
483 m_clip_program.bindAttributeLocation("vCoord", 0);
484 m_clip_program.link();
485 m_clip_matrix_id = m_clip_program.uniformLocation("matrix");
488 glStencilMask(0xff); // write mask
490 glClear(GL_STENCIL_BUFFER_BIT);
491 glEnable(GL_STENCIL_TEST);
492 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
493 glDepthMask(GL_FALSE);
495 m_clip_program.bind();
496 m_clip_program.enableAttributeArray(0);
498 stencilEnabled = true;
501 glStencilFunc(GL_EQUAL, clipDepth, 0xff); // stencil test, ref, test mask
502 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); // stencil fail, z fail, z pass
504 const QSGGeometry *g = clip->geometry();
505 Q_ASSERT(g->attributeCount() > 0);
506 const QSGGeometry::Attribute *a = g->attributes();
507 glVertexAttribPointer(0, a->tupleSize, a->type, GL_FALSE, g->sizeOfVertex(), g->vertexData());
509 m_clip_program.setUniformValue(m_clip_matrix_id, m);
510 if (g->indexCount()) {
511 glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData());
513 glDrawArrays(g->drawingMode(), 0, g->vertexCount());
519 clip = clip->clipList();
522 if (stencilEnabled) {
523 m_clip_program.disableAttributeArray(0);
524 glStencilFunc(GL_EQUAL, clipDepth, 0xff); // stencil test, ref, test mask
525 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // stencil fail, z fail, z pass
526 glStencilMask(0); // write mask
527 bindable()->reactivate();
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 QOpenGLContext *ctx = QOpenGLContext::currentContext();
574 QOpenGLFunctions *func = ctx->functions();
576 func->glDeleteBuffers(1, &vertexBuffer);
578 func->glDeleteBuffers(1, &indexBuffer);
584 static QSGRendererVBOGeometryData *get(const QSGGeometry *g) {
585 QSGRendererVBOGeometryData *gd = static_cast<QSGRendererVBOGeometryData *>(QSGGeometryData::data(g));
587 gd = new QSGRendererVBOGeometryData;
588 QSGGeometryData::install(g, gd);
595 static inline GLenum qt_drawTypeForPattern(QSGGeometry::DataPattern p)
597 Q_ASSERT(p > 0 && p <= 3);
598 static GLenum drawTypes[] = { 0,
608 Issues the GL draw call for the geometry \a g using the material \a shader.
610 The function assumes that attributes have been bound and set up prior
616 void QSGRenderer::draw(const QSGMaterialShader *shader, const QSGGeometry *g)
618 // ### remove before final release...
619 static bool use_vbo = !QGuiApplication::arguments().contains(QLatin1String("--no-vbo"));
621 const void *vertexData;
622 int vertexByteSize = g->vertexCount() * g->sizeOfVertex();
623 if (use_vbo && g->vertexDataPattern() != QSGGeometry::AlwaysUploadPattern && vertexByteSize > 1024) {
625 // The base pointer for a VBO is 0
628 bool updateData = QSGGeometryData::hasDirtyVertexData(g);
629 QSGRendererVBOGeometryData *gd = QSGRendererVBOGeometryData::get(g);
630 if (!gd->vertexBuffer) {
631 glGenBuffers(1, &gd->vertexBuffer);
635 glBindBuffer(GL_ARRAY_BUFFER, gd->vertexBuffer);
636 m_vertex_buffer_bound = true;
639 glBufferData(GL_ARRAY_BUFFER, vertexByteSize, g->vertexData(),
640 qt_drawTypeForPattern(g->vertexDataPattern()));
641 QSGGeometryData::clearDirtyVertexData(g);
645 if (m_vertex_buffer_bound) {
646 glBindBuffer(GL_ARRAY_BUFFER, 0);
647 m_vertex_buffer_bound = false;
649 vertexData = g->vertexData();
652 // Bind the vertices to attributes...
653 char const *const *attrNames = shader->attributeNames();
655 for (int j = 0; attrNames[j]; ++j) {
658 Q_ASSERT_X(j < g->attributeCount(), "QSGRenderer::bindGeometry()", "Geometry lacks attribute required by material");
659 const QSGGeometry::Attribute &a = g->attributes()[j];
660 Q_ASSERT_X(j == a.position, "QSGRenderer::bindGeometry()", "Geometry does not have continuous attribute positions");
662 #if defined(QT_OPENGL_ES_2)
663 GLboolean normalize = a.type != GL_FLOAT;
665 GLboolean normalize = a.type != GL_FLOAT && a.type != GL_DOUBLE;
667 glVertexAttribPointer(a.position, a.tupleSize, a.type, normalize, g->sizeOfVertex(), (char *) vertexData + offset);
668 offset += a.tupleSize * size_of_type(a.type);
671 // Set up the indices...
672 const void *indexData;
673 if (use_vbo && g->indexDataPattern() != QSGGeometry::AlwaysUploadPattern && g->indexCount() > 512) {
675 // Base pointer for a VBO is 0
678 bool updateData = QSGGeometryData::hasDirtyIndexData(g);
679 QSGRendererVBOGeometryData *gd = QSGRendererVBOGeometryData::get(g);
680 if (!gd->indexBuffer) {
681 glGenBuffers(1, &gd->indexBuffer);
685 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gd->indexBuffer);
686 m_index_buffer_bound = true;
689 glBufferData(GL_ELEMENT_ARRAY_BUFFER,
690 g->indexCount() * g->sizeOfIndex(),
692 qt_drawTypeForPattern(g->indexDataPattern()));
693 QSGGeometryData::clearDirtyIndexData(g);
697 if (m_index_buffer_bound) {
698 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
699 m_index_buffer_bound = false;
701 indexData = g->indexData();
704 // Set the line width if applicable
705 if (g->drawingMode() == GL_LINES || g->drawingMode() == GL_LINE_STRIP || g->drawingMode() == GL_LINE_LOOP) {
706 glLineWidth(g->lineWidth());
710 if (g->indexCount()) {
711 glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), indexData);
713 glDrawArrays(g->drawingMode(), 0, g->vertexCount());
716 // We leave buffers bound for now... They will be reset by bind on next draw() or
717 // set back to 0 if next draw is not using VBOs
723 \brief The QSGNodeDumper class provides a way of dumping a scene grahp to the console.
725 This class is solely for debugging purposes.
730 void QSGNodeDumper::dump(QSGNode *n)
736 void QSGNodeDumper::visitNode(QSGNode *n)
738 qDebug() << QString(m_indent * 2, QLatin1Char(' ')) << n;
739 QSGNodeVisitor::visitNode(n);
742 void QSGNodeDumper::visitChildren(QSGNode *n)
745 QSGNodeVisitor::visitChildren(n);