aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dist/changes-5.2.020
-rw-r--r--src/quick/doc/images/visualcanvas_list.pngbin0 -> 13440 bytes
-rw-r--r--src/quick/doc/images/visualcanvas_overlap.pngbin0 -> 6358 bytes
-rw-r--r--src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc327
-rw-r--r--src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp2289
-rw-r--r--src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h499
-rw-r--r--src/quick/scenegraph/coreapi/qsgdefaultrenderer.cpp546
-rw-r--r--src/quick/scenegraph/coreapi/qsgdefaultrenderer_p.h91
-rw-r--r--src/quick/scenegraph/coreapi/qsgmaterial.cpp14
-rw-r--r--src/quick/scenegraph/coreapi/qsgmaterial.h6
-rw-r--r--src/quick/scenegraph/coreapi/qsgnode.cpp29
-rw-r--r--src/quick/scenegraph/coreapi/qsgnode.h17
-rw-r--r--src/quick/scenegraph/coreapi/qsgrendernode_p.h5
-rw-r--r--src/quick/scenegraph/coreapi/qsgshaderrewriter.cpp290
-rw-r--r--src/quick/scenegraph/qsgcontext.cpp39
-rw-r--r--src/quick/scenegraph/qsgthreadedrenderloop.cpp4
-rw-r--r--src/quick/scenegraph/scenegraph.pri7
-rw-r--r--src/quick/scenegraph/util/qsgatlastexture.cpp416
-rw-r--r--src/quick/scenegraph/util/qsgatlastexture_p.h156
-rw-r--r--tests/auto/quick/nodes/nodes.pro2
-rw-r--r--tests/auto/quick/nodes/tst_nodestest.cpp157
21 files changed, 4109 insertions, 805 deletions
diff --git a/dist/changes-5.2.0 b/dist/changes-5.2.0
index 6da704b80f..969d4e30e6 100644
--- a/dist/changes-5.2.0
+++ b/dist/changes-5.2.0
@@ -29,6 +29,19 @@ Third party components
* Important Behavior Changes *
****************************************************************************
+ - QSGMaterialShader::compile() will not be called on all instances anymore.
+ To enforce the old behavior, set the QSGMaterial::CustomCompilation flag.
+
+ - QSGMaterialShader::activate() and QSGMaterialShader::deactivate() are
+ no longer responsible for calling glEnableVertexAttribPointer(),
+ glDisableVertexAttribPointer() and binding the shader program. This is
+ now done by the renderer. Reimplementations of these functions which
+ are not calling the baseclass will need to take this into account.
+
+ - The scene graph now requires a call to QSGNode::markDirty() with the
+ flag QSGNode::DirtySubtreeBlocked whenever the state of
+ QSGNode::isSubtreeBlocked() is changed.
+
****************************************************************************
* Library *
****************************************************************************
@@ -39,6 +52,13 @@ QtQml
QtQuick
------
+- New scene graph renderer should reduce state changes, number of draw calls,
+ CPU->GPU bandwidth and generally improve performance.
+
+- Textures in the scene graph can now be entered into an atlas, facilitating
+ in better batching in the renderer. Atlas textures are enabled by passing
+ QQuickWindow::TextureCanUseAtlas to QQuickWindow::createTextureFromImage()
+
****************************************************************************
* Database Drivers *
****************************************************************************
diff --git a/src/quick/doc/images/visualcanvas_list.png b/src/quick/doc/images/visualcanvas_list.png
new file mode 100644
index 0000000000..37bf72572d
--- /dev/null
+++ b/src/quick/doc/images/visualcanvas_list.png
Binary files differ
diff --git a/src/quick/doc/images/visualcanvas_overlap.png b/src/quick/doc/images/visualcanvas_overlap.png
new file mode 100644
index 0000000000..acca669016
--- /dev/null
+++ b/src/quick/doc/images/visualcanvas_overlap.png
Binary files differ
diff --git a/src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc b/src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc
index 40d77c3d9b..4939868d1c 100644
--- a/src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc
+++ b/src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc
@@ -170,6 +170,9 @@ attach application code. This can be to add custom scene graph
content or render raw OpenGL content. The integration points are
defined by the render loop.
+For detailed description of how the scene graph renderer works, see
+\l {Qt Quick Scene Graph Renderer}.
+
\section2 Threaded Render Loop
@@ -278,7 +281,7 @@ content either under a Qt Quick scene or over it. The benefit of
integrating in this manner is that no extra framebuffer nor memory is
needed to perform the rendering. The downside is that Qt Quick decides
when to call the signals and this is the only time the OpenGL
-application is allowed to draw.
+application is allowed to draw.
The \l {Scene Graph - OpenGL Under QML} example gives an example on
how to use use these signals.
@@ -301,7 +304,7 @@ or stencil-buffer or similar. Doing so can result in unpredictable
behavior.
\warning The OpenGL rendering code must be thread aware, as the
-rendering might be happening outside the GUI thread.
+rendering might be happening outside the GUI thread.
\section2 Custom Items using QPainter
@@ -348,3 +351,323 @@ with multiple windows.
\endlist
*/
+
+/*!
+ \title Qt Quick Scene Graph Renderer
+ \page qtquick-visualcanvas-scenegraph-renderer.html
+
+ This document explains how the scene graph renderer works internally
+ so that one can write code that uses it in an optimal fashion, both
+ performance-wise and feature-wise.
+
+ One does not need to understand the internals of the renderer to get
+ good performance. However, it might help when integrating with the
+ scene graph or to figure out why it is not possible to squeeze the
+ maximum efficiency out of the graphics chip.
+
+ \note Even in the case where every frame is unique and everything is
+ uploaded from scratch, the default renderer will perform well.
+
+ The Qt Quick items in a QML scene populates a tree of QSGNode
+ instances. Once created, this tree is a complete description of how
+ a certain frame should be rendered. It does not contain any
+ references back to the Qt Quick items at all and will on most
+ platforms be processed and rendered in a separate thread. The
+ renderer is a self contained part of the scene graph which traverses
+ the QSGNode tree and uses geometry defined in QSGGeometryNode and
+ shader state defined in QSGMaterial to schedule OpenGL state change
+ and draw calls.
+
+ If needed, the renderer can be completely replaced using the
+ internal scene graph back-end API. This is mostly interesting for
+ platform vendors who wish to take advantage of non-standard hardware
+ features. For majority of use cases, the default renderer will be
+ sufficient.
+
+ The default renderer focuses on two primary strategies to optimize
+ the rendering. Batching of draw calls and retention of geometry on
+ the GPU.
+
+ \section1 Batching
+
+ Where a traditional 2D API, such as QPainter, Cairo or Context2D, is
+ written to handle thousands of individual draw calls per frame,
+ OpenGL is a pure hardware API and performs best when the number of
+ draw calls is very low and state changes are kept to a
+ minimum. Consider the following use case:
+
+ \image visualcanvas_list.png
+
+ The simplest way of drawing this list is on a cell-by-cell basis. First
+ the background is drawn. This is a rectangle of a specific color. In
+ OpenGL terms this means selecting a shader program to do solid color
+ fills, setting up the fill color, setting the transformation matrix
+ containing the x and y offsets and then using for instance
+ \c glDrawArrays to draw two triangles making up the rectangle. The icon
+ is drawn next. In OpenGL terms this means selecting a shader program
+ to draw textures, selecting the active texture to use, setting the
+ transformation matrix, enabling alpha-blending and then using for
+ instance \c glDrawArrays to draw the two triangles making up the
+ bounding rectangle of the icon. The text and separator line between
+ cells follow a similar pattern. And this process is repeated for
+ every cell in the list, so for a longer list, the overhead imposed
+ by OpenGL state changes and draw calls completely outweighs the
+ benefit that using a hardware accelerated API could provide.
+
+ When each primitive is large, this overhead is negligible, but in
+ the case of a typical UI, there are many small items which add up to
+ a considerable overhead.
+
+ The default scene graph renderer works within these
+ limitations and will try to merge individual primitives together
+ into batches while preserving the exact same visual result. The
+ result is fewer OpenGL state changes and a minimal amount of draw
+ calls, resulting in optimal performance.
+
+ \section2 Opaque Primitives
+
+ The renderer separates between opaque primitives and primitives
+ which require alpha blending. By using OpenGL's Z-buffer and giving
+ each primitive a unique z position, the renderer can freely reorder
+ opaque primitives without any regard for their location on screen
+ and which other elements they overlap with. By looking at each
+ primitive's material state, the renderer will create opaque
+ batches. From Qt Quick core item set, this includes Rectangle items
+ with opaque colors and fully opaque images, such as JPEGs or BMPs.
+
+ Another benefit of using opaque primitives, is that opaque
+ primitives does not require \c GL_BLEND to be enabled which can be
+ quite costly, especially on mobile and embedded GPUs.
+
+ Opaque primitives are rendered in a front-to-back manner with
+ \c glDepthMask and \c GL_DEPTH_TEST enabled. On GPUs that internally do
+ early-z checks, this means that the fragment shader does not need to
+ run for pixels or blocks of pixels that are obscured. Beware that
+ the renderer still needs to take these nodes into account and the
+ vertex shader is still run for every vertex in these primitives, so
+ if the application knows that something is fully obscured, the best
+ thing to do is to explicitly hide it using Item::visible or
+ Item::opacity.
+
+ \note The Item::z is used to control an Item's stacking order
+ relative to its siblings. It has no direct relation to the renderer and
+ OpenGL's Z-buffer.
+
+ \section2 Alpha Blended Primitives
+
+ Once opaque primitives have been drawn, the renderer will disable
+ \c glDepthMask, enable \c GL_BLEND and render all alpha blended primitives
+ in a back-to-front manner.
+
+ Batching of alpha blended primitives requires a bit more effort in
+ the renderer as elements that are overlapping need to be rendered in
+ the correct order for alpha blending to look correct. Relying on the
+ Z-buffer alone is not enough. The renderer does a pass over all
+ alpha blended primitives and will look at their bounding rect in
+ addition to their material state to figure out which elements can be
+ batched and which can not.
+
+ \image visualcanvas_overlap.png
+
+ In the left-most case, the blue backgrounds can be drawn in one call
+ and the two text elements in another call, as the texts only overlap
+ a background which they are stacked in front of. In the right-most
+ case, the background of "Item 4" overlaps the text of "Item 3" so in
+ this case, each of backgrounds and texts need to be drawn using
+ separate calls.
+
+ Z-wise, the alpha primitives are interleaved with the opaque nodes
+ and may trigger early-z when available, but again, setting
+ Item::visible to false is always faster.
+
+ \section2 Mixing with 3D primitives
+
+ The scene graph can support pseudo 3D and proper 3D primitives. For
+ instance, one can implement a "page curl" effect using a
+ ShaderEffect or implement a bumpmapped torus using QSGGeometry and a
+ custom material. While doing so, one needs to take into account that
+ the default renderer already makes use of the depth buffer.
+
+ The renderer modifies the vertex shader returned from
+ QSGMaterialShader::vertexShader() and compresses the z values of the
+ vertex after the model-view and projection matrices has been applied
+ and then adds a small translation on the z to position it the
+ correct z position.
+
+ The compression assumes that the z values are in the range of 0 to
+ 1.
+
+ \section2 Texture Atlas
+
+ The active texture is a unique OpenGL state, which means that
+ multiple primitives using different OpenGL textures cannot be
+ batched. The Qt Quick scene graph for this reason allows multiple
+ QSGTexture instances to be allocated as smaller sub-regions of a
+ larger texture; a texture atlas.
+
+ The biggest benefit of texture atlases is that multiple QSGTexture
+ instances now refer to the same OpenGL texture instance. This makes
+ it possible to batch textured draw calls as well, such as Image
+ items, BorderImage items, ShaderEffect items and also C++ types such
+ as QSGSimpleTextureNode and custom QSGGeometryNodes using textures.
+
+ \note Large textures do not go into the texture atlas.
+
+ Atlas based textures are created by passing
+ QQuickWindow::TextureCanUseAtlas to the
+ QQuickWindow::createTextureFromImage().
+
+ \note Atlas based textures do not have texture coordinates ranging
+ from 0 to 1. Use QSGTexture::normalizedTextureSubRect() to get the
+ atlas texture coordinates.
+
+ The scene graph uses heuristics to figure out how large the atlas
+ should be and what the size threshold for being entered into the
+ atlas is. If different values are needed, it is possible to override
+ them using the environment variables \c {QSG_ATLAS_WIDTH=[width]},
+ \c {QSG_ATLAS_HEIGHT=[height]} and \c
+ {QSG_ATLAS_SIZE_LIMIT=[size]}. Changing these values will mostly be
+ interesting for platform vendors.
+
+ \section1 Batch Roots
+
+ In addition to mergin compatible primitives into batches, the
+ default renderer also tries to minimize the amount of data that
+ needs to be sent to the GPU for every frame. The default renderer
+ identifies subtrees which belong together and tries to put these
+ into separate batches. Once batches are identified, they are merged,
+ uploaded and stored in GPU memory, using Vertex Buffer Objects.
+
+ \section2 Transform Nodes
+
+ Each Qt Quick Item inserts a QSGTransformNode into the scene graph
+ tree to manage its x, y, scale or rotation. Child items will be
+ populated under this transform node. The default renderer tracks
+ the state of transform nodes between frames, and will look at
+ subtrees to decide if a transform node is a good candidate to become
+ a root for a set of batches. A transform node which changes between
+ frames and which has a fairly complex subtree, can become a batch
+ root.
+
+ QSGGeometryNodes in the subtree of a batch root are pre-transformed
+ relative to the root on the CPU. They are then uploaded and retained
+ on the GPU. When the transform changes, the renderer only needs to
+ update the matrix of the root, not each individual item, making list
+ and grid scrolling very fast. For successive frames, as long as
+ nodes are not being added or removed, rendering the list is
+ effectively for free. When new content enters the subtree, the batch
+ that gets it is rebuilt, but this is still relatively fast. There are
+ usually several unchanging frames for every frame with added or
+ removed nodes when panning through a grid or list.
+
+ Another benefit of identifying transform nodes as batch roots is
+ that it allows the renderer to retain the parts of the tree that has
+ not changed. For instance, say a UI consists of a list and a button
+ row. When the list is being scrolled and delegates are being added
+ and removed, the rest of the UI, the button row, is unchanged and
+ can be drawn using the geometry already stored on the GPU.
+
+ The node and vertex threshold for a transform node to become a batch
+ root can be overridden using the environment variables \c
+ {QSG_RENDERER_BATCH_NODE_THRESHOLD=[count]} and \c
+ {QSG_RENDERER_BATCH_VERTEX_THRESHOLD=[count]}. Overriding these flags
+ will be mostly useful for platform vendors.
+
+ \note Beneath a batch root, one batch is created for each unique
+ set of material state and geometry type.
+
+ \section2 Clipping
+
+ When setting Item::clip to true, it will create a QSGClipNode with a
+ rectangle in its geometry. The default renderer will apply this clip
+ by using scissoring in OpenGL. If the item is rotated by a
+ non-90-degree angle, the OpenGL's stencil buffer is used. Qt Quick
+ Item only supports setting a rectangle as clip through QML, but the
+ scene graph API and the default renderer can use any shape for
+ clipping.
+
+ When applying a clip to a subtree, that subtree needs to be rendered
+ with a unique OpenGL state. This means that when Item::clip is true,
+ batching of that item is limited to its children. When there are
+ many children, like a ListView or GridView, or complex children,
+ like a TextArea, this is fine. One should, however, use clip on
+ smaller items with caution as it prevents batching. This includes
+ button label, text field or list delegate and table cells.
+
+ \section2 Vertex Buffers
+
+ Each batch uses a vertex buffer object (VBO) to store its data on
+ the GPU. This vertex buffer is retained between frames and updated
+ when the part of the scene graph that it represents changes.
+
+ By default, the renderer will upload data into the VBO using
+ \c GL_STATIC_DRAW. It is possible to select different upload strategy
+ by setting the environment variable \c
+ {QSG_RENDERER_BUFFER_STRATEGY=[strategy]}. Valid values are \c
+ stream and \c dynamic. Changing this value is mostly useful for
+ platform vendors.
+
+ \section1 Performance
+
+ As stated in the beginning, understanding the finer details of the
+ renderer is not required to get good performance. It is written to
+ optimize for common use cases and will perform quite well under
+ almost any circumstance.
+
+ \list
+
+ \li Good performance comes from effective batching, with as little
+ as possible of the geometry being uploaded again and again. By
+ setting the environment variable \c {QSG_RENDERER_DEBUG=render}, the
+ renderer will output statistics on how well the batching goes, how
+ many batches, which batches are retained and which are opaque and
+ not. When striving for optimal performance, uploads should happen
+ only when really needed, batches should be fewer than 10 and at
+ least 3-4 of them should be opaque.
+
+ \li The default renderer does not do any CPU-side viewport clipping
+ nor occlusion detection. If something is not supposed to be visible,
+ it should not be shown. Use \c {Item::visible: false} for items that
+ should not be drawn. The primary reason for not adding such logic is
+ that it adds additional cost which would also hurt applications that
+ took care in behaving well.
+
+ \li Make sure the texture atlas is used. The Image and BorderImage
+ items will use it unless the image is too large. For textures
+ created in C++, pass QQuickWindow::TextureCanUseAtlas when
+ calling QQuickWindow::createTexture().
+ By setting the environment variable \c {QSG_ATLAS_OVERLAY} all atlas
+ textures will be colorized so they are easily identifiable in the
+ application.
+
+ \li Use opaque primitives where possible. Opaque primitives are
+ faster to process in the renderer and faster to draw on the GPU. For
+ instance, PNG files will often have an alpha channel, even though
+ each pixel is fully opaque. JPG files are always opaque. When
+ providing images to an QQuickImageProvider or creating images with
+ QQuickWindow::createTextureFromImage(), let the image have
+ QImage::Format_RGB32, when possible.
+
+ \li Be aware of that overlapping compond items, like in the
+ illustration above, can not be batched.
+
+ \li Clipping breaks batching. Never use on a per-item basis, inside
+ tables cells, item delegates or similar. Instead of clipping text,
+ use eliding. Instead of clipping an image, create a
+ QQuickImageProvider that returns a cropped image.
+
+ \li Batching only works for 16-bit indices. All built-in items use
+ 16-bit indices, but custom geometry is free to also use 32-bit
+ indices.
+
+ \li Some material flags prevent batching, the most limiting one
+ being QSGMaterial::RequiresFullMatrix which prevents all batching.
+
+ \endlist
+
+ If an application performs poorly, make sure that rendering is
+ actually the bottleneck. Use a profiler! The environment variable \c
+ {QSG_RENDER_TIMING=1} will output a number of useful timing
+ parameters which can be useful in pinpointing where a problem lies.
+
+ */
diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
new file mode 100644
index 0000000000..397d3b0a17
--- /dev/null
+++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
@@ -0,0 +1,2289 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsgbatchrenderer_p.h"
+
+#include <QtCore/QElapsedTimer>
+
+#include <QtGui/QGuiApplication>
+#include <QtGui/QOpenGLFramebufferObject>
+
+QT_BEGIN_NAMESPACE
+
+extern QByteArray qsgShaderRewriter_insertZAttributes(const char *input);
+
+namespace QSGBatchRenderer
+{
+
+const bool debug_render = qgetenv("QSG_RENDERER_DEBUG").contains("render");
+const bool debug_build = qgetenv("QSG_RENDERER_DEBUG").contains("build");
+const bool debug_change = qgetenv("QSG_RENDERER_DEBUG").contains("change");
+const bool debug_upload = qgetenv("QSG_RENDERER_DEBUG").contains("upload");
+const bool debug_roots = qgetenv("QSG_RENDERER_DEBUG").contains("roots");
+const bool debug_dump = qgetenv("QSG_RENDERER_DEBUG").contains("dump");
+const bool debug_noalpha = qgetenv("QSG_RENDERER_DEBUG").contains("noalpha");
+const bool debug_noopaque = qgetenv("QSG_RENDERER_DEBUG").contains("noopaque");
+const bool debug_noclip = qgetenv("QSG_RENDERER_DEBUG").contains("noclip");
+
+
+#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling())
+#define SHADOWNODE_TRAVERSE(NODE) for (QList<Node *>::const_iterator child = NODE->children.constBegin(); child != NODE->children.constEnd(); ++child)
+
+static inline int size_of_type(GLenum type)
+{
+ static int sizes[] = {
+ sizeof(char),
+ sizeof(unsigned char),
+ sizeof(short),
+ sizeof(unsigned short),
+ sizeof(int),
+ sizeof(unsigned int),
+ sizeof(float),
+ 2,
+ 3,
+ 4,
+ sizeof(double)
+ };
+ Q_ASSERT(type >= GL_BYTE && type <= 0x140A); // the value of GL_DOUBLE
+ return sizes[type - GL_BYTE];
+}
+
+bool qsg_sort_element_increasing_order(Element *a, Element *b) { return a->order < b->order; }
+bool qsg_sort_element_decreasing_order(Element *a, Element *b) { return a->order > b->order; }
+bool qsg_sort_batch_is_valid(Batch *a, Batch *b) { return a->first && !b->first; }
+bool qsg_sort_batch_increasing_order(Batch *a, Batch *b) { return a->first->order < b->first->order; }
+bool qsg_sort_batch_decreasing_order(Batch *a, Batch *b) { return a->first->order > b->first->order; }
+
+QSGMaterial::Flag QSGMaterial_RequiresFullMatrixBit = (QSGMaterial::Flag) (QSGMaterial::RequiresFullMatrix & ~QSGMaterial::RequiresFullMatrixExceptTranslate);
+
+struct QMatrix4x4_Accessor
+{
+ float m[4][4];
+ int flagBits;
+
+ static bool isTranslate(const QMatrix4x4 &m) { return ((const QMatrix4x4_Accessor &) m).flagBits <= 0x1; }
+ static bool is2DSafe(const QMatrix4x4 &m) { return ((const QMatrix4x4_Accessor &) m).flagBits < 0x8; }
+};
+
+const float OPAQUE_LIMIT = 0.999f;
+
+ShaderManager::Shader *ShaderManager::prepareMaterial(QSGMaterial *material)
+{
+ QSGMaterialType *type = material->type();
+ Shader *shader = rewrittenShaders.value(type, 0);
+ if (shader)
+ return shader;
+
+ QSGMaterialShader *s = material->createShader();
+
+ QOpenGLShaderProgram *p = s->program();
+ p->addShaderFromSourceCode(QOpenGLShader::Vertex,
+ qsgShaderRewriter_insertZAttributes(s->vertexShader()));
+ p->addShaderFromSourceCode(QOpenGLShader::Fragment,
+ s->fragmentShader());
+
+ char const *const *attr = s->attributeNames();
+ int i;
+ for (i = 0; attr[i]; ++i) {
+ if (*attr[i])
+ p->bindAttributeLocation(attr[i], i);
+ }
+ p->bindAttributeLocation("_qt_order", i);
+
+ p->link();
+ if (!p->isLinked()) {
+ qDebug() << "Renderer failed shader compilation:" << endl << p->log();
+ return 0;
+ }
+
+ s->initialize();
+
+ shader = new Shader;
+ shader->program = s;
+ shader->pos_order = i;
+ shader->id_zRange = p->uniformLocation("_qt_zRange");
+ shader->lastOpacity = 0;
+
+ Q_ASSERT(shader->pos_order >= 0);
+ Q_ASSERT(shader->id_zRange >= 0);
+
+ rewrittenShaders[type] = shader;
+ return shader;
+}
+
+ShaderManager::Shader *ShaderManager::prepareMaterialNoRewrite(QSGMaterial *material)
+{
+ QSGMaterialType *type = material->type();
+ Shader *shader = stockShaders.value(type, 0);
+ if (shader)
+ return shader;
+
+ QSGMaterialShader *s = static_cast<QSGMaterialShader *>(material->createShader());
+ s->compile();
+ s->initialize();
+
+ shader = new Shader();
+ shader->program = s;
+ shader->id_zRange = -1;
+ shader->pos_order = -1;
+ shader->lastOpacity = 0;
+
+ stockShaders[type] = shader;
+
+ return shader;
+}
+
+void ShaderManager::invalidated()
+{
+ qDeleteAll(stockShaders.values());
+ stockShaders.clear();
+ qDeleteAll(rewrittenShaders.values());
+ rewrittenShaders.clear();
+ delete blitProgram;
+ blitProgram = 0;
+}
+
+void qsg_dumpShadowRoots(BatchRootInfo *i, int indent)
+{
+ static int extraIndent = 0;
+ ++extraIndent;
+
+ QByteArray ind(indent + extraIndent + 10, ' ');
+
+ if (!i) {
+ qDebug() << ind.constData() << "- no info";
+ } else {
+ qDebug() << ind.constData() << "- parent:" << i->parentRoot;
+ for (QSet<Node *>::const_iterator it = i->subRoots.constBegin();
+ it != i->subRoots.constEnd(); ++it) {
+ qDebug() << ind.constData() << "-" << *it;
+ qsg_dumpShadowRoots((*it)->rootInfo(), indent);
+ }
+ }
+
+ --extraIndent;
+}
+
+void qsg_dumpShadowRoots(Node *n)
+{
+ static int indent = 0;
+ ++indent;
+
+ QByteArray ind(indent, ' ');
+
+ if (n->type() == QSGNode::ClipNodeType || n->isBatchRoot) {
+ qDebug() << ind.constData() << "[X]" << n->sgNode << hex << uint(n->sgNode->flags());
+ qsg_dumpShadowRoots(n->rootInfo(), indent);
+ } else {
+ qDebug() << ind.constData() << "[ ]" << n->sgNode << hex << uint(n->sgNode->flags());
+ }
+
+ SHADOWNODE_TRAVERSE(n)
+ qsg_dumpShadowRoots(*child);
+
+ --indent;
+}
+
+Updater::Updater(Renderer *r)
+ : renderer(r)
+ , m_roots(32)
+ , m_rootMatrices(8)
+{
+ m_roots.add(0);
+ m_combined_matrix_stack.add(&m_identityMatrix);
+ m_rootMatrices.add(m_identityMatrix);
+
+ Q_ASSERT(sizeof(QMatrix4x4_Accessor) == sizeof(QMatrix4x4));
+}
+
+void Updater::updateStates(QSGNode *n)
+{
+ m_toplevel_alpha = 1;
+ m_current_clip = 0;
+
+ m_added = 0;
+ m_transformChange = 0;
+
+ Node *sn = renderer->m_nodes.value(n, 0);
+ Q_ASSERT(sn);
+
+ if (Q_UNLIKELY(debug_roots))
+ qsg_dumpShadowRoots(sn);
+
+ if (Q_UNLIKELY(debug_build)) {
+ qDebug() << "Updater::updateStates()";
+ if (sn->dirtyState & (QSGNode::DirtyNodeAdded << 16))
+ qDebug() << " - nodes have been added";
+ if (sn->dirtyState & (QSGNode::DirtyMatrix << 16))
+ qDebug() << " - transforms have changed";
+ if (sn->dirtyState & (QSGNode::DirtyOpacity << 16))
+ qDebug() << " - opacity has changed";
+ }
+
+ visitNode(sn);
+}
+
+void Updater::visitNode(Node *n)
+{
+ if (m_added == 0 && n->dirtyState == 0 && m_force_update == 0 && m_transformChange == 0)
+ return;
+
+ int count = m_added;
+ if (n->dirtyState & QSGNode::DirtyNodeAdded)
+ ++m_added;
+
+ switch (n->type()) {
+ case QSGNode::OpacityNodeType:
+ visitOpacityNode(n);
+ break;
+ case QSGNode::TransformNodeType:
+ visitTransformNode(n);
+ break;
+ case QSGNode::GeometryNodeType:
+ visitGeometryNode(n);
+ break;
+ case QSGNode::ClipNodeType:
+ visitClipNode(n);
+ break;
+ case QSGNode::RenderNodeType:
+ if (m_added)
+ n->renderNodeElement()->root = m_roots.last();
+ // Fall through to visit children.
+ default:
+ SHADOWNODE_TRAVERSE(n) visitNode(*child);
+ break;
+ }
+
+ m_added = count;
+ n->dirtyState = 0;
+}
+
+void Updater::visitClipNode(Node *n)
+{
+ ClipBatchRootInfo *extra = n->clipInfo();
+
+ QSGClipNode *cn = static_cast<QSGClipNode *>(n->sgNode);
+
+ if (m_roots.last() && m_added > 0)
+ renderer->registerBatchRoot(n, m_roots.last());
+
+ cn->m_clip_list = m_current_clip;
+ m_current_clip = cn;
+ m_roots << n;
+ m_rootMatrices.add(m_rootMatrices.last() * *m_combined_matrix_stack.last());
+ extra->matrix = m_rootMatrices.last();
+ cn->m_matrix = &extra->matrix;
+ m_combined_matrix_stack << &m_identityMatrix;
+
+ SHADOWNODE_TRAVERSE(n) visitNode(*child);
+
+ m_current_clip = cn->m_clip_list;
+ m_rootMatrices.pop_back();
+ m_combined_matrix_stack.pop_back();
+ m_roots.pop_back();
+}
+
+void Updater::visitOpacityNode(Node *n)
+{
+ QSGOpacityNode *on = static_cast<QSGOpacityNode *>(n->sgNode);
+
+ qreal combined = m_opacity_stack.last() * on->opacity();
+ on->setCombinedOpacity(combined);
+ m_opacity_stack.add(combined);
+
+ if (m_added == 0 && n->dirtyState & QSGNode::DirtyOpacity) {
+ bool was = n->isOpaque;
+ bool is = on->opacity() > OPAQUE_LIMIT;
+ if (was != is) {
+ renderer->m_rebuild = Renderer::FullRebuild;
+ n->isOpaque = is;
+ }
+ ++m_force_update;
+ SHADOWNODE_TRAVERSE(n) visitNode(*child);
+ --m_force_update;
+ } else {
+ if (m_added > 0)
+ n->isOpaque = on->opacity() > OPAQUE_LIMIT;
+ SHADOWNODE_TRAVERSE(n) visitNode(*child);
+ }
+
+ m_opacity_stack.pop_back();
+}
+
+void Updater::visitTransformNode(Node *n)
+{
+ bool popMatrixStack = false;
+ bool popRootStack = false;
+ bool dirty = n->dirtyState & QSGNode::DirtyMatrix;
+
+ QSGTransformNode *tn = static_cast<QSGTransformNode *>(n->sgNode);
+
+ if (n->isBatchRoot) {
+ if (m_added > 0 && m_roots.size() > 0)
+ renderer->registerBatchRoot(n, m_roots.last());
+ tn->setCombinedMatrix(m_rootMatrices.last() * *m_combined_matrix_stack.last() * tn->matrix());
+
+ // The only change in this subtree is ourselves and we are a batch root, so
+ // only update subroots and return, saving tons of child-processing (flickable-panning)
+
+ if (!n->becameBatchRoot && m_added == 0 && m_force_update == 0 && dirty && (n->dirtyState & ~QSGNode::DirtyMatrix) == 0) {
+ BatchRootInfo *info = renderer->batchRootInfo(n);
+ for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
+ it != info->subRoots.constEnd(); ++it) {
+ updateRootTransforms(*it, n, tn->combinedMatrix());
+ }
+ return;
+ }
+
+ n->becameBatchRoot = false;
+
+ m_combined_matrix_stack.add(&m_identityMatrix);
+ m_roots.add(n);
+ m_rootMatrices.add(tn->combinedMatrix());
+
+ popMatrixStack = true;
+ popRootStack = true;
+ } else if (!tn->matrix().isIdentity()) {
+ tn->setCombinedMatrix(*m_combined_matrix_stack.last() * tn->matrix());
+ m_combined_matrix_stack.add(&tn->combinedMatrix());
+ popMatrixStack = true;
+ } else {
+ tn->setCombinedMatrix(*m_combined_matrix_stack.last());
+ }
+
+ if (dirty)
+ ++m_transformChange;
+
+ SHADOWNODE_TRAVERSE(n) visitNode(*child);
+
+ if (dirty)
+ --m_transformChange;
+ if (popMatrixStack)
+ m_combined_matrix_stack.pop_back();
+ if (popRootStack) {
+ m_roots.pop_back();
+ m_rootMatrices.pop_back();
+ }
+}
+
+void Updater::visitGeometryNode(Node *n)
+{
+ QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode);
+
+ gn->m_matrix = m_combined_matrix_stack.last();
+ gn->m_clip_list = m_current_clip;
+ gn->setInheritedOpacity(m_opacity_stack.last());
+
+ if (m_added) {
+ Element *e = n->element();
+ e->root = m_roots.last();
+ e->translateOnlyToRoot = QMatrix4x4_Accessor::isTranslate(*gn->matrix());
+
+ if (e->root) {
+ BatchRootInfo *info = renderer->batchRootInfo(e->root);
+ info->availableOrders--;
+ if (info->availableOrders < 0) {
+ renderer->m_rebuild |= Renderer::BuildRenderLists;
+ } else {
+ renderer->m_rebuild |= Renderer::BuildRenderListsForTaggedRoots;
+ renderer->m_taggedRoots << e->root;
+ }
+ } else {
+ renderer->m_rebuild |= Renderer::FullRebuild;
+ }
+ } else if (m_transformChange) {
+ Element *e = n->element();
+ e->translateOnlyToRoot = QMatrix4x4_Accessor::isTranslate(*gn->matrix());
+ }
+
+ SHADOWNODE_TRAVERSE(n) visitNode(*child);
+}
+
+void Updater::updateRootTransforms(Node *node, Node *root, const QMatrix4x4 &combined)
+{
+ BatchRootInfo *info = renderer->batchRootInfo(node);
+ QMatrix4x4 m;
+ Node *n = node;
+
+ while (n != root) {
+ if (n->type() == QSGNode::TransformNodeType)
+ m = static_cast<QSGTransformNode *>(n->sgNode)->matrix() * m;
+ n = n->parent;
+ }
+
+ m = combined * m;
+
+ if (node->type() == QSGNode::ClipNodeType) {
+ static_cast<ClipBatchRootInfo *>(info)->matrix = m;
+ } else {
+ Q_ASSERT(node->type() == QSGNode::TransformNodeType);
+ static_cast<QSGTransformNode *>(node->sgNode)->setCombinedMatrix(m);
+ }
+
+ for (QSet<Node *>::const_iterator it = info->subRoots.constBegin();
+ it != info->subRoots.constEnd(); ++it) {
+ updateRootTransforms(*it, node, m);
+ }
+}
+
+int qsg_positionAttribute(QSGGeometry *g) {
+ int vaOffset = 0;
+ for (int a=0; a<g->attributeCount(); ++a) {
+ const QSGGeometry::Attribute &attr = g->attributes()[a];
+ if (attr.isVertexCoordinate && attr.tupleSize == 2 && attr.type == GL_FLOAT) {
+ return vaOffset;
+ }
+ vaOffset += attr.tupleSize * size_of_type(attr.type);
+ }
+ return -1;
+}
+
+void Element::computeBounds()
+{
+ Q_ASSERT(!boundsComputed);
+ boundsComputed = true;
+
+ QSGGeometry *g = node->geometry();
+ int offset = qsg_positionAttribute(g);
+ if (offset == -1) {
+ // No position attribute means overlaps with everything..
+ bounds.set(-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX);
+ return;
+ }
+
+ bounds.set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
+ char *vd = (char *) g->vertexData() + offset;
+ for (int i=0; i<g->vertexCount(); ++i) {
+ bounds |= *(Pt *) vd;
+ vd += g->sizeOfVertex();
+ }
+ bounds.map(*node->matrix());
+}
+
+RenderNodeElement::~RenderNodeElement()
+{
+ delete fbo;
+}
+
+bool Batch::isMaterialCompatible(Element *e) const
+{
+ // If material has changed between opaque and translucent, it is not compatible
+ QSGMaterial *m = e->node->activeMaterial();
+ if (isOpaque != ((m->flags() & QSGMaterial::Blending) == 0))
+ return false;
+
+ Element *n = first;
+ // Skip to the first node other than e which has not been removed
+ while (n && (n == e || n->removed))
+ n = n->nextInBatch;
+
+ // Only 'e' in this batch, so a material change doesn't change anything as long as
+ // its blending is still in sync with this batch...
+ if (!n)
+ return true;
+
+ QSGMaterial *nm = n->node->activeMaterial();
+ return nm->type() == m->type() && nm->compare(m) == 0;
+}
+
+/*
+ * Marks this batch as dirty or in the case where the geometry node has
+ * changed to be incompatible with this batch, return false so that
+ * the caller can mark the entire sg for a full rebuild...
+ */
+bool Batch::geometryWasChanged(QSGGeometryNode *gn)
+{
+ Element *e = first;
+ Q_ASSERT_X(e, "Batch::geometryWasChanged", "Batch is expected to 'valid' at this time");
+ // 'gn' is the first node in the batch, compare against the next one.
+ while (e && (e->node == gn || e->removed))
+ e = e->nextInBatch;
+ if (!e || e->node->geometry()->attributes() == gn->geometry()->attributes()) {
+ needsUpload = true;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void Batch::cleanupRemovedElements()
+{
+ // remove from front of batch..
+ while (first && first->removed) {
+ first = first->nextInBatch;
+ }
+
+ // Then continue and remove other nodes further out in the batch..
+ if (first) {
+ Element *e = first;
+ while (e->nextInBatch) {
+ if (e->nextInBatch->removed)
+ e->nextInBatch = e->nextInBatch->nextInBatch;
+ else
+ e = e->nextInBatch;
+
+ }
+ }
+}
+
+/*
+ * Iterates through all geometry nodes in this batch and unsets their batch,
+ * thus forcing them to be rebuilt
+ */
+void Batch::invalidate()
+{
+ // If doing removal here is a performance issue, we might add a "hasRemoved" bit to
+ // the batch to do an early out..
+ cleanupRemovedElements();
+ Element *e = first;
+ first = 0;
+ root = 0;
+ while (e) {
+ e->batch = 0;
+ Element *n = e->nextInBatch;
+ e->nextInBatch = 0;
+ e = n;
+ }
+}
+
+bool Batch::isTranslateOnlyToRoot() const {
+ bool only = true;
+ Element *e = first;
+ while (e && only) {
+ only &= e->translateOnlyToRoot;
+ e = e->nextInBatch;
+ }
+ return only;
+}
+
+static int qsg_countNodesInBatch(const Batch *batch)
+{
+ int sum = 0;
+ Element *e = batch->first;
+ while (e) {
+ ++sum;
+ e = e->nextInBatch;
+ }
+ return sum;
+}
+
+static int qsg_countNodesInBatches(const QDataBuffer<Batch *> &batches)
+{
+ int sum = 0;
+ for (int i=0; i<batches.size(); ++i) {
+ sum += qsg_countNodesInBatch(batches.at(i));
+ }
+ return sum;
+}
+
+Renderer::Renderer(QSGContext *ctx)
+ : QSGRenderer(ctx)
+ , m_opaqueRenderList(64)
+ , m_alphaRenderList(64)
+ , m_nextRenderOrder(0)
+ , m_explicitOrdering(false)
+ , m_opaqueBatches(16)
+ , m_alphaBatches(16)
+ , m_batchPool(16)
+ , m_elementsToDelete(64)
+ , m_tmpAlphaElements(16)
+ , m_tmpOpaqueElements(16)
+ , m_rebuild(FullRebuild)
+ , m_zRange(0)
+ , m_currentMaterial(0)
+ , m_currentShader(0)
+{
+ setNodeUpdater(new Updater(this));
+
+ m_shaderManager = ctx->findChild<ShaderManager *>(QStringLiteral("__qt_ShaderManager"), Qt::FindDirectChildrenOnly);
+ if (!m_shaderManager) {
+ m_shaderManager = new ShaderManager();
+ m_shaderManager->setObjectName(QStringLiteral("__qt_ShaderManager"));
+ m_shaderManager->setParent(ctx);
+ QObject::connect(ctx, SIGNAL(invalidated()), m_shaderManager, SLOT(invalidated()), Qt::DirectConnection);
+ }
+
+ m_bufferStrategy = GL_STATIC_DRAW;
+ QByteArray strategy = qgetenv("QSG_RENDERER_BUFFER_STRATEGY");
+ if (strategy == "dynamic") {
+ m_bufferStrategy = GL_DYNAMIC_DRAW;
+ } else if (strategy == "stream") {
+ m_bufferStrategy = GL_STREAM_DRAW;
+ }
+
+ m_batchNodeThreshold = 64;
+ QByteArray alternateThreshold = qgetenv("QSG_RENDERER_BATCH_NODE_THRESHOLD");
+ if (alternateThreshold.length() > 0) {
+ bool ok = false;
+ int threshold = alternateThreshold.toInt(&ok);
+ if (ok)
+ m_batchNodeThreshold = threshold;
+ }
+
+ m_batchVertexThreshold = 1024;
+ alternateThreshold = qgetenv("QSG_RENDERER_BATCH_VERTEX_THRESHOLD");
+ if (alternateThreshold.length() > 0) {
+ bool ok = false;
+ int threshold = alternateThreshold.toInt(&ok);
+ if (ok)
+ m_batchVertexThreshold = threshold;
+ }
+ if (Q_UNLIKELY(debug_build || debug_render)) {
+ qDebug() << "Batch thresholds: nodes:" << m_batchNodeThreshold << " vertices:" << m_batchVertexThreshold;
+ qDebug() << "Using buffer strategy:" << (m_bufferStrategy == GL_STATIC_DRAW ? "static" : (m_bufferStrategy == GL_DYNAMIC_DRAW ? "dynamic" : "stream"));
+ }
+}
+
+static void qsg_wipeBuffer(Buffer *buffer, QOpenGLFunctions *funcs)
+{
+ funcs->glDeleteBuffers(1, &buffer->id);
+ free(buffer->data);
+}
+
+static void qsg_wipeBatch(Batch *batch, QOpenGLFunctions *funcs)
+{
+ qsg_wipeBuffer(&batch->vbo, funcs);
+ delete batch;
+}
+
+Renderer::~Renderer()
+{
+ // Clean up batches and buffers
+ for (int i=0; i<m_opaqueBatches.size(); ++i) qsg_wipeBatch(m_opaqueBatches.at(i), this);
+ for (int i=0; i<m_alphaBatches.size(); ++i) qsg_wipeBatch(m_alphaBatches.at(i), this);
+ for (int i=0; i<m_batchPool.size(); ++i) qsg_wipeBatch(m_batchPool.at(i), this);
+
+ // The shadowtree
+ qDeleteAll(m_nodes.values());
+
+ // Remaining elements...
+ for (int i=0; i<m_elementsToDelete.size(); ++i) {
+ Element *e = m_elementsToDelete.at(i);
+ if (e->isRenderNode)
+ delete static_cast<RenderNodeElement *>(e);
+ else
+ delete e;
+ }
+}
+
+void Renderer::invalidateAndRecycleBatch(Batch *b)
+{
+ b->invalidate();
+ for (int i=0; i<m_batchPool.size(); ++i)
+ if (b == m_batchPool.at(i))
+ return;
+ m_batchPool.add(b);
+}
+
+/* The code here does a CPU-side allocation which might seem like a performance issue
+ * compared to using glMapBuffer or glMapBufferRange which would give me back
+ * potentially GPU allocated memory and saving me one deep-copy, but...
+ *
+ * Because we do a lot of CPU-side transformations, we need random-access memory
+ * and the memory returned from glMapBuffer/glMapBufferRange is typically
+ * uncached and thus very slow for our purposes.
+ *
+ * ref: http://www.opengl.org/wiki/Buffer_Object
+ */
+void Renderer::map(Buffer *buffer, int byteSize)
+{
+ if (buffer->size != byteSize) {
+ if (buffer->data)
+ free(buffer->data);
+ buffer->data = (char *) malloc(byteSize);
+ buffer->size = byteSize;
+ }
+}
+
+void Renderer::unmap(Buffer *buffer)
+{
+ if (buffer->id == 0)
+ glGenBuffers(1, &buffer->id);
+ glBindBuffer(GL_ARRAY_BUFFER, buffer->id);
+ glBufferData(GL_ARRAY_BUFFER, buffer->size, buffer->data, m_bufferStrategy);
+}
+
+BatchRootInfo *Renderer::batchRootInfo(Node *node)
+{
+ BatchRootInfo *info = node->rootInfo();
+ if (!info) {
+ if (node->type() == QSGNode::ClipNodeType)
+ info = new ClipBatchRootInfo;
+ else {
+ Q_ASSERT(node->type() == QSGNode::TransformNodeType);
+ info = new BatchRootInfo;
+ }
+ node->data = info;
+ }
+ return info;
+}
+
+void Renderer::removeBatchRootFromParent(Node *childRoot)
+{
+ BatchRootInfo *childInfo = batchRootInfo(childRoot);
+ if (!childInfo->parentRoot)
+ return;
+ BatchRootInfo *parentInfo = batchRootInfo(childInfo->parentRoot);
+
+ Q_ASSERT(parentInfo->subRoots.contains(childRoot));
+ parentInfo->subRoots.remove(childRoot);
+ childInfo->parentRoot = 0;
+}
+
+void Renderer::registerBatchRoot(Node *subRoot, Node *parentRoot)
+{
+ BatchRootInfo *subInfo = batchRootInfo(subRoot);
+ BatchRootInfo *parentInfo = batchRootInfo(parentRoot);
+ subInfo->parentRoot = parentRoot;
+ parentInfo->subRoots << subRoot;
+}
+
+bool Renderer::changeBatchRoot(Node *node, Node *root)
+{
+ BatchRootInfo *subInfo = batchRootInfo(node);
+ if (subInfo->parentRoot == root)
+ return false;
+ if (subInfo->parentRoot) {
+ BatchRootInfo *oldRootInfo = batchRootInfo(subInfo->parentRoot);
+ oldRootInfo->subRoots.remove(node);
+ }
+ BatchRootInfo *newRootInfo = batchRootInfo(root);
+ newRootInfo->subRoots << node;
+ subInfo->parentRoot = root;
+ return true;
+}
+
+void Renderer::nodeChangedBatchRoot(Node *node, Node *root)
+{
+ if (node->type() == QSGNode::ClipNodeType || node->isBatchRoot) {
+ if (!changeBatchRoot(node, root))
+ return;
+ node = root;
+ } else if (node->type() == QSGNode::GeometryNodeType) {
+ // Only need to change the root as nodeChanged anyway flags a full update.
+ Element *e = node->element();
+ if (e)
+ e->root = root;
+ }
+
+ SHADOWNODE_TRAVERSE(node)
+ nodeChangedBatchRoot(*child, root);
+}
+
+void Renderer::nodeWasTransformed(Node *node, int *vertexCount)
+{
+ if (node->type() == QSGNode::GeometryNodeType) {
+ QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node->sgNode);
+ *vertexCount += gn->geometry()->vertexCount();
+ Element *e = node->element();
+ if (e) {
+ e->boundsComputed = false;
+ if (e->batch) {
+ if (!e->batch->isOpaque) {
+ e->batch->invalidate();
+ m_rebuild |= BuildBatches;
+ } else if (e->batch->merged) {
+ e->batch->needsUpload = true;
+ }
+ }
+ if (e->batch && e->batch->merged)
+ e->batch->needsUpload = true;
+ }
+ }
+
+ SHADOWNODE_TRAVERSE(node)
+ nodeWasTransformed(*child, vertexCount);
+}
+
+void Renderer::nodeWasAdded(QSGNode *node, Node *shadowParent)
+{
+ Q_ASSERT(!m_nodes.contains(node));
+ if (node->isSubtreeBlocked())
+ return;
+
+ Node *snode = new Node(node);
+ m_nodes.insert(node, snode);
+ if (shadowParent) {
+ snode->parent = shadowParent;
+ shadowParent->children.append(snode);
+ }
+
+ if (node->type() == QSGNode::GeometryNodeType) {
+ snode->data = new Element(static_cast<QSGGeometryNode *>(node));
+
+ } else if (node->type() == QSGNode::ClipNodeType) {
+ snode->data = new ClipBatchRootInfo;
+
+ } else if (node->type() == QSGNode::RenderNodeType) {
+ RenderNodeElement *e = new RenderNodeElement(static_cast<QSGRenderNode *>(node));
+ snode->data = e;
+ Q_ASSERT(!m_renderNodeElements.contains(static_cast<QSGRenderNode *>(node)));
+ m_renderNodeElements.insert(e->renderNode, e);
+ }
+
+ QSGNODE_TRAVERSE(node)
+ nodeWasAdded(child, snode);
+}
+
+void Renderer::nodeWasRemoved(Node *node)
+{
+ // Prefix traversal as removeBatchFromParent below removes nodes
+ // in a bottom-up manner
+ SHADOWNODE_TRAVERSE(node)
+ nodeWasRemoved(*child);
+
+ if (node->type() == QSGNode::GeometryNodeType) {
+ Element *e = node->element();
+ if (e) {
+ e->removed = true;
+ m_elementsToDelete.add(e);
+ e->node = 0;
+ if (e->root) {
+ BatchRootInfo *info = batchRootInfo(e->root);
+ info->availableOrders++;
+ }
+ if (e->batch) {
+ e->batch->needsUpload = true;
+ }
+
+ }
+
+ } else if (node->type() == QSGNode::ClipNodeType) {
+ removeBatchRootFromParent(node);
+ delete node->clipInfo();
+ m_rebuild |= FullRebuild;
+ m_taggedRoots.remove(node);
+
+ } else if (node->isBatchRoot) {
+ removeBatchRootFromParent(node);
+ delete node->rootInfo();
+ m_rebuild |= FullRebuild;
+ m_taggedRoots.remove(node);
+
+ } else if (node->type() == QSGNode::RenderNodeType) {
+ RenderNodeElement *e = m_renderNodeElements.take(static_cast<QSGRenderNode *>(node->sgNode));
+ if (e) {
+ e->removed = true;
+ m_elementsToDelete.add(e);
+ }
+ }
+
+ Q_ASSERT(m_nodes.contains(node->sgNode));
+ delete m_nodes.take(node->sgNode);
+}
+
+void Renderer::turnNodeIntoBatchRoot(Node *node)
+{
+ if (Q_UNLIKELY(debug_change)) qDebug() << " - new batch root";
+ m_rebuild |= FullRebuild;
+ node->isBatchRoot = true;
+ node->becameBatchRoot = true;
+
+ Node *p = node->parent;
+ while (p) {
+ if (p->type() == QSGNode::ClipNodeType || p->isBatchRoot) {
+ registerBatchRoot(node, p);
+ break;
+ }
+ p = p->parent;
+ }
+
+ SHADOWNODE_TRAVERSE(node)
+ nodeChangedBatchRoot(*child, node);
+}
+
+
+void Renderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state)
+{
+ if (Q_UNLIKELY(debug_change)) {
+ QDebug debug = qDebug();
+ debug << "dirty:";
+ if (state & QSGNode::DirtyGeometry)
+ debug << "Geometry";
+ if (state & QSGNode::DirtyMaterial)
+ debug << "Material";
+ if (state & QSGNode::DirtyMatrix)
+ debug << "Matrix";
+ if (state & QSGNode::DirtyNodeAdded)
+ debug << "Added";
+ if (state & QSGNode::DirtyNodeRemoved)
+ debug << "Removed";
+ if (state & QSGNode::DirtyOpacity)
+ debug << "Opacity";
+ if (state & QSGNode::DirtySubtreeBlocked)
+ debug << "SubtreeBlocked";
+ if (state & QSGNode::DirtyForceUpdate)
+ debug << "ForceUpdate";
+
+ // when removed, some parts of the node could already have been destroyed
+ // so don't debug it out.
+ if (state & QSGNode::DirtyNodeRemoved)
+ debug << (void *) node << node->type();
+ else
+ debug << node;
+ }
+
+ // As this function calls nodeChanged recursively, we do it at the top
+ // to avoid that any of the others are processed twice.
+ if (state & QSGNode::DirtySubtreeBlocked) {
+ Node *sn = m_nodes.value(node);
+ bool blocked = node->isSubtreeBlocked();
+ if (blocked && sn) {
+ nodeChanged(node, QSGNode::DirtyNodeRemoved);
+ Q_ASSERT(m_nodes.value(node) == 0);
+ } else if (!blocked && !sn) {
+ nodeChanged(node, QSGNode::DirtyNodeAdded);
+ }
+ return;
+ }
+
+ if (state & QSGNode::DirtyNodeAdded) {
+ if (nodeUpdater()->isNodeBlocked(node, rootNode())) {
+ QSGRenderer::nodeChanged(node, state);
+ return;
+ }
+ if (node == rootNode())
+ nodeWasAdded(node, 0);
+ else
+ nodeWasAdded(node, m_nodes.value(node->parent()));
+ }
+
+ // Mark this node dirty in the shadow tree.
+ Node *shadowNode = m_nodes.value(node);
+
+ // Blocked subtrees won't have shadow nodes, so we can safely abort
+ // here..
+ if (!shadowNode) {
+ QSGRenderer::nodeChanged(node, state);
+ return;
+ }
+
+ shadowNode->dirtyState |= state;
+
+ if (state & QSGNode::DirtyMatrix && !shadowNode->isBatchRoot) {
+ Q_ASSERT(node->type() == QSGNode::TransformNodeType);
+ if (node->m_subtreeRenderableCount > m_batchNodeThreshold) {
+ turnNodeIntoBatchRoot(shadowNode);
+ } else {
+ int vertices = 0;
+ nodeWasTransformed(shadowNode, &vertices);
+ if (vertices > m_batchVertexThreshold) {
+ turnNodeIntoBatchRoot(shadowNode);
+ }
+ }
+ }
+
+ if (state & QSGNode::DirtyGeometry && node->type() == QSGNode::GeometryNodeType) {
+ QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
+ Element *e = shadowNode->element();
+ if (e) {
+ e->boundsComputed = false;
+ Batch *b = e->batch;
+ if (b) {
+ if (!e->batch->geometryWasChanged(gn)) {
+ m_rebuild |= Renderer::FullRebuild;
+ } else if (!b->isOpaque) {
+ m_rebuild |= Renderer::BuildBatches;
+ } else {
+ b->needsUpload = true;
+ }
+ }
+ }
+ }
+
+ if (state & QSGNode::DirtyMaterial) {
+ Q_ASSERT(node->type() == QSGNode::GeometryNodeType);
+ Element *e = shadowNode->element();
+ if (e) {
+ if (e->batch) {
+ if (!e->batch->isMaterialCompatible(e))
+ m_rebuild = Renderer::FullRebuild;
+ } else {
+ m_rebuild = Renderer::BuildBatches;
+ }
+ }
+ }
+
+ // Mark the shadow tree dirty all the way back to the root...
+ QSGNode::DirtyState dirtyChain = state & (QSGNode::DirtyNodeAdded
+ | QSGNode::DirtyOpacity
+ | QSGNode::DirtyMatrix
+ | QSGNode::DirtySubtreeBlocked);
+ if (dirtyChain != 0) {
+ dirtyChain = QSGNode::DirtyState(dirtyChain << 16);
+ Node *sn = shadowNode->parent;
+ while (sn) {
+ sn->dirtyState |= dirtyChain;
+ sn = sn->parent;
+ }
+ }
+
+ // Delete happens at the very end because it deletes the shadownode.
+ if (state & QSGNode::DirtyNodeRemoved) {
+ Node *parent = shadowNode->parent;
+ if (parent)
+ parent->children.removeOne(shadowNode);
+ nodeWasRemoved(shadowNode);
+ Q_ASSERT(m_nodes.value(node) == 0);
+ }
+
+ QSGRenderer::nodeChanged(node, state);
+}
+
+/*
+ * Traverses the tree and builds two list of geometry nodes. One for
+ * the opaque and one for the translucent. These are populated
+ * in the order they should visually appear in, meaning first
+ * to the back and last to the front.
+ *
+ * We split opaque and translucent as we can perform different
+ * types of reordering / batching strategies on them, depending
+ *
+ * Note: It would be tempting to use the shadow nodes instead of the QSGNodes
+ * for traversal to avoid hash lookups, but the order of the children
+ * is important and they are not preserved in the shadow tree, so we must
+ * use the actual QSGNode tree.
+ */
+void Renderer::buildRenderLists(QSGNode *node)
+{
+ if (node->isSubtreeBlocked())
+ return;
+
+ Q_ASSERT(m_nodes.contains(node));
+ Node *shadowNode = m_nodes.value(node);
+
+ if (node->type() == QSGNode::GeometryNodeType) {
+ QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
+
+ Element *e = shadowNode->element();
+ Q_ASSERT(e);
+
+ bool opaque = gn->inheritedOpacity() > OPAQUE_LIMIT && !(gn->activeMaterial()->flags() & QSGMaterial::Blending);
+ if (opaque)
+ m_opaqueRenderList << e;
+ else
+ m_alphaRenderList << e;
+
+ e->order = ++m_nextRenderOrder;
+
+ // Used while rebuilding partial roots.
+ if (m_explicitOrdering)
+ e->orphaned = false;
+
+ } else if (node->type() == QSGNode::ClipNodeType || shadowNode->isBatchRoot) {
+ Q_ASSERT(m_nodes.contains(node));
+ BatchRootInfo *info = batchRootInfo(m_nodes.value(node));
+ if (m_explicitOrdering)
+ m_nextRenderOrder = info->firstOrder;
+ int currentOrder = m_nextRenderOrder;
+ QSGNODE_TRAVERSE(node)
+ buildRenderLists(child);
+ int padding = (m_nextRenderOrder - currentOrder) >> 2;
+ if (m_explicitOrdering) {
+ m_nextRenderOrder = info->lastOrder + 1;
+ } else {
+ info->firstOrder = currentOrder;
+ info->availableOrders = padding;
+ info->lastOrder = m_nextRenderOrder + padding;
+ m_nextRenderOrder += padding;
+ }
+ return;
+ } else if (node->type() == QSGNode::RenderNodeType) {
+ RenderNodeElement *e = shadowNode->renderNodeElement();
+ m_alphaRenderList << e;
+ e->order = ++m_nextRenderOrder;
+ Q_ASSERT(e);
+ }
+
+ QSGNODE_TRAVERSE(node)
+ buildRenderLists(child);
+}
+
+void Renderer::tagSubRoots(Node *node)
+{
+ BatchRootInfo *i = batchRootInfo(node);
+ m_taggedRoots << node;
+ for (QSet<Node *>::const_iterator it = i->subRoots.constBegin();
+ it != i->subRoots.constEnd(); ++it) {
+ tagSubRoots(*it);
+ }
+}
+
+static void qsg_addOrphanedElements(QDataBuffer<Element *> &orphans, const QDataBuffer<Element *> &renderList)
+{
+ orphans.reset();
+ for (int i=0; i<renderList.size(); ++i) {
+ Element *e = renderList.at(i);
+ if (e && !e->removed && e->batch == 0) {
+ e->orphaned = true;
+ orphans.add(e);
+ }
+ }
+}
+
+static void qsg_addBackOrphanedElements(QDataBuffer<Element *> &orphans, QDataBuffer<Element *> &renderList)
+{
+ for (int i=0; i<orphans.size(); ++i) {
+ Element *e = orphans.at(i);
+ if (e->orphaned)
+ renderList.add(e);
+ }
+ orphans.reset();
+}
+
+/*
+ * To rebuild the tagged roots, we start by putting all subroots of tagged
+ * roots into the list of tagged roots. This is to make the rest of the
+ * algorithm simpler.
+ *
+ * Second, we invalidate all batches which belong to tagged roots, which now
+ * includes the entire subtree under a given root
+ *
+ * Then we call buildRenderLists for all tagged subroots which do not have
+ * parents which are tagged, aka, we traverse only the topmosts roots.
+ *
+ * Then we sort the render lists based on their render order, to restore the
+ * right order for rendering.
+ */
+void Renderer::buildRenderListsForTaggedRoots()
+{
+ // Flag any element that is currently in the render lists, but which
+ // is not in a batch. This happens when we have a partial rebuild
+ // in one sub tree while we have a BuildBatches change in another
+ // isolated subtree. So that batch-building takes into account
+ // these "orphaned" nodes, we flag them now. The ones under tagged
+ // roots will be cleared again. The remaining ones are added into the
+ // render lists so that they contain all visual nodes after the
+ // function completes.
+ qsg_addOrphanedElements(m_tmpOpaqueElements, m_opaqueRenderList);
+ qsg_addOrphanedElements(m_tmpAlphaElements, m_alphaRenderList);
+
+ // Take a copy now, as we will be adding to this while traversing..
+ QSet<Node *> roots = m_taggedRoots;
+ for (QSet<Node *>::const_iterator it = roots.constBegin();
+ it != roots.constEnd(); ++it) {
+ tagSubRoots(*it);
+ }
+
+ for (int i=0; i<m_opaqueBatches.size(); ++i) {
+ Batch *b = m_opaqueBatches.at(i);
+ if (m_taggedRoots.contains(b->root))
+ invalidateAndRecycleBatch(b);
+
+ }
+ for (int i=0; i<m_alphaBatches.size(); ++i) {
+ Batch *b = m_alphaBatches.at(i);
+ if (m_taggedRoots.contains(b->root))
+ invalidateAndRecycleBatch(b);
+ }
+
+ m_opaqueRenderList.reset();
+ m_alphaRenderList.reset();
+ int maxRenderOrder = m_nextRenderOrder;
+ m_explicitOrdering = true;
+ // Traverse each root, assigning it
+ for (QSet<Node *>::const_iterator it = m_taggedRoots.constBegin();
+ it != m_taggedRoots.constEnd(); ++it) {
+ Node *root = *it;
+ BatchRootInfo *i = batchRootInfo(root);
+ if ((!i->parentRoot || !m_taggedRoots.contains(i->parentRoot))
+ && !nodeUpdater()->isNodeBlocked(root->sgNode, rootNode())) {
+ m_nextRenderOrder = i->firstOrder;
+ buildRenderLists(root->sgNode);
+ }
+ }
+ m_taggedRoots.clear();
+ m_explicitOrdering = false;
+ m_nextRenderOrder = qMax(m_nextRenderOrder, maxRenderOrder);
+
+ // Add orphaned elements back into the list and then sort it..
+ qsg_addBackOrphanedElements(m_tmpOpaqueElements, m_opaqueRenderList);
+ qsg_addBackOrphanedElements(m_tmpAlphaElements, m_alphaRenderList);
+
+ if (m_opaqueRenderList.size())
+ qSort(&m_opaqueRenderList.first(), &m_opaqueRenderList.last() + 1, qsg_sort_element_decreasing_order);
+ if (m_alphaRenderList.size())
+ qSort(&m_alphaRenderList.first(), &m_alphaRenderList.last() + 1, qsg_sort_element_increasing_order);
+
+}
+
+void Renderer::buildRenderListsFromScratch()
+{
+ m_opaqueRenderList.reset();
+ m_alphaRenderList.reset();
+
+ for (int i=0; i<m_opaqueBatches.size(); ++i)
+ invalidateAndRecycleBatch(m_opaqueBatches.at(i));
+ for (int i=0; i<m_alphaBatches.size(); ++i)
+ invalidateAndRecycleBatch(m_alphaBatches.at(i));
+ m_opaqueBatches.reset();
+ m_alphaBatches.reset();
+
+ m_nextRenderOrder = 0;
+
+ buildRenderLists(rootNode());
+}
+
+/* Clean up batches by making it a consecutive list of "valid"
+ * batches and moving all invalidated batches to the batches pool.
+ */
+void Renderer::cleanupBatches(QDataBuffer<Batch *> *batches) {
+ if (batches->size()) {
+ qSort(&batches->first(), &batches->last() + 1, qsg_sort_batch_is_valid);
+ int count = 0;
+ while (count < batches->size() && batches->at(count)->first)
+ ++count;
+ for (int i=count+1; i<batches->size(); ++i)
+ invalidateAndRecycleBatch(batches->at(i));
+ batches->resize(count);
+ }
+}
+
+void Renderer::prepareOpaqueBatches()
+{
+ for (int i=m_opaqueRenderList.size() - 1; i >= 0; --i) {
+ Element *ei = m_opaqueRenderList.at(i);
+ if (!ei || ei->batch)
+ continue;
+ Batch *batch = newBatch();
+ batch->first = ei;
+ batch->root = ei->root;
+ batch->isOpaque = true;
+ batch->needsUpload = true;
+ batch->positionAttribute = qsg_positionAttribute(ei->node->geometry());
+
+ m_opaqueBatches.add(batch);
+
+ ei->batch = batch;
+ Element *next = ei;
+
+ QSGGeometryNode *gni = ei->node;
+
+ for (int j = i - 1; j >= 0; --j) {
+ Element *ej = m_opaqueRenderList.at(j);
+ if (!ej)
+ continue;
+ if (ej->root != ei->root)
+ break;
+ if (ej->batch)
+ continue;
+
+ QSGGeometryNode *gnj = ej->node;
+
+ if (gni->clipList() == gnj->clipList()
+ && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode()
+ && gni->geometry()->attributes() == gnj->geometry()->attributes()
+ && gni->inheritedOpacity() == gnj->inheritedOpacity()
+ && gni->activeMaterial()->type() == gnj->activeMaterial()->type()
+ && gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) {
+ ej->batch = batch;
+ next->nextInBatch = ej;
+ next = ej;
+ }
+ }
+ }
+}
+
+bool Renderer::checkOverlap(int first, int last, const Rect &bounds)
+{
+ for (int i=first; i<=last; ++i) {
+ Element *e = m_alphaRenderList.at(i);
+ if (!e || e->batch)
+ continue;
+ Q_ASSERT(e->boundsComputed);
+ if (e->bounds.intersects(bounds))
+ return true;
+ }
+ return false;
+}
+
+/*
+ *
+ * To avoid the O(n^2) checkOverlap check in most cases, we have the
+ * overlapBounds which is the union of all bounding rects to check overlap
+ * for. We know that if it does not overlap, then none of the individual
+ * ones will either. For the typical list case, this results in no calls
+ * to checkOverlap what-so-ever. This also ensures that when all consecutive
+ * items are matching (such as a table of text), we don't build up an
+ * overlap bounds and thus do not require full overlap checks.
+ */
+
+void Renderer::prepareAlphaBatches()
+{
+ for (int i=0; i<m_alphaRenderList.size(); ++i) {
+ Element *e = m_alphaRenderList.at(i);
+ if (!e || e->isRenderNode)
+ continue;
+ Q_ASSERT(!e->removed);
+ e->ensureBoundsValid();
+ }
+
+ for (int i=0; i<m_alphaRenderList.size(); ++i) {
+ Element *ei = m_alphaRenderList.at(i);
+ if (!ei || ei->batch)
+ continue;
+
+ if (ei->isRenderNode) {
+ Batch *rnb = newBatch();
+ rnb->first = ei;
+ rnb->root = ei->root;
+ rnb->isOpaque = false;
+ rnb->isRenderNode = true;
+ ei->batch = rnb;
+ m_alphaBatches.add(rnb);
+ continue;
+ }
+
+ Batch *batch = newBatch();
+ batch->first = ei;
+ batch->root = ei->root;
+ batch->isOpaque = false;
+ batch->needsUpload = true;
+ m_alphaBatches.add(batch);
+ ei->batch = batch;
+
+ QSGGeometryNode *gni = ei->node;
+ batch->positionAttribute = qsg_positionAttribute(gni->geometry());
+
+ Rect overlapBounds;
+ overlapBounds.set(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
+
+ Element *next = ei;
+
+ for (int j = i + 1; j < m_alphaRenderList.size(); ++j) {
+ Element *ej = m_alphaRenderList.at(j);
+ if (!ej)
+ continue;
+ if (ej->root != ei->root || ej->isRenderNode)
+ break;
+ if (ej->batch)
+ continue;
+
+ QSGGeometryNode *gnj = ej->node;
+
+ if (gni->clipList() == gnj->clipList()
+ && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode()
+ && gni->geometry()->attributes() == gnj->geometry()->attributes()
+ && gni->inheritedOpacity() == gnj->inheritedOpacity()
+ && gni->activeMaterial()->type() == gnj->activeMaterial()->type()
+ && gni->activeMaterial()->compare(gnj->activeMaterial()) == 0) {
+ if (!overlapBounds.intersects(ej->bounds) || !checkOverlap(i+1, j - 1, ej->bounds)) {
+ ej->batch = batch;
+ next->nextInBatch = ej;
+ next = ej;
+ }
+ } else {
+ overlapBounds |= ej->bounds;
+ }
+ }
+ }
+
+
+}
+
+/* These parameters warrant some explanation...
+ *
+ * vaOffset: The byte offset into the vertex data to the location of the
+ * 2D float point vertex attributes.
+ *
+ * vertexData: destination where the geometry's vertex data should go
+ *
+ * zData: destination of geometries injected Z positioning
+ *
+ * indexData: destination of the indices for this element
+ *
+ * iBase: The starting index for this element in the batch
+ */
+
+void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, quint16 *iBase, int *indexCount)
+{
+ if (Q_UNLIKELY(debug_upload)) qDebug() << " - uploading element:" << e << e->node << (void *) *vertexData << (qintptr) (*zData - *vertexData) << (qintptr) (*indexData - *vertexData);
+ QSGGeometry *g = e->node->geometry();
+
+ const QMatrix4x4 &localx = *e->node->matrix();
+
+ const int vCount = g->vertexCount();
+ const int vSize = g->sizeOfVertex();
+ memcpy(*vertexData, g->vertexData(), vSize * vCount);
+
+ // apply vertex transform..
+ char *vdata = *vertexData + vaOffset;
+ if (((const QMatrix4x4_Accessor &) localx).flagBits == 1) {
+ for (int i=0; i<vCount; ++i) {
+ Pt *p = (Pt *) vdata;
+ p->x += ((QMatrix4x4_Accessor &) localx).m[3][0];
+ p->y += ((QMatrix4x4_Accessor &) localx).m[3][1];
+ vdata += vSize;
+ }
+ } else if (((const QMatrix4x4_Accessor &) localx).flagBits > 1) {
+ for (int i=0; i<vCount; ++i) {
+ ((Pt *) vdata)->map(localx);
+ vdata += vSize;
+ }
+ }
+
+ float *vzorder = (float *) *zData;
+ float zorder = 1.0f - e->order * m_zRange;
+ for (int i=0; i<vCount; ++i)
+ vzorder[i] = zorder;
+
+ int iCount = g->indexCount();
+ quint16 *indices = (quint16 *) *indexData;
+
+ if (iCount == 0) {
+ if (g->drawingMode() == GL_TRIANGLE_STRIP)
+ *indices++ = *iBase;
+ iCount = vCount;
+ for (int i=0; i<iCount; ++i)
+ indices[i] = *iBase + i;
+ } else {
+ const quint16 *srcIndices = g->indexDataAsUShort();
+ if (g->drawingMode() == GL_TRIANGLE_STRIP)
+ *indices++ = *iBase + srcIndices[0];
+ for (int i=0; i<iCount; ++i)
+ indices[i] = *iBase + srcIndices[i];
+ }
+ if (g->drawingMode() == GL_TRIANGLE_STRIP) {
+ indices[iCount] = indices[iCount - 1];
+ iCount += 2;
+ }
+
+ *vertexData += vCount * vSize;
+ *zData += vCount * sizeof(float);
+ *indexData += iCount * sizeof(quint16);
+ *iBase += vCount;
+ *indexCount += iCount;
+}
+
+const QMatrix4x4 &Renderer::matrixForRoot(Node *node)
+{
+ if (node->type() == QSGNode::TransformNodeType)
+ return static_cast<QSGTransformNode *>(node->sgNode)->combinedMatrix();
+ Q_ASSERT(node->type() == QSGNode::ClipNodeType);
+ QSGClipNode *c = static_cast<QSGClipNode *>(node->sgNode);
+ return *c->matrix();
+}
+
+void Renderer::uploadBatch(Batch *b)
+{
+ // Early out if nothing has changed in this batch..
+ if (!b->needsUpload) {
+ if (Q_UNLIKELY(debug_upload)) qDebug() << " Batch:" << b << "already uploaded...";
+ return;
+ }
+
+ if (!b->first) {
+ if (Q_UNLIKELY(debug_upload)) qDebug() << " Batch:" << b << "is invalid...";
+ return;
+ }
+
+ if (b->isRenderNode) {
+ if (Q_UNLIKELY(debug_upload)) qDebug() << " Batch: " << b << "is a render node...";
+ return;
+ }
+
+ // Figure out if we can merge or not, if not, then just render the batch as is..
+ Q_ASSERT(b->first);
+ Q_ASSERT(b->first->node);
+
+ QSGGeometryNode *gn = b->first->node;
+ QSGGeometry *g = gn->geometry();
+
+ bool canMerge = (g->drawingMode() == GL_TRIANGLES || g->drawingMode() == GL_TRIANGLE_STRIP)
+ && b->positionAttribute >= 0
+ && g->indexType() == GL_UNSIGNED_SHORT
+ && QMatrix4x4_Accessor::is2DSafe(*gn->matrix())
+ && (gn->activeMaterial()->flags() & QSGMaterial::CustomCompileStep) == 0
+ && (((gn->activeMaterial()->flags() & QSGMaterial::RequiresDeterminant) == 0)
+ || (((gn->activeMaterial()->flags() & QSGMaterial_RequiresFullMatrixBit) == 0) && b->isTranslateOnlyToRoot())
+ );
+
+ b->merged = canMerge;
+
+ // Figure out how much memory we need...
+ b->vertexCount = 0;
+ b->indexCount = 0;
+ int unmergedIndexSize = 0;
+ Element *e = b->first;
+
+ while (e) {
+ QSGGeometry *eg = e->node->geometry();
+ b->vertexCount += eg->vertexCount();
+ int iCount = eg->indexCount();
+ if (b->merged) {
+ if (iCount == 0)
+ iCount = eg->vertexCount();
+ // merged Triangle strips need to contain degenerate triangles at the beginning and end.
+ // One could save 2 ushorts here by ditching the the padding for the front of the
+ // first and the end of the last, but for simplicity, we simply don't care.
+ if (g->drawingMode() == GL_TRIANGLE_STRIP)
+ iCount += sizeof(quint16);
+ } else {
+ unmergedIndexSize += iCount * eg->sizeOfIndex();
+ }
+ b->indexCount += iCount;
+ e = e->nextInBatch;
+ }
+
+ // Abort if there are no vertices in this batch.. We abort this late as
+ // this is a broken usecase which we do not care to optimize for...
+ if (b->vertexCount == 0 || (b->merged && b->indexCount == 0))
+ return;
+
+ /* Allocate memory for this batch. Merged batches are divided into three separate blocks
+ 1. Vertex data for all elements, as they were in the QSGGeometry object, but
+ with the tranform relative to this batch's root applied. The vertex data
+ is otherwise unmodified.
+ 2. Z data for all elements, derived from each elements "render order".
+ This is present for merged data only.
+ 3. Indices for all elements, as they were in the QSGGeometry object, but
+ adjusted so that each index matches its.
+ And for TRIANGLE_STRIPs, we need to insert degenerate between each
+ primitive. These are unsigned shorts for merged and arbitrary for
+ non-merged.
+ */
+ int bufferSize = b->vertexCount * g->sizeOfVertex();
+ if (b->merged)
+ bufferSize += b->vertexCount * sizeof(float) + b->indexCount * sizeof(quint16);
+ else
+ bufferSize += unmergedIndexSize;
+ map(&b->vbo, bufferSize);
+
+ if (Q_UNLIKELY(debug_upload)) qDebug() << " - batch" << b << " first:" << b->first << " root:"
+ << b->root << " merged:" << b->merged << " positionAttribute" << b->positionAttribute
+ << " vbo:" << b->vbo.id << ":" << b->vbo.size;
+
+ if (b->merged) {
+ char *vertexData = b->vbo.data;
+ char *zData = vertexData + b->vertexCount * g->sizeOfVertex();
+ char *indexData = zData + b->vertexCount * sizeof(float);
+
+ quint16 iOffset = 0;
+ e = b->first;
+ int verticesInSet = 0;
+ int indicesInSet = 0;
+ b->drawSets.reset();
+ b->drawSets << DrawSet(0, zData - vertexData, indexData - vertexData);
+ while (e) {
+ verticesInSet += e->node->geometry()->vertexCount();
+ if (verticesInSet > 0xffff) {
+ b->drawSets.last().indexCount = indicesInSet;
+ b->drawSets << DrawSet(vertexData - b->vbo.data,
+ zData - b->vbo.data,
+ indexData - b->vbo.data);
+ iOffset = 0;
+ verticesInSet = e->node->geometry()->vertexCount();
+ indicesInSet = 0;
+ }
+ uploadMergedElement(e, b->positionAttribute, &vertexData, &zData, &indexData, &iOffset, &indicesInSet);
+ e = e->nextInBatch;
+ }
+ b->drawSets.last().indexCount = indicesInSet;
+ } else {
+ char *vboData = b->vbo.data;
+ char *iboData = vboData + b->vertexCount * g->sizeOfVertex();
+ Element *e = b->first;
+ while (e) {
+ QSGGeometry *g = e->node->geometry();
+ int vbs = g->vertexCount() * g->sizeOfVertex();
+ memcpy(vboData, g->vertexData(), vbs);
+ vboData = vboData + vbs;
+ if (g->indexCount()) {
+ int ibs = g->indexCount() * g->sizeOfIndex();
+ memcpy(iboData, g->indexData(), ibs);
+ iboData += ibs;
+ }
+ e = e->nextInBatch;
+ }
+ }
+
+ if (Q_UNLIKELY(debug_upload)) {
+ const char *vd = b->vbo.data;
+ qDebug() << " -- Vertex Data, count:" << b->vertexCount << " - " << g->sizeOfVertex() << "bytes/vertex";
+ for (int i=0; i<b->vertexCount; ++i) {
+ QDebug dump = qDebug().nospace();
+ dump << " --- " << i << ": ";
+ int offset = 0;
+ for (int a=0; a<g->attributeCount(); ++a) {
+ const QSGGeometry::Attribute &attr = g->attributes()[a];
+ dump << attr.position << ":(" << attr.tupleSize << ",";
+ if (attr.type == GL_FLOAT) {
+ dump << "float ";
+ if (attr.isVertexCoordinate)
+ dump << "* ";
+ for (int t=0; t<attr.tupleSize; ++t)
+ dump << *(float *)(vd + offset + t * sizeof(float)) << " ";
+ } else if (attr.type == GL_UNSIGNED_BYTE) {
+ dump << "ubyte ";
+ for (int t=0; t<attr.tupleSize; ++t)
+ dump << *(unsigned char *)(vd + offset + t * sizeof(unsigned char)) << " ";
+ }
+ dump << ") ";
+ offset += attr.tupleSize * size_of_type(attr.type);
+ }
+ if (b->merged) {
+ float zorder = ((float*)(b->vbo.data + b->vertexCount * g->sizeOfVertex()))[i];
+ dump << " Z:(" << zorder << ")";
+ }
+ vd += g->sizeOfVertex();
+ }
+
+ const quint16 *id = (const quint16 *) (b->vbo.data
+ + b->vertexCount * g->sizeOfVertex()
+ + (b->merged ? b->vertexCount * sizeof(float) : 0));
+ {
+ QDebug iDump = qDebug();
+ iDump << " -- Index Data, count:" << b->indexCount;
+ for (int i=0; i<b->indexCount; ++i) {
+ if ((i % 24) == 0)
+ iDump << endl << " --- ";
+ iDump << id[i];
+ }
+ }
+
+ for (int i=0; i<b->drawSets.size(); ++i) {
+ const DrawSet &s = b->drawSets.at(i);
+ qDebug() << " -- DrawSet: indexCount:" << s.indexCount << " vertices:" << s.vertices << " z:" << s.zorders << " indices:" << s.indices;
+ }
+ }
+
+ unmap(&b->vbo);
+
+ if (Q_UNLIKELY(debug_upload)) qDebug() << " --- vertex/index buffers unmapped, batch upload completed...";
+
+ b->needsUpload = false;
+
+ if (Q_UNLIKELY(debug_render))
+ b->uploadedThisFrame = true;
+}
+
+void Renderer::updateClip(const QSGClipNode *clipList, const Batch *batch)
+{
+ if (clipList != m_currentClip && Q_LIKELY(!debug_noclip)) {
+ m_currentClip = clipList;
+ // updateClip sets another program, so force-reactivate our own
+ if (m_currentShader)
+ setActiveShader(0, 0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ if (batch->isOpaque)
+ glDisable(GL_DEPTH_TEST);
+ ClipType type = updateStencilClip(m_currentClip);
+ if (batch->isOpaque) {
+ glEnable(GL_DEPTH_TEST);
+ if (type & StencilClip)
+ glDepthMask(true);
+ }
+ }
+}
+
+/*!
+ * Look at the attribute arrays and potentially the injected z attribute to figure out
+ * which vertex attribute arrays need to be enabled and not. Then update the current
+ * Shader and current QSGMaterialShader.
+ */
+void Renderer::setActiveShader(QSGMaterialShader *program, ShaderManager::Shader *shader)
+{
+ const char * const *c = m_currentProgram ? m_currentProgram->attributeNames() : 0;
+ const char * const *n = program ? program->attributeNames() : 0;
+
+ int cza = m_currentShader ? m_currentShader->pos_order : -1;
+ int nza = shader ? shader->pos_order : -1;
+
+ int i = 0;
+ while (c || n) {
+
+ bool was = c;
+ if (cza == i) {
+ was = true;
+ c = 0;
+ } else if (c && !c[i]) { // end of the attribute array names
+ c = 0;
+ was = false;
+ }
+
+ bool is = n;
+ if (nza == i) {
+ is = true;
+ n = 0;
+ } else if (n && !n[i]) {
+ n = 0;
+ is = false;
+ }
+
+ if (is && !was)
+ glEnableVertexAttribArray(i);
+ else if (was && !is)
+ glDisableVertexAttribArray(i);
+
+ ++i;
+ }
+
+ if (m_currentProgram)
+ m_currentProgram->deactivate();
+ m_currentProgram = program;
+ m_currentShader = shader;
+ m_currentMaterial = 0;
+ if (m_currentProgram) {
+ m_currentProgram->program()->bind();
+ m_currentProgram->activate();
+ }
+}
+
+void Renderer::renderMergedBatch(const Batch *batch)
+{
+ if (batch->vertexCount == 0 || batch->indexCount == 0)
+ return;
+
+ Element *e = batch->first;
+ Q_ASSERT(e);
+
+ if (Q_UNLIKELY(debug_render)) {
+ QDebug debug = qDebug();
+ debug << " -"
+ << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
+ << (e->node->clipList() ? "[ clip]" : "[noclip]")
+ << (batch->isOpaque ? "[opaque]" : "[ alpha]")
+ << "[ merged]"
+ << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
+ << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
+ << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
+ << " root:" << batch->root;
+ if (batch->drawSets.size() > 1)
+ debug << "sets:" << batch->drawSets.size();
+ batch->uploadedThisFrame = false;
+ }
+
+ QSGGeometryNode *gn = e->node;
+
+ // We always have dirty matrix as all batches are at a unique z range.
+ QSGMaterialShader::RenderState::DirtyStates dirty = QSGMaterialShader::RenderState::DirtyMatrix;
+ if (batch->root)
+ m_current_model_view_matrix = matrixForRoot(batch->root);
+ else
+ m_current_model_view_matrix.setToIdentity();
+ m_current_determinant = m_current_model_view_matrix.determinant();
+ m_current_projection_matrix = projectionMatrix(); // has potentially been changed by renderUnmergedBatch..
+
+ // updateClip() uses m_current_projection_matrix.
+ updateClip(gn->clipList(), batch);
+
+ glBindBuffer(GL_ARRAY_BUFFER, batch->vbo.id);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vbo.id);
+
+
+ QSGMaterial *material = gn->activeMaterial();
+ ShaderManager::Shader *sms = m_shaderManager->prepareMaterial(material);
+ QSGMaterialShader *program = sms->program;
+
+ if (m_currentShader != sms)
+ setActiveShader(program, sms);
+
+ m_current_opacity = gn->inheritedOpacity();
+ if (sms->lastOpacity != m_current_opacity) {
+ dirty |= QSGMaterialShader::RenderState::DirtyOpacity;
+ sms->lastOpacity = m_current_opacity;
+ }
+
+ program->updateState(state(dirty), material, m_currentMaterial);
+
+ m_currentMaterial = material;
+
+ QSGGeometry* g = gn->geometry();
+ char const *const *attrNames = program->attributeNames();
+ for (int i=0; i<batch->drawSets.size(); ++i) {
+ const DrawSet &draw = batch->drawSets.at(i);
+ int offset = 0;
+ for (int j = 0; attrNames[j]; ++j) {
+ if (!*attrNames[j])
+ continue;
+ const QSGGeometry::Attribute &a = g->attributes()[j];
+ GLboolean normalize = a.type != GL_FLOAT && a.type != GL_DOUBLE;
+ glVertexAttribPointer(a.position, a.tupleSize, a.type, normalize, g->sizeOfVertex(), (void *) (qintptr) (offset + draw.vertices));
+ offset += a.tupleSize * size_of_type(a.type);
+ }
+ glVertexAttribPointer(sms->pos_order, 1, GL_FLOAT, false, 0, (void *) (qintptr) (draw.zorders));
+
+ glDrawElements(g->drawingMode(), draw.indexCount, GL_UNSIGNED_SHORT, (void *) (qintptr) (draw.indices));
+ }
+}
+
+void Renderer::renderUnmergedBatch(const Batch *batch)
+{
+ if (batch->vertexCount == 0)
+ return;
+
+ Element *e = batch->first;
+ Q_ASSERT(e);
+
+ if (Q_UNLIKELY(debug_render)) {
+ qDebug() << " -"
+ << (batch->uploadedThisFrame ? "[ upload]" : "[retained]")
+ << (e->node->clipList() ? "[ clip]" : "[noclip]")
+ << (batch->isOpaque ? "[opaque]" : "[ alpha]")
+ << "[unmerged]"
+ << " Nodes:" << QString::fromLatin1("%1").arg(qsg_countNodesInBatch(batch), 4).toLatin1().constData()
+ << " Vertices:" << QString::fromLatin1("%1").arg(batch->vertexCount, 5).toLatin1().constData()
+ << " Indices:" << QString::fromLatin1("%1").arg(batch->indexCount, 5).toLatin1().constData()
+ << " root:" << batch->root;
+
+ batch->uploadedThisFrame = false;
+ }
+
+ QSGGeometryNode *gn = e->node;
+
+ m_current_projection_matrix = projectionMatrix();
+ updateClip(gn->clipList(), batch);
+
+ glBindBuffer(GL_ARRAY_BUFFER, batch->vbo.id);
+ if (batch->indexCount)
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vbo.id);
+
+ // We always have dirty matrix as all batches are at a unique z range.
+ QSGMaterialShader::RenderState::DirtyStates dirty = QSGMaterialShader::RenderState::DirtyMatrix;
+
+ QSGMaterial *material = gn->activeMaterial();
+ ShaderManager::Shader *sms = m_shaderManager->prepareMaterialNoRewrite(material);
+ QSGMaterialShader *program = sms->program;
+
+ if (sms != m_currentShader)
+ setActiveShader(program, sms);
+
+ m_current_opacity = gn->inheritedOpacity();
+ if (sms->lastOpacity != m_current_opacity) {
+ dirty |= QSGMaterialShader::RenderState::DirtyOpacity;
+ sms->lastOpacity = m_current_opacity;
+ }
+
+ int vOffset = 0;
+ int iOffset = batch->vertexCount * gn->geometry()->sizeOfVertex();
+
+ QMatrix4x4 rootMatrix = batch->root ? matrixForRoot(batch->root) : QMatrix4x4();
+
+ while (e) {
+ gn = e->node;
+
+ m_current_model_view_matrix = rootMatrix * *gn->matrix();
+ m_current_determinant = m_current_model_view_matrix.determinant();
+
+ m_current_projection_matrix = projectionMatrix();
+ m_current_projection_matrix(2, 2) = m_zRange;
+ m_current_projection_matrix(2, 3) = 1.0f - e->order * m_zRange;
+
+ program->updateState(state(dirty), material, m_currentMaterial);
+
+ m_currentMaterial = gn->activeMaterial();
+
+ QSGGeometry* g = gn->geometry();
+ char const *const *attrNames = program->attributeNames();
+ int offset = 0;
+ for (int j = 0; attrNames[j]; ++j) {
+ if (!*attrNames[j])
+ continue;
+ const QSGGeometry::Attribute &a = g->attributes()[j];
+ GLboolean normalize = a.type != GL_FLOAT && a.type != GL_DOUBLE;
+ glVertexAttribPointer(a.position, a.tupleSize, a.type, normalize, g->sizeOfVertex(), (void *) (qintptr) (offset + vOffset));
+ offset += a.tupleSize * size_of_type(a.type);
+ }
+
+ if (g->indexCount())
+ glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), (void *) (qintptr) iOffset);
+ else
+ glDrawArrays(g->drawingMode(), 0, g->vertexCount());
+
+ vOffset += g->sizeOfVertex() * g->vertexCount();
+ iOffset += g->indexCount() * g->sizeOfIndex();
+
+ // We only need to push this on the very first iteration...
+ dirty &= ~QSGMaterialShader::RenderState::DirtyOpacity;
+
+ e = e->nextInBatch;
+ }
+}
+
+void Renderer::renderBatches()
+{
+ if (Q_UNLIKELY(debug_render)) {
+ qDebug().nospace() << "Rendering:" << endl
+ << " -> Opaque: " << qsg_countNodesInBatches(m_opaqueBatches) << " nodes in " << m_opaqueBatches.size() << " batches..." << endl
+ << " -> Alpha: " << qsg_countNodesInBatches(m_alphaBatches) << " nodes in " << m_alphaBatches.size() << " batches...";
+ }
+
+ QRect r = viewportRect();
+ glViewport(r.x(), deviceRect().bottom() - r.bottom(), r.width(), r.height());
+
+ for (QHash<QSGRenderNode *, RenderNodeElement *>::const_iterator it = m_renderNodeElements.constBegin();
+ it != m_renderNodeElements.constEnd(); ++it) {
+ prepareRenderNode(it.value());
+ }
+
+ glClearColor(clearColor().redF(), clearColor().greenF(), clearColor().blueF(), clearColor().alphaF());
+#if defined(QT_OPENGL_ES)
+ glClearDepthf(1);
+#else
+ glClearDepth(1);
+#endif
+
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_BLEND);
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_LESS);
+ glDepthMask(true);
+ glColorMask(true, true, true, true);
+ glDisable(GL_SCISSOR_TEST);
+ glDisable(GL_STENCIL_TEST);
+
+ bindable()->clear(clearMode());
+
+ m_current_opacity = 1;
+ m_currentMaterial = 0;
+ m_currentShader = 0;
+ m_currentProgram = 0;
+ m_currentClip = 0;
+
+ bool renderOpaque = !debug_noopaque;
+ bool renderAlpha = !debug_noalpha;
+
+ if (Q_LIKELY(renderOpaque)) {
+ for (int i=0; i<m_opaqueBatches.size(); ++i) {
+ Batch *b = m_opaqueBatches.at(i);
+ if (b->merged)
+ renderMergedBatch(b);
+ else
+ renderUnmergedBatch(b);
+ }
+ }
+
+ glEnable(GL_BLEND);
+ glDepthMask(false);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ if (Q_LIKELY(renderAlpha)) {
+ for (int i=0; i<m_alphaBatches.size(); ++i) {
+ Batch *b = m_alphaBatches.at(i);
+ if (b->merged)
+ renderMergedBatch(b);
+ else if (b->isRenderNode)
+ renderRenderNode(b);
+ else
+ renderUnmergedBatch(b);
+ }
+ }
+
+ if (m_currentShader)
+ setActiveShader(0, 0);
+ updateStencilClip(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+}
+
+void Renderer::deleteRemovedElements()
+{
+ if (!m_elementsToDelete.size())
+ return;
+
+ for (int i=0; i<m_opaqueRenderList.size(); ++i) {
+ Element **e = m_opaqueRenderList.data() + i;
+ if (*e && (*e)->removed)
+ *e = 0;
+ }
+ for (int i=0; i<m_alphaRenderList.size(); ++i) {
+ Element **e = m_alphaRenderList.data() + i;
+ if (*e && (*e)->removed)
+ *e = 0;
+ }
+
+ for (int i=0; i<m_elementsToDelete.size(); ++i) {
+ Element *e = m_elementsToDelete.at(i);
+ if (e->isRenderNode)
+ delete static_cast<RenderNodeElement *>(e);
+ else
+ delete e;
+ }
+ m_elementsToDelete.reset();
+}
+
+void Renderer::render()
+{
+ if (Q_UNLIKELY(debug_dump)) {
+ printf("\n\n");
+ QSGNodeDumper::dump(rootNode());
+ }
+
+ if (Q_UNLIKELY(debug_render || debug_build)) {
+
+ QByteArray type("rebuild:");
+ if (m_rebuild == 0)
+ type += " none";
+ if (m_rebuild == FullRebuild)
+ type += " full";
+ else {
+ if (m_rebuild & BuildRenderLists)
+ type += " renderlists";
+ else if (m_rebuild & BuildRenderListsForTaggedRoots)
+ type += " partial";
+ else if (m_rebuild & BuildBatches)
+ type += " batches";
+ }
+
+
+ qDebug() << "Renderer::render()" << this << type;
+ }
+
+ if (m_rebuild & (BuildRenderLists | BuildRenderListsForTaggedRoots)) {
+ bool complete = (m_rebuild & BuildRenderLists) != 0;
+ if (complete)
+ buildRenderListsFromScratch();
+ else
+ buildRenderListsForTaggedRoots();
+ m_rebuild |= BuildBatches;
+
+ if (Q_UNLIKELY(debug_build)) {
+ qDebug() << "Opaque render lists" << (complete ? "(complete)" : "(partial)") << ":";
+ for (int i=0; i<m_opaqueRenderList.size(); ++i) {
+ Element *e = m_opaqueRenderList.at(i);
+ qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
+ }
+ qDebug() << "Alpha render list:" << (complete ? "(complete)" : "(partial)") << ":";
+ for (int i=0; i<m_alphaRenderList.size(); ++i) {
+ Element *e = m_alphaRenderList.at(i);
+ qDebug() << " - element:" << e << " batch:" << e->batch << " node:" << e->node << " order:" << e->order;
+ }
+ }
+ }
+
+ for (int i=0; i<m_opaqueBatches.size(); ++i)
+ m_opaqueBatches.at(i)->cleanupRemovedElements();
+ for (int i=0; i<m_alphaBatches.size(); ++i)
+ m_alphaBatches.at(i)->cleanupRemovedElements();
+ deleteRemovedElements();
+
+ cleanupBatches(&m_opaqueBatches);
+ cleanupBatches(&m_alphaBatches);
+
+
+ if (m_rebuild & BuildBatches) {
+ prepareOpaqueBatches();
+ prepareAlphaBatches();
+
+ if (Q_UNLIKELY(debug_build)) {
+ qDebug() << "Opaque Batches:";
+ for (int i=0; i<m_opaqueBatches.size(); ++i) {
+ Batch *b = m_opaqueBatches.at(i);
+ qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
+ for (Element *e = b->first; e; e = e->nextInBatch) {
+ qDebug() << " - element:" << e << " node:" << e->node << e->order;
+ }
+ }
+ qDebug() << "Alpha Batches:";
+ for (int i=0; i<m_alphaBatches.size(); ++i) {
+ Batch *b = m_alphaBatches.at(i);
+ qDebug() << " - Batch " << i << b << (b->needsUpload ? "upload" : "") << " root:" << b->root;
+ for (Element *e = b->first; e; e = e->nextInBatch) {
+ qDebug() << " - element:" << e << e->bounds << " node:" << e->node << " order:" << e->order;
+ }
+ }
+ }
+ }
+
+ deleteRemovedElements();
+
+ // Then sort opaque batches so that we're drawing the batches with the highest
+ // order first, maximizing the benefit of front-to-back z-ordering.
+ if (m_opaqueBatches.size())
+ qSort(&m_opaqueBatches.first(), &m_opaqueBatches.last() + 1, qsg_sort_batch_decreasing_order);
+
+ // Sort alpha batches back to front so that they render correctly.
+ if (m_alphaBatches.size())
+ qSort(&m_alphaBatches.first(), &m_alphaBatches.last() + 1, qsg_sort_batch_increasing_order);
+
+ m_zRange = 1.0 / (m_nextRenderOrder);
+
+ if (Q_UNLIKELY(debug_upload)) qDebug() << "Uploading Opaque Batches:";
+ for (int i=0; i<m_opaqueBatches.size(); ++i)
+ uploadBatch(m_opaqueBatches.at(i));
+
+ if (Q_UNLIKELY(debug_upload)) qDebug() << "Uploading Alpha Batches:";
+ for (int i=0; i<m_alphaBatches.size(); ++i)
+ uploadBatch(m_alphaBatches.at(i));
+
+ renderBatches();
+
+ m_rebuild = 0;
+}
+
+void Renderer::prepareRenderNode(RenderNodeElement *e)
+{
+ if (e->fbo && e->fbo->size() != deviceRect().size()) {
+ delete e->fbo;
+ e->fbo = 0;
+ }
+
+ if (!e->fbo)
+ e->fbo = new QOpenGLFramebufferObject(deviceRect().size(), QOpenGLFramebufferObject::CombinedDepthStencil);
+ e->fbo->bind();
+
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_SCISSOR_TEST);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_BLEND);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ QSGRenderNode::RenderState state;
+ QMatrix4x4 pm = projectionMatrix();
+ state.projectionMatrix = &pm;
+ state.scissorEnabled = false;
+ state.stencilEnabled = false;
+
+ QSGNode *clip = e->renderNode->parent();
+ e->renderNode->m_clip_list = 0;
+ while (clip != rootNode()) {
+ if (clip->type() == QSGNode::ClipNodeType) {
+ e->renderNode->m_clip_list = static_cast<QSGClipNode *>(clip);
+ break;
+ }
+ clip = clip->parent();
+ }
+
+ QSGNode *xform = e->renderNode->parent();
+ QMatrix4x4 matrix;
+ while (xform != rootNode()) {
+ if (xform->type() == QSGNode::TransformNodeType) {
+ matrix = matrixForRoot(e->root) * static_cast<QSGTransformNode *>(xform)->combinedMatrix();
+ break;
+ }
+ xform = xform->parent();
+ }
+ e->renderNode->m_matrix = &matrix;
+
+ QSGNode *opacity = e->renderNode->parent();
+ e->renderNode->m_opacity = 1.0;
+ while (opacity != rootNode()) {
+ if (opacity->type() == QSGNode::OpacityNodeType) {
+ e->renderNode->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity();
+ break;
+ }
+ opacity = opacity->parent();
+ }
+
+ e->renderNode->render(state);
+
+ e->renderNode->m_matrix = 0;
+
+ bindable()->bind();
+}
+
+void Renderer::renderRenderNode(Batch *batch)
+{
+ updateStencilClip(0);
+ m_currentClip = 0;
+
+ setActiveShader(0, 0);
+
+ if (!m_shaderManager->blitProgram) {
+ m_shaderManager->blitProgram = new QOpenGLShaderProgram();
+ const char *vs =
+ "attribute highp vec4 av; "
+ "attribute highp vec2 at; "
+ "varying highp vec2 t; "
+ "void main() { "
+ " gl_Position = av; "
+ " t = at; "
+ "} ";
+ const char *fs =
+ "uniform lowp sampler2D tex; "
+ "varying highp vec2 t; "
+ "void main() { "
+ " gl_FragColor = texture2D(tex, t); "
+ "} ";
+
+ m_shaderManager->blitProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vs);
+ m_shaderManager->blitProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fs);
+ m_shaderManager->blitProgram->bindAttributeLocation("av", 0);
+ m_shaderManager->blitProgram->bindAttributeLocation("at", 1);
+ m_shaderManager->blitProgram->link();
+
+ Q_ASSERT(m_shaderManager->blitProgram->isLinked());
+ }
+
+ RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
+ glBindTexture(GL_TEXTURE_2D, e->fbo->texture());
+
+ m_shaderManager->blitProgram->bind();
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+
+ float z = 1.0f - e->order * m_zRange;
+
+ float av[] = { -1, -1, z,
+ 1, -1, z,
+ -1, 1, z,
+ 1, 1, z };
+ float at[] = { 0, 0,
+ 1, 0,
+ 0, 1,
+ 1, 1 };
+
+ glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, av);
+ glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, at);
+
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+ glDisableVertexAttribArray(0);
+ glDisableVertexAttribArray(1);
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+QT_END_NAMESPACE
+
+}
diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h b/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h
new file mode 100644
index 0000000000..d47fcbd8d3
--- /dev/null
+++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h
@@ -0,0 +1,499 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ULTRARENDERER_H
+#define ULTRARENDERER_H
+
+#include <private/qsgrenderer_p.h>
+#include <private/qsgnodeupdater_p.h>
+#include <private/qdatabuffer_p.h>
+
+#include <private/qsgrendernode_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QSGBatchRenderer
+{
+
+struct Vec;
+struct Rect;
+struct Buffer;
+struct Chunk;
+struct AlphaChunk;
+struct Batch;
+struct Node;
+class Updater;
+class Renderer;
+class ShaderManager;
+
+struct Pt {
+ float x, y;
+
+ void map(const QMatrix4x4 &mat) {
+ Pt r;
+ const float *m = mat.constData();
+ r.x = x * m[0] + y * m[4] + m[12];
+ r.y = x * m[1] + y * m[5] + m[13];
+ x = r.x;
+ y = r.y;
+ }
+
+ void set(float nx, float ny) {
+ x = nx;
+ y = ny;
+ }
+};
+
+inline QDebug operator << (QDebug d, const Pt &p) {
+ d << "Pt(" << p.x << p.y << ")";
+ return d;
+}
+
+
+
+struct Rect {
+ Pt tl, br; // Top-Left (min) and Bottom-Right (max)
+
+ void operator |= (const Pt &pt) {
+ if (pt.x < tl.x)
+ tl.x = pt.x;
+ if (pt.x > br.x)
+ br.x = pt.x;
+ if (pt.y < tl.y)
+ tl.y = pt.y;
+ if (pt.y > br.y)
+ br.y = pt.y;
+ Q_ASSERT(tl.x <= br.x);
+ Q_ASSERT(tl.y <= br.y);
+ }
+
+ void operator |= (const Rect &r) {
+ if (r.tl.x < tl.x)
+ tl.x = r.tl.x;
+ if (r.tl.y < tl.y)
+ tl.y = r.tl.y;
+ if (r.br.x > br.x)
+ br.x = r.br.x;
+ if (r.br.y > br.y)
+ br.y = r.br.y;
+ Q_ASSERT(tl.x <= br.x);
+ Q_ASSERT(tl.y <= br.y);
+ }
+
+ void map(const QMatrix4x4 &m) {
+ tl.map(m);
+ br.map(m);
+ if (br.x < tl.x)
+ qSwap(br.x, tl.x);
+ if (br.y < tl.y)
+ qSwap(br.y, tl.y);
+ }
+
+ void set(float left, float top, float right, float bottom) {
+ tl.set(left, top);
+ br.set(right, bottom);
+ }
+
+ bool intersects(const Rect &r) {
+ bool xOverlap = r.tl.x < br.x && r.br.x > tl.x;
+ bool yOverlap = r.tl.y < br.y && r.br.y > tl.y;
+ return xOverlap && yOverlap;
+ }
+};
+
+inline QDebug operator << (QDebug d, const Rect &r) {
+ d << "Rect(" << r.tl.x << r.tl.y << r.br.x << r.br.y << ")";
+ return d;
+}
+
+struct Buffer {
+ GLuint id;
+ int size;
+ char *data;
+};
+
+struct Element {
+
+ Element(QSGGeometryNode *n)
+ : node(n)
+ , batch(0)
+ , nextInBatch(0)
+ , root(0)
+ , order(0)
+ , boundsComputed(false)
+ , translateOnlyToRoot(false)
+ , removed(false)
+ , orphaned(false)
+ , isRenderNode(false)
+ {
+ }
+
+ inline void ensureBoundsValid() {
+ if (!boundsComputed)
+ computeBounds();
+ }
+ void computeBounds();
+
+ QSGGeometryNode *node;
+ Batch *batch;
+ Element *nextInBatch;
+ Node *root;
+
+ Rect bounds; // in device coordinates
+
+ int order;
+
+ uint boundsComputed : 1;
+ uint translateOnlyToRoot : 1;
+ uint removed : 1;
+ uint orphaned : 1;
+ uint isRenderNode : 1;
+};
+
+struct RenderNodeElement : public Element {
+
+ RenderNodeElement(QSGRenderNode *rn)
+ : Element(0)
+ , renderNode(rn)
+ , fbo(0)
+ {
+ isRenderNode = true;
+ }
+
+ ~RenderNodeElement();
+
+ QSGRenderNode *renderNode;
+ QOpenGLFramebufferObject *fbo;
+};
+
+struct BatchRootInfo {
+ BatchRootInfo() : parentRoot(0), availableOrders(0) { }
+ QSet<Node *> subRoots;
+ Node *parentRoot;
+ int lastOrder;
+ int firstOrder;
+ int availableOrders;
+};
+
+struct ClipBatchRootInfo : public BatchRootInfo
+{
+ QMatrix4x4 matrix;
+};
+
+struct DrawSet
+{
+ DrawSet(int v, int z, int i)
+ : vertices(v)
+ , zorders(z)
+ , indices(i)
+ , indexCount(0)
+ {
+ }
+ DrawSet() : vertices(0), zorders(0), indices(0), indexCount(0) {}
+ int vertices;
+ int zorders;
+ int indices;
+ int indexCount;
+};
+
+struct Batch
+{
+ Batch() : drawSets(1) {}
+ bool geometryWasChanged(QSGGeometryNode *gn);
+ bool isMaterialCompatible(Element *e) const;
+ void invalidate();
+ void cleanupRemovedElements();
+
+ bool isTranslateOnlyToRoot() const;
+
+ // pseudo-constructor...
+ void init() {
+ first = 0;
+ root = 0;
+ vertexCount = 0;
+ indexCount = 0;
+ isOpaque = false;
+ needsUpload = false;
+ merged = false;
+ positionAttribute = -1;
+ uploadedThisFrame = false;
+ isRenderNode = false;
+ }
+
+ Element *first;
+ Node *root;
+
+ int positionAttribute;
+
+ int vertexCount;
+ int indexCount;
+
+ uint isOpaque : 1;
+ uint needsUpload : 1;
+ uint merged : 1;
+ uint isRenderNode : 1;
+
+ mutable uint uploadedThisFrame : 1; // solely for debugging purposes
+
+ Buffer vbo;
+
+ QDataBuffer<DrawSet> drawSets;
+};
+
+struct Node
+{
+ Node(QSGNode *node, Node *sparent = 0)
+ : sgNode(node)
+ , parent(sparent)
+ , data(0)
+ , dirtyState(0)
+ , isOpaque(false)
+ , isBatchRoot(false)
+ {
+
+ }
+
+ QSGNode *sgNode;
+ Node *parent;
+ void *data;
+ QList<Node *> children;
+
+ QSGNode::DirtyState dirtyState;
+
+ uint isOpaque : 1;
+ uint isBatchRoot : 1;
+ uint becameBatchRoot : 1;
+
+ inline QSGNode::NodeType type() const { return sgNode->type(); }
+
+ inline Element *element() const {
+ Q_ASSERT(sgNode->type() == QSGNode::GeometryNodeType);
+ return (Element *) data;
+ }
+
+ inline RenderNodeElement *renderNodeElement() const {
+ Q_ASSERT(sgNode->type() == QSGNode::RenderNodeType);
+ return (RenderNodeElement *) data;
+ }
+
+ inline ClipBatchRootInfo *clipInfo() const {
+ Q_ASSERT(sgNode->type() == QSGNode::ClipNodeType);
+ return (ClipBatchRootInfo *) data;
+ }
+
+ inline BatchRootInfo *rootInfo() const {
+ Q_ASSERT(sgNode->type() == QSGNode::ClipNodeType
+ || (sgNode->type() == QSGNode::TransformNodeType && isBatchRoot));
+ return (BatchRootInfo *) data;
+ }
+};
+
+class Updater : public QSGNodeUpdater
+{
+public:
+ Updater(Renderer *r);
+
+ void visitOpacityNode(Node *n);
+ void visitTransformNode(Node *n);
+ void visitGeometryNode(Node *n);
+ void visitClipNode(Node *n);
+ void updateRootTransforms(Node *n);
+ void updateRootTransforms(Node *n, Node *root, const QMatrix4x4 &combined);
+
+ void updateStates(QSGNode *n);
+ void visitNode(Node *n);
+ void registerWithParentRoot(QSGNode *subRoot, QSGNode *parentRoot);
+
+private:
+ Renderer *renderer;
+
+ QDataBuffer<Node *> m_roots;
+ QDataBuffer<QMatrix4x4> m_rootMatrices;
+
+ int m_added;
+ int m_transformChange;
+
+ QMatrix4x4 m_identityMatrix;
+};
+
+class ShaderManager : public QObject
+{
+ Q_OBJECT
+public:
+ struct Shader {
+ ~Shader() { delete program; }
+ int id_zRange;
+ int pos_order;
+ QSGMaterialShader *program;
+
+ float lastOpacity;
+ };
+
+ ShaderManager() : blitProgram(0) { }
+ ~ShaderManager() {
+ qDeleteAll(rewrittenShaders.values());
+ qDeleteAll(stockShaders.values());
+ }
+
+public slots:
+ void invalidated();
+
+public:
+ Shader *prepareMaterial(QSGMaterial *material);
+ Shader *prepareMaterialNoRewrite(QSGMaterial *material);
+
+ QHash<QSGMaterialType *, Shader *> rewrittenShaders;
+ QHash<QSGMaterialType *, Shader *> stockShaders;
+
+ QOpenGLShaderProgram *blitProgram;
+};
+
+class Q_QUICK_PRIVATE_EXPORT Renderer : public QSGRenderer
+{
+public:
+ Renderer(QSGContext *);
+ ~Renderer();
+
+protected:
+ void nodeChanged(QSGNode *node, QSGNode::DirtyState state);
+ void render();
+
+private:
+ enum RebuildFlag {
+ BuildRenderListsForTaggedRoots = 0x0001,
+ BuildRenderLists = 0x0002,
+ BuildBatches = 0x0004,
+ FullRebuild = 0xffff
+ };
+
+ friend class Updater;
+
+
+ void map(Buffer *buffer, int size);
+ void unmap(Buffer *buffer);
+
+ void buildRenderListsFromScratch();
+ void buildRenderListsForTaggedRoots();
+ void tagSubRoots(Node *node);
+ void buildRenderLists(QSGNode *node);
+
+ void deleteRemovedElements();
+ void cleanupBatches(QDataBuffer<Batch *> *batches);
+ void prepareOpaqueBatches();
+ bool checkOverlap(int first, int last, const Rect &bounds);
+ void prepareAlphaBatches();
+
+ void uploadBatch(Batch *b);
+ void uploadMergedElement(Element *e, int vaOffset, char **vertexData, char **zData, char **indexData, quint16 *iBase, int *indexCount);
+
+ void renderBatches();
+ void renderMergedBatch(const Batch *batch);
+ void renderUnmergedBatch(const Batch *batch);
+ void updateClip(const QSGClipNode *clipList, const Batch *batch);
+ const QMatrix4x4 &matrixForRoot(Node *node);
+ void prepareRenderNode(RenderNodeElement *e);
+ void renderRenderNode(Batch *batch);
+ void setActiveShader(QSGMaterialShader *program, ShaderManager::Shader *shader);
+
+ bool changeBatchRoot(Node *node, Node *newRoot);
+ void registerBatchRoot(Node *childRoot, Node *parentRoot);
+ void removeBatchRootFromParent(Node *childRoot);
+ void nodeChangedBatchRoot(Node *node, Node *root);
+ void turnNodeIntoBatchRoot(Node *node);
+ void nodeWasTransformed(Node *node, int *vertexCount);
+ void nodeWasRemoved(Node *node);
+ void nodeWasAdded(QSGNode *node, Node *shadowParent);
+ BatchRootInfo *batchRootInfo(Node *node);
+
+ inline Batch *newBatch();
+ void invalidateAndRecycleBatch(Batch *b);
+
+ QSet<Node *> m_taggedRoots;
+ QDataBuffer<Element *> m_opaqueRenderList;
+ QDataBuffer<Element *> m_alphaRenderList;
+ int m_nextRenderOrder;
+ bool m_explicitOrdering;
+
+ QHash<QSGRenderNode *, RenderNodeElement *> m_renderNodeElements;
+ QDataBuffer<Batch *> m_opaqueBatches;
+ QDataBuffer<Batch *> m_alphaBatches;
+ QHash<QSGNode *, Node *> m_nodes;
+
+ QDataBuffer<Batch *> m_batchPool;
+ QDataBuffer<Element *> m_elementsToDelete;
+ QDataBuffer<Element *> m_tmpAlphaElements;
+ QDataBuffer<Element *> m_tmpOpaqueElements;
+
+ uint m_rebuild;
+ qreal m_zRange;
+
+ GLuint m_bufferStrategy;
+ int m_batchNodeThreshold;
+ int m_batchVertexThreshold;
+
+ // Stuff used during rendering only...
+ ShaderManager *m_shaderManager;
+ QSGMaterial *m_currentMaterial;
+ QSGMaterialShader *m_currentProgram;
+ ShaderManager::Shader *m_currentShader;
+ const QSGClipNode *m_currentClip;
+};
+
+Batch *Renderer::newBatch()
+{
+ Batch *b;
+ int size = m_batchPool.size();
+ if (size) {
+ b = m_batchPool.at(size - 1);
+ m_batchPool.resize(size - 1);
+ } else {
+ b = new Batch();
+ memset(&b->vbo, 0, sizeof(Buffer));
+ }
+ b->init();
+ return b;
+}
+
+}
+
+QT_END_NAMESPACE
+
+#endif // ULTRARENDERER_H
diff --git a/src/quick/scenegraph/coreapi/qsgdefaultrenderer.cpp b/src/quick/scenegraph/coreapi/qsgdefaultrenderer.cpp
deleted file mode 100644
index a343a81e1a..0000000000
--- a/src/quick/scenegraph/coreapi/qsgdefaultrenderer.cpp
+++ /dev/null
@@ -1,546 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
-** Contact: http://www.qt-project.org/legal
-**
-** This file is part of the QtQml module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and Digia. For licensing terms and
-** conditions see http://qt.digia.com/licensing. For further information
-** use the contact form at http://qt.digia.com/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 2.1 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 2.1 requirements
-** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
-**
-** In addition, as a special exception, Digia gives you certain additional
-** rights. These rights are described in the Digia Qt LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3.0 as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU General Public License version 3.0 requirements will be
-** met: http://www.gnu.org/copyleft/gpl.html.
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-
-#define GL_GLEXT_PROTOTYPES
-
-#include "qsgdefaultrenderer_p.h"
-#include "qsgmaterial.h"
-
-#include <QtCore/qvarlengtharray.h>
-#include <QtGui/qguiapplication.h>
-#include <QtCore/qpair.h>
-#include <QtCore/QElapsedTimer>
-
-//#define FORCE_NO_REORDER
-
-// #define RENDERER_DEBUG
-#ifdef RENDERER_DEBUG
-#define DEBUG_THRESHOLD 0
-QElapsedTimer debugTimer;
-int materialChanges;
-int geometryNodesDrawn;
-#endif
-
-QT_BEGIN_NAMESPACE
-
-static bool nodeLessThan(QSGNode *nodeA, QSGNode *nodeB)
-{
- if (nodeA->type() != nodeB->type())
- return nodeA->type() < nodeB->type();
- if (nodeA->type() != QSGNode::GeometryNodeType)
- return nodeA < nodeB;
- QSGGeometryNode *a = static_cast<QSGGeometryNode *>(nodeA);
- QSGGeometryNode *b = static_cast<QSGGeometryNode *>(nodeB);
-
- // Sort by clip...
- if (a->clipList() != b->clipList())
- return a->clipList() < b->clipList();
-
- // Sort by material definition
- QSGMaterialType *aDef = a->material()->type();
- QSGMaterialType *bDef = b->material()->type();
-
- if (aDef != bDef)
- return aDef < bDef;
-
- // Sort by material state
- int cmp = a->material()->compare(b->material());
- if (cmp != 0)
- return cmp < 0;
-
- return a->matrix() < b->matrix();
-}
-
-static bool nodeLessThanWithRenderOrder(QSGNode *nodeA, QSGNode *nodeB)
-{
- if (nodeA->type() != nodeB->type())
- return nodeA->type() < nodeB->type();
- if (nodeA->type() != QSGNode::GeometryNodeType)
- return nodeA < nodeB;
- QSGGeometryNode *a = static_cast<QSGGeometryNode *>(nodeA);
- QSGGeometryNode *b = static_cast<QSGGeometryNode *>(nodeB);
-
- // Sort by clip...
- if (a->clipList() != b->clipList())
- return a->clipList() < b->clipList();
-
- // Sort by material definition
- QSGMaterialType *aDef = a->material()->type();
- QSGMaterialType *bDef = b->material()->type();
-
- if (!(a->material()->flags() & QSGMaterial::Blending)) {
- int aOrder = a->renderOrder();
- int bOrder = b->renderOrder();
- if (aOrder != bOrder)
- return aOrder > bOrder;
- }
-
- if (aDef != bDef)
- return aDef < bDef;
-
- // Sort by material state
- int cmp = a->material()->compare(b->material());
- if (cmp != 0)
- return cmp < 0;
-
- return a->matrix() < b->matrix();
-}
-
-QSGDefaultRenderer::QSGDefaultRenderer(QSGContext *context)
- : QSGRenderer(context)
- , m_opaqueNodes(64)
- , m_transparentNodes(64)
- , m_renderGroups(4)
- , m_rebuild_lists(false)
- , m_sort_front_to_back(false)
- , m_render_node_added(false)
- , m_currentRenderOrder(1)
-{
-#if defined(QML_RUNTIME_TESTING)
- QStringList args = qApp->arguments();
- m_render_opaque_nodes = !args.contains(QLatin1String("--no-opaque-nodes"));
- m_render_alpha_nodes = !args.contains(QLatin1String("--no-alpha-nodes"));
-#endif
-}
-
-void QSGDefaultRenderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state)
-{
- QSGRenderer::nodeChanged(node, state);
-
- const quint32 rebuildBits = QSGNode::DirtyNodeAdded | QSGNode::DirtyNodeRemoved
- | QSGNode::DirtyMaterial | QSGNode::DirtyOpacity
- | QSGNode::DirtyForceUpdate;
-
- if (state & rebuildBits)
- m_rebuild_lists = true;
-}
-
-void QSGDefaultRenderer::render()
-{
- static bool dumpTree = qApp->arguments().contains(QLatin1String("--dump-tree"));
- if (dumpTree) {
- printf("\n\n");
- QSGNodeDumper::dump(rootNode());
- }
-
-#ifdef RENDERER_DEBUG
- debugTimer.invalidate();
- debugTimer.start();
- geometryNodesDrawn = 0;
- materialChanges = 0;
-#endif
-
- glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
- glDisable(GL_BLEND);
-
- glFrontFace(isMirrored() ? GL_CW : GL_CCW);
- glDisable(GL_CULL_FACE);
-
- glEnable(GL_DEPTH_TEST);
- glDepthMask(true);
- glDepthFunc(GL_LESS);
-#if defined(QT_OPENGL_ES)
- glClearDepthf(1);
-#else
- glClearDepth(1);
-#endif
-
- glDisable(GL_SCISSOR_TEST);
- glClearColor(m_clear_color.redF(), m_clear_color.greenF(), m_clear_color.blueF(), m_clear_color.alphaF());
-
-#ifdef RENDERER_DEBUG
- int debugtimeSetup = debugTimer.elapsed();
-#endif
-
- bindable()->clear(clearMode());
-
-#ifdef RENDERER_DEBUG
- int debugtimeClear = debugTimer.elapsed();
-#endif
-
- QRect r = viewportRect();
- glViewport(r.x(), deviceRect().bottom() - r.bottom(), r.width(), r.height());
- m_current_projection_matrix = projectionMatrix();
- m_current_model_view_matrix.setToIdentity();
-
- m_currentClip = 0;
- glDisable(GL_STENCIL_TEST);
-
- m_currentMaterial = 0;
- m_currentProgram = 0;
- m_currentMatrix = 0;
-
- bool sortNodes = m_rebuild_lists;
-
- if (m_rebuild_lists) {
- m_opaqueNodes.reset();
- m_transparentNodes.reset();
- m_renderGroups.reset();
- m_currentRenderOrder = 1;
- buildLists(rootNode());
- m_rebuild_lists = false;
- m_render_node_added = false;
- RenderGroup group = { m_opaqueNodes.size(), m_transparentNodes.size() };
- m_renderGroups.add(group);
- }
-
-#ifdef RENDERER_DEBUG
- int debugtimeLists = debugTimer.elapsed();
-#endif
-
- if (sortNodes) {
- if (!m_opaqueNodes.isEmpty()) {
- bool (*lessThan)(QSGNode *, QSGNode *);
- lessThan = m_sort_front_to_back ? nodeLessThanWithRenderOrder : nodeLessThan;
- int start = 0;
- for (int i = 0; i < m_renderGroups.size(); ++i) {
- int end = m_renderGroups.at(i).opaqueEnd;
- if (end != start)
- qSort(&m_opaqueNodes.first() + start, &m_opaqueNodes.first() + end, lessThan);
- start = end;
- }
- }
- }
-
-#ifdef RENDERER_DEBUG
- int debugtimeSorting = debugTimer.elapsed();
-#endif
-
- int opaqueStart = 0;
- int transparentStart = 0;
- for (int i = 0; i < m_renderGroups.size(); ++i) {
- int opaqueEnd = m_renderGroups.at(i).opaqueEnd;
- int transparentEnd = m_renderGroups.at(i).transparentEnd;
-
- glDisable(GL_BLEND);
- glDepthMask(true);
-#ifdef QML_RUNTIME_TESTING
- if (m_render_opaque_nodes)
-#endif
- {
-#if defined (QML_RUNTIME_TESTING)
- if (dumpTree)
- qDebug() << "Opaque Nodes:";
-#endif
- if (opaqueEnd != opaqueStart)
- renderNodes(&m_opaqueNodes.first() + opaqueStart, opaqueEnd - opaqueStart);
- }
-
- glEnable(GL_BLEND);
- glDepthMask(false);
-#ifdef QML_RUNTIME_TESTING
- if (m_render_alpha_nodes)
-#endif
- {
-#if defined (QML_RUNTIME_TESTING)
- if (dumpTree)
- qDebug() << "Alpha Nodes:";
-#endif
- if (transparentEnd != transparentStart)
- renderNodes(&m_transparentNodes.first() + transparentStart, transparentEnd - transparentStart);
- }
-
- opaqueStart = opaqueEnd;
- transparentStart = transparentEnd;
- }
-
-#ifdef RENDERER_DEBUG
- int debugtimeRender = debugTimer.elapsed();
-#endif
-
- if (m_currentProgram)
- m_currentProgram->deactivate();
-
-#ifdef RENDERER_DEBUG
- if (debugTimer.elapsed() > DEBUG_THRESHOLD) {
- printf(" --- Renderer breakdown:\n"
- " - setup=%d, clear=%d, building=%d, sorting=%d, render=%d\n"
- " - material changes: total=%d\n"
- " - geometry nodes: total=%d\n",
- debugtimeSetup,
- debugtimeClear - debugtimeSetup,
- debugtimeLists - debugtimeClear,
- debugtimeSorting - debugtimeLists,
- debugtimeRender - debugtimeSorting,
- materialChanges,
- geometryNodesDrawn);
- }
-#endif
-
-}
-
-void QSGDefaultRenderer::setSortFrontToBackEnabled(bool sort)
-{
- printf("setting sorting to... %d\n", sort);
- m_sort_front_to_back = sort;
-}
-
-bool QSGDefaultRenderer::isSortFrontToBackEnabled() const
-{
- return m_sort_front_to_back;
-}
-
-void QSGDefaultRenderer::buildLists(QSGNode *node)
-{
- if (node->isSubtreeBlocked())
- return;
-
- if (node->type() == QSGNode::GeometryNodeType) {
- QSGGeometryNode *geomNode = static_cast<QSGGeometryNode *>(node);
- qreal opacity = geomNode->inheritedOpacity();
- QSGMaterial *m = geomNode->activeMaterial();
-
-#ifdef FORCE_NO_REORDER
- if (true) {
-#else
- if ((m->flags() & QSGMaterial::Blending) || opacity < 1) {
-#endif
- geomNode->setRenderOrder(m_currentRenderOrder - 1);
- m_transparentNodes.add(geomNode);
- } else {
- if (m_render_node_added) {
- // Start new group of nodes so that this opaque node is render on top of the
- // render node.
- RenderGroup group = { m_opaqueNodes.size(), m_transparentNodes.size() };
- m_renderGroups.add(group);
- m_render_node_added = false;
- }
- geomNode->setRenderOrder(m_currentRenderOrder);
- m_opaqueNodes.add(geomNode);
- m_currentRenderOrder += 2;
- }
- } else if (node->type() == QSGNode::RenderNodeType) {
- QSGRenderNode *renderNode = static_cast<QSGRenderNode *>(node);
- m_transparentNodes.add(renderNode);
- m_render_node_added = true;
- }
-
- if (!node->firstChild())
- return;
-
- for (QSGNode *c = node->firstChild(); c; c = c->nextSibling())
- buildLists(c);
-}
-
-void QSGDefaultRenderer::renderNodes(QSGNode *const *nodes, int count)
-{
- const float scale = 1.0f / m_currentRenderOrder;
- int currentRenderOrder = 0x80000000;
- ClipType currentClipType = NoClip;
- QMatrix4x4 projection = projectionMatrix();
- m_current_projection_matrix.setColumn(2, scale * projection.column(2));
-
- //int clipChangeCount = 0;
- //int programChangeCount = 0;
- //int materialChangeCount = 0;
-
- for (int i = 0; i < count; ++i) {
- if (nodes[i]->type() == QSGNode::RenderNodeType) {
- QSGRenderNode *renderNode = static_cast<QSGRenderNode *>(nodes[i]);
-
- if (m_currentProgram)
- m_currentProgram->deactivate();
- m_currentMaterial = 0;
- m_currentProgram = 0;
- m_currentMatrix = 0;
- currentRenderOrder = 0x80000000;
-
- bool changeClip = renderNode->clipList() != m_currentClip;
- // The clip function relies on there not being any depth testing..
- glDisable(GL_DEPTH_TEST);
- if (changeClip) {
- currentClipType = updateStencilClip(renderNode->clipList());
- m_currentClip = renderNode->clipList();
- //++clipChangeCount;
- }
-
- glDepthMask(false);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
-
- QSGRenderNode::RenderState state;
- state.projectionMatrix = &projection;
- state.scissorEnabled = currentClipType & ScissorClip;
- state.stencilEnabled = currentClipType & StencilClip;
- state.scissorRect = m_current_scissor_rect;
- state.stencilValue = m_current_stencil_value;
-
- renderNode->render(state);
-
- QSGRenderNode::StateFlags changes = renderNode->changedStates();
- if (changes & QSGRenderNode::ViewportState) {
- QRect r = viewportRect();
- glViewport(r.x(), deviceRect().bottom() - r.bottom(), r.width(), r.height());
- }
- if (changes & QSGRenderNode::StencilState) {
- glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
- glStencilMask(0xff);
- glDisable(GL_STENCIL_TEST);
- }
- if (changes & (QSGRenderNode::StencilState | QSGRenderNode::ScissorState)) {
- glDisable(GL_SCISSOR_TEST);
- m_currentClip = 0;
- currentClipType = NoClip;
- }
- if (changes & QSGRenderNode::DepthState) {
-#if defined(QT_OPENGL_ES)
- glClearDepthf(1);
-#else
- glClearDepth(1);
-#endif
- if (m_clear_mode & QSGRenderer::ClearDepthBuffer) {
- glDepthMask(true);
- glClear(GL_DEPTH_BUFFER_BIT);
- }
- glDepthMask(false);
- glDepthFunc(GL_LESS);
- }
- if (changes & QSGRenderNode::ColorState)
- bindable()->reactivate();
- if (changes & QSGRenderNode::BlendState) {
- glEnable(GL_BLEND);
- glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
- }
- if (changes & QSGRenderNode::CullState) {
- glFrontFace(isMirrored() ? GL_CW : GL_CCW);
- glDisable(GL_CULL_FACE);
- }
-
- glEnable(GL_DEPTH_TEST);
-
- m_current_model_view_matrix.setToIdentity();
- m_current_determinant = 1;
- } else if (nodes[i]->type() == QSGNode::GeometryNodeType) {
- QSGGeometryNode *geomNode = static_cast<QSGGeometryNode *>(nodes[i]);
-
- QSGMaterialShader::RenderState::DirtyStates updates;
-
-#if defined (QML_RUNTIME_TESTING)
- static bool dumpTree = qApp->arguments().contains(QLatin1String("--dump-tree"));
- if (dumpTree)
- qDebug() << geomNode;
-#endif
-
- bool changeMatrix = m_currentMatrix != geomNode->matrix();
-
- if (changeMatrix) {
- m_currentMatrix = geomNode->matrix();
- if (m_currentMatrix)
- m_current_model_view_matrix = *m_currentMatrix;
- else
- m_current_model_view_matrix.setToIdentity();
- m_current_determinant = m_current_model_view_matrix.determinant();
- updates |= QSGMaterialShader::RenderState::DirtyMatrix;
- }
-
- bool changeOpacity = m_current_opacity != geomNode->inheritedOpacity();
- if (changeOpacity) {
- updates |= QSGMaterialShader::RenderState::DirtyOpacity;
- m_current_opacity = geomNode->inheritedOpacity();
- }
-
- Q_ASSERT(geomNode->activeMaterial());
-
- QSGMaterial *material = geomNode->activeMaterial();
- QSGMaterialShader *program = m_context->prepareMaterial(material);
- Q_ASSERT(program->program()->isLinked());
-
- bool changeClip = geomNode->clipList() != m_currentClip;
- if (changeClip) {
- // The clip function relies on there not being any depth testing..
- glDisable(GL_DEPTH_TEST);
- currentClipType = updateStencilClip(geomNode->clipList());
- glEnable(GL_DEPTH_TEST);
- m_currentClip = geomNode->clipList();
-#ifdef FORCE_NO_REORDER
- glDepthMask(false);
-#else
- glDepthMask((material->flags() & QSGMaterial::Blending) == 0 && m_current_opacity == 1);
-#endif
- //++clipChangeCount;
- }
-
- bool changeProgram = (changeClip && (currentClipType & StencilClip)) || m_currentProgram != program;
- if (changeProgram) {
- if (m_currentProgram)
- m_currentProgram->deactivate();
- m_currentProgram = program;
- m_currentProgram->activate();
- //++programChangeCount;
- updates |= (QSGMaterialShader::RenderState::DirtyMatrix | QSGMaterialShader::RenderState::DirtyOpacity);
-
-#ifdef RENDERER_DEBUG
- materialChanges++;
-#endif
- }
-
- bool changeRenderOrder = currentRenderOrder != geomNode->renderOrder();
- if (changeRenderOrder) {
- currentRenderOrder = geomNode->renderOrder();
- m_current_projection_matrix.setColumn(3, projection.column(3)
- + (m_currentRenderOrder - 1 - 2 * currentRenderOrder)
- * m_current_projection_matrix.column(2));
- updates |= QSGMaterialShader::RenderState::DirtyMatrix;
- }
-
- if (changeProgram || m_currentMaterial != material) {
- program->updateState(state(updates), material, changeProgram ? 0 : m_currentMaterial);
- m_currentMaterial = material;
- //++materialChangeCount;
- }
-
- //glDepthRange((geomNode->renderOrder() + 0.1) * scale, (geomNode->renderOrder() + 0.9) * scale);
-
- const QSGGeometry *g = geomNode->geometry();
- draw(program, g);
-
-#ifdef RENDERER_DEBUG
- geometryNodesDrawn++;
-#endif
- }
- }
- //qDebug("Clip: %i, shader program: %i, material: %i times changed while drawing %s items",
- // clipChangeCount, programChangeCount, materialChangeCount,
- // &list == &m_transparentNodes ? "transparent" : "opaque");
-}
-
-QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/coreapi/qsgdefaultrenderer_p.h b/src/quick/scenegraph/coreapi/qsgdefaultrenderer_p.h
deleted file mode 100644
index 6fba0d18bc..0000000000
--- a/src/quick/scenegraph/coreapi/qsgdefaultrenderer_p.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
-** Contact: http://www.qt-project.org/legal
-**
-** This file is part of the QtQml module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and Digia. For licensing terms and
-** conditions see http://qt.digia.com/licensing. For further information
-** use the contact form at http://qt.digia.com/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 2.1 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 2.1 requirements
-** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
-**
-** In addition, as a special exception, Digia gives you certain additional
-** rights. These rights are described in the Digia Qt LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3.0 as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU General Public License version 3.0 requirements will be
-** met: http://www.gnu.org/copyleft/gpl.html.
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#ifndef QSGDEFAULTRENDERER_P_H
-#define QSGDEFAULTRENDERER_P_H
-
-#include "qsgrenderer_p.h"
-
-#include <QtGui/private/qdatabuffer_p.h>
-#include "qsgrendernode_p.h"
-
-QT_BEGIN_NAMESPACE
-
-class Q_QUICK_PRIVATE_EXPORT QSGDefaultRenderer : public QSGRenderer
-{
- Q_OBJECT
-public:
- QSGDefaultRenderer(QSGContext *context);
-
- void render();
-
- void nodeChanged(QSGNode *node, QSGNode::DirtyState state);
-
- void setSortFrontToBackEnabled(bool sort);
- bool isSortFrontToBackEnabled() const;
-
-private:
- void buildLists(QSGNode *node);
- void renderNodes(QSGNode *const *nodes, int count);
-
- const QSGClipNode *m_currentClip;
- QSGMaterial *m_currentMaterial;
- QSGMaterialShader *m_currentProgram;
- const QMatrix4x4 *m_currentMatrix;
- QDataBuffer<QSGNode *> m_opaqueNodes;
- QDataBuffer<QSGNode *> m_transparentNodes;
- struct RenderGroup { int opaqueEnd, transparentEnd; };
- QDataBuffer<RenderGroup> m_renderGroups;
-
- bool m_rebuild_lists;
- bool m_sort_front_to_back;
- bool m_render_node_added;
- int m_currentRenderOrder;
-
-#ifdef QML_RUNTIME_TESTING
- bool m_render_opaque_nodes;
- bool m_render_alpha_nodes;
-#endif
-};
-
-QT_END_NAMESPACE
-
-#endif // QMLRENDERER_H
diff --git a/src/quick/scenegraph/coreapi/qsgmaterial.cpp b/src/quick/scenegraph/coreapi/qsgmaterial.cpp
index affe357799..713469f94a 100644
--- a/src/quick/scenegraph/coreapi/qsgmaterial.cpp
+++ b/src/quick/scenegraph/coreapi/qsgmaterial.cpp
@@ -234,14 +234,6 @@ QSGMaterialShader::QSGMaterialShader()
void QSGMaterialShader::activate()
{
- Q_ASSERT(program()->isLinked());
-
- program()->bind();
- char const *const *attr = attributeNames();
- for (int i = 0; attr[i]; ++i) {
- if (*attr[i])
- program()->enableAttributeArray(i);
- }
}
@@ -256,11 +248,6 @@ void QSGMaterialShader::activate()
void QSGMaterialShader::deactivate()
{
- char const *const *attr = attributeNames();
- for (int i = 0; attr[i]; ++i) {
- if (*attr[i])
- program()->disableAttributeArray(i);
- }
}
@@ -603,6 +590,7 @@ QSGMaterial::~QSGMaterial()
the full matrix of the geometry nodes for rendering.
\value CustomCompileStep Starting with Qt 5.2, the scene graph will not always call
+
QSGMaterialShader::compile() when its shader program is compiled and linked.
Set this flag to enforce that the function is called.
diff --git a/src/quick/scenegraph/coreapi/qsgmaterial.h b/src/quick/scenegraph/coreapi/qsgmaterial.h
index 00970a5801..3483ae59c8 100644
--- a/src/quick/scenegraph/coreapi/qsgmaterial.h
+++ b/src/quick/scenegraph/coreapi/qsgmaterial.h
@@ -49,6 +49,10 @@ QT_BEGIN_NAMESPACE
class QSGMaterial;
+namespace QSGBatchRenderer {
+ class ShaderManager;
+}
+
class Q_QUICK_EXPORT QSGMaterialShader
{
public:
@@ -95,8 +99,8 @@ public:
inline QOpenGLShaderProgram *program() { return &m_program; }
protected:
-
friend class QSGContext;
+ friend class QSGBatchRenderer::ShaderManager;
virtual void compile();
virtual void initialize() { }
diff --git a/src/quick/scenegraph/coreapi/qsgnode.cpp b/src/quick/scenegraph/coreapi/qsgnode.cpp
index a4a47de216..a55a5abb0a 100644
--- a/src/quick/scenegraph/coreapi/qsgnode.cpp
+++ b/src/quick/scenegraph/coreapi/qsgnode.cpp
@@ -367,7 +367,7 @@ QSGNode::~QSGNode()
bool QSGNode::isSubtreeBlocked() const
{
- return m_subtreeRenderableCount == 0;
+ return false;
}
/*!
@@ -656,10 +656,6 @@ void QSGNode::setFlags(Flags f, bool enabled)
void QSGNode::markDirty(DirtyState bits)
{
- m_dirtyState |= (bits & DirtyPropagationMask);
-
- DirtyState subtreeBits = DirtyState((bits & DirtyPropagationMask) << 16);
-
int renderableCountDiff = 0;
if (bits & DirtyNodeAdded)
renderableCountDiff += m_subtreeRenderableCount;
@@ -668,7 +664,6 @@ void QSGNode::markDirty(DirtyState bits)
QSGNode *p = m_parent;
while (p) {
- p->m_dirtyState |= subtreeBits;
p->m_subtreeRenderableCount += renderableCountDiff;
if (p->type() == RootNodeType)
static_cast<QSGRootNode *>(p)->notifyNodeChange(this, bits);
@@ -1309,7 +1304,7 @@ QSGOpacityNode::~QSGOpacityNode()
Returns this opacity node's opacity.
*/
-
+const qreal OPACITY_THRESHOLD = 0.001;
/*!
Sets the opacity of this node to \a opacity.
@@ -1325,8 +1320,14 @@ void QSGOpacityNode::setOpacity(qreal opacity)
opacity = qBound<qreal>(0, opacity, 1);
if (m_opacity == opacity)
return;
+ DirtyState dirtyState = DirtyOpacity;
+
+ if ((m_opacity < OPACITY_THRESHOLD && opacity > OPACITY_THRESHOLD)
+ || (m_opacity > OPACITY_THRESHOLD && opacity < OPACITY_THRESHOLD))
+ dirtyState |= DirtySubtreeBlocked;
+
m_opacity = opacity;
- markDirty(DirtyOpacity);
+ markDirty(dirtyState);
}
@@ -1370,7 +1371,7 @@ void QSGOpacityNode::setCombinedOpacity(qreal opacity)
bool QSGOpacityNode::isSubtreeBlocked() const
{
- return QSGNode::isSubtreeBlocked() || m_opacity < 0.001;
+ return m_opacity < OPACITY_THRESHOLD;
}
@@ -1584,6 +1585,16 @@ QDebug operator<<(QDebug d, const QSGNode *n)
case QSGNode::OpacityNodeType:
d << static_cast<const QSGOpacityNode *>(n);
break;
+ case QSGNode::RenderNodeType:
+ d << "RenderNode(" << hex << (void *) n << dec
+ << "dirty=" << hex << (int) n->dirtyState()
+ << "flags=" << (int) n->flags() << dec
+ << (n->isSubtreeBlocked() ? "*BLOCKED*" : "");
+#ifdef QSG_RUNTIME_DESCRIPTION
+ d << QSGNodePrivate::description(n);
+#endif
+ d << ')';
+ break;
default:
d << "Node(" << hex << (void *) n << dec
<< "dirty=" << hex << (int) n->dirtyState()
diff --git a/src/quick/scenegraph/coreapi/qsgnode.h b/src/quick/scenegraph/coreapi/qsgnode.h
index 26af56f3d0..3475206945 100644
--- a/src/quick/scenegraph/coreapi/qsgnode.h
+++ b/src/quick/scenegraph/coreapi/qsgnode.h
@@ -3,7 +3,7 @@
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
-** This file is part of the QtQml module of the Qt Toolkit.
+** This file is part of the QtQuick module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
@@ -62,6 +62,11 @@ class QSGNodePrivate;
class QSGBasicGeometryNodePrivate;
class QSGGeometryNodePrivate;
+namespace QSGBatchRenderer {
+ class Renderer;
+ class Updater;
+}
+
class Q_QUICK_EXPORT QSGNode
{
public:
@@ -82,16 +87,19 @@ public:
OwnedByParent = 0x0001,
UsePreprocess = 0x0002,
- // Upper 16 bits reserved for node subclasses
+ // 0x00ff0000 bits reserved for node subclasses
// QSGBasicGeometryNode
OwnsGeometry = 0x00010000,
OwnsMaterial = 0x00020000,
OwnsOpaqueMaterial = 0x00040000
+
+ // Uppermost 8 bits are reserved for internal use.
};
Q_DECLARE_FLAGS(Flags, Flag)
enum DirtyStateBit {
+ DirtySubtreeBlocked = 0x0080,
DirtyMatrix = 0x0100,
DirtyNodeAdded = 0x0400,
DirtyNodeRemoved = 0x0800,
@@ -152,6 +160,7 @@ protected:
private:
friend class QSGRootNode;
+ friend class QSGBatchRenderer::Renderer;
void init();
void destroy();
@@ -165,7 +174,7 @@ private:
int m_subtreeRenderableCount;
Flags m_nodeFlags;
- DirtyState m_dirtyState;
+ DirtyState m_dirtyState; // Obsolete, remove in Qt 6
protected:
friend class QSGNodePrivate;
@@ -195,6 +204,8 @@ protected:
private:
friend class QSGNodeUpdater;
+ friend class QSGBatchRenderer::Updater;
+
QSGGeometry *m_geometry;
int m_reserved_start_index;
diff --git a/src/quick/scenegraph/coreapi/qsgrendernode_p.h b/src/quick/scenegraph/coreapi/qsgrendernode_p.h
index 1f1bc23123..e90e2abd87 100644
--- a/src/quick/scenegraph/coreapi/qsgrendernode_p.h
+++ b/src/quick/scenegraph/coreapi/qsgrendernode_p.h
@@ -58,6 +58,10 @@
QT_BEGIN_NAMESPACE
+namespace QSGBatchRenderer {
+ class Renderer;
+}
+
class Q_QUICK_PRIVATE_EXPORT QSGRenderNode : public QSGNode
{
public:
@@ -98,6 +102,7 @@ public:
private:
friend class QSGNodeUpdater;
+ friend class QSGBatchRenderer::Renderer;
const QMatrix4x4 *m_matrix;
const QSGClipNode *m_clip_list;
diff --git a/src/quick/scenegraph/coreapi/qsgshaderrewriter.cpp b/src/quick/scenegraph/coreapi/qsgshaderrewriter.cpp
new file mode 100644
index 0000000000..830dbbbd7d
--- /dev/null
+++ b/src/quick/scenegraph/coreapi/qsgshaderrewriter.cpp
@@ -0,0 +1,290 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore>
+
+// Duct Tape tokenizer for the purpose of parsing and rewriting
+// shader source code
+
+QT_BEGIN_NAMESPACE
+
+namespace QSGShaderRewriter {
+
+struct Tokenizer {
+
+ enum Token {
+ Token_Invalid,
+ Token_Void,
+ Token_OpenBrace,
+ Token_CloseBrace,
+ Token_SemiColon,
+ Token_Identifier,
+ Token_Macro,
+ Token_Unspecified,
+
+ Token_EOF
+ };
+
+ static const char *NAMES[];
+
+ void initialize(const char *input);
+ Token next();
+
+ const char *stream;
+ const char *pos;
+ const char *identifier;
+};
+
+const char *Tokenizer::NAMES[] = {
+ "Invalid",
+ "Void",
+ "OpenBrace",
+ "CloseBrace",
+ "SemiColon",
+ "Identifier",
+ "Macro",
+ "Unspecified",
+ "EOF"
+};
+
+void Tokenizer::initialize(const char *input)
+{
+ stream = input;
+ pos = input;
+ identifier = input;
+}
+
+#define foo
+
+
+Tokenizer::Token Tokenizer::next()
+{
+ while (*pos != 0) {
+ char c = *pos++;
+ switch (c) {
+ case '/':
+
+ if (*pos == '/') {
+ // '//' comment
+ ++pos;
+ while (*pos != 0 && *pos != '\n') ++pos;
+ if (*pos != 0) ++pos; // skip the newline
+
+ } else if (*pos == '*') {
+ // /* */ comment
+ ++pos;
+ while (*pos != 0 && *pos != '*' && pos[1] != '/') ++pos;
+ if (*pos != 0) pos += 2;
+ }
+ break;
+
+ case '#': {
+ while (*pos != 0) {
+ if (*pos == '\n') {
+ ++pos;
+ break;
+ } else if (*pos == '\\') {
+ ++pos;
+ while (*pos != 0 && (*pos == ' ' || *pos == '\t'))
+ ++pos;
+ if (*pos != 0 && (*pos == '\n' || (*pos == '\r' && pos[1] == '\n')))
+ pos+=2;
+ } else {
+ ++pos;
+ }
+ }
+ break;
+ }
+
+ case 'v': {
+ if (*pos == 'o' && pos[1] == 'i' && pos[2] == 'd') {
+ pos += 3;
+ return Token_Void;
+ }
+ }
+
+ case ';': return Token_SemiColon;
+ case 0: return Token_EOF;
+ case '{': return Token_OpenBrace;
+ case '}': return Token_CloseBrace;
+
+ case ' ':
+ case '\n':
+ case '\r': break;
+ default:
+ // Identifier...
+ if ((c >= 'a' && c <= 'z' ) || (c >= 'A' && c <= 'Z' ) || c == '_') {
+ identifier = pos - 1;
+ while (*pos != 0 && ((*pos >= 'a' && *pos <= 'z')
+ || (*pos >= 'A' && *pos <= 'Z')
+ || *pos == '_'
+ || (*pos >= '0' && *pos <= '9'))) {
+ ++pos;
+ }
+ return Token_Identifier;
+ } else {
+ return Token_Unspecified;
+ }
+ }
+ }
+
+ return Token_Invalid;
+}
+
+}
+
+using namespace QSGShaderRewriter;
+
+QByteArray qsgShaderRewriter_insertZAttributes(const char *input)
+{
+ Tokenizer tok;
+ tok.initialize(input);
+
+ Tokenizer::Token lt = tok.next();
+ Tokenizer::Token t = tok.next();
+
+ // First find "void main() { ... "
+ while (t != Tokenizer::Token_EOF) {
+ if (lt == Tokenizer::Token_Void && t == Tokenizer::Token_Identifier) {
+ if (qstrncmp("main", tok.identifier, 4) == 0)
+ break;
+ }
+ lt = t;
+ t = tok.next();
+ }
+
+ // Find first brace '{'
+ while (t != Tokenizer::Token_EOF && t != Tokenizer::Token_OpenBrace) t = tok.next();
+ int braceDepth = 1;
+ t = tok.next();
+
+ // Find matching brace and insert our code there...
+ while (t != Tokenizer::Token_EOF) {
+ switch (t) {
+ case Tokenizer::Token_CloseBrace:
+ braceDepth--;
+ if (braceDepth == 0) {
+ QByteArray result;
+ result.reserve(1024);
+ result += "attribute highp float _qt_order;\n";
+ result += "uniform highp float _qt_zRange;\n";
+ result += QByteArray::fromRawData(input, tok.pos - 1 - input);
+ result += " gl_Position.z = gl_Position.z * _qt_zRange + _qt_order;\n";
+ result += QByteArray(tok.pos - 1);
+ return result;
+ }
+ break;
+ case Tokenizer::Token_OpenBrace:
+ ++braceDepth;
+ break;
+ default:
+ break;
+ }
+ t = tok.next();
+ }
+ return QByteArray();
+}
+
+#ifdef QSGSHADERREWRITER_STANDALONE
+
+const char *selftest =
+ "#define highp lowp stuff \n"
+ "#define multiline \\ \n"
+ " continue defining multiline \n"
+ " \n"
+ "attribute highp vec4 qt_Position; \n"
+ "attribute highp vec2 qt_TexCoord; \n"
+ " \n"
+ "uniform highp mat4 qt_Matrix; \n"
+ " \n"
+ "varying lowp vec2 vTexCoord; \n"
+ " \n"
+ "// commented out main(){} \n"
+ "/* commented out main() { } again */ \n"
+ "/* \n"
+ " multline comment with main() { } \n"
+ " */ \n"
+ " \n"
+ "void main() { \n"
+ " gl_Position = qt_Matrix * qt_Position; \n"
+ " vTexCoord = qt_TexCoord; \n"
+ " if (gl_Position < 0) { \n"
+ " vTexCoord.y = -vTexCoord.y; \n"
+ " } \n"
+ "} \n"
+ "";
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+
+ QString fileName;
+ QStringList args = app.arguments();
+
+ QByteArray content;
+
+ for (int i=0; i<args.length(); ++i) {
+ const QString &a = args.at(i);
+ if (a == QStringLiteral("--file") && i < args.length() - 1) {
+ qDebug() << "Reading file: " << args.at(i);
+ QFile file(args.at(++i));
+ if (!file.open(QFile::ReadOnly)) {
+ qDebug() << "Error: failed to open file," << file.errorString();
+ return 1;
+ }
+ content = file.readAll();
+ } else if (a == QStringLiteral("--selftest")) {
+ qDebug() << "doing a selftest";
+ content = QByteArray(selftest);
+ } else if (a == QStringLiteral("--help") || a == QStringLiteral("-h")) {
+ qDebug() << "usage:" << endl
+ << " --file [name] A vertex shader file to rewrite" << endl;
+ }
+ }
+
+ QByteArray rewritten = qsgShaderRewriter_insertZAttributes(content);
+
+ qDebug() << "Rewritten to:";
+ qDebug() << rewritten.constData();
+}
+#endif
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcontext.cpp b/src/quick/scenegraph/qsgcontext.cpp
index cb0a6d5afc..070a8422d0 100644
--- a/src/quick/scenegraph/qsgcontext.cpp
+++ b/src/quick/scenegraph/qsgcontext.cpp
@@ -40,7 +40,7 @@
****************************************************************************/
#include <QtQuick/private/qsgcontext_p.h>
-#include <QtQuick/private/qsgdefaultrenderer_p.h>
+#include <QtQuick/private/qsgbatchrenderer_p.h>
#include <QtQuick/private/qsgdistancefieldutil_p.h>
#include <QtQuick/private/qsgdefaultdistancefieldglyphcache_p.h>
#include <QtQuick/private/qsgdefaultrectanglenode_p.h>
@@ -50,6 +50,7 @@
#include <QtQuick/private/qsgdistancefieldglyphnode_p_p.h>
#include <QtQuick/private/qsgshareddistancefieldglyphcache_p.h>
#include <QtQuick/QSGFlatColorMaterial>
+#include <QtQuick/private/qsgatlastexture_p.h>
#include <QtQuick/private/qsgtexture_p.h>
#include <QtQuick/private/qquickpixmapcache_p.h>
@@ -114,6 +115,7 @@ public:
#else
, distanceFieldAntialiasing(QSGGlyphNode::GrayAntialiasing)
#endif
+ , atlasManager(0)
, flashMode(qmlFlashMode())
, distanceFieldDisabled(qmlDisableDistanceField())
{
@@ -134,6 +136,8 @@ public:
QSGDistanceFieldGlyphNode::AntialiasingMode distanceFieldAntialiasing;
+ QSGAtlasTexture::Manager *atlasManager;
+
bool flashMode;
float renderAlpha;
bool distanceFieldDisabled;
@@ -196,6 +200,25 @@ void QSGContext::invalidate()
d->gl = 0;
emit invalidated();
+
+ /* The cleanup of the atlas textures is a bit intruiging.
+ As part of the cleanup in the threaded render loop, we
+ do:
+ 1. call this function
+ 2. call QCoreApp::sendPostedEvents() to immediately process
+ any pending deferred deletes.
+ 3. delete the GL context.
+ As textures need the atlas manager while cleaning up, the
+ manager needs to be cleaned up after the textures, so
+ we post a deleteLater here at the very bottom so it gets
+ deferred deleted last.
+
+ Another alternative would be to use a QPointer in
+ QSGAtlasTexture::Texture, but this seemed simpler.
+ */
+
+ d->atlasManager->deleteLater();
+ d->atlasManager = 0;
}
@@ -260,6 +283,8 @@ void QSGContext::initialize(QOpenGLContext *context)
if (requested.stencilBufferSize() > 0 && actual.stencilBufferSize() <= 0)
qWarning("QSGContext::initialize: stencil buffer support missing, expect rendering errors");
+ d->atlasManager = new QSGAtlasTexture::Manager();
+
Q_ASSERT(!d->gl);
d->gl = context;
@@ -414,7 +439,7 @@ QSGGlyphNode *QSGContext::createGlyphNode()
*/
QSGRenderer *QSGContext::createRenderer()
{
- return new QSGDefaultRenderer(this);
+ return new QSGBatchRenderer::Renderer(this);
}
@@ -441,10 +466,11 @@ QSurfaceFormat QSGContext::defaultSurfaceFormat() const
QSGTexture *QSGContext::createTexture(const QImage &image) const
{
- QSGPlainTexture *t = new QSGPlainTexture();
- if (!image.isNull())
- t->setImage(image);
- return t;
+ Q_D(const QSGContext);
+ QSGTexture *at = d->atlasManager->create(image);
+ if (at)
+ return at;
+ return createTextureNoAtlas(image);
}
QSGTexture *QSGContext::createTextureNoAtlas(const QImage &image) const
@@ -455,7 +481,6 @@ QSGTexture *QSGContext::createTextureNoAtlas(const QImage &image) const
return t;
}
-
/*!
Returns the minimum supported framebuffer object size.
*/
diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp
index 6edb3fe05d..d91c6f5823 100644
--- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp
+++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp
@@ -606,7 +606,7 @@ void QSGRenderThread::syncAndRender()
#ifndef QSG_NO_RENDER_TIMING
if (qsg_render_timing)
- qDebug("window Time: sinceLast=%d, sync=%d, first render=%d, after final swap=%d",
+ qDebug("Render Thread: framedelta=%d, sync=%d, first render=%d, after final swap=%d",
int(sinceLastTime/1000000),
int(syncTime/1000000),
int((renderTime - syncTime)/1000000),
@@ -1030,7 +1030,7 @@ void QSGThreadedRenderLoop::polishAndSync()
#ifndef QSG_NO_RENDER_TIMING
if (qsg_render_timing)
- qDebug(" - polish=%d, wait=%d, sync=%d -- animations=%d",
+ qDebug(" - on GUI: polish=%d, lock=%d, block/sync=%d -- animations=%d",
int(polishTime/1000000),
int((waitTime - polishTime)/1000000),
int((syncTime - waitTime)/1000000),
diff --git a/src/quick/scenegraph/scenegraph.pri b/src/quick/scenegraph/scenegraph.pri
index 34432ffd9c..3b8221264b 100644
--- a/src/quick/scenegraph/scenegraph.pri
+++ b/src/quick/scenegraph/scenegraph.pri
@@ -2,7 +2,7 @@
# Core API
HEADERS += \
- $$PWD/coreapi/qsgdefaultrenderer_p.h \
+ $$PWD/coreapi/qsgbatchrenderer_p.h \
$$PWD/coreapi/qsggeometry.h \
$$PWD/coreapi/qsgmaterial.h \
$$PWD/coreapi/qsgnode.h \
@@ -13,18 +13,20 @@ HEADERS += \
$$PWD/coreapi/qsggeometry_p.h
SOURCES += \
- $$PWD/coreapi/qsgdefaultrenderer.cpp \
+ $$PWD/coreapi/qsgbatchrenderer.cpp \
$$PWD/coreapi/qsggeometry.cpp \
$$PWD/coreapi/qsgmaterial.cpp \
$$PWD/coreapi/qsgnode.cpp \
$$PWD/coreapi/qsgnodeupdater.cpp \
$$PWD/coreapi/qsgrenderer.cpp \
$$PWD/coreapi/qsgrendernode.cpp \
+ $$PWD/coreapi/qsgshaderrewriter.cpp \
scenegraph/util/qsgsimplematerial.cpp
# Util API
HEADERS += \
$$PWD/util/qsgareaallocator_p.h \
+ $$PWD/util/qsgatlastexture_p.h \
$$PWD/util/qsgdepthstencilbuffer_p.h \
$$PWD/util/qsgflatcolormaterial.h \
$$PWD/util/qsgsimplematerial.h \
@@ -41,6 +43,7 @@ HEADERS += \
SOURCES += \
$$PWD/util/qsgareaallocator.cpp \
+ $$PWD/util/qsgatlastexture.cpp \
$$PWD/util/qsgdepthstencilbuffer.cpp \
$$PWD/util/qsgflatcolormaterial.cpp \
$$PWD/util/qsgsimplerectnode.cpp \
diff --git a/src/quick/scenegraph/util/qsgatlastexture.cpp b/src/quick/scenegraph/util/qsgatlastexture.cpp
new file mode 100644
index 0000000000..ad90911b9c
--- /dev/null
+++ b/src/quick/scenegraph/util/qsgatlastexture.cpp
@@ -0,0 +1,416 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsgatlastexture_p.h"
+
+#include <QtCore/QVarLengthArray>
+#include <QtCore/QElapsedTimer>
+
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QGuiApplication>
+#include <QtGui/QScreen>
+
+#include <private/qsgtexture_p.h>
+
+#ifndef GL_BGRA
+#define GL_BGRA 0x80E1
+#endif
+
+
+#ifndef QSG_NO_RENDERER_TIMING
+static bool qsg_render_timing = !qgetenv("QSG_RENDERER_TIMING").isEmpty();
+#endif
+
+namespace QSGAtlasTexture
+{
+
+static inline int qsg_powerOfTwo(int v)
+{
+ v--;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+ ++v;
+ return v;
+}
+
+static int qsg_envInt(const char *name, int defaultValue)
+{
+ QByteArray content = qgetenv(name);
+
+ bool ok = false;
+ int value = content.toInt(&ok);
+ return ok ? value : defaultValue;
+}
+
+Manager::Manager()
+ : m_atlas(0)
+{
+ QSize screenSize = QGuiApplication::primaryScreen()->geometry().size();
+ int max;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
+ int w = qMin(max, qsg_envInt("QSG_ATLAS_WIDTH", qsg_powerOfTwo(screenSize.width())));
+ int h = qMin(max, qsg_envInt("QSG_ATLAS_HEIGHT", qsg_powerOfTwo(screenSize.height())));
+
+ m_atlas_size_limit = qsg_envInt("QSG_ATLAS_SIZE_LIMIT", qMax(w, h) / 2);
+ m_atlas_size = QSize(w, h);
+}
+
+
+Manager::~Manager()
+{
+ invalidate();
+}
+
+void Manager::invalidate()
+{
+ delete m_atlas;
+ m_atlas = 0;
+}
+
+QSGTexture *Manager::create(const QImage &image)
+{
+ QSGTexture *t = 0;
+ if (image.width() < m_atlas_size_limit && image.height() < m_atlas_size_limit) {
+ if (!m_atlas)
+ m_atlas = new Atlas(m_atlas_size);
+ t = m_atlas->create(image);
+ if (t)
+ return t;
+ }
+
+ return t;
+}
+
+Atlas::Atlas(const QSize &size)
+ : m_allocator(size)
+ , m_texture_id(0)
+ , m_size(size)
+ , m_filtering(QSGTexture::Linear)
+ , m_allocated(false)
+{
+
+#ifdef QT_OPENGL_ES
+ const char *ext = (const char *) glGetString(GL_EXTENSIONS);
+ if (strstr(ext, "GL_EXT_bgra")
+ || strstr(ext, "GL_EXT_texture_format_BGRA8888")
+ || strstr(ext, "GL_IMG_texture_format_BGRA8888")) {
+ m_internalFormat = m_externalFormat = GL_BGRA;
+ } else {
+ m_internalFormat = m_externalFormat = GL_RGBA;
+ }
+#else
+ m_internalFormat = GL_RGBA;
+ m_externalFormat = GL_BGRA;
+#endif
+
+ m_use_bgra_fallback = qEnvironmentVariableIsSet("QSG_ATLAS_USE_BGRA_FALLBACK");
+ m_debug_overlay = qEnvironmentVariableIsSet("QSG_ATLAS_OVERLAY");
+}
+
+Atlas::~Atlas()
+{
+ if (m_texture_id)
+ glDeleteTextures(1, &m_texture_id);
+}
+
+
+Texture *Atlas::create(const QImage &image)
+{
+ QRect rect = m_allocator.allocate(QSize(image.width() + 2, image.height() + 2));
+ if (rect.width() > 0 && rect.height() > 0) {
+ Texture *t = new Texture(this, rect, image);
+ m_pending_uploads << t;
+ return t;
+ }
+ return 0;
+}
+
+
+int Atlas::textureId() const
+{
+ if (!m_texture_id) {
+ Q_ASSERT(QOpenGLContext::currentContext());
+ glGenTextures(1, &const_cast<Atlas *>(this)->m_texture_id);
+ }
+
+ return m_texture_id;
+}
+
+static void swizzleBGRAToRGBA(QImage *image)
+{
+ const int width = image->width();
+ const int height = image->height();
+ uint *p = (uint *) image->bits();
+ int stride = image->bytesPerLine() / 4;
+ for (int i = 0; i < height; ++i) {
+ for (int x = 0; x < width; ++x)
+ p[x] = ((p[x] << 16) & 0xff0000) | ((p[x] >> 16) & 0xff) | (p[x] & 0xff00ff00);
+ p += stride;
+ }
+}
+
+void Atlas::upload(Texture *texture)
+{
+ const QImage &image = texture->image();
+ const QRect &r = texture->atlasSubRect();
+
+ QImage tmp(r.width(), r.height(), QImage::Format_ARGB32_Premultiplied);
+ {
+ QPainter p(&tmp);
+ p.setCompositionMode(QPainter::CompositionMode_Source);
+
+ int w = r.width();
+ int h = r.height();
+ int iw = image.width();
+ int ih = image.height();
+
+ p.drawImage(1, 1, image);
+ p.drawImage(1, 0, image, 0, 0, iw, 1);
+ p.drawImage(1, h - 1, image, 0, ih - 1, iw, 1);
+ p.drawImage(0, 1, image, 0, 0, 1, ih);
+ p.drawImage(w - 1, 1, image, iw - 1, 0, 1, ih);
+ p.drawImage(0, 0, image, 0, 0, 1, 1);
+ p.drawImage(0, h - 1, image, 0, ih - 1, 1, 1);
+ p.drawImage(w - 1, 0, image, iw - 1, 0, 1, 1);
+ p.drawImage(w - 1, h - 1, image, iw - 1, ih - 1, 1, 1);
+ if (m_debug_overlay) {
+ p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
+ p.fillRect(0, 0, iw, ih, QBrush(QColor::fromRgbF(1, 0, 1, 0.5)));
+ }
+ }
+
+ if (m_externalFormat == GL_RGBA)
+ swizzleBGRAToRGBA(&tmp);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, r.x(), r.y(), r.width(), r.height(), m_externalFormat, GL_UNSIGNED_BYTE, tmp.constBits());
+}
+
+void Atlas::uploadBgra(Texture *texture)
+{
+ const QRect &r = texture->atlasSubRect();
+ QImage image = texture->image();
+
+ if (image.format() != QImage::Format_ARGB32_Premultiplied
+ || image.format() != QImage::Format_RGB32) {
+ image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
+ }
+
+ if (m_debug_overlay) {
+ QPainter p(&image);
+ p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
+ p.fillRect(0, 0, image.width(), image.height(), QBrush(QColor::fromRgbF(0, 1, 1, 0.5)));
+ }
+
+ QVarLengthArray<quint32, 512> tmpBits(qMax(image.width() + 2, image.height() + 2));
+ int iw = image.width();
+ int ih = image.height();
+ int bpl = image.bytesPerLine() / 4;
+ const quint32 *src = (const quint32 *) image.constBits();
+ quint32 *dst = tmpBits.data();
+
+ // top row, padding corners
+ dst[0] = src[0];
+ memcpy(dst + 1, src, iw * sizeof(quint32));
+ dst[1 + iw] = src[iw-1];
+ glTexSubImage2D(GL_TEXTURE_2D, 0, r.x(), r.y(), iw + 2, 1, m_externalFormat, GL_UNSIGNED_BYTE, dst);
+
+ // bottom row, padded corners
+ const quint32 *lastRow = src + bpl * (ih - 1);
+ dst[0] = lastRow[0];
+ memcpy(dst + 1, lastRow, iw * sizeof(quint32));
+ dst[1 + iw] = lastRow[iw-1];
+ glTexSubImage2D(GL_TEXTURE_2D, 0, r.x(), r.y() + ih + 1, iw + 2, 1, m_externalFormat, GL_UNSIGNED_BYTE, dst);
+
+ // left column
+ for (int i=0; i<ih; ++i)
+ dst[i] = src[i * bpl];
+ glTexSubImage2D(GL_TEXTURE_2D, 0, r.x(), r.y() + 1, 1, ih, m_externalFormat, GL_UNSIGNED_BYTE, dst);
+
+ // right column
+ for (int i=0; i<ih; ++i)
+ dst[i] = src[i * bpl + iw - 1];
+ glTexSubImage2D(GL_TEXTURE_2D, 0, r.x() + iw + 1, r.y() + 1, 1, ih, m_externalFormat, GL_UNSIGNED_BYTE, dst);
+
+ // Inner part of the image....
+ glTexSubImage2D(GL_TEXTURE_2D, 0, r.x() + 1, r.y() + 1, r.width() - 2, r.height() - 2, m_externalFormat, GL_UNSIGNED_BYTE, src);
+
+}
+
+bool Atlas::bind(QSGTexture::Filtering filtering)
+{
+ bool forceUpdate = false;
+ if (!m_allocated) {
+ m_allocated = true;
+
+ while (glGetError() != GL_NO_ERROR) ;
+
+ glGenTextures(1, &m_texture_id);
+ glBindTexture(GL_TEXTURE_2D, m_texture_id);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_size.width(), m_size.height(), 0, m_externalFormat, GL_UNSIGNED_BYTE, 0);
+
+#if 0
+ QImage pink(m_size.width(), m_size.height(), QImage::Format_ARGB32_Premultiplied);
+ pink.fill(0);
+ QPainter p(&pink);
+ QLinearGradient redGrad(0, 0, m_size.width(), 0);
+ redGrad.setColorAt(0, Qt::black);
+ redGrad.setColorAt(1, Qt::red);
+ p.fillRect(0, 0, m_size.width(), m_size.height(), redGrad);
+ p.setCompositionMode(QPainter::CompositionMode_Plus);
+ QLinearGradient blueGrad(0, 0, 0, m_size.height());
+ blueGrad.setColorAt(0, Qt::black);
+ blueGrad.setColorAt(1, Qt::blue);
+ p.fillRect(0, 0, m_size.width(), m_size.height(), blueGrad);
+ p.end();
+
+ glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_size.width(), m_size.height(), 0, m_externalFormat, GL_UNSIGNED_BYTE, pink.constBits());
+#endif
+
+ GLenum errorCode = glGetError();
+ if (errorCode == GL_OUT_OF_MEMORY) {
+ qDebug("QSGTextureAtlas: texture atlas allocation failed, out of memory");
+ glDeleteTextures(1, &m_texture_id);
+ m_texture_id = 0;
+ } else if (errorCode != GL_NO_ERROR) {
+ qDebug("QSGTextureAtlas: texture atlas allocation failed, code=%x", errorCode);
+ glDeleteTextures(1, &m_texture_id);
+ m_texture_id = 0;
+ }
+ forceUpdate = true;
+ } else {
+ glBindTexture(GL_TEXTURE_2D, m_texture_id);
+ }
+
+ if (m_texture_id == 0)
+ return false;
+
+ // Upload all pending images..
+ for (int i=0; i<m_pending_uploads.size(); ++i) {
+
+#ifndef QSG_NO_RENDERER_TIMING
+ QElapsedTimer timer;
+ if (qsg_render_timing)
+ timer.start();
+#endif
+
+ if (m_externalFormat == GL_BGRA &&
+ !m_use_bgra_fallback) {
+ uploadBgra(m_pending_uploads.at(i));
+ } else {
+ upload(m_pending_uploads.at(i));
+ }
+#ifndef QSG_NO_RENDERER_TIMING
+ if (qsg_render_timing) {
+ printf(" - AtlasTexture(%dx%d), uploaded in %d ms\n",
+ m_pending_uploads.at(i)->image().width(),
+ m_pending_uploads.at(i)->image().height(),
+ (int) timer.elapsed());
+ }
+#endif
+ }
+
+ if (filtering != m_filtering) {
+ GLenum f = filtering == QSGTexture::Nearest ? GL_NEAREST : GL_LINEAR;
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, f);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, f);
+ m_filtering = filtering;
+ }
+
+ m_pending_uploads.clear();
+
+ return forceUpdate;
+}
+
+void Atlas::remove(Texture *t)
+{
+ QRect atlasRect = t->atlasSubRect();
+ m_allocator.deallocate(atlasRect);
+
+ m_pending_uploads.removeOne(t);
+}
+
+
+
+Texture::Texture(Atlas *atlas, const QRect &textureRect, const QImage &image)
+ : QSGTexture()
+ , m_allocated_rect(textureRect)
+ , m_image(image)
+ , m_atlas(atlas)
+ , m_nonatlas_texture(0)
+{
+ m_allocated_rect_without_padding = m_allocated_rect.adjusted(1, 1, -1, -1);
+ float w = atlas->size().width();
+ float h = atlas->size().height();
+
+ m_texture_coords_rect = QRectF(m_allocated_rect_without_padding.x() / w,
+ m_allocated_rect_without_padding.y() / h,
+ m_allocated_rect_without_padding.width() / w,
+ m_allocated_rect_without_padding.height() / h);
+}
+
+Texture::~Texture()
+{
+ m_atlas->remove(this);
+ if (m_nonatlas_texture)
+ delete m_nonatlas_texture;
+}
+
+void Texture::bind()
+{
+ m_atlas->bind(filtering());
+}
+
+QSGTexture *Texture::removedFromAtlas() const
+{
+ if (!m_nonatlas_texture) {
+ m_nonatlas_texture = new QSGPlainTexture();
+ m_nonatlas_texture->setImage(m_image);
+ }
+ return m_nonatlas_texture;
+}
+
+}
diff --git a/src/quick/scenegraph/util/qsgatlastexture_p.h b/src/quick/scenegraph/util/qsgatlastexture_p.h
new file mode 100644
index 0000000000..f8edd96f47
--- /dev/null
+++ b/src/quick/scenegraph/util/qsgatlastexture_p.h
@@ -0,0 +1,156 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSGATLASTEXTURE_P_H
+#define QSGATLASTEXTURE_P_H
+
+#include <QtCore/QSize>
+
+#include <QtGui/qopengl.h>
+
+#include <QtQuick/QSGTexture>
+#include <QtQuick/private/qsgtexture_p.h>
+#include <QtQuick/private/qsgareaallocator_p.h>
+
+namespace QSGAtlasTexture
+{
+
+class Texture;
+class Atlas;
+
+class Manager : public QObject
+{
+ Q_OBJECT
+
+public:
+ Manager();
+ ~Manager();
+
+ QSGTexture *create(const QImage &image);
+ void invalidate();
+
+private:
+ Atlas *m_atlas;
+ Atlas *m_secondary_atlas;
+
+ QSize m_atlas_size;
+ int m_atlas_size_limit;
+};
+
+class Atlas
+{
+public:
+ Atlas(const QSize &size);
+ ~Atlas();
+
+ void initialize();
+
+ int textureId() const;
+ bool bind(QSGTexture::Filtering filteing);
+
+ void upload(Texture *texture);
+ void uploadBgra(Texture *texture);
+
+ Texture *create(const QImage &image);
+ void remove(Texture *t);
+
+ QSize size() const { return m_size; }
+
+private:
+
+ QSGAreaAllocator m_allocator;
+ GLuint m_texture_id;
+ QSize m_size;
+ QList<Texture *> m_pending_uploads;
+
+ GLuint m_internalFormat;
+ GLuint m_externalFormat;
+
+ QSGTexture::Filtering m_filtering;
+
+ uint m_allocated : 1;
+ uint m_use_bgra_fallback: 1;
+
+ uint m_debug_overlay : 1;
+};
+
+class Texture : public QSGTexture
+{
+ Q_OBJECT
+public:
+ Texture(Atlas *atlas, const QRect &textureRect, const QImage &image);
+ ~Texture();
+
+ int textureId() const { return m_atlas->textureId(); }
+ QSize textureSize() const { return m_allocated_rect_without_padding.size(); }
+ bool hasAlphaChannel() const { return m_image.hasAlphaChannel(); }
+ bool hasMipmaps() const { return false; }
+ bool isAtlasTexture() const { return true; }
+
+ QRectF normalizedTextureSubRect() const { return m_texture_coords_rect; }
+
+ QRect atlasSubRect() const { return m_allocated_rect; }
+ QRect atlasSubRectWithoutPadding() const { return m_allocated_rect_without_padding; }
+
+ bool isTexture() const { return true; }
+
+ QSGTexture *removedFromAtlas() const;
+
+ const QImage &image() const { return m_image; }
+
+ void bind();
+
+private:
+ QRect m_allocated_rect_without_padding;
+ QRect m_allocated_rect;
+ QRectF m_texture_coords_rect;
+
+ QImage m_image;
+
+ Atlas *m_atlas;
+
+ mutable QSGPlainTexture *m_nonatlas_texture;
+
+};
+
+}
+
+#endif
diff --git a/tests/auto/quick/nodes/nodes.pro b/tests/auto/quick/nodes/nodes.pro
index 2e3a4455ea..256318a441 100644
--- a/tests/auto/quick/nodes/nodes.pro
+++ b/tests/auto/quick/nodes/nodes.pro
@@ -6,5 +6,5 @@ SOURCES += tst_nodestest.cpp
CONFIG+=parallel_test
-QT += core-private gui-private qml-private quick-private opengl widgets testlib
+QT += core-private gui-private qml-private quick-private testlib
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0
diff --git a/tests/auto/quick/nodes/tst_nodestest.cpp b/tests/auto/quick/nodes/tst_nodestest.cpp
index a8094002dd..3ba7771538 100644
--- a/tests/auto/quick/nodes/tst_nodestest.cpp
+++ b/tests/auto/quick/nodes/tst_nodestest.cpp
@@ -42,12 +42,15 @@
#include <QtCore/QString>
#include <QtTest/QtTest>
+#include <QtGui/QOffscreenSurface>
+#include <QtGui/QOpenGLContext>
+
#include <QtQuick/qsgnode.h>
-#include <QtQuick/private/qsgrenderer_p.h>
+#include <QtQuick/private/qsgbatchrenderer_p.h>
#include <QtQuick/private/qsgnodeupdater_p.h>
#include <QtQuick/qsgsimplerectnode.h>
-#include <QtOpenGL/QGLWidget>
+
class NodesTest : public QObject
{
Q_OBJECT
@@ -57,41 +60,45 @@ public:
private Q_SLOTS:
void initTestCase();
- void cleanupTestCase() {
- delete widget;
- }
+ void cleanupTestCase();
// Root nodes
void propegate();
void propegateWithMultipleRoots();
- void simulatedEffect_data();
- void simulatedEffect();
// Opacity nodes
void basicOpacityNode();
void opacityPropegation();
- // QSGNodeUpdater
void isBlockedCheck();
private:
- QGLWidget *widget;
-
- QSGNodeUpdater updater;
+ QOffscreenSurface *surface;
+ QOpenGLContext *context;
};
void NodesTest::initTestCase()
{
- widget = new QGLWidget();
- widget->resize(100, 30);
- widget->show();
+ surface = new QOffscreenSurface;
+ surface->create();
+
+ context = new QOpenGLContext();
+ context->create();
+ context->makeCurrent(surface);
}
-class DummyRenderer : public QSGRenderer
+void NodesTest::cleanupTestCase()
+{
+ context->doneCurrent();
+ delete context;
+ delete surface;
+}
+
+class DummyRenderer : public QSGBatchRenderer::Renderer
{
public:
DummyRenderer(QSGRootNode *root)
- : QSGRenderer(QSGContext::createDefaultContext())
+ : QSGBatchRenderer::Renderer(QSGContext::createDefaultContext())
, changedNode(0)
, changedState(0)
, renderCount(0)
@@ -107,7 +114,7 @@ public:
void nodeChanged(QSGNode *node, QSGNode::DirtyState state) {
changedNode = node;
changedState = state;
- QSGRenderer::nodeChanged(node, state);
+ QSGBatchRenderer::Renderer::nodeChanged(node, state);
}
QSGNode *changedNode;
@@ -163,114 +170,6 @@ void NodesTest::propegateWithMultipleRoots()
QCOMPARE((int) ren2.changedState, (int) QSGNode::DirtyGeometry);
}
-
-
-class SimulatedEffectRenderer : public DummyRenderer
-{
-public:
- SimulatedEffectRenderer(QSGRootNode *root, QSGBasicGeometryNode *c)
- : DummyRenderer(root)
- {
- child = c;
- }
-
- void render() {
- matrix = child->matrix() ? *child->matrix() : QMatrix4x4();
- DummyRenderer::render();
- }
-
- QSGBasicGeometryNode *child;
- QMatrix4x4 matrix;
-};
-
-
-class PseudoEffectNode : public QSGNode {
-public:
- PseudoEffectNode(QSGRenderer *r)
- : renderer(r)
- {
- setFlag(UsePreprocess);
- }
-
- void preprocess() {
-
- if (renderer->rootNode()->parent()) {
- // Mark the root dirty to build a clean state from the root and down
- renderer->rootNode()->markDirty(QSGNode::DirtyForceUpdate);
- }
-
- renderer->renderScene();
-
- if (renderer->rootNode()->parent()) {
- // Mark the parent of the root dirty to force the root and down to be updated.
- renderer->rootNode()->parent()->markDirty(QSGNode::DirtyForceUpdate);
- }
- }
-
- QSGRenderer *renderer;
-};
-
-void NodesTest::simulatedEffect_data()
-{
- QTest::addColumn<bool>("connected");
-
- QTest::newRow("connected") << true;
- QTest::newRow("disconnected") << false;
-}
-
-void NodesTest::simulatedEffect()
-{
- QFETCH(bool, connected);
-
- QSGRootNode root;
- QSGRootNode source;
- QSGTransformNode xform;
- QSGSimpleRectNode geometry;
- geometry.setRect(QRectF(0, 0, 1, 1));
- geometry.setColor(Qt::red);
-
- root.setFlag(QSGNode::OwnedByParent, false);
- source.setFlag(QSGNode::OwnedByParent, false);
- xform.setFlag(QSGNode::OwnedByParent, false);
- geometry.setFlag(QSGNode::OwnedByParent, false);
-
- SimulatedEffectRenderer rootRenderer(&root, &geometry);
- SimulatedEffectRenderer sourceRenderer(&source, &geometry);
-
- PseudoEffectNode effect(&sourceRenderer);
-
- /*
- root Source is redirected into effect using the SimulatedEffectRenderer
- / \
- xform effect
- |
- source
- |
- geometry
- */
-
- root.appendChildNode(&xform);
- root.appendChildNode(&effect);
- if (connected)
- xform.appendChildNode(&source);
- source.appendChildNode(&geometry);
- QMatrix4x4 m; m.translate(1, 2, 3);
- xform.setMatrix(m);
-
- // Clear all dirty states...
- updater.updateStates(&root);
-
- rootRenderer.renderScene();
-
- // compare that we got one render call to each
- QCOMPARE(rootRenderer.renderCount, 1);
- QCOMPARE(sourceRenderer.renderCount, 1);
- QVERIFY(sourceRenderer.renderingOrder < rootRenderer.renderingOrder);
- if (connected) // geometry is not rendered in this case, so skip it...
- QCOMPARE(rootRenderer.matrix, xform.matrix());
- QCOMPARE(sourceRenderer.matrix, QMatrix4x4());
-}
-
void NodesTest::basicOpacityNode()
{
QSGOpacityNode n;
@@ -296,6 +195,8 @@ void NodesTest::opacityPropegation()
QSGSimpleRectNode *geometry = new QSGSimpleRectNode;
geometry->setRect(0, 0, 100, 100);
+ DummyRenderer renderer(&root);
+
root.appendChildNode(a);
a->appendChildNode(b);
b->appendChildNode(c);
@@ -305,7 +206,7 @@ void NodesTest::opacityPropegation()
b->setOpacity(0.8);
c->setOpacity(0.7);
- updater.updateStates(&root);
+ renderer.renderScene();
QCOMPARE(a->combinedOpacity(), 0.9);
QCOMPARE(b->combinedOpacity(), 0.9 * 0.8);
@@ -313,7 +214,7 @@ void NodesTest::opacityPropegation()
QCOMPARE(geometry->inheritedOpacity(), 0.9 * 0.8 * 0.7);
b->setOpacity(0.1);
- updater.updateStates(&root);
+ renderer.renderScene();
QCOMPARE(a->combinedOpacity(), 0.9);
QCOMPARE(b->combinedOpacity(), 0.9 * 0.1);
@@ -321,7 +222,7 @@ void NodesTest::opacityPropegation()
QCOMPARE(geometry->inheritedOpacity(), 0.9 * 0.1 * 0.7);
b->setOpacity(0);
- updater.updateStates(&root);
+ renderer.renderScene();
QVERIFY(b->isSubtreeBlocked());