aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/plugins.pro2
-rw-r--r--src/plugins/scenegraph/scenegraph.pro2
-rw-r--r--src/plugins/scenegraph/softwarecontext/context.cpp216
-rw-r--r--src/plugins/scenegraph/softwarecontext/context.h98
-rw-r--r--src/plugins/scenegraph/softwarecontext/glyphnode.cpp91
-rw-r--r--src/plugins/scenegraph/softwarecontext/glyphnode.h49
-rw-r--r--src/plugins/scenegraph/softwarecontext/imagenode.cpp423
-rw-r--r--src/plugins/scenegraph/softwarecontext/imagenode.h108
-rw-r--r--src/plugins/scenegraph/softwarecontext/ninepatchnode.cpp66
-rw-r--r--src/plugins/scenegraph/softwarecontext/ninepatchnode.h45
-rw-r--r--src/plugins/scenegraph/softwarecontext/painternode.cpp189
-rw-r--r--src/plugins/scenegraph/softwarecontext/painternode.h96
-rw-r--r--src/plugins/scenegraph/softwarecontext/pixmaptexture.cpp56
-rw-r--r--src/plugins/scenegraph/softwarecontext/pixmaptexture.h44
-rw-r--r--src/plugins/scenegraph/softwarecontext/pluginmain.cpp54
-rw-r--r--src/plugins/scenegraph/softwarecontext/pluginmain.h47
-rw-r--r--src/plugins/scenegraph/softwarecontext/rectanglenode.cpp219
-rw-r--r--src/plugins/scenegraph/softwarecontext/rectanglenode.h61
-rw-r--r--src/plugins/scenegraph/softwarecontext/renderingvisitor.cpp169
-rw-r--r--src/plugins/scenegraph/softwarecontext/renderingvisitor.h55
-rw-r--r--src/plugins/scenegraph/softwarecontext/renderloop.cpp251
-rw-r--r--src/plugins/scenegraph/softwarecontext/renderloop.h72
-rw-r--r--src/plugins/scenegraph/softwarecontext/softwarecontext.json3
-rw-r--r--src/plugins/scenegraph/softwarecontext/softwarecontext.pro43
-rw-r--r--src/plugins/scenegraph/softwarecontext/softwarelayer.cpp219
-rw-r--r--src/plugins/scenegraph/softwarecontext/softwarelayer.h86
-rw-r--r--src/plugins/scenegraph/softwarecontext/threadedrenderloop.cpp1129
-rw-r--r--src/plugins/scenegraph/softwarecontext/threadedrenderloop.h97
28 files changed, 3990 insertions, 0 deletions
diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro
new file mode 100644
index 0000000000..e0aabc4098
--- /dev/null
+++ b/src/plugins/plugins.pro
@@ -0,0 +1,2 @@
+TEMPLATE = subdirs
+SUBDIRS += scenegraph
diff --git a/src/plugins/scenegraph/scenegraph.pro b/src/plugins/scenegraph/scenegraph.pro
new file mode 100644
index 0000000000..807b6871b3
--- /dev/null
+++ b/src/plugins/scenegraph/scenegraph.pro
@@ -0,0 +1,2 @@
+TEMPLATE = subdirs
+SUBDIRS += softwarecontext
diff --git a/src/plugins/scenegraph/softwarecontext/context.cpp b/src/plugins/scenegraph/softwarecontext/context.cpp
new file mode 100644
index 0000000000..33dc7e87e4
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/context.cpp
@@ -0,0 +1,216 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "context.h"
+
+#include "rectanglenode.h"
+#include "imagenode.h"
+#include "painternode.h"
+#include "pixmaptexture.h"
+#include "glyphnode.h"
+#include "ninepatchnode.h"
+#include "renderingvisitor.h"
+#include "softwarelayer.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QElapsedTimer>
+
+#include <QtGui/QWindow>
+
+#include <QtQuick/QSGFlatColorMaterial>
+#include <QtQuick/QSGVertexColorMaterial>
+#include <QtQuick/QSGOpaqueTextureMaterial>
+#include <QtQuick/QSGTextureMaterial>
+#include <private/qsgdefaultimagenode_p.h>
+#include <private/qsgdefaultrectanglenode_p.h>
+#include <private/qsgdistancefieldglyphnode_p_p.h>
+#include <private/qsgdefaultglyphnode_p.h>
+
+#ifndef QSG_NO_RENDERER_TIMING
+static bool qsg_render_timing = !qgetenv("QSG_RENDER_TIMING").isEmpty();
+#endif
+
+namespace SoftwareContext
+{
+
+Renderer::Renderer(QSGRenderContext *context)
+ : QSGRenderer(context)
+{
+}
+
+void Renderer::renderScene(GLuint fboId)
+{
+ class B : public QSGBindable
+ {
+ public:
+ void bind() const { }
+ } bindable;
+ QSGRenderer::renderScene(bindable);
+}
+
+void Renderer::render()
+{
+ QWindow *currentWindow = static_cast<RenderContext*>(m_context)->currentWindow;
+ if (!m_backingStore)
+ m_backingStore.reset(new QBackingStore(currentWindow));
+
+ if (m_backingStore->size() != currentWindow->size())
+ m_backingStore->resize(currentWindow->size());
+
+ const QRect rect(0, 0, currentWindow->width(), currentWindow->height());
+ m_backingStore->beginPaint(rect);
+
+ QPaintDevice *device = m_backingStore->paintDevice();
+ QPainter painter(device);
+ painter.setRenderHint(QPainter::Antialiasing);
+
+ painter.fillRect(rect, clearColor());
+ RenderingVisitor(&painter).visitChildren(rootNode());
+
+ m_backingStore->endPaint();
+ m_backingStore->flush(rect);
+}
+
+void Renderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state)
+{
+
+ QSGRenderer::nodeChanged(node, state);
+}
+
+PixmapRenderer::PixmapRenderer(QSGRenderContext *context)
+ : QSGRenderer(context)
+{
+
+}
+
+void PixmapRenderer::renderScene(GLuint)
+{
+ Q_UNREACHABLE();
+}
+
+void PixmapRenderer::render()
+{
+ Q_UNREACHABLE();
+}
+
+void PixmapRenderer::render(QPixmap *target)
+{
+ const QRect rect(0, 0, target->width(), target->height());
+ target->fill(clearColor());
+ QPainter painter(target);
+ painter.setRenderHint(QPainter::Antialiasing);
+
+ RenderingVisitor(&painter).visitChildren(rootNode());
+}
+
+RenderContext::RenderContext(QSGContext *ctx)
+ : QSGRenderContext(ctx)
+ , currentWindow(0)
+ , m_initialized(false)
+{
+}
+Context::Context(QObject *parent)
+ : QSGContext(parent)
+{
+ setDistanceFieldEnabled(false);
+}
+
+QSGRectangleNode *Context::createRectangleNode()
+{
+ return new RectangleNode();
+}
+
+QSGImageNode *Context::createImageNode()
+{
+ return new ImageNode();
+}
+
+QSGPainterNode *Context::createPainterNode(QQuickPaintedItem *item)
+{
+ return new PainterNode(item);
+}
+
+QSGGlyphNode *Context::createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode)
+{
+ Q_UNUSED(rc);
+ Q_UNUSED(preferNativeGlyphNode);
+ return new GlyphNode();
+}
+
+QSGNinePatchNode *Context::createNinePatchNode()
+{
+ return new NinePatchNode();
+}
+
+QSGLayer *Context::createLayer(QSGRenderContext *renderContext)
+{
+ return new SoftwareLayer(renderContext);
+}
+
+QSurfaceFormat Context::defaultSurfaceFormat() const
+{
+ QSurfaceFormat format = QSurfaceFormat::defaultFormat();
+ format.setRenderableType(QSurfaceFormat::DefaultRenderableType);
+ format.setMajorVersion(0);
+ format.setMinorVersion(0);
+ return format;
+}
+
+void RenderContext::initialize(QOpenGLContext *context)
+{
+ Q_UNUSED(context)
+ Q_UNREACHABLE();
+}
+
+void RenderContext::initializeIfNeeded()
+{
+ if (m_initialized)
+ return;
+ m_initialized = true;
+ emit initialized();
+}
+
+void RenderContext::invalidate()
+{
+ QSGRenderContext::invalidate();
+}
+
+QSGTexture *RenderContext::createTexture(const QImage &image) const
+{
+ return new PixmapTexture(image);
+}
+
+QSGTexture *RenderContext::createTextureNoAtlas(const QImage &image) const
+{
+ return new PixmapTexture(image);
+}
+
+QSGRenderer *RenderContext::createRenderer()
+{
+ return new Renderer(this);
+}
+
+
+void RenderContext::renderNextFrame(QSGRenderer *renderer, GLuint fbo)
+{
+ QSGRenderContext::renderNextFrame(renderer, fbo);
+}
+
+} // namespace
diff --git a/src/plugins/scenegraph/softwarecontext/context.h b/src/plugins/scenegraph/softwarecontext/context.h
new file mode 100644
index 0000000000..88fe4e447f
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/context.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef CONTEXT_H
+#define CONTEXT_H
+
+#include <private/qsgcontext_p.h>
+#include <private/qsgrenderer_p.h>
+#include <private/qsgadaptationlayer_p.h>
+#include <QtCore/QElapsedTimer>
+#include <QtGui/QOpenGLShaderProgram>
+#include <QtGui/QBackingStore>
+
+namespace SoftwareContext
+{
+
+class Renderer : public QSGRenderer
+{
+public:
+ Renderer(QSGRenderContext *context);
+
+ virtual void renderScene(GLuint fboId = 0);
+
+ virtual void render();
+
+ void nodeChanged(QSGNode *node, QSGNode::DirtyState state);
+
+ QBackingStore *backingStore() const { return m_backingStore.data(); }
+
+private:
+ QScopedPointer<QBackingStore> m_backingStore;
+ QRect m_dirtyRect;
+};
+
+class PixmapRenderer : public QSGRenderer
+{
+public:
+ PixmapRenderer(QSGRenderContext *context);
+
+ virtual void renderScene(GLuint fboId = 0);
+
+ virtual void render();
+
+ void render(QPixmap *target);
+};
+
+class RenderContext : public QSGRenderContext
+{
+public:
+ RenderContext(QSGContext *ctx);
+ void initialize(QOpenGLContext *context);
+ void initializeIfNeeded();
+ void invalidate();
+ void renderNextFrame(QSGRenderer *renderer, GLuint fbo);
+ QSGTexture *createTexture(const QImage &image) const;
+ QSGTexture *createTextureNoAtlas(const QImage &image) const;
+ QSGRenderer *createRenderer();
+
+ QWindow *currentWindow;
+ bool m_initialized;
+};
+
+class Context : public QSGContext
+{
+ Q_OBJECT
+public:
+ explicit Context(QObject *parent = 0);
+
+ QSGRenderContext *createRenderContext() { return new RenderContext(this); }
+
+ virtual QSGRectangleNode *createRectangleNode();
+ virtual QSGImageNode *createImageNode();
+ virtual QSGPainterNode *createPainterNode(QQuickPaintedItem *item);
+ virtual QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode);
+ virtual QSGNinePatchNode *createNinePatchNode();
+ virtual QSGLayer *createLayer(QSGRenderContext *renderContext);
+ virtual QSurfaceFormat defaultSurfaceFormat() const;
+};
+
+} // namespace
+
+#endif // CONTEXT_H
diff --git a/src/plugins/scenegraph/softwarecontext/glyphnode.cpp b/src/plugins/scenegraph/softwarecontext/glyphnode.cpp
new file mode 100644
index 0000000000..eae8b626cb
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/glyphnode.cpp
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "glyphnode.h"
+
+GlyphNode::GlyphNode()
+ : m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 0)
+ , m_style(QQuickText::Normal)
+{
+ setMaterial((QSGMaterial*)1);
+ setGeometry(&m_geometry);
+}
+
+
+void GlyphNode::setGlyphs(const QPointF &position, const QGlyphRun &glyphs)
+{
+ m_position = position;
+ m_glyphRun = glyphs;
+}
+
+void GlyphNode::setColor(const QColor &color)
+{
+ m_color = color;
+}
+
+void GlyphNode::setStyle(QQuickText::TextStyle style)
+{
+ m_style = style;
+}
+
+void GlyphNode::setStyleColor(const QColor &color)
+{
+ m_styleColor = color;
+}
+
+QPointF GlyphNode::baseLine() const
+{
+ return QPointF();
+}
+
+void GlyphNode::setPreferredAntialiasingMode(QSGGlyphNode::AntialiasingMode)
+{
+}
+
+void GlyphNode::update()
+{
+}
+
+void GlyphNode::paint(QPainter *painter)
+{
+ painter->setBrush(QBrush());
+ QPointF pos = m_position - QPointF(0, m_glyphRun.rawFont().ascent());
+
+ switch (m_style) {
+ case QQuickText::Normal: break;
+ case QQuickText::Outline:
+ painter->setPen(m_styleColor);
+ painter->drawGlyphRun(pos + QPointF(0, 1), m_glyphRun);
+ painter->drawGlyphRun(pos + QPointF(0, -1), m_glyphRun);
+ painter->drawGlyphRun(pos + QPointF(1, 0), m_glyphRun);
+ painter->drawGlyphRun(pos + QPointF(-1, 0), m_glyphRun);
+ break;
+ case QQuickText::Raised:
+ painter->setPen(m_styleColor);
+ painter->drawGlyphRun(pos + QPointF(0, 1), m_glyphRun);
+ break;
+ case QQuickText::Sunken:
+ painter->setPen(m_styleColor);
+ painter->drawGlyphRun(pos + QPointF(0, -1), m_glyphRun);
+ break;
+ }
+
+ painter->setPen(m_color);
+ painter->drawGlyphRun(pos, m_glyphRun);
+}
diff --git a/src/plugins/scenegraph/softwarecontext/glyphnode.h b/src/plugins/scenegraph/softwarecontext/glyphnode.h
new file mode 100644
index 0000000000..d34013be5b
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/glyphnode.h
@@ -0,0 +1,49 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef GLYPHNODE_H
+#define GLYPHNODE_H
+
+#include <private/qsgadaptationlayer_p.h>
+
+class GlyphNode : public QSGGlyphNode
+{
+public:
+ GlyphNode();
+
+ virtual void setGlyphs(const QPointF &position, const QGlyphRun &glyphs);
+ virtual void setColor(const QColor &color);
+ virtual void setStyle(QQuickText::TextStyle style);
+ virtual void setStyleColor(const QColor &color);
+ virtual QPointF baseLine() const;
+ virtual void setPreferredAntialiasingMode(AntialiasingMode);
+ virtual void update();
+
+ void paint(QPainter *painter);
+
+private:
+ QPointF m_position;
+ QGlyphRun m_glyphRun;
+ QColor m_color;
+ QSGGeometry m_geometry;
+ QQuickText::TextStyle m_style;
+ QColor m_styleColor;
+};
+
+#endif // GLYPHNODE_H
diff --git a/src/plugins/scenegraph/softwarecontext/imagenode.cpp b/src/plugins/scenegraph/softwarecontext/imagenode.cpp
new file mode 100644
index 0000000000..7718831b65
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/imagenode.cpp
@@ -0,0 +1,423 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "imagenode.h"
+
+#include "pixmaptexture.h"
+#include "softwarelayer.h"
+#include <QPainter>
+#include <qmath.h>
+
+// Helper from widgets/styles/qdrawutil.cpp
+
+namespace SoftwareContext {
+
+void qDrawBorderPixmap(QPainter *painter, const QRect &targetRect, const QMargins &targetMargins,
+ const QPixmap &pixmap, const QRect &sourceRect,const QMargins &sourceMargins,
+ const QTileRules &rules, QDrawBorderPixmap::DrawingHints hints)
+{
+ QPainter::PixmapFragment d;
+ d.opacity = 1.0;
+ d.rotation = 0.0;
+
+ QPixmapFragmentsArray opaqueData;
+ QPixmapFragmentsArray translucentData;
+
+ // source center
+ const int sourceCenterTop = sourceRect.top() + sourceMargins.top();
+ const int sourceCenterLeft = sourceRect.left() + sourceMargins.left();
+ const int sourceCenterBottom = sourceRect.bottom() - sourceMargins.bottom() + 1;
+ const int sourceCenterRight = sourceRect.right() - sourceMargins.right() + 1;
+ const int sourceCenterWidth = sourceCenterRight - sourceCenterLeft;
+ const int sourceCenterHeight = sourceCenterBottom - sourceCenterTop;
+ // target center
+ const int targetCenterTop = targetRect.top() + targetMargins.top();
+ const int targetCenterLeft = targetRect.left() + targetMargins.left();
+ const int targetCenterBottom = targetRect.bottom() - targetMargins.bottom() + 1;
+ const int targetCenterRight = targetRect.right() - targetMargins.right() + 1;
+ const int targetCenterWidth = targetCenterRight - targetCenterLeft;
+ const int targetCenterHeight = targetCenterBottom - targetCenterTop;
+
+ QVarLengthArray<qreal, 16> xTarget; // x-coordinates of target rectangles
+ QVarLengthArray<qreal, 16> yTarget; // y-coordinates of target rectangles
+
+ int columns = 3;
+ int rows = 3;
+ if (rules.horizontal != Qt::StretchTile && sourceCenterWidth != 0)
+ columns = qMax(3, 2 + qCeil(targetCenterWidth / qreal(sourceCenterWidth)));
+ if (rules.vertical != Qt::StretchTile && sourceCenterHeight != 0)
+ rows = qMax(3, 2 + qCeil(targetCenterHeight / qreal(sourceCenterHeight)));
+
+ xTarget.resize(columns + 1);
+ yTarget.resize(rows + 1);
+
+ bool oldAA = painter->testRenderHint(QPainter::Antialiasing);
+ if (painter->paintEngine()->type() != QPaintEngine::OpenGL
+ && painter->paintEngine()->type() != QPaintEngine::OpenGL2
+ && oldAA && painter->combinedTransform().type() != QTransform::TxNone) {
+ painter->setRenderHint(QPainter::Antialiasing, false);
+ }
+
+ xTarget[0] = targetRect.left();
+ xTarget[1] = targetCenterLeft;
+ xTarget[columns - 1] = targetCenterRight;
+ xTarget[columns] = targetRect.left() + targetRect.width();
+
+ yTarget[0] = targetRect.top();
+ yTarget[1] = targetCenterTop;
+ yTarget[rows - 1] = targetCenterBottom;
+ yTarget[rows] = targetRect.top() + targetRect.height();
+
+ qreal dx = targetCenterWidth;
+ qreal dy = targetCenterHeight;
+
+ switch (rules.horizontal) {
+ case Qt::StretchTile:
+ dx = targetCenterWidth;
+ break;
+ case Qt::RepeatTile:
+ dx = sourceCenterWidth;
+ break;
+ case Qt::RoundTile:
+ dx = targetCenterWidth / qreal(columns - 2);
+ break;
+ }
+
+ for (int i = 2; i < columns - 1; ++i)
+ xTarget[i] = xTarget[i - 1] + dx;
+
+ switch (rules.vertical) {
+ case Qt::StretchTile:
+ dy = targetCenterHeight;
+ break;
+ case Qt::RepeatTile:
+ dy = sourceCenterHeight;
+ break;
+ case Qt::RoundTile:
+ dy = targetCenterHeight / qreal(rows - 2);
+ break;
+ }
+
+ for (int i = 2; i < rows - 1; ++i)
+ yTarget[i] = yTarget[i - 1] + dy;
+
+ // corners
+ if (targetMargins.top() > 0 && targetMargins.left() > 0 && sourceMargins.top() > 0 && sourceMargins.left() > 0) { // top left
+ d.x = (0.5 * (xTarget[1] + xTarget[0]));
+ d.y = (0.5 * (yTarget[1] + yTarget[0]));
+ d.sourceLeft = sourceRect.left();
+ d.sourceTop = sourceRect.top();
+ d.width = sourceMargins.left();
+ d.height = sourceMargins.top();
+ d.scaleX = qreal(xTarget[1] - xTarget[0]) / d.width;
+ d.scaleY = qreal(yTarget[1] - yTarget[0]) / d.height;
+ if (hints & QDrawBorderPixmap::OpaqueTopLeft)
+ opaqueData.append(d);
+ else
+ translucentData.append(d);
+ }
+ if (targetMargins.top() > 0 && targetMargins.right() > 0 && sourceMargins.top() > 0 && sourceMargins.right() > 0) { // top right
+ d.x = (0.5 * (xTarget[columns] + xTarget[columns - 1]));
+ d.y = (0.5 * (yTarget[1] + yTarget[0]));
+ d.sourceLeft = sourceCenterRight;
+ d.sourceTop = sourceRect.top();
+ d.width = sourceMargins.right();
+ d.height = sourceMargins.top();
+ d.scaleX = qreal(xTarget[columns] - xTarget[columns - 1]) / d.width;
+ d.scaleY = qreal(yTarget[1] - yTarget[0]) / d.height;
+ if (hints & QDrawBorderPixmap::OpaqueTopRight)
+ opaqueData.append(d);
+ else
+ translucentData.append(d);
+ }
+ if (targetMargins.bottom() > 0 && targetMargins.left() > 0 && sourceMargins.bottom() > 0 && sourceMargins.left() > 0) { // bottom left
+ d.x = (0.5 * (xTarget[1] + xTarget[0]));
+ d.y =(0.5 * (yTarget[rows] + yTarget[rows - 1]));
+ d.sourceLeft = sourceRect.left();
+ d.sourceTop = sourceCenterBottom;
+ d.width = sourceMargins.left();
+ d.height = sourceMargins.bottom();
+ d.scaleX = qreal(xTarget[1] - xTarget[0]) / d.width;
+ d.scaleY = qreal(yTarget[rows] - yTarget[rows - 1]) / d.height;
+ if (hints & QDrawBorderPixmap::OpaqueBottomLeft)
+ opaqueData.append(d);
+ else
+ translucentData.append(d);
+ }
+ if (targetMargins.bottom() > 0 && targetMargins.right() > 0 && sourceMargins.bottom() > 0 && sourceMargins.right() > 0) { // bottom right
+ d.x = (0.5 * (xTarget[columns] + xTarget[columns - 1]));
+ d.y = (0.5 * (yTarget[rows] + yTarget[rows - 1]));
+ d.sourceLeft = sourceCenterRight;
+ d.sourceTop = sourceCenterBottom;
+ d.width = sourceMargins.right();
+ d.height = sourceMargins.bottom();
+ d.scaleX = qreal(xTarget[columns] - xTarget[columns - 1]) / d.width;
+ d.scaleY = qreal(yTarget[rows] - yTarget[rows - 1]) / d.height;
+ if (hints & QDrawBorderPixmap::OpaqueBottomRight)
+ opaqueData.append(d);
+ else
+ translucentData.append(d);
+ }
+
+ // horizontal edges
+ if (targetCenterWidth > 0 && sourceCenterWidth > 0) {
+ if (targetMargins.top() > 0 && sourceMargins.top() > 0) { // top
+ QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueTop ? opaqueData : translucentData;
+ d.sourceLeft = sourceCenterLeft;
+ d.sourceTop = sourceRect.top();
+ d.width = sourceCenterWidth;
+ d.height = sourceMargins.top();
+ d.y = (0.5 * (yTarget[1] + yTarget[0]));
+ d.scaleX = dx / d.width;
+ d.scaleY = qreal(yTarget[1] - yTarget[0]) / d.height;
+ for (int i = 1; i < columns - 1; ++i) {
+ d.x = (0.5 * (xTarget[i + 1] + xTarget[i]));
+ data.append(d);
+ }
+ if (rules.horizontal == Qt::RepeatTile)
+ data[data.size() - 1].width = ((xTarget[columns - 1] - xTarget[columns - 2]) / d.scaleX);
+ }
+ if (targetMargins.bottom() > 0 && sourceMargins.bottom() > 0) { // bottom
+ QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueBottom ? opaqueData : translucentData;
+ d.sourceLeft = sourceCenterLeft;
+ d.sourceTop = sourceCenterBottom;
+ d.width = sourceCenterWidth;
+ d.height = sourceMargins.bottom();
+ d.y = (0.5 * (yTarget[rows] + yTarget[rows - 1]));
+ d.scaleX = dx / d.width;
+ d.scaleY = qreal(yTarget[rows] - yTarget[rows - 1]) / d.height;
+ for (int i = 1; i < columns - 1; ++i) {
+ d.x = (0.5 * (xTarget[i + 1] + xTarget[i]));
+ data.append(d);
+ }
+ if (rules.horizontal == Qt::RepeatTile)
+ data[data.size() - 1].width = ((xTarget[columns - 1] - xTarget[columns - 2]) / d.scaleX);
+ }
+ }
+
+ // vertical edges
+ if (targetCenterHeight > 0 && sourceCenterHeight > 0) {
+ if (targetMargins.left() > 0 && sourceMargins.left() > 0) { // left
+ QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueLeft ? opaqueData : translucentData;
+ d.sourceLeft = sourceRect.left();
+ d.sourceTop = sourceCenterTop;
+ d.width = sourceMargins.left();
+ d.height = sourceCenterHeight;
+ d.x = (0.5 * (xTarget[1] + xTarget[0]));
+ d.scaleX = qreal(xTarget[1] - xTarget[0]) / d.width;
+ d.scaleY = dy / d.height;
+ for (int i = 1; i < rows - 1; ++i) {
+ d.y = (0.5 * (yTarget[i + 1] + yTarget[i]));
+ data.append(d);
+ }
+ if (rules.vertical == Qt::RepeatTile)
+ data[data.size() - 1].height = ((yTarget[rows - 1] - yTarget[rows - 2]) / d.scaleY);
+ }
+ if (targetMargins.right() > 0 && sourceMargins.right() > 0) { // right
+ QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueRight ? opaqueData : translucentData;
+ d.sourceLeft = sourceCenterRight;
+ d.sourceTop = sourceCenterTop;
+ d.width = sourceMargins.right();
+ d.height = sourceCenterHeight;
+ d.x = (0.5 * (xTarget[columns] + xTarget[columns - 1]));
+ d.scaleX = qreal(xTarget[columns] - xTarget[columns - 1]) / d.width;
+ d.scaleY = dy / d.height;
+ for (int i = 1; i < rows - 1; ++i) {
+ d.y = (0.5 * (yTarget[i + 1] + yTarget[i]));
+ data.append(d);
+ }
+ if (rules.vertical == Qt::RepeatTile)
+ data[data.size() - 1].height = ((yTarget[rows - 1] - yTarget[rows - 2]) / d.scaleY);
+ }
+ }
+
+ // center
+ if (targetCenterWidth > 0 && targetCenterHeight > 0 && sourceCenterWidth > 0 && sourceCenterHeight > 0) {
+ QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueCenter ? opaqueData : translucentData;
+ d.sourceLeft = sourceCenterLeft;
+ d.sourceTop = sourceCenterTop;
+ d.width = sourceCenterWidth;
+ d.height = sourceCenterHeight;
+ d.scaleX = dx / d.width;
+ d.scaleY = dy / d.height;
+
+ qreal repeatWidth = (xTarget[columns - 1] - xTarget[columns - 2]) / d.scaleX;
+ qreal repeatHeight = (yTarget[rows - 1] - yTarget[rows - 2]) / d.scaleY;
+
+ for (int j = 1; j < rows - 1; ++j) {
+ d.y = (0.5 * (yTarget[j + 1] + yTarget[j]));
+ for (int i = 1; i < columns - 1; ++i) {
+ d.x = (0.5 * (xTarget[i + 1] + xTarget[i]));
+ data.append(d);
+ }
+ if (rules.horizontal == Qt::RepeatTile)
+ data[data.size() - 1].width = repeatWidth;
+ }
+ if (rules.vertical == Qt::RepeatTile) {
+ for (int i = 1; i < columns - 1; ++i)
+ data[data.size() - i].height = repeatHeight;
+ }
+ }
+
+ if (opaqueData.size())
+ painter->drawPixmapFragments(opaqueData.data(), opaqueData.size(), pixmap, QPainter::OpaqueHint);
+ if (translucentData.size())
+ painter->drawPixmapFragments(translucentData.data(), translucentData.size(), pixmap);
+
+ if (oldAA)
+ painter->setRenderHint(QPainter::Antialiasing, true);
+}
+
+}
+
+ImageNode::ImageNode()
+ : m_innerSourceRect(0, 0, 1, 1)
+ , m_subSourceRect(0, 0, 1, 1)
+ , m_texture(0)
+ , m_mirror(false)
+ , m_smooth(true)
+ , m_tileHorizontal(false)
+ , m_tileVertical(false)
+{
+ setMaterial((QSGMaterial*)1);
+ setGeometry((QSGGeometry*)1);
+}
+
+
+void ImageNode::setTargetRect(const QRectF &rect)
+{
+ m_targetRect = rect;
+}
+
+void ImageNode::setInnerTargetRect(const QRectF &rect)
+{
+ m_innerTargetRect = rect;
+}
+
+void ImageNode::setInnerSourceRect(const QRectF &rect)
+{
+ m_innerSourceRect = rect;
+}
+
+void ImageNode::setSubSourceRect(const QRectF &rect)
+{
+ m_subSourceRect = rect;
+}
+
+void ImageNode::setTexture(QSGTexture *texture)
+{
+ m_texture = texture;
+}
+
+void ImageNode::setMirror(bool mirror)
+{
+ // ### implement support for mirrored pixmaps
+ m_mirror = mirror;
+}
+
+void ImageNode::setMipmapFiltering(QSGTexture::Filtering /*filtering*/)
+{
+}
+
+void ImageNode::setFiltering(QSGTexture::Filtering filtering)
+{
+ m_smooth = (filtering == QSGTexture::Nearest);
+}
+
+void ImageNode::setHorizontalWrapMode(QSGTexture::WrapMode wrapMode)
+{
+ m_tileHorizontal = (wrapMode == QSGTexture::Repeat);
+}
+
+void ImageNode::setVerticalWrapMode(QSGTexture::WrapMode wrapMode)
+{
+ m_tileVertical = (wrapMode == QSGTexture::Repeat);
+}
+
+void ImageNode::update()
+{
+}
+
+void ImageNode::preprocess()
+{
+ bool doDirty = false;
+ QSGLayer *t = qobject_cast<QSGLayer *>(m_texture);
+ if (t) {
+ doDirty = t->updateTexture();
+ markDirty(DirtyGeometry);
+ }
+ if (doDirty)
+ markDirty(DirtyMaterial);
+}
+
+static Qt::TileRule getTileRule(qreal factor)
+{
+ int ifactor = qRound(factor);
+ if (qFuzzyCompare(factor, ifactor )) {
+ if (ifactor == 1 || ifactor == 0)
+ return Qt::StretchTile;
+ return Qt::RoundTile;
+ }
+ return Qt::RepeatTile;
+}
+
+
+void ImageNode::paint(QPainter *painter)
+{
+ painter->setRenderHint(QPainter::SmoothPixmapTransform, m_smooth);
+
+ const QPixmap &pm = pixmap();
+
+ if (m_innerTargetRect != m_targetRect) {
+ // border image
+ QMargins margins(m_innerTargetRect.left() - m_targetRect.left(), m_innerTargetRect.top() - m_targetRect.top(),
+ m_targetRect.right() - m_innerTargetRect.right(), m_targetRect.bottom() - m_innerTargetRect.bottom());
+ QTileRules tilerules(getTileRule(m_subSourceRect.width()), getTileRule(m_subSourceRect.height()));
+ SoftwareContext::qDrawBorderPixmap(painter, m_targetRect.toRect(), margins, pm, QRect(0, 0, pm.width(), pm.height()),
+ margins, tilerules, QDrawBorderPixmap::DrawingHints(0));
+ return;
+ }
+
+ if (m_tileHorizontal || m_tileVertical) {
+ painter->save();
+ qreal sx = m_targetRect.width()/(m_subSourceRect.width()*pm.width());
+ qreal sy = m_targetRect.height()/(m_subSourceRect.height()*pm.height());
+ QMatrix transform(sx, 0, 0, sy, 0, 0);
+ painter->setMatrix(transform, true);
+ painter->drawTiledPixmap(QRectF(m_targetRect.x()/sx, m_targetRect.y()/sy, m_targetRect.width()/sx, m_targetRect.height()/sy),
+ pm,
+ QPointF(m_subSourceRect.left()*pm.width(), m_subSourceRect.top()*pm.height()));
+ painter->restore();
+ } else {
+ QRectF sr(m_subSourceRect.left()*pm.width(), m_subSourceRect.top()*pm.height(),
+ m_subSourceRect.width()*pm.width(), m_subSourceRect.height()*pm.height());
+ painter->drawPixmap(m_targetRect, pm, sr);
+ }
+}
+
+const QPixmap &ImageNode::pixmap() const
+{
+ if (PixmapTexture *pt = qobject_cast<PixmapTexture*>(m_texture)) {
+ return pt->pixmap();
+ } else if (SoftwareLayer *layer = qobject_cast<SoftwareLayer*>(m_texture)) {
+ return layer->pixmap();
+ } else {
+ qFatal("Image used with invalid texture format.");
+ }
+}
diff --git a/src/plugins/scenegraph/softwarecontext/imagenode.h b/src/plugins/scenegraph/softwarecontext/imagenode.h
new file mode 100644
index 0000000000..a0c059a889
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/imagenode.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef IMAGENODE_H
+#define IMAGENODE_H
+
+#include <private/qsgadaptationlayer_p.h>
+#include <private/qsgtexturematerial_p.h>
+
+typedef QVarLengthArray<QPainter::PixmapFragment, 16> QPixmapFragmentsArray;
+
+struct QTileRules
+{
+ inline QTileRules(Qt::TileRule horizontalRule, Qt::TileRule verticalRule)
+ : horizontal(horizontalRule), vertical(verticalRule) {}
+ inline QTileRules(Qt::TileRule rule = Qt::StretchTile)
+ : horizontal(rule), vertical(rule) {}
+ Qt::TileRule horizontal;
+ Qt::TileRule vertical;
+};
+
+#ifndef Q_QDOC
+// For internal use only.
+namespace QDrawBorderPixmap
+{
+ enum DrawingHint
+ {
+ OpaqueTopLeft = 0x0001,
+ OpaqueTop = 0x0002,
+ OpaqueTopRight = 0x0004,
+ OpaqueLeft = 0x0008,
+ OpaqueCenter = 0x0010,
+ OpaqueRight = 0x0020,
+ OpaqueBottomLeft = 0x0040,
+ OpaqueBottom = 0x0080,
+ OpaqueBottomRight = 0x0100,
+ OpaqueCorners = OpaqueTopLeft | OpaqueTopRight | OpaqueBottomLeft | OpaqueBottomRight,
+ OpaqueEdges = OpaqueTop | OpaqueLeft | OpaqueRight | OpaqueBottom,
+ OpaqueFrame = OpaqueCorners | OpaqueEdges,
+ OpaqueAll = OpaqueCenter | OpaqueFrame
+ };
+
+ Q_DECLARE_FLAGS(DrawingHints, DrawingHint)
+}
+#endif
+
+namespace SoftwareContext {
+
+void qDrawBorderPixmap(QPainter *painter, const QRect &targetRect, const QMargins &targetMargins,
+ const QPixmap &pixmap, const QRect &sourceRect,const QMargins &sourceMargins,
+ const QTileRules &rules, QDrawBorderPixmap::DrawingHints hints);
+
+}
+
+class ImageNode : public QSGImageNode
+{
+public:
+ ImageNode();
+
+ virtual void setTargetRect(const QRectF &rect);
+ virtual void setInnerTargetRect(const QRectF &rect);
+ virtual void setInnerSourceRect(const QRectF &rect);
+ virtual void setSubSourceRect(const QRectF &rect);
+ virtual void setTexture(QSGTexture *texture);
+ virtual void setMirror(bool mirror);
+ virtual void setMipmapFiltering(QSGTexture::Filtering filtering);
+ virtual void setFiltering(QSGTexture::Filtering filtering);
+ virtual void setHorizontalWrapMode(QSGTexture::WrapMode wrapMode);
+ virtual void setVerticalWrapMode(QSGTexture::WrapMode wrapMode);
+ virtual void update();
+
+ virtual void preprocess();
+
+ void paint(QPainter *painter);
+
+private:
+ const QPixmap &pixmap() const;
+
+ QRectF m_targetRect;
+ QRectF m_innerTargetRect;
+ QRectF m_innerSourceRect;
+ QRectF m_subSourceRect;
+
+ QSGTexture *m_texture;
+
+ bool m_mirror;
+ bool m_smooth;
+ bool m_tileHorizontal;
+ bool m_tileVertical;
+};
+
+#endif // IMAGENODE_H
diff --git a/src/plugins/scenegraph/softwarecontext/ninepatchnode.cpp b/src/plugins/scenegraph/softwarecontext/ninepatchnode.cpp
new file mode 100644
index 0000000000..ab503b7af5
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/ninepatchnode.cpp
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "ninepatchnode.h"
+#include "pixmaptexture.h"
+#include "imagenode.h"
+
+NinePatchNode::NinePatchNode()
+{
+ setMaterial((QSGMaterial*)1);
+ setGeometry((QSGGeometry*)1);
+}
+
+void NinePatchNode::setTexture(QSGTexture *texture)
+{
+ PixmapTexture *pt = qobject_cast<PixmapTexture*>(texture);
+ if (!pt) {
+ qWarning() << "Image used with invalid texture format.";
+ return;
+ }
+ m_pixmap = pt->pixmap();
+}
+
+void NinePatchNode::setBounds(const QRectF &bounds)
+{
+ m_bounds = bounds;
+}
+
+void NinePatchNode::setDevicePixelRatio(qreal ratio)
+{
+ m_pixelRatio = ratio;
+}
+
+void NinePatchNode::setPadding(qreal left, qreal top, qreal right, qreal bottom)
+{
+ m_margins = QMargins(qRound(left), qRound(top), qRound(right), qRound(bottom));
+}
+
+void NinePatchNode::update()
+{
+}
+
+void NinePatchNode::paint(QPainter *painter)
+{
+ if (m_margins.isNull())
+ painter->drawPixmap(m_bounds, m_pixmap, QRectF(0, 0, m_pixmap.width(), m_pixmap.height()));
+ else
+ SoftwareContext::qDrawBorderPixmap(painter, m_bounds.toRect(), m_margins, m_pixmap, QRect(0, 0, m_pixmap.width(), m_pixmap.height()),
+ m_margins, Qt::StretchTile, QDrawBorderPixmap::DrawingHints(0));
+}
diff --git a/src/plugins/scenegraph/softwarecontext/ninepatchnode.h b/src/plugins/scenegraph/softwarecontext/ninepatchnode.h
new file mode 100644
index 0000000000..2bcd1214e3
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/ninepatchnode.h
@@ -0,0 +1,45 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef NINEPATCHNODE_H
+#define NINEPATCHNODE_H
+
+#include <private/qsgadaptationlayer_p.h>
+
+class NinePatchNode : public QSGNinePatchNode
+{
+public:
+ NinePatchNode();
+
+ virtual void setTexture(QSGTexture *texture);
+ virtual void setBounds(const QRectF &bounds);
+ virtual void setDevicePixelRatio(qreal ratio);
+ virtual void setPadding(qreal left, qreal top, qreal right, qreal bottom);
+ virtual void update();
+
+ void paint(QPainter *painter);
+
+private:
+ QPixmap m_pixmap;
+ QRectF m_bounds;
+ qreal m_pixelRatio;
+ QMargins m_margins;
+};
+
+#endif // NINEPATCHNODE_H
diff --git a/src/plugins/scenegraph/softwarecontext/painternode.cpp b/src/plugins/scenegraph/softwarecontext/painternode.cpp
new file mode 100644
index 0000000000..bf5ec5f202
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/painternode.cpp
@@ -0,0 +1,189 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "painternode.h"
+#include "pixmaptexture.h"
+#include <qmath.h>
+
+PainterNode::PainterNode(QQuickPaintedItem *item)
+ : QSGPainterNode()
+ , m_preferredRenderTarget(QQuickPaintedItem::Image)
+ , m_actualRenderTarget(QQuickPaintedItem::Image)
+ , m_item(item)
+ , m_texture(0)
+ , m_dirtyContents(false)
+ , m_opaquePainting(false)
+ , m_linear_filtering(false)
+ , m_mipmapping(false)
+ , m_smoothPainting(false)
+ , m_extensionsChecked(false)
+ , m_multisamplingSupported(false)
+ , m_fastFBOResizing(false)
+ , m_fillColor(Qt::transparent)
+ , m_contentsScale(1.0)
+ , m_dirtyGeometry(false)
+{
+ setMaterial((QSGMaterial*)1);
+ setGeometry((QSGGeometry*)1);
+}
+
+PainterNode::~PainterNode()
+{
+ delete m_texture;
+}
+
+void PainterNode::setPreferredRenderTarget(QQuickPaintedItem::RenderTarget target)
+{
+ if (m_preferredRenderTarget == target)
+ return;
+
+ m_preferredRenderTarget = target;
+}
+
+void PainterNode::setSize(const QSize &size)
+{
+ if (size == m_size)
+ return;
+
+ m_size = size;
+
+ m_dirtyGeometry = true;
+}
+
+void PainterNode::setDirty(const QRect &dirtyRect)
+{
+ m_dirtyContents = true;
+ m_dirtyRect = dirtyRect;
+ markDirty(DirtyMaterial);
+}
+
+void PainterNode::setOpaquePainting(bool opaque)
+{
+ if (opaque == m_opaquePainting)
+ return;
+
+ m_opaquePainting = opaque;
+}
+
+void PainterNode::setLinearFiltering(bool linearFiltering)
+{
+ if (linearFiltering == m_linear_filtering)
+ return;
+
+ m_linear_filtering = linearFiltering;
+}
+
+void PainterNode::setMipmapping(bool mipmapping)
+{
+ if (mipmapping == m_mipmapping)
+ return;
+
+ m_mipmapping = mipmapping;
+}
+
+void PainterNode::setSmoothPainting(bool s)
+{
+ if (s == m_smoothPainting)
+ return;
+
+ m_smoothPainting = s;
+}
+
+void PainterNode::setFillColor(const QColor &c)
+{
+ if (c == m_fillColor)
+ return;
+
+ m_fillColor = c;
+ markDirty(DirtyMaterial);
+}
+
+void PainterNode::setContentsScale(qreal s)
+{
+ if (s == m_contentsScale)
+ return;
+
+ m_contentsScale = s;
+ markDirty(DirtyMaterial);
+}
+
+void PainterNode::setFastFBOResizing(bool dynamic)
+{
+ m_fastFBOResizing = dynamic;
+}
+
+QImage PainterNode::toImage() const
+{
+ return m_pixmap.toImage();
+}
+
+void PainterNode::update()
+{
+ if (m_dirtyGeometry) {
+ m_pixmap = QPixmap(m_size);
+ if (!m_opaquePainting)
+ m_pixmap.fill(Qt::transparent);
+
+ if (m_texture)
+ delete m_texture;
+ m_texture = new PixmapTexture(m_pixmap);
+ }
+
+ if (m_dirtyContents)
+ paint();
+
+ m_dirtyGeometry = false;
+ m_dirtyContents = false;
+}
+
+void PainterNode::paint(QPainter *painter)
+{
+ painter->drawPixmap(0, 0, m_size.width(), m_size.height(), m_pixmap);
+}
+
+void PainterNode::paint()
+{
+ QRect dirtyRect = m_dirtyRect.isNull() ? QRect(0, 0, m_size.width(), m_size.height()) : m_dirtyRect;
+
+ QPainter painter;
+
+ painter.begin(&m_pixmap);
+ if (m_smoothPainting) {
+ painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
+ }
+
+ painter.scale(m_contentsScale, m_contentsScale);
+
+ QRect sclip(qFloor(dirtyRect.x()/m_contentsScale),
+ qFloor(dirtyRect.y()/m_contentsScale),
+ qCeil(dirtyRect.width()/m_contentsScale+dirtyRect.x()/m_contentsScale-qFloor(dirtyRect.x()/m_contentsScale)),
+ qCeil(dirtyRect.height()/m_contentsScale+dirtyRect.y()/m_contentsScale-qFloor(dirtyRect.y()/m_contentsScale)));
+
+ if (!m_dirtyRect.isNull())
+ painter.setClipRect(sclip);
+
+ painter.setCompositionMode(QPainter::CompositionMode_Source);
+ painter.fillRect(sclip, m_fillColor);
+ painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
+
+ m_item->paint(&painter);
+ painter.end();
+
+ m_dirtyRect = QRect();
+}
diff --git a/src/plugins/scenegraph/softwarecontext/painternode.h b/src/plugins/scenegraph/softwarecontext/painternode.h
new file mode 100644
index 0000000000..db0d03e28a
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/painternode.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef PAINTERNODE_H
+#define PAINTERNODE_H
+
+#include <private/qsgadaptationlayer_p.h>
+#include <QtQuick/qquickpainteditem.h>
+
+#include <QtGui/QPixmap>
+
+class PainterNode : public QSGPainterNode
+{
+public:
+ PainterNode(QQuickPaintedItem *item);
+ ~PainterNode();
+
+ void setPreferredRenderTarget(QQuickPaintedItem::RenderTarget target);
+
+ void setSize(const QSize &size);
+ QSize size() const { return m_size; }
+
+ void setDirty(const QRect &dirtyRect = QRect());
+
+ void setOpaquePainting(bool opaque);
+ bool opaquePainting() const { return m_opaquePainting; }
+
+ void setLinearFiltering(bool linearFiltering);
+ bool linearFiltering() const { return m_linear_filtering; }
+
+ void setMipmapping(bool mipmapping);
+ bool mipmapping() const { return m_mipmapping; }
+
+ void setSmoothPainting(bool s);
+ bool smoothPainting() const { return m_smoothPainting; }
+
+ void setFillColor(const QColor &c);
+ QColor fillColor() const { return m_fillColor; }
+
+ void setContentsScale(qreal s);
+ qreal contentsScale() const { return m_contentsScale; }
+
+ void setFastFBOResizing(bool dynamic);
+ bool fastFBOResizing() const { return m_fastFBOResizing; }
+
+ QImage toImage() const;
+ void update();
+ QSGTexture *texture() const { return m_texture; }
+
+ void paint(QPainter *painter);
+
+ void paint();
+
+private:
+
+ QQuickPaintedItem::RenderTarget m_preferredRenderTarget;
+ QQuickPaintedItem::RenderTarget m_actualRenderTarget;
+
+ QQuickPaintedItem *m_item;
+
+ QPixmap m_pixmap;
+ QSGTexture *m_texture;
+
+ QSize m_size;
+ bool m_dirtyContents;
+ QRect m_dirtyRect;
+ bool m_opaquePainting;
+ bool m_linear_filtering;
+ bool m_mipmapping;
+ bool m_smoothPainting;
+ bool m_extensionsChecked;
+ bool m_multisamplingSupported;
+ bool m_fastFBOResizing;
+ QColor m_fillColor;
+ qreal m_contentsScale;
+
+ bool m_dirtyGeometry;
+};
+
+#endif // PAINTERNODE_H
diff --git a/src/plugins/scenegraph/softwarecontext/pixmaptexture.cpp b/src/plugins/scenegraph/softwarecontext/pixmaptexture.cpp
new file mode 100644
index 0000000000..d3dd747a9e
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/pixmaptexture.cpp
@@ -0,0 +1,56 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "pixmaptexture.h"
+
+PixmapTexture::PixmapTexture(const QImage &image)
+ : m_pixmap(QPixmap::fromImage(image))
+{
+}
+
+PixmapTexture::PixmapTexture(const QPixmap &pixmap)
+ : m_pixmap(pixmap)
+{
+}
+
+
+int PixmapTexture::textureId() const
+{
+ return 0;
+}
+
+QSize PixmapTexture::textureSize() const
+{
+ return m_pixmap.size();
+}
+
+bool PixmapTexture::hasAlphaChannel() const
+{
+ return m_pixmap.hasAlphaChannel();
+}
+
+bool PixmapTexture::hasMipmaps() const
+{
+ return false;
+}
+
+void PixmapTexture::bind()
+{
+ Q_UNREACHABLE();
+}
diff --git a/src/plugins/scenegraph/softwarecontext/pixmaptexture.h b/src/plugins/scenegraph/softwarecontext/pixmaptexture.h
new file mode 100644
index 0000000000..c3ee09dfe1
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/pixmaptexture.h
@@ -0,0 +1,44 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef PIXMAPTEXTURE_H
+#define PIXMAPTEXTURE_H
+
+#include <private/qsgtexture_p.h>
+
+class PixmapTexture : public QSGTexture
+{
+ Q_OBJECT
+public:
+ PixmapTexture(const QImage &image);
+ PixmapTexture(const QPixmap &pixmap);
+
+ virtual int textureId() const;
+ virtual QSize textureSize() const;
+ virtual bool hasAlphaChannel() const;
+ virtual bool hasMipmaps() const;
+ virtual void bind();
+
+ const QPixmap &pixmap() const { return m_pixmap; }
+
+private:
+ QPixmap m_pixmap;
+};
+
+#endif // PIXMAPTEXTURE_H
diff --git a/src/plugins/scenegraph/softwarecontext/pluginmain.cpp b/src/plugins/scenegraph/softwarecontext/pluginmain.cpp
new file mode 100644
index 0000000000..afb91a8ea2
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/pluginmain.cpp
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include "pluginmain.h"
+#include "context.h"
+#include "renderloop.h"
+#include "threadedrenderloop.h"
+
+ContextPlugin::ContextPlugin(QObject *parent)
+ : QSGContextPlugin(parent)
+{
+}
+
+QStringList ContextPlugin::keys() const
+{
+ return QStringList() << QLatin1String("softwarecontext");
+}
+
+QSGContext *ContextPlugin::create(const QString &) const
+{
+ if (!instance)
+ instance = new SoftwareContext::Context();
+ return instance;
+}
+
+QSGRenderLoop *ContextPlugin::createWindowManager()
+{
+ if (qgetenv("QSG_RENDER_LOOP") == QByteArrayLiteral("basic"))
+ return new RenderLoop();
+ return new ThreadedRenderLoop();
+}
+
+SoftwareContext::Context *ContextPlugin::instance = 0;
+
+
+
diff --git a/src/plugins/scenegraph/softwarecontext/pluginmain.h b/src/plugins/scenegraph/softwarecontext/pluginmain.h
new file mode 100644
index 0000000000..fd90823322
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/pluginmain.h
@@ -0,0 +1,47 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef PLUGINMAIN_H
+#define PLUGINMAIN_H
+
+#include <private/qsgcontext_p.h>
+#include <private/qsgcontextplugin_p.h>
+
+#include <qplugin.h>
+
+#include "context.h"
+
+class ContextPlugin : public QSGContextPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QSGContextFactoryInterface" FILE "softwarecontext.json")
+
+public:
+ ContextPlugin(QObject *parent = 0);
+
+ QStringList keys() const;
+ QSGContext *create(const QString &key) const;
+ QSGRenderLoop *createWindowManager();
+
+ static SoftwareContext::Context *instance;
+};
+
+#endif // PLUGINMAIN_H
diff --git a/src/plugins/scenegraph/softwarecontext/rectanglenode.cpp b/src/plugins/scenegraph/softwarecontext/rectanglenode.cpp
new file mode 100644
index 0000000000..054ffd3ecf
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/rectanglenode.cpp
@@ -0,0 +1,219 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "rectanglenode.h"
+#include <qmath.h>
+
+#include <QtGui/QPainter>
+
+
+RectangleNode::RectangleNode()
+ : m_penWidth(0)
+ , m_radius(0)
+ , m_cornerPixmapIsDirty(true)
+{
+ setMaterial((QSGMaterial*)1);
+ setGeometry((QSGGeometry*)1);
+}
+
+void RectangleNode::setRect(const QRectF &rect)
+{
+ if (m_rect != rect) {
+ m_rect = rect;
+ }
+}
+
+void RectangleNode::setColor(const QColor &color)
+{
+ if (m_color != color) {
+ m_color = color;
+ m_cornerPixmapIsDirty = true;
+ }
+}
+
+void RectangleNode::setPenColor(const QColor &color)
+{
+ if (m_penColor != color) {
+ m_penColor = color;
+ m_cornerPixmapIsDirty = true;
+ }
+}
+
+void RectangleNode::setPenWidth(qreal width)
+{
+ if (m_penWidth != width) {
+ m_penWidth = width;
+ m_cornerPixmapIsDirty = true;
+ }
+}
+
+void RectangleNode::setGradientStops(const QGradientStops &stops)
+{
+ m_stops = stops;
+ m_cornerPixmapIsDirty = true;
+}
+
+void RectangleNode::setRadius(qreal radius)
+{
+ if (m_radius != radius) {
+ m_radius = radius;
+ m_cornerPixmapIsDirty = true;
+ }
+}
+
+void RectangleNode::setAligned(bool /*aligned*/)
+{
+}
+
+void RectangleNode::update()
+{
+ if (!m_penWidth || m_penColor == Qt::transparent) {
+ m_pen = Qt::NoPen;
+ } else {
+ m_pen = QPen(m_penColor);
+ m_pen.setWidthF(m_penWidth);
+ }
+
+ if (!m_stops.isEmpty()) {
+ QLinearGradient gradient(QPoint(0,0), QPoint(0,1));
+ gradient.setStops(m_stops);
+ gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
+ m_brush = QBrush(gradient);
+ } else {
+ m_brush = QBrush(m_color);
+ }
+
+ if (m_cornerPixmapIsDirty) {
+ //Generate new corner Pixmap
+ int radius = qRound(qMin(qMin(m_rect.width(), m_rect.height()) * 0.5f, m_radius));
+
+ m_cornerPixmap = QPixmap(radius * 2, radius * 2);
+ m_cornerPixmap.fill(Qt::transparent);
+
+ if (radius > 0) {
+ QPainter cornerPainter(&m_cornerPixmap);
+ cornerPainter.setRenderHint(QPainter::Antialiasing);
+ cornerPainter.setCompositionMode(QPainter::CompositionMode_Source);
+
+ //Paint outer cicle
+ if (m_penWidth > 0) {
+ cornerPainter.setPen(Qt::NoPen);
+ cornerPainter.setBrush(m_penColor);
+ cornerPainter.drawRoundedRect(QRectF(0, 0, radius * 2, radius *2), radius, radius);
+ }
+
+ //Paint inner circle
+ if (radius > m_penWidth) {
+ cornerPainter.setPen(Qt::NoPen);
+ if (m_stops.isEmpty())
+ cornerPainter.setBrush(m_brush);
+ else
+ cornerPainter.setBrush(Qt::transparent);
+
+ QMarginsF adjustmentMargins(m_penWidth, m_penWidth, m_penWidth, m_penWidth);
+ QRectF cornerCircleRect = QRectF(0, 0, radius * 2, radius * 2).marginsRemoved(adjustmentMargins);
+ cornerPainter.drawRoundedRect(cornerCircleRect, radius, radius);
+ }
+ cornerPainter.end();
+ }
+ m_cornerPixmapIsDirty = false;
+ }
+}
+
+void RectangleNode::paint(QPainter *painter)
+{
+ //Radius should never exceeds half of the width or half of the height
+ int radius = qRound(qMin(qMin(m_rect.width(), m_rect.height()) * 0.5, m_radius));
+
+ QPainter::RenderHints previousRenderHints = painter->renderHints();
+ painter->setRenderHint(QPainter::Antialiasing, false);
+
+ if (m_penWidth > 0) {
+ //Borders can not be more than half the height/width of a rect
+ double borderWidth = qMin(m_penWidth, m_rect.width() * 0.5);
+ double borderHeight = qMin(m_penWidth, m_rect.height() * 0.5);
+
+ //Fill 4 border Rects
+ QRectF borderTop(QPointF(m_rect.x() + radius, m_rect.y()),
+ QPointF(m_rect.x() + m_rect.width() - radius, m_rect.y() + borderHeight));
+ painter->fillRect(borderTop, m_penColor);
+ QRectF borderBottom(QPointF(m_rect.x() + radius, m_rect.y() + m_rect.height() - borderHeight),
+ QPointF(m_rect.x() + m_rect.width() - radius, m_rect.y() + m_rect.height()));
+ painter->fillRect(borderBottom, m_penColor);
+ QRectF borderLeft(QPointF(m_rect.x(), m_rect.y() + radius),
+ QPointF(m_rect.x() + borderWidth, m_rect.y() + m_rect.height() - radius));
+ painter->fillRect(borderLeft, m_penColor);
+ QRectF borderRight(QPointF(m_rect.x() + m_rect.width() - borderWidth, m_rect.y() + radius),
+ QPointF(m_rect.x() + m_rect.width(), m_rect.y() + m_rect.height() - radius));
+ painter->fillRect(borderRight, m_penColor);
+ }
+
+ if (radius > 0) {
+ //blit 4 corners to border
+ QRectF topLeftCorner(QPointF(m_rect.x(), m_rect.y()),
+ QPointF(m_rect.x() + radius, m_rect.y() + radius));
+ painter->drawPixmap(topLeftCorner, m_cornerPixmap, QRectF(0, 0, radius, radius));
+ QRectF topRightCorner(QPointF(m_rect.x() + m_rect.width() - radius, m_rect.y()),
+ QPointF(m_rect.x() + m_rect.width(), m_rect.y() + radius));
+ painter->drawPixmap(topRightCorner, m_cornerPixmap, QRectF(radius, 0, radius, radius));
+ QRectF bottomLeftCorner(QPointF(m_rect.x(), m_rect.y() + m_rect.height() - radius),
+ QPointF(m_rect.x() + radius, m_rect.y() + m_rect.height()));
+ painter->drawPixmap(bottomLeftCorner, m_cornerPixmap, QRectF(0, radius, radius, radius));
+ QRectF bottomRightCorner(QPointF(m_rect.x() + m_rect.width() - radius, m_rect.y() + m_rect.height() - radius),
+ QPointF(m_rect.x() + m_rect.width(), m_rect.y() + m_rect.height()));
+ painter->drawPixmap(bottomRightCorner, m_cornerPixmap, QRectF(radius, radius, radius, radius));
+
+ }
+
+ QRectF brushRect = m_rect.marginsRemoved(QMarginsF(m_penWidth, m_penWidth, m_penWidth, m_penWidth));
+ if (brushRect.width() < 0)
+ brushRect.setWidth(0);
+ if (brushRect.height() < 0)
+ brushRect.setHeight(0);
+ double innerRectRadius = qMax(0.0, radius - m_penWidth);
+
+ //If not completely transparent or has a gradient
+ if (m_color.alpha() > 0 || !m_stops.empty()) {
+ if (innerRectRadius > 0) {
+ //Rounded Rect
+ if (m_stops.empty()) {
+ //Rounded Rects without gradient need 3 blits
+ QRectF centerRect(QPointF(brushRect.x() + innerRectRadius, brushRect.y()),
+ QPointF(brushRect.x() + brushRect.width() - innerRectRadius, brushRect.y() + brushRect.height()));
+ painter->fillRect(centerRect, m_color);
+ QRectF leftRect(QPointF(brushRect.x(), brushRect.y() + innerRectRadius),
+ QPointF(brushRect.x() + innerRectRadius, brushRect.y() + brushRect.height() - innerRectRadius));
+ painter->fillRect(leftRect, m_color);
+ QRectF rightRect(QPointF(brushRect.x() + brushRect.width() - innerRectRadius, brushRect.y() + innerRectRadius),
+ QPointF(brushRect.x() + brushRect.width(), brushRect.y() + brushRect.height() - innerRectRadius));
+ painter->fillRect(rightRect, m_color);
+ } else {
+ //Rounded Rect with gradient (slow)
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(m_brush);
+ painter->drawRoundedRect(brushRect, innerRectRadius, innerRectRadius);
+ }
+ } else {
+ //non-rounded rects only need 1 blit
+ painter->fillRect(brushRect, m_brush);
+ }
+ }
+
+ painter->setRenderHints(previousRenderHints);
+}
diff --git a/src/plugins/scenegraph/softwarecontext/rectanglenode.h b/src/plugins/scenegraph/softwarecontext/rectanglenode.h
new file mode 100644
index 0000000000..74c0971c82
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/rectanglenode.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef RECTANGLENODE_H
+#define RECTANGLENODE_H
+
+#include <private/qsgadaptationlayer_p.h>
+
+#include <QPen>
+#include <QBrush>
+#include <QPixmap>
+
+class RectangleNode : public QSGRectangleNode
+{
+public:
+ RectangleNode();
+
+ virtual void setRect(const QRectF &rect);
+ virtual void setColor(const QColor &color);
+ virtual void setPenColor(const QColor &color);
+ virtual void setPenWidth(qreal width);
+ virtual void setGradientStops(const QGradientStops &stops);
+ virtual void setRadius(qreal radius);
+ virtual void setAntialiasing(bool antialiasing) { Q_UNUSED(antialiasing) }
+ virtual void setAligned(bool aligned);
+
+ virtual void update();
+
+ void paint(QPainter *);
+
+private:
+ QRectF m_rect;
+ QColor m_color;
+ QColor m_penColor;
+ double m_penWidth;
+ QGradientStops m_stops;
+ double m_radius;
+ QPen m_pen;
+ QBrush m_brush;
+
+ bool m_cornerPixmapIsDirty;
+ QPixmap m_cornerPixmap;
+};
+
+#endif // RECTANGLENODE_H
diff --git a/src/plugins/scenegraph/softwarecontext/renderingvisitor.cpp b/src/plugins/scenegraph/softwarecontext/renderingvisitor.cpp
new file mode 100644
index 0000000000..879c30eb4f
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/renderingvisitor.cpp
@@ -0,0 +1,169 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "renderingvisitor.h"
+
+#include "imagenode.h"
+#include "rectanglenode.h"
+#include "glyphnode.h"
+#include "ninepatchnode.h"
+#include "painternode.h"
+#include "pixmaptexture.h"
+
+#include <QtQuick/QSGSimpleRectNode>
+#include <QtQuick/qsgsimpletexturenode.h>
+#include <private/qsgtexture_p.h>
+#include <private/qquickshadereffectnode_p.h>
+
+RenderingVisitor::RenderingVisitor(QPainter *painter)
+ : painter(painter)
+{
+
+}
+
+bool RenderingVisitor::visit(QSGTransformNode *node)
+{
+ painter->save();
+ painter->setTransform(node->matrix().toTransform(), /*combine*/true);
+ return true;
+}
+
+void RenderingVisitor::endVisit(QSGTransformNode *)
+{
+ painter->restore();
+}
+
+bool RenderingVisitor::visit(QSGClipNode *node)
+{
+ painter->save();
+ painter->setClipRect(node->clipRect(), Qt::IntersectClip);
+ return true;
+}
+
+void RenderingVisitor::endVisit(QSGClipNode *)
+{
+ painter->restore();
+}
+
+bool RenderingVisitor::visit(QSGGeometryNode *node)
+{
+ if (QSGSimpleRectNode *rectNode = dynamic_cast<QSGSimpleRectNode *>(node)) {
+ if (!rectNode->material()->flags() & QSGMaterial::Blending)
+ painter->setCompositionMode(QPainter::CompositionMode_Source);
+ painter->fillRect(rectNode->rect(), rectNode->color());
+ painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
+ } else if (QSGSimpleTextureNode *tn = dynamic_cast<QSGSimpleTextureNode *>(node)) {
+ QSGTexture *texture = tn->texture();
+ if (PixmapTexture *pt = dynamic_cast<PixmapTexture *>(texture)) {
+ const QPixmap &pm = pt->pixmap();
+ painter->drawPixmap(tn->rect(), pm, QRectF(0, 0, pm.width(), pm.height()));
+ } else if (QSGPlainTexture *pt = dynamic_cast<QSGPlainTexture *>(texture)) {
+ const QImage &im = pt->image();
+ painter->drawImage(tn->rect(), im, QRectF(0, 0, im.width(), im.height()));
+ } else {
+ Q_UNREACHABLE();
+ }
+ } else if (QQuickShaderEffectNode *sn = dynamic_cast<QQuickShaderEffectNode *>(node)) {
+ Q_UNUSED(sn)
+ } else {
+ Q_UNREACHABLE();
+ }
+ return true;
+}
+
+void RenderingVisitor::endVisit(QSGGeometryNode *)
+{
+}
+
+bool RenderingVisitor::visit(QSGOpacityNode *node)
+{
+ painter->save();
+
+ const qreal newOpacity = painter->opacity() * node->opacity();
+ if (qFuzzyIsNull(newOpacity))
+ return false;
+
+ painter->setOpacity(newOpacity);
+ return true;
+}
+
+void RenderingVisitor::endVisit(QSGOpacityNode *)
+{
+ painter->restore();
+}
+
+bool RenderingVisitor::visit(QSGImageNode *node)
+{
+ static_cast<ImageNode*>(node)->paint(painter);
+ return true;
+}
+
+void RenderingVisitor::endVisit(QSGImageNode *)
+{
+}
+
+bool RenderingVisitor::visit(QSGPainterNode *node)
+{
+ static_cast<PainterNode*>(node)->paint(painter);
+ return true;
+}
+
+void RenderingVisitor::endVisit(QSGPainterNode *node)
+{
+
+}
+
+bool RenderingVisitor::visit(QSGRectangleNode *node)
+{
+ static_cast<RectangleNode*>(node)->paint(painter);
+ return true;
+}
+
+void RenderingVisitor::endVisit(QSGRectangleNode *)
+{
+}
+
+bool RenderingVisitor::visit(QSGGlyphNode *node)
+{
+ static_cast<GlyphNode*>(node)->paint(painter);
+ return true;
+}
+
+void RenderingVisitor::endVisit(QSGGlyphNode *)
+{
+}
+
+bool RenderingVisitor::visit(QSGNinePatchNode *node)
+{
+ static_cast<NinePatchNode*>(node)->paint(painter);
+ return true;
+}
+
+void RenderingVisitor::endVisit(QSGNinePatchNode *)
+{
+}
+
+bool RenderingVisitor::visit(QSGRootNode *)
+{
+ return true;
+}
+
+void RenderingVisitor::endVisit(QSGRootNode *)
+{
+}
diff --git a/src/plugins/scenegraph/softwarecontext/renderingvisitor.h b/src/plugins/scenegraph/softwarecontext/renderingvisitor.h
new file mode 100644
index 0000000000..d65faa4293
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/renderingvisitor.h
@@ -0,0 +1,55 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef RENDERINGVISITOR_H
+#define RENDERINGVISITOR_H
+
+#include <private/qsgadaptationlayer_p.h>
+
+class RenderingVisitor : public QSGNodeVisitorEx
+{
+public:
+ RenderingVisitor(QPainter *painter);
+
+ virtual bool visit(QSGTransformNode *node);
+ virtual void endVisit(QSGTransformNode *);
+ virtual bool visit(QSGClipNode *node);
+ virtual void endVisit(QSGClipNode *node);
+ virtual bool visit(QSGGeometryNode *node);
+ virtual void endVisit(QSGGeometryNode *node);
+ virtual bool visit(QSGOpacityNode *node);
+ virtual void endVisit(QSGOpacityNode *node);
+ virtual bool visit(QSGImageNode *node);
+ virtual void endVisit(QSGImageNode *node);
+ virtual bool visit(QSGPainterNode *node);
+ virtual void endVisit(QSGPainterNode *node);
+ virtual bool visit(QSGRectangleNode *node);
+ virtual void endVisit(QSGRectangleNode *node);
+ virtual bool visit(QSGGlyphNode *node);
+ virtual void endVisit(QSGGlyphNode *node);
+ virtual bool visit(QSGNinePatchNode *node);
+ virtual void endVisit(QSGNinePatchNode *);
+ virtual bool visit(QSGRootNode *);
+ virtual void endVisit(QSGRootNode *);
+
+private:
+ QPainter *painter;
+};
+
+#endif // RENDERINGVISITOR_H
diff --git a/src/plugins/scenegraph/softwarecontext/renderloop.cpp b/src/plugins/scenegraph/softwarecontext/renderloop.cpp
new file mode 100644
index 0000000000..7dd20342bc
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/renderloop.cpp
@@ -0,0 +1,251 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "renderloop.h"
+
+#include "context.h"
+#include <private/qquickwindow_p.h>
+#include <QElapsedTimer>
+#include <private/qquickprofiler_p.h>
+
+// Used for very high-level info about the renderering and gl context
+// Includes GL_VERSION, type of render loop, atlas size, etc.
+Q_LOGGING_CATEGORY(QSG_LOG_INFO, "qt.scenegraph.info")
+
+// Used to debug the renderloop logic. Primarily useful for platform integrators
+// and when investigating the render loop logic.
+Q_LOGGING_CATEGORY(QSG_LOG_RENDERLOOP, "qt.scenegraph.renderloop")
+
+
+// GLSL shader compilation
+Q_LOGGING_CATEGORY(QSG_LOG_TIME_COMPILATION, "qt.scenegraph.time.compilation")
+
+// polish, animations, sync, render and swap in the render loop
+Q_LOGGING_CATEGORY(QSG_LOG_TIME_RENDERLOOP, "qt.scenegraph.time.renderloop")
+
+// Texture uploads and swizzling
+Q_LOGGING_CATEGORY(QSG_LOG_TIME_TEXTURE, "qt.scenegraph.time.texture")
+
+// Glyph preparation (only for distance fields atm)
+Q_LOGGING_CATEGORY(QSG_LOG_TIME_GLYPH, "qt.scenegraph.time.glyph")
+
+// Timing inside the renderer base class
+Q_LOGGING_CATEGORY(QSG_LOG_TIME_RENDERER, "qt.scenegraph.time.renderer")
+
+RenderLoop::RenderLoop()
+ : eventPending(false)
+{
+ sg = QSGContext::createDefaultContext();
+ rc = sg->createRenderContext();
+}
+
+RenderLoop::~RenderLoop()
+{
+ delete rc;
+ delete sg;
+}
+
+void RenderLoop::show(QQuickWindow *window)
+{
+ WindowData data;
+ data.updatePending = false;
+ data.grabOnly = false;
+ m_windows[window] = data;
+
+ maybeUpdate(window);
+}
+
+void RenderLoop::hide(QQuickWindow *window)
+{
+ if (!m_windows.contains(window))
+ return;
+
+ m_windows.remove(window);
+ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
+ cd->fireAboutToStop();
+ cd->cleanupNodesOnShutdown();
+
+ if (m_windows.size() == 0) {
+ if (!cd->persistentSceneGraph) {
+ rc->invalidate();
+ QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
+ }
+ }
+}
+
+void RenderLoop::windowDestroyed(QQuickWindow *window)
+{
+ hide(window);
+ if (m_windows.size() == 0) {
+ rc->invalidate();
+ QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
+ }
+}
+
+void RenderLoop::renderWindow(QQuickWindow *window)
+{
+ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
+ if (!cd->isRenderable() || !m_windows.contains(window))
+ return;
+
+ WindowData &data = const_cast<WindowData &>(m_windows[window]);
+
+ // ### create QPainter and set up pointer to current window/painter
+ SoftwareContext::RenderContext *ctx = static_cast<SoftwareContext::RenderContext*>(cd->context);
+ ctx->currentWindow = window;
+ ctx->initializeIfNeeded();
+
+ bool alsoSwap = data.updatePending;
+ data.updatePending = false;
+
+ if (!data.grabOnly) {
+ cd->flushDelayedTouchEvent();
+ // Event delivery/processing triggered the window to be deleted or stop rendering.
+ if (!m_windows.contains(window))
+ return;
+ }
+ QElapsedTimer renderTimer;
+ qint64 renderTime = 0, syncTime = 0, polishTime = 0;
+ bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled() || QQuickProfiler::enabled;
+ if (profileFrames)
+ renderTimer.start();
+
+ cd->polishItems();
+
+ if (profileFrames) {
+ polishTime = renderTimer.nsecsElapsed();
+ Q_QUICK_SG_PROFILE(QQuickProfiler::SceneGraphPolishFrame, (polishTime));
+ }
+
+ emit window->afterAnimating();
+
+ cd->syncSceneGraph();
+
+ if (profileFrames)
+ syncTime = renderTimer.nsecsElapsed();
+
+ cd->renderSceneGraph(window->size());
+
+ if (profileFrames)
+ renderTime = renderTimer.nsecsElapsed();
+
+ if (data.grabOnly) {
+ // #### grabContent = qt_gl_read_framebuffer(window->size() * window->devicePixelRatio(), false, false);
+ data.grabOnly = false;
+ }
+
+ if (alsoSwap && window->isVisible()) {
+// ### gl->swapBuffers(window);
+ cd->fireFrameSwapped();
+ }
+
+ qint64 swapTime = 0;
+ if (profileFrames)
+ swapTime = renderTimer.nsecsElapsed();
+
+ if (QSG_LOG_TIME_RENDERLOOP().isDebugEnabled()) {
+ static QTime lastFrameTime = QTime::currentTime();
+ qCDebug(QSG_LOG_TIME_RENDERLOOP,
+ "Frame rendered with 'basic' renderloop in %dms, polish=%d, sync=%d, render=%d, swap=%d, frameDelta=%d",
+ int(swapTime / 1000000),
+ int(polishTime / 1000000),
+ int((syncTime - polishTime) / 1000000),
+ int((renderTime - syncTime) / 1000000),
+ int((swapTime - renderTime) / 10000000),
+ int(lastFrameTime.msecsTo(QTime::currentTime())));
+ lastFrameTime = QTime::currentTime();
+ }
+
+ Q_QUICK_SG_PROFILE(QQuickProfiler::SceneGraphRenderLoopFrame, (
+ syncTime - polishTime,
+ renderTime - syncTime,
+ swapTime - renderTime));
+
+ // Might have been set during syncSceneGraph()
+ if (data.updatePending)
+ maybeUpdate(window);
+}
+
+void RenderLoop::exposureChanged(QQuickWindow *window)
+{
+ if (window->isExposed()) {
+ m_windows[window].updatePending = true;
+ renderWindow(window);
+ }
+}
+
+QImage RenderLoop::grab(QQuickWindow *window)
+{
+ if (!m_windows.contains(window))
+ return QImage();
+
+ m_windows[window].grabOnly = true;
+
+ renderWindow(window);
+
+ QImage grabbed = grabContent;
+ grabContent = QImage();
+ return grabbed;
+}
+
+
+
+void RenderLoop::maybeUpdate(QQuickWindow *window)
+{
+ if (!m_windows.contains(window))
+ return;
+
+ m_windows[window].updatePending = true;
+
+ if (!eventPending) {
+ const int exhaust_delay = 5;
+ m_update_timer = startTimer(exhaust_delay, Qt::PreciseTimer);
+ eventPending = true;
+ }
+}
+
+QSurface::SurfaceType RenderLoop::windowSurfaceType() const
+{
+ return QSurface::RasterSurface;
+}
+
+
+
+QSGContext *RenderLoop::sceneGraphContext() const
+{
+ return sg;
+}
+
+
+bool RenderLoop::event(QEvent *e)
+{
+ if (e->type() == QEvent::Timer) {
+ eventPending = false;
+ killTimer(m_update_timer);
+ m_update_timer = 0;
+ for (QHash<QQuickWindow *, WindowData>::const_iterator it = m_windows.constBegin();
+ it != m_windows.constEnd(); ++it) {
+ const WindowData &data = it.value();
+ if (data.updatePending)
+ renderWindow(it.key());
+ }
+ return true;
+ }
+ return QObject::event(e);
+}
diff --git a/src/plugins/scenegraph/softwarecontext/renderloop.h b/src/plugins/scenegraph/softwarecontext/renderloop.h
new file mode 100644
index 0000000000..b6993a830a
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/renderloop.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef RENDERLOOP_H
+#define RENDERLOOP_H
+
+#include <private/qsgrenderloop_p.h>
+
+class RenderLoop : public QSGRenderLoop
+{
+ Q_OBJECT
+public:
+ RenderLoop();
+ ~RenderLoop();
+
+ void show(QQuickWindow *window);
+ void hide(QQuickWindow *window);
+
+ void windowDestroyed(QQuickWindow *window);
+
+ void renderWindow(QQuickWindow *window);
+ void exposureChanged(QQuickWindow *window);
+ QImage grab(QQuickWindow *window);
+
+ void maybeUpdate(QQuickWindow *window);
+ void update(QQuickWindow *window) { maybeUpdate(window); } // identical for this implementation.
+
+ void releaseResources(QQuickWindow *) { }
+
+ virtual QSurface::SurfaceType windowSurfaceType() const;
+
+ QAnimationDriver *animationDriver() const { return 0; }
+
+ QSGContext *sceneGraphContext() const;
+ QSGRenderContext *createRenderContext(QSGContext *) const { return rc; }
+
+ bool event(QEvent *);
+
+ struct WindowData {
+ bool updatePending : 1;
+ bool grabOnly : 1;
+ };
+
+ QHash<QQuickWindow *, WindowData> m_windows;
+
+ QSGContext *sg;
+ QSGRenderContext *rc;
+
+ QImage grabContent;
+ int m_update_timer;
+
+ bool eventPending;
+
+};
+
+#endif // RENDERLOOP_H
diff --git a/src/plugins/scenegraph/softwarecontext/softwarecontext.json b/src/plugins/scenegraph/softwarecontext/softwarecontext.json
new file mode 100644
index 0000000000..ca87c6c38a
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/softwarecontext.json
@@ -0,0 +1,3 @@
+{
+ "Keys": ["softwarecontext"]
+}
diff --git a/src/plugins/scenegraph/softwarecontext/softwarecontext.pro b/src/plugins/scenegraph/softwarecontext/softwarecontext.pro
new file mode 100644
index 0000000000..d60260c1eb
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/softwarecontext.pro
@@ -0,0 +1,43 @@
+TEMPLATE=lib
+TARGET=softwarecontext
+
+CONFIG += plugin
+
+QT += gui-private core-private quick-private qml-private
+
+SOURCES += \
+ context.cpp \
+ pluginmain.cpp \
+ renderloop.cpp \
+ rectanglenode.cpp \
+ imagenode.cpp \
+ pixmaptexture.cpp \
+ glyphnode.cpp \
+ renderingvisitor.cpp \
+ ninepatchnode.cpp \
+ softwarelayer.cpp \
+ threadedrenderloop.cpp \
+ painternode.cpp
+
+HEADERS += \
+ context.h \
+ pluginmain.h \
+ renderloop.h \
+ rectanglenode.h \
+ imagenode.h \
+ pixmaptexture.h \
+ glyphnode.h \
+ renderingvisitor.h \
+ ninepatchnode.h \
+ softwarelayer.h \
+ threadedrenderloop.h \
+ painternode.h
+
+OTHER_FILES += softwarecontext.json
+
+target.path += $$[QT_INSTALL_PLUGINS]/scenegraph
+
+files.path += $$[QT_INSTALL_PLUGINS]/scenegraph
+
+INSTALLS += target files
+
diff --git a/src/plugins/scenegraph/softwarecontext/softwarelayer.cpp b/src/plugins/scenegraph/softwarecontext/softwarelayer.cpp
new file mode 100644
index 0000000000..70cb73fdaa
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/softwarelayer.cpp
@@ -0,0 +1,219 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "softwarelayer.h"
+
+#include "context.h"
+
+SoftwareLayer::SoftwareLayer(QSGRenderContext *renderContext)
+ : m_item(0)
+ , m_shaderSourceNode(0)
+ , m_context(renderContext)
+ , m_renderer(0)
+ , m_device_pixel_ratio(1)
+ , m_live(true)
+ , m_grab(true)
+ , m_recursive(false)
+ , m_dirtyTexture(true)
+{
+
+}
+
+SoftwareLayer::~SoftwareLayer()
+{
+ invalidated();
+}
+
+int SoftwareLayer::textureId() const
+{
+ return 0;
+}
+
+QSize SoftwareLayer::textureSize() const
+{
+ return m_pixmap.size();
+}
+
+bool SoftwareLayer::hasAlphaChannel() const
+{
+ return m_pixmap.hasAlphaChannel();
+}
+
+bool SoftwareLayer::hasMipmaps() const
+{
+ return false;
+}
+
+void SoftwareLayer::bind()
+{
+}
+
+bool SoftwareLayer::updateTexture()
+{
+ bool doGrab = (m_live || m_grab) && m_dirtyTexture;
+ if (doGrab)
+ grab();
+ if (m_grab)
+ emit scheduledUpdateCompleted();
+ m_grab = false;
+ return doGrab;
+}
+
+void SoftwareLayer::setItem(QSGNode *item)
+{
+ if (item == m_item)
+ return;
+ m_item = item;
+
+ if (m_live && !m_item)
+ m_pixmap = QPixmap();
+
+ markDirtyTexture();
+}
+
+void SoftwareLayer::setShaderSourceNode(QSGNode *node)
+{
+ m_shaderSourceNode = node;
+}
+
+void SoftwareLayer::setRect(const QRectF &rect)
+{
+ if (rect == m_rect)
+ return;
+ m_rect = rect;
+ markDirtyTexture();
+}
+
+void SoftwareLayer::setSize(const QSize &size)
+{
+ if (size == m_size)
+ return;
+ m_size = size;
+
+ if (m_live && m_size.isNull())
+ m_pixmap = QPixmap();
+
+ markDirtyTexture();
+}
+
+void SoftwareLayer::scheduleUpdate()
+{
+ if (m_grab)
+ return;
+ m_grab = true;
+ if (m_dirtyTexture) {
+ emit updateRequested();
+ if (m_shaderSourceNode)
+ m_shaderSourceNode->markDirty(QSGNode::DirtyMaterial);
+ }
+}
+
+QImage SoftwareLayer::toImage() const
+{
+ return m_pixmap.toImage();
+}
+
+void SoftwareLayer::setLive(bool live)
+{
+ if (live == m_live)
+ return;
+ m_live = live;
+
+ if (m_live && (!m_item || m_size.isNull()))
+ m_pixmap = QPixmap();
+
+ markDirtyTexture();
+}
+
+void SoftwareLayer::setRecursive(bool recursive)
+{
+ m_recursive = recursive;
+}
+
+void SoftwareLayer::setFormat(GLenum)
+{
+}
+
+void SoftwareLayer::setHasMipmaps(bool)
+{
+}
+
+void SoftwareLayer::setDevicePixelRatio(qreal ratio)
+{
+ m_device_pixel_ratio = ratio;
+}
+
+void SoftwareLayer::markDirtyTexture()
+{
+ m_dirtyTexture = true;
+ if (m_live || m_grab) {
+ emit updateRequested();
+ if (m_shaderSourceNode)
+ m_shaderSourceNode->markDirty(QSGNode::DirtyMaterial);
+ }
+}
+
+void SoftwareLayer::invalidated()
+{
+ delete m_renderer;
+ m_renderer = 0;
+}
+
+void SoftwareLayer::grab()
+{
+ if (!m_item || m_size.isNull()) {
+ m_pixmap = QPixmap();
+ m_dirtyTexture = false;
+ return;
+ }
+ QSGNode *root = m_item;
+ while (root->firstChild() && root->type() != QSGNode::RootNodeType)
+ root = root->firstChild();
+ if (root->type() != QSGNode::RootNodeType)
+ return;
+
+ if (!m_renderer) {
+ m_renderer = new SoftwareContext::PixmapRenderer(m_context);
+ connect(m_renderer, SIGNAL(sceneGraphChanged()), this, SLOT(markDirtyTexture()));
+ }
+ m_renderer->setDevicePixelRatio(m_device_pixel_ratio);
+ m_renderer->setRootNode(static_cast<QSGRootNode *>(root));
+
+ if (m_pixmap.size() != m_size)
+ m_pixmap = QPixmap(m_size);
+
+ // Render texture.
+ root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip and opacity update.
+ m_renderer->nodeChanged(root, QSGNode::DirtyForceUpdate); // Force render list update.
+
+ m_dirtyTexture = false;
+
+ m_renderer->setDeviceRect(m_size);
+ m_renderer->setViewportRect(m_size);
+ QRectF mirrored(m_rect.left(), m_rect.bottom(), m_rect.width(), -m_rect.height());
+ m_renderer->setProjectionMatrixToRect(mirrored);
+ m_renderer->setClearColor(Qt::transparent);
+
+ m_renderer->render(&m_pixmap);
+
+ root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip, opacity and render list update.
+
+ if (m_recursive)
+ markDirtyTexture(); // Continuously update if 'live' and 'recursive'.
+}
diff --git a/src/plugins/scenegraph/softwarecontext/softwarelayer.h b/src/plugins/scenegraph/softwarecontext/softwarelayer.h
new file mode 100644
index 0000000000..3352e6085e
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/softwarelayer.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef SOFTWARELAYER_H
+#define SOFTWARELAYER_H
+
+#include <private/qsgadaptationlayer_p.h>
+#include <private/qsgcontext_p.h>
+
+namespace SoftwareContext {
+class PixmapRenderer;
+}
+
+class SoftwareLayer : public QSGLayer
+{
+ Q_OBJECT
+public:
+ SoftwareLayer(QSGRenderContext *renderContext);
+ ~SoftwareLayer();
+
+ const QPixmap &pixmap() const { return m_pixmap; }
+
+ // QSGTexture interface
+public:
+ virtual int textureId() const;
+ virtual QSize textureSize() const;
+ virtual bool hasAlphaChannel() const;
+ virtual bool hasMipmaps() const;
+ virtual void bind();
+
+ // QSGDynamicTexture interface
+public:
+ virtual bool updateTexture();
+
+ // QSGLayer interface
+public:
+ virtual void setItem(QSGNode *item);
+ virtual void setShaderSourceNode(QSGNode *node);
+ virtual void setRect(const QRectF &rect);
+ virtual void setSize(const QSize &size);
+ virtual void scheduleUpdate();
+ virtual QImage toImage() const;
+ virtual void setLive(bool live);
+ virtual void setRecursive(bool recursive);
+ virtual void setFormat(GLenum);
+ virtual void setHasMipmaps(bool);
+ virtual void setDevicePixelRatio(qreal ratio);
+
+public slots:
+ virtual void markDirtyTexture();
+ virtual void invalidated();
+
+private:
+ void grab();
+
+ QSGNode *m_item;
+ QSGNode *m_shaderSourceNode;
+ QSGRenderContext *m_context;
+ SoftwareContext::PixmapRenderer *m_renderer;
+ QRectF m_rect;
+ QSize m_size;
+ QPixmap m_pixmap;
+ qreal m_device_pixel_ratio;
+ bool m_live;
+ bool m_grab;
+ bool m_recursive;
+ bool m_dirtyTexture;
+};
+
+#endif // SOFTWARELAYER_H
diff --git a/src/plugins/scenegraph/softwarecontext/threadedrenderloop.cpp b/src/plugins/scenegraph/softwarecontext/threadedrenderloop.cpp
new file mode 100644
index 0000000000..6b8991552d
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/threadedrenderloop.cpp
@@ -0,0 +1,1129 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** Copyright (C) 2014 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "threadedrenderloop.h"
+
+#include <QtCore/QMutex>
+#include <QtCore/QWaitCondition>
+#include <QtCore/QAnimationDriver>
+#include <QtCore/QQueue>
+#include <QtCore/QTime>
+
+#include <QtGui/QGuiApplication>
+#include <QtGui/QScreen>
+#include <QtGui/QOffscreenSurface>
+
+#include <qpa/qwindowsysteminterface.h>
+#include <qpa/qplatformbackingstore.h>
+
+#include <QtQuick/QQuickWindow>
+#include <private/qquickwindow_p.h>
+
+#include <QtQuick/private/qsgrenderer_p.h>
+
+#include <private/qquickanimatorcontroller_p.h>
+
+#include <private/qquickprofiler_p.h>
+#include <private/qqmldebugservice_p.h>
+#include "context.h"
+
+/*
+ Overall design:
+
+ There are two classes here. ThreadedRenderLoop and
+ RenderThread. All communication between the two is based on
+ event passing and we have a number of custom events.
+
+ In this implementation, the render thread is never blocked and the
+ GUI thread will initiate a polishAndSync which will block and wait
+ for the render thread to pick it up and release the block only
+ after the render thread is done syncing. The reason for this
+ is:
+
+ 1. Clear blocking paradigm. We only have one real "block" point
+ (polishAndSync()) and all blocking is initiated by GUI and picked
+ up by Render at specific times based on events. This makes the
+ execution deterministic.
+
+ 2. Render does not have to interact with GUI. This is done so that
+ the render thread can run its own animation system which stays
+ alive even when the GUI thread is blocked doing i/o, object
+ instantiation, QPainter-painting or any other non-trivial task.
+
+ ---
+
+ There is one thread per window and one opengl context per thread.
+
+ ---
+
+ The render thread has affinity to the GUI thread until a window
+ is shown. From that moment and until the window is destroyed, it
+ will have affinity to the render thread. (moved back at the end
+ of run for cleanup).
+
+ ---
+
+ The render loop is active while any window is exposed. All visible
+ windows are tracked, but only exposed windows are actually added to
+ the render thread and rendered. That means that if all windows are
+ obscured, we might end up cleaning up the SG and GL context (if all
+ windows have disabled persistency). Especially for multiprocess,
+ low-end systems, this should be quite important.
+
+ */
+
+QT_BEGIN_NAMESPACE
+
+#define QSG_RT_PAD " (RT)"
+
+static int get_env_int(const char *name, int defaultValue)
+{
+ QByteArray content = qgetenv(name);
+
+ bool ok = false;
+ int value = content.toInt(&ok);
+ return ok ? value : defaultValue;
+}
+
+
+static inline int qsgrl_animation_interval() {
+ qreal refreshRate = QGuiApplication::primaryScreen()->refreshRate();
+ // To work around that some platforms wrongfully return 0 or something
+ // bogus for refreshrate
+ if (refreshRate < 1)
+ return 16;
+ return int(1000 / refreshRate);
+}
+
+
+static QElapsedTimer threadTimer;
+static qint64 syncTime;
+static qint64 renderTime;
+static qint64 sinceLastTime;
+
+extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
+
+// RL: Render Loop
+// RT: Render Thread
+
+// Passed from the RL to the RT when a window is removed obscured and
+// should be removed from the render loop.
+const QEvent::Type WM_Obscure = QEvent::Type(QEvent::User + 1);
+
+// Passed from the RL to RT when GUI has been locked, waiting for sync
+// (updatePaintNode())
+const QEvent::Type WM_RequestSync = QEvent::Type(QEvent::User + 2);
+
+// Passed by the RT to itself to trigger another render pass. This is
+// typically a result of QQuickWindow::update().
+const QEvent::Type WM_RequestRepaint = QEvent::Type(QEvent::User + 3);
+
+// Passed by the RL to the RT to free up maybe release SG and GL contexts
+// if no windows are rendering.
+const QEvent::Type WM_TryRelease = QEvent::Type(QEvent::User + 4);
+
+// Passed by the RL to the RT when a QQuickWindow::grabWindow() is
+// called.
+const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 5);
+
+template <typename T> T *windowFor(const QList<T> list, QQuickWindow *window)
+{
+ for (int i=0; i<list.size(); ++i) {
+ const T &t = list.at(i);
+ if (t.window == window)
+ return const_cast<T *>(&t);
+ }
+ return 0;
+}
+
+
+class WMWindowEvent : public QEvent
+{
+public:
+ WMWindowEvent(QQuickWindow *c, QEvent::Type type) : QEvent(type), window(c) { }
+ QQuickWindow *window;
+};
+
+class WMTryReleaseEvent : public WMWindowEvent
+{
+public:
+ WMTryReleaseEvent(QQuickWindow *win, bool destroy, QOffscreenSurface *fallback)
+ : WMWindowEvent(win, WM_TryRelease)
+ , inDestructor(destroy)
+ , fallbackSurface(fallback)
+ {}
+
+ bool inDestructor;
+ QOffscreenSurface *fallbackSurface;
+};
+
+class WMSyncEvent : public WMWindowEvent
+{
+public:
+ WMSyncEvent(QQuickWindow *c, bool inExpose) : WMWindowEvent(c, WM_RequestSync), size(c->size()), syncInExpose(inExpose) { }
+ QSize size;
+ bool syncInExpose;
+};
+
+
+class WMGrabEvent : public WMWindowEvent
+{
+public:
+ WMGrabEvent(QQuickWindow *c, QImage *result) : WMWindowEvent(c, WM_Grab), image(result) {}
+ QImage *image;
+};
+
+
+class RenderThreadEventQueue : public QQueue<QEvent *>
+{
+public:
+ RenderThreadEventQueue()
+ : waiting(false)
+ {
+ }
+
+ void addEvent(QEvent *e) {
+ mutex.lock();
+ enqueue(e);
+ if (waiting)
+ condition.wakeOne();
+ mutex.unlock();
+ }
+
+ QEvent *takeEvent(bool wait) {
+ mutex.lock();
+ if (size() == 0 && wait) {
+ waiting = true;
+ condition.wait(&mutex);
+ waiting = false;
+ }
+ QEvent *e = dequeue();
+ mutex.unlock();
+ return e;
+ }
+
+ bool hasMoreEvents() {
+ mutex.lock();
+ bool has = !isEmpty();
+ mutex.unlock();
+ return has;
+ }
+
+private:
+ QMutex mutex;
+ QWaitCondition condition;
+ bool waiting;
+};
+
+
+class RenderThread : public QThread
+{
+ Q_OBJECT
+public:
+ RenderThread(ThreadedRenderLoop *w, QSGRenderContext *renderContext)
+ : wm(w)
+ , sgrc(renderContext)
+ , animatorDriver(0)
+ , pendingUpdate(0)
+ , sleeping(false)
+ , syncResultedInChanges(false)
+ , active(false)
+ , window(0)
+ , stopEventProcessing(false)
+ {
+#if defined(Q_OS_QNX) && !defined(Q_OS_BLACKBERRY) && defined(Q_PROCESSOR_X86)
+ // The SDP 6.6.0 x86 MESA driver requires a larger stack than the default.
+ setStackSize(1024 * 1024);
+#endif
+ vsyncDelta = qsgrl_animation_interval();
+ }
+
+ ~RenderThread()
+ {
+ delete sgrc;
+ }
+
+ bool event(QEvent *);
+ void run();
+
+ void syncAndRender();
+ void sync(bool inExpose);
+
+ void requestRepaint()
+ {
+ if (sleeping)
+ stopEventProcessing = true;
+ if (window)
+ pendingUpdate |= RepaintRequest;
+ }
+
+ void processEventsAndWaitForMore();
+ void processEvents();
+ void postEvent(QEvent *e);
+
+public slots:
+ void sceneGraphChanged() {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "sceneGraphChanged";
+ syncResultedInChanges = true;
+ }
+
+public:
+ enum UpdateRequest {
+ SyncRequest = 0x01,
+ RepaintRequest = 0x02,
+ ExposeRequest = 0x04 | RepaintRequest | SyncRequest
+ };
+
+ ThreadedRenderLoop *wm;
+ QSGRenderContext *sgrc;
+
+ QAnimationDriver *animatorDriver;
+
+ uint pendingUpdate;
+ bool sleeping;
+ bool syncResultedInChanges;
+
+ volatile bool active;
+
+ float vsyncDelta;
+
+ QMutex mutex;
+ QWaitCondition waitCondition;
+
+ QElapsedTimer m_timer;
+
+ QQuickWindow *window; // Will be 0 when window is not exposed
+ QSize windowSize;
+
+ // Local event queue stuff...
+ bool stopEventProcessing;
+ RenderThreadEventQueue eventQueue;
+};
+
+bool RenderThread::event(QEvent *e)
+{
+ switch ((int) e->type()) {
+
+ case WM_Obscure: {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "WM_Obscure";
+
+ Q_ASSERT(!window || window == static_cast<WMWindowEvent *>(e)->window);
+
+ mutex.lock();
+ if (window) {
+ QQuickWindowPrivate::get(window)->fireAboutToStop();
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- window removed";
+ window = 0;
+ }
+ waitCondition.wakeOne();
+ mutex.unlock();
+
+ return true; }
+
+ case WM_RequestSync: {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "WM_RequestSync";
+ WMSyncEvent *se = static_cast<WMSyncEvent *>(e);
+ if (sleeping)
+ stopEventProcessing = true;
+ window = se->window;
+ windowSize = se->size;
+
+ pendingUpdate |= SyncRequest;
+ if (se->syncInExpose) {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- triggered from expose";
+ pendingUpdate |= ExposeRequest;
+ }
+ return true; }
+
+ case WM_TryRelease: {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "WM_TryRelease";
+ mutex.lock();
+ wm->m_lockedForSync = true;
+ WMTryReleaseEvent *wme = static_cast<WMTryReleaseEvent *>(e);
+ if (!window || wme->inDestructor) {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- setting exit flag and invalidating OpenGL";
+ active = false;
+ Q_ASSERT_X(!wme->inDestructor || !active, "RenderThread::invalidateOpenGL()", "Thread's active state is not set to false when shutting down");
+ if (sleeping)
+ stopEventProcessing = true;
+ } else {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- not releasing because window is still active";
+ }
+ waitCondition.wakeOne();
+ wm->m_lockedForSync = false;
+ mutex.unlock();
+ return true;
+ }
+
+ case WM_Grab: {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "WM_Grab";
+ WMGrabEvent *ce = static_cast<WMGrabEvent *>(e);
+ Q_ASSERT(ce->window == window);
+ mutex.lock();
+ if (window) {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- sync scene graph";
+ QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
+ static_cast<SoftwareContext::RenderContext*>(d->context)->currentWindow = window;
+ d->syncSceneGraph();
+
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- rendering scene graph";
+ QQuickWindowPrivate::get(window)->renderSceneGraph(windowSize);
+
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- grabbing result";
+ *ce->image = static_cast<SoftwareContext::Renderer*>(d->renderer)->backingStore()->handle()->toImage();
+ }
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- waking gui to handle result";
+ waitCondition.wakeOne();
+ mutex.unlock();
+ return true;
+ }
+
+ case WM_RequestRepaint:
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "WM_RequestPaint";
+ // When GUI posts this event, it is followed by a polishAndSync, so we mustn't
+ // exit the event loop yet.
+ pendingUpdate |= RepaintRequest;
+ break;
+
+ default:
+ break;
+ }
+ return QThread::event(e);
+}
+
+/*!
+ Enters the mutex lock to make sure GUI is blocking and performs
+ sync, then wakes GUI.
+ */
+void RenderThread::sync(bool inExpose)
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "sync()";
+ mutex.lock();
+
+ Q_ASSERT_X(wm->m_lockedForSync, "RenderThread::sync()", "sync triggered on bad terms as gui is not already locked...");
+
+ bool current = false;
+ if (windowSize.width() > 0 && windowSize.height() > 0)
+ current = true;
+ if (current) {
+ QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
+ static_cast<SoftwareContext::RenderContext*>(d->context)->currentWindow = window;
+ bool hadRenderer = d->renderer != 0;
+ // If the scene graph was touched since the last sync() make sure it sends the
+ // changed signal.
+ if (d->renderer)
+ d->renderer->clearChangedFlag();
+ d->syncSceneGraph();
+ if (!hadRenderer && d->renderer) {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- renderer was created";
+ syncResultedInChanges = true;
+ connect(d->renderer, SIGNAL(sceneGraphChanged()), this, SLOT(sceneGraphChanged()), Qt::DirectConnection);
+ }
+
+ // Process deferred deletes now, directly after the sync as
+ // deleteLater on the GUI must now also have resulted in SG changes
+ // and the delete is a safe operation.
+ QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
+ } else {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- window has bad size, sync aborted";
+ }
+
+ if (!inExpose) {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- sync complete, waking Gui";
+ waitCondition.wakeOne();
+ mutex.unlock();
+ }
+}
+
+void RenderThread::syncAndRender()
+{
+ bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled() || QQuickProfiler::enabled;
+ if (profileFrames) {
+ sinceLastTime = threadTimer.nsecsElapsed();
+ threadTimer.start();
+ }
+
+ QElapsedTimer waitTimer;
+ waitTimer.start();
+
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "syncAndRender()";
+
+ syncResultedInChanges = false;
+
+ uint pending = pendingUpdate;
+ pendingUpdate = 0;
+
+ if (pending & SyncRequest) {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- updatePending, doing sync";
+ sync(pending == ExposeRequest);
+ }
+
+ if (!syncResultedInChanges && ((pending & RepaintRequest) == 0)) {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- no changes, render aborted";
+ int waitTime = vsyncDelta - (int) waitTimer.elapsed();
+ if (waitTime > 0)
+ msleep(waitTime);
+ return;
+ }
+
+ if (profileFrames)
+ syncTime = threadTimer.nsecsElapsed();
+
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- rendering started";
+
+ QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
+
+ if (animatorDriver->isRunning()) {
+ d->animationController->lock();
+ animatorDriver->advance();
+ d->animationController->unlock();
+ }
+
+ bool current = false;
+ if (d->renderer && windowSize.width() > 0 && windowSize.height() > 0)
+ current = true;
+ if (current) {
+ static_cast<SoftwareContext::RenderContext*>(d->context)->currentWindow = window;
+ d->renderSceneGraph(windowSize);
+ if (profileFrames)
+ renderTime = threadTimer.nsecsElapsed();
+ // ### used to be swappBuffers here
+ d->fireFrameSwapped();
+ } else {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- window not ready, skipping render";
+ }
+
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- rendering done";
+
+ // Though it would be more correct to put this block directly after
+ // fireFrameSwapped in the if (current) branch above, we don't do
+ // that to avoid blocking the GUI thread in the case where it
+ // has started rendering with a bad window, causing makeCurrent to
+ // fail or if the window has a bad size.
+ if (pending == ExposeRequest) {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "- wake Gui after initial expose";
+ waitCondition.wakeOne();
+ mutex.unlock();
+ }
+
+ qCDebug(QSG_LOG_TIME_RENDERLOOP,
+ "Frame rendered with 'threaded' renderloop in %dms, sync=%d, render=%d, swap=%d - (on render thread)",
+ int(threadTimer.elapsed()),
+ int((syncTime/1000000)),
+ int((renderTime - syncTime) / 1000000),
+ int(threadTimer.elapsed() - renderTime / 1000000));
+
+
+ Q_QUICK_SG_PROFILE(QQuickProfiler::SceneGraphRenderLoopFrame, (
+ syncTime,
+ renderTime - syncTime,
+ threadTimer.nsecsElapsed() - renderTime));
+}
+
+
+
+void RenderThread::postEvent(QEvent *e)
+{
+ eventQueue.addEvent(e);
+}
+
+
+
+void RenderThread::processEvents()
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "--- begin processEvents()";
+ while (eventQueue.hasMoreEvents()) {
+ QEvent *e = eventQueue.takeEvent(false);
+ event(e);
+ delete e;
+ }
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "--- done processEvents()";
+}
+
+void RenderThread::processEventsAndWaitForMore()
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "--- begin processEventsAndWaitForMore()";
+ stopEventProcessing = false;
+ while (!stopEventProcessing) {
+ QEvent *e = eventQueue.takeEvent(true);
+ event(e);
+ delete e;
+ }
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "--- done processEventsAndWaitForMore()";
+}
+
+void RenderThread::run()
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "run()";
+ animatorDriver = sgrc->sceneGraphContext()->createAnimationDriver(0);
+ animatorDriver->install();
+ QUnifiedTimer::instance(true)->setConsistentTiming(QSGRenderLoop::useConsistentTiming());
+ if (QQmlDebugService::isDebuggingEnabled())
+ QQuickProfiler::registerAnimationCallback();
+
+ while (active) {
+
+ if (window) {
+ static_cast<SoftwareContext::RenderContext*>(sgrc)->initializeIfNeeded();
+ syncAndRender();
+ }
+
+ processEvents();
+ QCoreApplication::processEvents();
+
+ if (active && (pendingUpdate == 0 || !window)) {
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "done drawing, sleep...";
+ sleeping = true;
+ processEventsAndWaitForMore();
+ sleeping = false;
+ }
+ }
+
+ qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "run() completed";
+
+ delete animatorDriver;
+ animatorDriver = 0;
+
+ sgrc->moveToThread(wm->thread());
+ moveToThread(wm->thread());
+}
+
+ThreadedRenderLoop::ThreadedRenderLoop()
+ : sg(QSGContext::createDefaultContext())
+ , m_animation_timer(0)
+{
+#if defined(QSG_RENDER_LOOP_DEBUG)
+ qsgrl_timer.start();
+#endif
+
+ m_animation_driver = sg->createAnimationDriver(this);
+
+ m_exhaust_delay = get_env_int("QML_EXHAUST_DELAY", 5);
+
+ connect(m_animation_driver, SIGNAL(started()), this, SLOT(animationStarted()));
+ connect(m_animation_driver, SIGNAL(stopped()), this, SLOT(animationStopped()));
+
+ m_animation_driver->install();
+}
+
+QSGRenderContext *ThreadedRenderLoop::createRenderContext(QSGContext *sg) const
+{
+ return sg->createRenderContext();
+}
+
+void ThreadedRenderLoop::maybePostPolishRequest(Window *w)
+{
+ if (w->timerId == 0) {
+ qCDebug(QSG_LOG_RENDERLOOP) << "- posting update";
+ w->timerId = startTimer(m_exhaust_delay, Qt::PreciseTimer);
+ }
+}
+
+QAnimationDriver *ThreadedRenderLoop::animationDriver() const
+{
+ return m_animation_driver;
+}
+
+QSGContext *ThreadedRenderLoop::sceneGraphContext() const
+{
+ return sg;
+}
+
+bool ThreadedRenderLoop::anyoneShowing() const
+{
+ for (int i=0; i<m_windows.size(); ++i) {
+ QQuickWindow *c = m_windows.at(i).window;
+ if (c->isVisible() && c->isExposed())
+ return true;
+ }
+ return false;
+}
+
+bool ThreadedRenderLoop::interleaveIncubation() const
+{
+ return m_animation_driver->isRunning() && anyoneShowing();
+}
+
+void ThreadedRenderLoop::animationStarted()
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << "- animationStarted()";
+ startOrStopAnimationTimer();
+
+ for (int i=0; i<m_windows.size(); ++i)
+ maybePostPolishRequest(const_cast<Window *>(&m_windows.at(i)));
+}
+
+void ThreadedRenderLoop::animationStopped()
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << "- animationStopped()";
+ startOrStopAnimationTimer();
+}
+
+
+void ThreadedRenderLoop::startOrStopAnimationTimer()
+{
+ int exposedWindows = 0;
+ Window *theOne = 0;
+ for (int i=0; i<m_windows.size(); ++i) {
+ Window &w = m_windows[i];
+ if (w.window->isVisible() && w.window->isExposed()) {
+ ++exposedWindows;
+ theOne = &w;
+ }
+ }
+
+ if (m_animation_timer != 0 && (exposedWindows == 1 || !m_animation_driver->isRunning())) {
+ killTimer(m_animation_timer);
+ m_animation_timer = 0;
+ // If animations are running, make sure we keep on animating
+ if (m_animation_driver->isRunning())
+ maybePostPolishRequest(theOne);
+
+ } else if (m_animation_timer == 0 && exposedWindows != 1 && m_animation_driver->isRunning()) {
+ m_animation_timer = startTimer(qsgrl_animation_interval());
+ }
+}
+
+/*
+ Removes this window from the list of tracked windowes in this
+ window manager. hide() will trigger obscure, which in turn will
+ stop rendering.
+
+ This function will be called during QWindow::close() which will
+ also destroy the QPlatformWindow so it is important that this
+ triggers handleObscurity() and that rendering for that window
+ is fully done and over with by the time this function exits.
+ */
+
+void ThreadedRenderLoop::hide(QQuickWindow *window)
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << "hide()" << window;
+
+ if (window->isExposed())
+ handleObscurity(windowFor(m_windows, window));
+
+ releaseResources(window);
+}
+
+
+/*!
+ If the window is first hide it, then perform a complete cleanup
+ with releaseResources which will take down the GL context and
+ exit the rendering thread.
+ */
+void ThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << "begin windowDestroyed()" << window;
+
+ Window *w = windowFor(m_windows, window);
+ if (!w)
+ return;
+
+ handleObscurity(w);
+ releaseResources(w, true);
+
+ RenderThread *thread = w->thread;
+ while (thread->isRunning())
+ QThread::yieldCurrentThread();
+ Q_ASSERT(thread->thread() == QThread::currentThread());
+ delete thread;
+
+ for (int i=0; i<m_windows.size(); ++i) {
+ if (m_windows.at(i).window == window) {
+ m_windows.removeAt(i);
+ break;
+ }
+ }
+
+ qCDebug(QSG_LOG_RENDERLOOP) << "done windowDestroyed()" << window;
+}
+
+
+void ThreadedRenderLoop::exposureChanged(QQuickWindow *window)
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << "exposureChanged()" << window;
+ if (window->isExposed()) {
+ handleExposure(window);
+ } else {
+ Window *w = windowFor(m_windows, window);
+ if (w)
+ handleObscurity(w);
+ }
+}
+
+/*!
+ Will post an event to the render thread that this window should
+ start to render.
+ */
+void ThreadedRenderLoop::handleExposure(QQuickWindow *window)
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << "handleExposure()" << window;
+
+ Window *w = windowFor(m_windows, window);
+ if (!w) {
+ qCDebug(QSG_LOG_RENDERLOOP) << "- adding window to list";
+ Window win;
+ win.window = window;
+ win.actualWindowFormat = window->format();
+ win.thread = new RenderThread(this, QQuickWindowPrivate::get(window)->context);
+ win.timerId = 0;
+ win.updateDuringSync = false;
+ m_windows << win;
+ w = &m_windows.last();
+ }
+
+ // set this early as we'll be rendering shortly anyway and this avoids
+ // specialcasing exposure in polishAndSync.
+ w->thread->window = window;
+
+ if (w->window->width() <= 0 || w->window->height() <= 0
+ || !w->window->geometry().intersects(w->window->screen()->availableGeometry())) {
+#ifndef QT_NO_DEBUG
+ qWarning("ThreadedRenderLoop: expose event received for window with invalid geometry.");
+#endif
+ }
+
+ // Because we are going to bind a GL context to it, make sure it
+ // is created.
+ if (!w->window->handle())
+ w->window->create();
+
+ // Start render thread if it is not running
+ if (!w->thread->isRunning()) {
+
+ qCDebug(QSG_LOG_RENDERLOOP) << "- starting render thread";
+
+ QQuickAnimatorController *controller = QQuickWindowPrivate::get(w->window)->animationController;
+ if (controller->thread() != w->thread)
+ controller->moveToThread(w->thread);
+
+ w->thread->active = true;
+ if (w->thread->thread() == QThread::currentThread()) {
+ w->thread->sgrc->moveToThread(w->thread);
+ w->thread->moveToThread(w->thread);
+ }
+ w->thread->start();
+
+ } else {
+ qCDebug(QSG_LOG_RENDERLOOP) << "- render thread already running";
+ }
+
+ polishAndSync(w, true);
+ qCDebug(QSG_LOG_RENDERLOOP) << "- done with handleExposure()";
+
+ startOrStopAnimationTimer();
+}
+
+/*!
+ This function posts an event to the render thread to remove the window
+ from the list of windowses to render.
+
+ It also starts up the non-vsync animation tick if no more windows
+ are showing.
+ */
+void ThreadedRenderLoop::handleObscurity(Window *w)
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << "handleObscurity()" << w->window;
+ if (w->thread->isRunning()) {
+ w->thread->mutex.lock();
+ w->thread->postEvent(new WMWindowEvent(w->window, WM_Obscure));
+ w->thread->waitCondition.wait(&w->thread->mutex);
+ w->thread->mutex.unlock();
+ }
+ startOrStopAnimationTimer();
+}
+
+
+void ThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
+{
+ Window *w = windowFor(m_windows, window);
+ if (w)
+ maybeUpdate(w);
+}
+
+/*!
+ Called whenever the QML scene has changed. Will post an event to
+ ourselves that a sync is needed.
+ */
+void ThreadedRenderLoop::maybeUpdate(Window *w)
+{
+ if (!QCoreApplication::instance())
+ return;
+
+ QThread *current = QThread::currentThread();
+ if (current != QCoreApplication::instance()->thread() && (current != w->thread || !m_lockedForSync)) {
+ qWarning() << "Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode()";
+ return;
+ }
+
+ if (!w || !w->thread->isRunning()) {
+ return;
+ }
+ qCDebug(QSG_LOG_RENDERLOOP) << "update from item" << w->window;
+
+ // Call this function from the Gui thread later as startTimer cannot be
+ // called from the render thread.
+ if (QThread::currentThread() == w->thread) {
+ qCDebug(QSG_LOG_RENDERLOOP) << "- on render thread";
+ w->updateDuringSync = true;
+ return;
+ }
+
+ maybePostPolishRequest(w);
+}
+
+/*!
+ Called when the QQuickWindow should be explicitly repainted. This function
+ can also be called on the render thread when the GUI thread is blocked to
+ keep render thread animations alive.
+ */
+void ThreadedRenderLoop::update(QQuickWindow *window)
+{
+ Window *w = windowFor(m_windows, window);
+ if (!w)
+ return;
+
+ if (w->thread == QThread::currentThread()) {
+ qCDebug(QSG_LOG_RENDERLOOP) << "update on window - on render thread" << w->window;
+ w->thread->requestRepaint();
+ return;
+ }
+
+ qCDebug(QSG_LOG_RENDERLOOP) << "update on window" << w->window;
+ w->thread->postEvent(new QEvent(WM_RequestRepaint));
+ maybeUpdate(w);
+}
+
+
+void ThreadedRenderLoop::releaseResources(QQuickWindow *window)
+{
+ Window *w = windowFor(m_windows, window);
+ if (w)
+ releaseResources(w, false);
+}
+
+/*!
+ * Release resources will post an event to the render thread to
+ * free up the SG and GL resources and exists the render thread.
+ */
+void ThreadedRenderLoop::releaseResources(Window *w, bool inDestructor)
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << "releaseResources()" << (inDestructor ? "in destructor" : "in api-call") << w->window;
+
+ w->thread->mutex.lock();
+ if (w->thread->isRunning() && w->thread->active) {
+ QQuickWindow *window = w->window;
+
+ // The platform window might have been destroyed before
+ // hide/release/windowDestroyed is called, so we need to have a
+ // fallback surface to perform the cleanup of the scene graph
+ // and the OpenGL resources.
+ // QOffscreenSurface must be created on the GUI thread, so we
+ // create it here and pass it on to RenderThread::invalidateGL()
+ QOffscreenSurface *fallback = 0;
+ if (!window->handle()) {
+ qCDebug(QSG_LOG_RENDERLOOP) << "- using fallback surface";
+ fallback = new QOffscreenSurface();
+ fallback->setFormat(w->actualWindowFormat);
+ fallback->create();
+ }
+
+ qCDebug(QSG_LOG_RENDERLOOP) << "- posting release request to render thread";
+ w->thread->postEvent(new WMTryReleaseEvent(window, inDestructor, fallback));
+ w->thread->waitCondition.wait(&w->thread->mutex);
+ delete fallback;
+ }
+ w->thread->mutex.unlock();
+}
+
+
+/* Calls polish on all items, then requests synchronization with the render thread
+ * and blocks until that is complete. Returns false if it aborted; otherwise true.
+ */
+void ThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << "polishAndSync" << (inExpose ? "(in expose)" : "(normal)") << w->window;
+
+ QQuickWindow *window = w->window;
+ if (!w->thread || !w->thread->window) {
+ qCDebug(QSG_LOG_RENDERLOOP) << "- not exposed, abort";
+ killTimer(w->timerId);
+ w->timerId = 0;
+ return;
+ }
+
+ // Flush pending touch events.
+ QQuickWindowPrivate::get(window)->flushDelayedTouchEvent();
+ // The delivery of the event might have caused the window to stop rendering
+ w = windowFor(m_windows, window);
+ if (!w || !w->thread || !w->thread->window) {
+ qCDebug(QSG_LOG_RENDERLOOP) << "- removed after event flushing, abort";
+ killTimer(w->timerId);
+ w->timerId = 0;
+ return;
+ }
+
+
+ QElapsedTimer timer;
+ qint64 polishTime = 0;
+ qint64 waitTime = 0;
+ qint64 syncTime = 0;
+ bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled() || QQuickProfiler::enabled;
+ if (profileFrames)
+ timer.start();
+
+ QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
+ d->polishItems();
+
+ if (profileFrames)
+ polishTime = timer.nsecsElapsed();
+
+ w->updateDuringSync = false;
+
+ emit window->afterAnimating();
+
+ qCDebug(QSG_LOG_RENDERLOOP) << "- lock for sync";
+ w->thread->mutex.lock();
+ m_lockedForSync = true;
+ w->thread->postEvent(new WMSyncEvent(window, inExpose));
+
+ qCDebug(QSG_LOG_RENDERLOOP) << "- wait for sync";
+ if (profileFrames)
+ waitTime = timer.nsecsElapsed();
+ w->thread->waitCondition.wait(&w->thread->mutex);
+ m_lockedForSync = false;
+ w->thread->mutex.unlock();
+ qCDebug(QSG_LOG_RENDERLOOP) << "- unlock after sync";
+
+ if (profileFrames)
+ syncTime = timer.nsecsElapsed();
+
+ killTimer(w->timerId);
+ w->timerId = 0;
+
+ if (m_animation_timer == 0 && m_animation_driver->isRunning()) {
+ qCDebug(QSG_LOG_RENDERLOOP) << "- advancing animations";
+ m_animation_driver->advance();
+ qCDebug(QSG_LOG_RENDERLOOP) << "- animations done..";
+ // We need to trigger another sync to keep animations running...
+ maybePostPolishRequest(w);
+ emit timeToIncubate();
+ } else if (w->updateDuringSync) {
+ maybePostPolishRequest(w);
+ }
+
+ qCDebug(QSG_LOG_TIME_RENDERLOOP()).nospace()
+ << "Frame prepared with 'threaded' renderloop"
+ << ", polish=" << (polishTime / 1000000)
+ << ", lock=" << (waitTime - polishTime) / 1000000
+ << ", blockedForSync=" << (syncTime - waitTime) / 1000000
+ << ", animations=" << (timer.nsecsElapsed() - syncTime) / 1000000
+ << " - (on Gui thread) " << window;
+
+ Q_QUICK_SG_PROFILE(QQuickProfiler::SceneGraphPolishAndSync, (
+ polishTime,
+ waitTime - polishTime,
+ syncTime - waitTime,
+ timer.nsecsElapsed() - syncTime));
+}
+
+ThreadedRenderLoop::Window *ThreadedRenderLoop::windowForTimer(int timerId) const
+{
+ for (int i=0; i<m_windows.size(); ++i) {
+ if (m_windows.at(i).timerId == timerId) {
+ return const_cast<Window *>(&m_windows.at(i));
+ break;
+ }
+ }
+ return 0;
+}
+
+bool ThreadedRenderLoop::event(QEvent *e)
+{
+ switch ((int) e->type()) {
+
+ case QEvent::Timer: {
+ QTimerEvent *te = static_cast<QTimerEvent *>(e);
+ if (te->timerId() == m_animation_timer) {
+ qCDebug(QSG_LOG_RENDERLOOP) << "- ticking non-visual timer";
+ m_animation_driver->advance();
+ emit timeToIncubate();
+ } else {
+ qCDebug(QSG_LOG_RENDERLOOP) << "- polish and sync timer";
+ Window *w = windowForTimer(te->timerId());
+ if (w)
+ polishAndSync(w);
+ else
+ killTimer(te->timerId());
+ }
+ return true;
+ }
+
+ default:
+ break;
+ }
+
+ return QObject::event(e);
+}
+
+
+
+/*
+ Locks down GUI and performs a grab the scene graph, then returns the result.
+
+ Since the QML scene could have changed since the last time it was rendered,
+ we need to polish and sync the scene graph. This might seem superfluous, but
+ - QML changes could have triggered deleteLater() which could have removed
+ textures or other objects from the scene graph, causing render to crash.
+ - Autotests rely on grab(), setProperty(), grab(), compare behavior.
+ */
+
+QImage ThreadedRenderLoop::grab(QQuickWindow *window)
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << "grab()" << window;
+
+ Window *w = windowFor(m_windows, window);
+ Q_ASSERT(w);
+
+ if (!w->thread->isRunning())
+ return QImage();
+
+ if (!window->handle())
+ window->create();
+
+ qCDebug(QSG_LOG_RENDERLOOP) << "- polishing items";
+ QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
+ d->polishItems();
+
+ QImage result;
+ w->thread->mutex.lock();
+ m_lockedForSync = true;
+ qCDebug(QSG_LOG_RENDERLOOP) << "- posting grab event";
+ w->thread->postEvent(new WMGrabEvent(window, &result));
+ w->thread->waitCondition.wait(&w->thread->mutex);
+ m_lockedForSync = false;
+ w->thread->mutex.unlock();
+
+ qCDebug(QSG_LOG_RENDERLOOP) << "- grab complete";
+
+ return result;
+}
+
+#include "threadedrenderloop.moc"
diff --git a/src/plugins/scenegraph/softwarecontext/threadedrenderloop.h b/src/plugins/scenegraph/softwarecontext/threadedrenderloop.h
new file mode 100644
index 0000000000..7e3bcb2d79
--- /dev/null
+++ b/src/plugins/scenegraph/softwarecontext/threadedrenderloop.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** Copyright (C) 2014 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt SceneGraph Raster Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef THREADEDRENDERLOOP_H
+#define THREADEDRENDERLOOP_H
+
+#include <private/qsgrenderloop_p.h>
+
+class RenderThread;
+
+class ThreadedRenderLoop : public QSGRenderLoop
+{
+ Q_OBJECT
+public:
+ ThreadedRenderLoop();
+
+ void show(QQuickWindow *) {}
+ void hide(QQuickWindow *);
+
+ void windowDestroyed(QQuickWindow *window);
+ void exposureChanged(QQuickWindow *window);
+
+ QImage grab(QQuickWindow *);
+
+ void update(QQuickWindow *window);
+ void maybeUpdate(QQuickWindow *window);
+ QSGContext *sceneGraphContext() const;
+ QSGRenderContext *createRenderContext(QSGContext *) const;
+
+ QAnimationDriver *animationDriver() const;
+
+ void releaseResources(QQuickWindow *window);
+
+ bool event(QEvent *);
+
+ bool interleaveIncubation() const;
+
+public Q_SLOTS:
+ void animationStarted();
+ void animationStopped();
+
+private:
+ struct Window {
+ QQuickWindow *window;
+ RenderThread *thread;
+ QSurfaceFormat actualWindowFormat;
+ int timerId;
+ uint updateDuringSync : 1;
+ };
+
+ friend class RenderThread;
+
+ void releaseResources(Window *window, bool inDestructor);
+ bool checkAndResetForceUpdate(QQuickWindow *window);
+ Window *windowForTimer(int timerId) const;
+
+ bool anyoneShowing() const;
+ void initialize();
+
+ void startOrStopAnimationTimer();
+ void maybePostPolishRequest(Window *w);
+ void waitForReleaseComplete();
+ void polishAndSync(Window *w, bool inExpose = false);
+ void maybeUpdate(Window *window);
+
+ void handleExposure(QQuickWindow *w);
+ void handleObscurity(Window *w);
+
+
+ QSGContext *sg;
+ QAnimationDriver *m_animation_driver;
+ QList<Window> m_windows;
+
+ int m_animation_timer;
+ int m_exhaust_delay;
+
+ bool m_lockedForSync;
+};
+
+#endif // THREADEDRENDERLOOP_H