From ba0d8e93d8525bb459e9b6cb384f2fe0701d5a02 Mon Sep 17 00:00:00 2001 From: Andy Nichols Date: Mon, 1 Sep 2014 16:49:58 +0200 Subject: Reformat project to be a Qt Module Change-Id: I2fe8df530a687247a9cd7ea12c1d8de79fef506e Reviewed-by: Simon Hausmann --- src/plugins/scenegraph/softwarecontext/context.cpp | 216 ++++ src/plugins/scenegraph/softwarecontext/context.h | 98 ++ .../scenegraph/softwarecontext/glyphnode.cpp | 91 ++ src/plugins/scenegraph/softwarecontext/glyphnode.h | 49 + .../scenegraph/softwarecontext/imagenode.cpp | 423 ++++++++ src/plugins/scenegraph/softwarecontext/imagenode.h | 108 ++ .../scenegraph/softwarecontext/ninepatchnode.cpp | 66 ++ .../scenegraph/softwarecontext/ninepatchnode.h | 45 + .../scenegraph/softwarecontext/painternode.cpp | 189 ++++ .../scenegraph/softwarecontext/painternode.h | 96 ++ .../scenegraph/softwarecontext/pixmaptexture.cpp | 56 + .../scenegraph/softwarecontext/pixmaptexture.h | 44 + .../scenegraph/softwarecontext/pluginmain.cpp | 54 + .../scenegraph/softwarecontext/pluginmain.h | 47 + .../scenegraph/softwarecontext/rectanglenode.cpp | 219 ++++ .../scenegraph/softwarecontext/rectanglenode.h | 61 ++ .../softwarecontext/renderingvisitor.cpp | 169 +++ .../scenegraph/softwarecontext/renderingvisitor.h | 55 + .../scenegraph/softwarecontext/renderloop.cpp | 251 +++++ .../scenegraph/softwarecontext/renderloop.h | 72 ++ .../softwarecontext/softwarecontext.json | 3 + .../scenegraph/softwarecontext/softwarecontext.pro | 43 + .../scenegraph/softwarecontext/softwarelayer.cpp | 219 ++++ .../scenegraph/softwarecontext/softwarelayer.h | 86 ++ .../softwarecontext/threadedrenderloop.cpp | 1129 ++++++++++++++++++++ .../softwarecontext/threadedrenderloop.h | 97 ++ 26 files changed, 3986 insertions(+) create mode 100644 src/plugins/scenegraph/softwarecontext/context.cpp create mode 100644 src/plugins/scenegraph/softwarecontext/context.h create mode 100644 src/plugins/scenegraph/softwarecontext/glyphnode.cpp create mode 100644 src/plugins/scenegraph/softwarecontext/glyphnode.h create mode 100644 src/plugins/scenegraph/softwarecontext/imagenode.cpp create mode 100644 src/plugins/scenegraph/softwarecontext/imagenode.h create mode 100644 src/plugins/scenegraph/softwarecontext/ninepatchnode.cpp create mode 100644 src/plugins/scenegraph/softwarecontext/ninepatchnode.h create mode 100644 src/plugins/scenegraph/softwarecontext/painternode.cpp create mode 100644 src/plugins/scenegraph/softwarecontext/painternode.h create mode 100644 src/plugins/scenegraph/softwarecontext/pixmaptexture.cpp create mode 100644 src/plugins/scenegraph/softwarecontext/pixmaptexture.h create mode 100644 src/plugins/scenegraph/softwarecontext/pluginmain.cpp create mode 100644 src/plugins/scenegraph/softwarecontext/pluginmain.h create mode 100644 src/plugins/scenegraph/softwarecontext/rectanglenode.cpp create mode 100644 src/plugins/scenegraph/softwarecontext/rectanglenode.h create mode 100644 src/plugins/scenegraph/softwarecontext/renderingvisitor.cpp create mode 100644 src/plugins/scenegraph/softwarecontext/renderingvisitor.h create mode 100644 src/plugins/scenegraph/softwarecontext/renderloop.cpp create mode 100644 src/plugins/scenegraph/softwarecontext/renderloop.h create mode 100644 src/plugins/scenegraph/softwarecontext/softwarecontext.json create mode 100644 src/plugins/scenegraph/softwarecontext/softwarecontext.pro create mode 100644 src/plugins/scenegraph/softwarecontext/softwarelayer.cpp create mode 100644 src/plugins/scenegraph/softwarecontext/softwarelayer.h create mode 100644 src/plugins/scenegraph/softwarecontext/threadedrenderloop.cpp create mode 100644 src/plugins/scenegraph/softwarecontext/threadedrenderloop.h (limited to 'src/plugins/scenegraph/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 +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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 +#include +#include +#include +#include +#include + +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 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 + +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 +#include + +// 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 xTarget; // x-coordinates of target rectangles + QVarLengthArray 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(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(m_texture)) { + return pt->pixmap(); + } else if (SoftwareLayer *layer = qobject_cast(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 +#include + +typedef QVarLengthArray 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(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 + +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 + +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 +#include + +#include + +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 + +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 +#include + +#include + +#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 + +#include + + +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 + +#include +#include +#include + +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 +#include +#include +#include + +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(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(node)) { + QSGTexture *texture = tn->texture(); + if (PixmapTexture *pt = dynamic_cast(texture)) { + const QPixmap &pm = pt->pixmap(); + painter->drawPixmap(tn->rect(), pm, QRectF(0, 0, pm.width(), pm.height())); + } else if (QSGPlainTexture *pt = dynamic_cast(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(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(node)->paint(painter); + return true; +} + +void RenderingVisitor::endVisit(QSGImageNode *) +{ +} + +bool RenderingVisitor::visit(QSGPainterNode *node) +{ + static_cast(node)->paint(painter); + return true; +} + +void RenderingVisitor::endVisit(QSGPainterNode *node) +{ + +} + +bool RenderingVisitor::visit(QSGRectangleNode *node) +{ + static_cast(node)->paint(painter); + return true; +} + +void RenderingVisitor::endVisit(QSGRectangleNode *) +{ +} + +bool RenderingVisitor::visit(QSGGlyphNode *node) +{ + static_cast(node)->paint(painter); + return true; +} + +void RenderingVisitor::endVisit(QSGGlyphNode *) +{ +} + +bool RenderingVisitor::visit(QSGNinePatchNode *node) +{ + static_cast(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 + +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 +#include +#include + +// 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(m_windows[window]); + + // ### create QPainter and set up pointer to current window/painter + SoftwareContext::RenderContext *ctx = static_cast(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::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 + +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 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(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 +#include + +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: +** 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 +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include + +#include +#include +#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 T *windowFor(const QList list, QQuickWindow *window) +{ + for (int i=0; i(&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 +{ +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(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(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(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(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(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(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(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(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(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; iisVisible() && 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.at(i))); +} + +void ThreadedRenderLoop::animationStopped() +{ + qCDebug(QSG_LOG_RENDERLOOP) << "- animationStopped()"; + startOrStopAnimationTimer(); +} + + +void ThreadedRenderLoop::startOrStopAnimationTimer() +{ + int exposedWindows = 0; + Window *theOne = 0; + for (int i=0; iisVisible() && 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; iisExposed()) { + 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.at(i)); + break; + } + } + return 0; +} + +bool ThreadedRenderLoop::event(QEvent *e) +{ + switch ((int) e->type()) { + + case QEvent::Timer: { + QTimerEvent *te = static_cast(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: +** 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 + +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 m_windows; + + int m_animation_timer; + int m_exhaust_delay; + + bool m_lockedForSync; +}; + +#endif // THREADEDRENDERLOOP_H -- cgit v1.2.3