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 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
42 #include "qsgrenderer_p.h"
44 #include "qsgmaterial.h"
45 #include "qsgnodeupdater_p.h"
47 #include "private/qsgadaptationlayer_p.h"
49 #include <QGLShaderProgram>
50 #include <qglframebufferobject.h>
51 #include <QtGui/qapplication.h>
53 #include <qdatetime.h>
57 //#define RENDERER_DEBUG
58 //#define QT_GL_NO_SCISSOR_TEST
60 // #define QSG_RENDERER_TIMING
61 #ifdef QSG_RENDERER_TIMING
62 static QTime frameTimer;
63 static int preprocessTime;
64 static int updatePassTime;
65 static int frameNumber = 0;
68 void Bindable::clear(QSGRenderer::ClearMode mode) const
71 if (mode & QSGRenderer::ClearColorBuffer) bits |= GL_COLOR_BUFFER_BIT;
72 if (mode & QSGRenderer::ClearDepthBuffer) bits |= GL_DEPTH_BUFFER_BIT;
73 if (mode & QSGRenderer::ClearStencilBuffer) bits |= GL_STENCIL_BUFFER_BIT;
77 // Reactivate the color buffer after switching to the stencil.
78 void Bindable::reactivate() const
80 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
83 BindableFbo::BindableFbo(QGLFramebufferObject *fbo) : m_fbo(fbo)
88 void BindableFbo::bind() const
95 \brief The renderer class is the abstract baseclass use for rendering the
98 The renderer is not tied to any particular surface. It expects a context to
99 be current and will render into that surface according to how the device rect,
100 viewport rect and projection transformation are set up.
102 Rendering is a sequence of steps initiated by calling renderScene(). This will
103 effectively draw the scene graph starting at the root node. The QSGNode::preprocess()
104 function will be called for all the nodes in the graph, followed by an update
105 pass which updates all matrices, opacity, clip states and similar in the graph.
106 Because the update pass is called after preprocess, it is safe to modify the graph
107 during preprocess. To run a custom update pass over the graph, install a custom
108 QSGNodeUpdater using setNodeUpdater(). Once all the graphs dirty states are updated,
109 the virtual render() function is called.
111 The render() function is implemented by QSGRenderer subclasses to render the graph
112 in the most optimal way for a given hardware.
114 The renderer can make use of stencil, depth and color buffers in addition to the
121 QSGRenderer::QSGRenderer(QSGContext *context)
123 , m_clear_color(Qt::transparent)
124 , m_clear_mode(ClearColorBuffer | ClearDepthBuffer)
125 , m_current_opacity(1)
130 , m_changed_emitted(false)
132 , m_is_rendering(false)
134 initializeGLFunctions();
138 QSGRenderer::~QSGRenderer()
141 delete m_node_updater;
145 Returns the scene graph context for this renderer.
150 QSGContext *QSGRenderer::context()
159 Returns the node updater that this renderer uses to update states in the
162 If no updater is specified a default one is constructed.
165 QSGNodeUpdater *QSGRenderer::nodeUpdater() const
168 const_cast<QSGRenderer *>(this)->m_node_updater = new QSGNodeUpdater();
169 return m_node_updater;
174 Sets the node updater that this renderer uses to update states in the
177 This will delete and override any existing node updater
179 void QSGRenderer::setNodeUpdater(QSGNodeUpdater *updater)
182 delete m_node_updater;
183 m_node_updater = updater;
187 void QSGRenderer::setRootNode(QSGRootNode *node)
189 if (m_root_node == node)
192 m_root_node->m_renderers.removeOne(this);
193 nodeChanged(m_root_node, QSGNode::DirtyNodeRemoved);
197 Q_ASSERT(!m_root_node->m_renderers.contains(this));
198 m_root_node->m_renderers << this;
199 nodeChanged(m_root_node, QSGNode::DirtyNodeAdded);
204 void QSGRenderer::renderScene()
206 class B : public Bindable
209 void bind() const { QGLFramebufferObject::bindDefault(); }
214 void QSGRenderer::renderScene(const Bindable &bindable)
219 m_is_rendering = true;
220 #ifdef QSG_RENDERER_TIMING
224 m_bindable = &bindable;
228 #ifdef QSG_RENDERER_TIMING
229 int bindTime = frameTimer.elapsed();
233 // Sanity check that attribute registers are disabled
236 glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &count);
238 for (int i=0; i<count; ++i) {
239 glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled);
241 qWarning("QSGRenderer: attribute %d is enabled, this can lead to memory corruption and crashes.", i);
248 #ifdef QSG_RENDERER_TIMING
249 int renderTime = frameTimer.elapsed();
252 glDisable(GL_SCISSOR_TEST);
253 m_is_rendering = false;
254 m_changed_emitted = false;
257 #ifdef QSG_RENDERER_TIMING
258 printf(" - Breakdown of frametime: preprocess=%d, updates=%d, binding=%d, render=%d, total=%d\n",
260 updatePassTime - preprocessTime,
261 bindTime - updatePassTime,
262 renderTime - bindTime,
267 void QSGRenderer::setProjectionMatrixToDeviceRect()
269 setProjectionMatrixToRect(m_device_rect);
272 void QSGRenderer::setProjectionMatrixToRect(const QRectF &rect)
275 matrix.ortho(rect.x(),
276 rect.x() + rect.width(),
277 rect.y() + rect.height(),
281 setProjectionMatrix(matrix);
284 void QSGRenderer::setProjectionMatrix(const QMatrix4x4 &matrix)
286 m_projection_matrix = matrix;
287 // Mirrored relative to the usual Qt coordinate system with origin in the top left corner.
288 m_mirrored = matrix(0, 0) * matrix(1, 1) - matrix(0, 1) * matrix(1, 0) > 0;
291 void QSGRenderer::setClearColor(const QColor &color)
293 m_clear_color = color;
297 Updates internal data structures and emits the sceneGraphChanged() signal.
299 If \a flags contains the QSGNode::DirtyNodeRemoved flag, the node might be
300 in the process of being destroyed. It is then not safe to downcast the node
304 void QSGRenderer::nodeChanged(QSGNode *node, QSGNode::DirtyFlags flags)
309 if (flags & QSGNode::DirtyNodeAdded)
310 addNodesToPreprocess(node);
311 if (flags & QSGNode::DirtyNodeRemoved)
312 removeNodesToPreprocess(node);
314 if (!m_changed_emitted && !m_is_rendering) {
315 // Premature overoptimization to avoid excessive signal emissions
316 m_changed_emitted = true;
317 emit sceneGraphChanged();
321 void QSGRenderer::materialChanged(QSGGeometryNode *, QSGMaterial *, QSGMaterial *)
325 void QSGRenderer::preprocess()
327 Q_ASSERT(m_root_node);
329 // We need to take a copy here, in case any of the preprocess calls deletes a node that
330 // is in the preprocess list and thus, changes the m_nodes_to_preprocess behind our backs
331 // For the default case, when this does not happen, the cost is neglishible.
332 QSet<QSGNode *> items = m_nodes_to_preprocess;
334 for (QSet<QSGNode *>::const_iterator it = items.constBegin();
335 it != items.constEnd(); ++it) {
337 if (!nodeUpdater()->isNodeBlocked(n, m_root_node)) {
342 #ifdef QSG_RENDERER_TIMING
343 preprocessTime = frameTimer.elapsed();
346 nodeUpdater()->setToplevelOpacity(context()->renderAlpha());
347 nodeUpdater()->updateStates(m_root_node);
349 #ifdef QSG_RENDERER_TIMING
350 updatePassTime = frameTimer.elapsed();
355 void QSGRenderer::addNodesToPreprocess(QSGNode *node)
357 for (QSGNode *c = node->firstChild(); c; c = c->nextSibling())
358 addNodesToPreprocess(c);
359 if (node->flags() & QSGNode::UsePreprocess)
360 m_nodes_to_preprocess.insert(node);
363 void QSGRenderer::removeNodesToPreprocess(QSGNode *node)
365 for (QSGNode *c = node->firstChild(); c; c = c->nextSibling())
366 removeNodesToPreprocess(c);
367 if (node->flags() & QSGNode::UsePreprocess)
368 m_nodes_to_preprocess.remove(node);
373 Convenience function to set up the stencil buffer for clipping based on \a clip.
375 If the clip is a pixel aligned rectangle, this function will use glScissor instead
379 QSGRenderer::ClipType QSGRenderer::updateStencilClip(const QSGClipNode *clip)
382 glDisable(GL_STENCIL_TEST);
383 glDisable(GL_SCISSOR_TEST);
387 bool stencilEnabled = false;
388 bool scissorEnabled = false;
390 glDisable(GL_SCISSOR_TEST);
395 QMatrix4x4 m = m_current_projection_matrix;
397 m *= *clip->matrix();
399 // TODO: Check for multisampling and pixel grid alignment.
400 bool canUseScissor = clip->isRectangular()
401 && qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(0, 2))
402 && qFuzzyIsNull(m(1, 0)) && qFuzzyIsNull(m(1, 2));
405 QRectF bbox = clip->clipRect();
406 qreal invW = 1 / m(3, 3);
407 qreal fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
408 qreal fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
409 qreal fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
410 qreal fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
412 GLint ix1 = qRound((fx1 + 1) * m_device_rect.width() * qreal(0.5));
413 GLint iy1 = qRound((fy1 + 1) * m_device_rect.height() * qreal(0.5));
414 GLint ix2 = qRound((fx2 + 1) * m_device_rect.width() * qreal(0.5));
415 GLint iy2 = qRound((fy2 + 1) * m_device_rect.height() * qreal(0.5));
417 if (!scissorEnabled) {
418 clipRect = QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
419 glEnable(GL_SCISSOR_TEST);
420 scissorEnabled = true;
422 clipRect &= QRect(ix1, iy1, ix2 - ix1, iy2 - iy1);
425 clipRect = clipRect.normalized();
426 glScissor(clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());
428 if (!stencilEnabled) {
429 if (!m_clip_program.isLinked()) {
430 m_clip_program.addShaderFromSourceCode(QGLShader::Vertex,
431 "attribute highp vec4 vCoord; \n"
432 "uniform highp mat4 matrix; \n"
434 " gl_Position = matrix * vCoord; \n"
436 m_clip_program.addShaderFromSourceCode(QGLShader::Fragment,
438 " gl_FragColor = vec4(0.81, 0.83, 0.12, 1.0); \n" // Trolltech green ftw!
440 m_clip_program.bindAttributeLocation("vCoord", 0);
441 m_clip_program.link();
442 m_clip_matrix_id = m_clip_program.uniformLocation("matrix");
445 glStencilMask(0xff); // write mask
447 glClear(GL_STENCIL_BUFFER_BIT);
448 glEnable(GL_STENCIL_TEST);
449 glDisable(GL_DEPTH_TEST);
450 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
451 glDepthMask(GL_FALSE);
453 m_clip_program.bind();
454 m_clip_program.enableAttributeArray(0);
456 stencilEnabled = true;
459 glStencilFunc(GL_EQUAL, clipDepth, 0xff); // stencil test, ref, test mask
460 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); // stencil fail, z fail, z pass
462 const QSGGeometry *geometry = clip->geometry();
463 Q_ASSERT(geometry->attributeCount() > 0);
464 const QSGGeometry::Attribute *a = geometry->attributes();
466 glVertexAttribPointer(0, a->tupleSize, a->type, GL_FALSE, geometry->stride(), geometry->vertexData());
468 m_clip_program.setUniformValue(m_clip_matrix_id, m);
474 clip = clip->clipList();
477 if (stencilEnabled) {
478 m_clip_program.disableAttributeArray(0);
479 glEnable(GL_DEPTH_TEST);
480 glStencilFunc(GL_EQUAL, clipDepth, 0xff); // stencil test, ref, test mask
481 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // stencil fail, z fail, z pass
482 glStencilMask(0); // write mask
483 bindable()->reactivate();
484 //glDepthMask(GL_TRUE); // must be reset correctly by caller.
486 glDisable(GL_STENCIL_TEST);
490 glDisable(GL_SCISSOR_TEST);
492 return stencilEnabled ? StencilClip : ScissorClip;
497 Issues the GL draw call for \a geometryNode.
499 The function assumes that attributes have been bound and set up prior
505 void QSGRenderer::draw(const QSGBasicGeometryNode *node)
507 const QSGGeometry *g = node->geometry();
508 if (g->indexCount()) {
509 glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData());
511 glDrawArrays(g->drawingMode(), 0, g->vertexCount());
516 static inline int size_of_type(GLenum type)
518 static int sizes[] = {
520 sizeof(unsigned char),
522 sizeof(unsigned short),
524 sizeof(unsigned int),
531 return sizes[type - GL_BYTE];
535 Convenience function to set up and bind the vertex data in \a g to the
536 required attribute positions defined in \a material.
541 void QSGRenderer::bindGeometry(QSGMaterialShader *material, const QSGGeometry *g)
543 char const *const *attrNames = material->attributeNames();
545 for (int j = 0; attrNames[j]; ++j) {
548 Q_ASSERT_X(j < g->attributeCount(), "QSGRenderer::bindGeometry()", "Geometry lacks attribute required by material");
549 const QSGGeometry::Attribute &a = g->attributes()[j];
550 Q_ASSERT_X(j == a.position, "QSGRenderer::bindGeometry()", "Geometry does not have continuous attribute positions");
551 #if defined(QT_OPENGL_ES_2)
552 GLboolean normalize = a.type != GL_FLOAT;
554 GLboolean normalize = a.type != GL_FLOAT && a.type != GL_DOUBLE;
556 glVertexAttribPointer(a.position, a.tupleSize, a.type, normalize, g->stride(), (char *) g->vertexData() + offset);
557 offset += a.tupleSize * size_of_type(a.type);