diff options
author | Gunnar Sletta <gunnar.sletta@digia.com> | 2013-11-27 17:04:06 +0100 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-12-03 18:06:36 +0100 |
commit | 9ad9615d0003c9fb84255152f0cbb473ee2a7a70 (patch) | |
tree | c4aba681ddacd7eadb4defe7312e9020b43407c2 /src/quick | |
parent | 9e77d6c31b406e5941a2d287f3c8842954815db4 (diff) |
Improve the Canvas threading model
The canvas classes were mixing scene graph resources and GL
content across threads. This led to a number of potential crashes
in addition to that the FBO based rendering had significant
potential for stalling.
QQuickContext2DTexture is no longer a QSGTexture with ambiguous
ownership. Instead we use textureForNextFrame which is called
on the render thread while the GUI is locked to synchronize
state from the Context2D's "texture" into the actual QSGTexture.
This means that cleanup of the QQuickContext2DTexture and
the QSGTexture used for display is no longer in conflict.
QQuickPixmap no longer contains a QSGTexture either as these
are strictly for use on the scene graph thread. The Images are
anyway loaded explicitly as QImage files in QQuickContext2DContext
and uploaded again for every Canvas, so relying on the GL paint
engine to do the caching will give us the same with less code.
I also changed the default strategy to Immediate as that one
supports the full API (cooperative does not support readback)
and because cooperative is pretty bad for performance since the
rendering happens in the sync() step.
Task-number: QTBUG-34268
Task-number: QTBUG-31052
Task-number: QTBUG-21935
Task-number: QTBUG-30689
Task-number: QTBUG-29007
Change-Id: Ic540b22d5faa1188e21e56a3beee24191d13f423
Reviewed-by: Mitch Curtis <mitch.curtis@digia.com>
Reviewed-by: Laszlo Agocs <laszlo.agocs@digia.com>
Diffstat (limited to 'src/quick')
-rw-r--r-- | src/quick/items/context2d/qquickcanvascontext_p.h | 2 | ||||
-rw-r--r-- | src/quick/items/context2d/qquickcanvasitem.cpp | 64 | ||||
-rw-r--r-- | src/quick/items/context2d/qquickcanvasitem_p.h | 8 | ||||
-rw-r--r-- | src/quick/items/context2d/qquickcontext2d.cpp | 48 | ||||
-rw-r--r-- | src/quick/items/context2d/qquickcontext2d_p.h | 6 | ||||
-rw-r--r-- | src/quick/items/context2d/qquickcontext2dcommandbuffer.cpp | 34 | ||||
-rw-r--r-- | src/quick/items/context2d/qquickcontext2dcommandbuffer_p.h | 2 | ||||
-rw-r--r-- | src/quick/items/context2d/qquickcontext2dtexture.cpp | 193 | ||||
-rw-r--r-- | src/quick/items/context2d/qquickcontext2dtexture_p.h | 70 |
9 files changed, 251 insertions, 176 deletions
diff --git a/src/quick/items/context2d/qquickcanvascontext_p.h b/src/quick/items/context2d/qquickcanvascontext_p.h index 559f41e546..2d8fbb5f85 100644 --- a/src/quick/items/context2d/qquickcanvascontext_p.h +++ b/src/quick/items/context2d/qquickcanvascontext_p.h @@ -71,8 +71,6 @@ public: virtual void setV8Engine(QV8Engine *engine) = 0; virtual QV4::ReturnedValue v4value() const = 0; - virtual QSGDynamicTexture *texture() const = 0; - virtual QImage toImage(const QRectF& bounds) = 0; Q_SIGNALS: diff --git a/src/quick/items/context2d/qquickcanvasitem.cpp b/src/quick/items/context2d/qquickcanvasitem.cpp index ba3592986c..19a161a605 100644 --- a/src/quick/items/context2d/qquickcanvasitem.cpp +++ b/src/quick/items/context2d/qquickcanvasitem.cpp @@ -44,8 +44,10 @@ #include <private/qquickitem_p.h> #include <private/qquickcanvascontext_p.h> #include <private/qquickcontext2d_p.h> +#include <private/qquickcontext2dtexture_p.h> #include <qsgsimpletexturenode.h> #include <QtQuick/private/qquickpixmapcache_p.h> +#include <QtGui/QGuiApplication> #include <qqmlinfo.h> #include <private/qqmlengine_p.h> @@ -58,19 +60,15 @@ QT_BEGIN_NAMESPACE -QQuickCanvasPixmap::QQuickCanvasPixmap(const QImage& image, QQuickWindow *window) +QQuickCanvasPixmap::QQuickCanvasPixmap(const QImage& image) : m_pixmap(0) , m_image(image) - , m_texture(0) - , m_window(window) { } -QQuickCanvasPixmap::QQuickCanvasPixmap(QQuickPixmap *pixmap, QQuickWindow *window) +QQuickCanvasPixmap::QQuickCanvasPixmap(QQuickPixmap *pixmap) : m_pixmap(pixmap) - , m_texture(0) - , m_window(window) { } @@ -78,8 +76,6 @@ QQuickCanvasPixmap::QQuickCanvasPixmap(QQuickPixmap *pixmap, QQuickWindow *windo QQuickCanvasPixmap::~QQuickCanvasPixmap() { delete m_pixmap; - if (m_texture) - m_texture->deleteLater(); } qreal QQuickCanvasPixmap::width() const @@ -105,18 +101,6 @@ bool QQuickCanvasPixmap::isValid() const return !m_image.isNull(); } -QSGTexture *QQuickCanvasPixmap::texture() -{ - if (!m_texture) { - if (m_pixmap) { - Q_ASSERT(m_pixmap->textureFactory()); - m_texture = m_pixmap->textureFactory()->createTexture(m_window); - } else { - m_texture = m_window->createTextureFromImage(m_image, QQuickWindow::TextureCanUseAtlas); - } - } - return m_texture; -} QImage QQuickCanvasPixmap::image() { if (m_image.isNull() && m_pixmap) @@ -194,7 +178,7 @@ QQuickCanvasItemPrivate::QQuickCanvasItemPrivate() , hasCanvasWindow(false) , available(false) , renderTarget(QQuickCanvasItem::Image) - , renderStrategy(QQuickCanvasItem::Cooperative) + , renderStrategy(QQuickCanvasItem::Immediate) { antialiasing = true; } @@ -246,10 +230,14 @@ QQuickCanvasItemPrivate::~QQuickCanvasItemPrivate() The Canvas.FramebufferObject render target utilizes OpenGL hardware acceleration rather than rendering into system memory, which in many cases - results in faster rendering. + results in faster rendering. Canvas.FramebufferObject relies on the + OpenGL extensions \c GL_EXT_framebuffer_multisample and + \c GL_EXT_framebuffer_blit for antialiasing. It will also use more + graphics memory when rendering strategy is anything other than + Canvas.Cooperative. The default render target is Canvas.Image and the default renderStrategy is - Canvas.Cooperative. + Canvas.Immediate. \section1 Tiled Canvas The Canvas item supports tiled rendering by setting \l canvasSize, \l tileSize @@ -489,7 +477,7 @@ void QQuickCanvasItem::setCanvasWindow(const QRectF& rect) context will choose appropriate options and Canvas will signal the change to the properties. - The default render target is \c Canvas.FramebufferObject. + The default render target is \c Canvas.Image. */ QQuickCanvasItem::RenderTarget QQuickCanvasItem::renderTarget() const { @@ -531,7 +519,7 @@ void QQuickCanvasItem::setRenderTarget(QQuickCanvasItem::RenderTarget target) the GUI thread. Selecting \c Canvas.Cooperative, does not guarantee rendering will occur on a thread separate from the GUI thread. - The default value is \c Canvas.Cooperative. + The default value is \c Canvas.Immediate. \sa renderTarget */ @@ -689,6 +677,15 @@ void QQuickCanvasItem::updatePolish() } } +class QQuickCanvasNode : public QSGSimpleTextureNode +{ +public: + ~QQuickCanvasNode() + { + delete texture(); + } +}; + QSGNode *QQuickCanvasItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { Q_D(QQuickCanvasItem); @@ -698,9 +695,9 @@ QSGNode *QQuickCanvasItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData return 0; } - QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode*>(oldNode); + QQuickCanvasNode *node = static_cast<QQuickCanvasNode*>(oldNode); if (!node) - node = new QSGSimpleTextureNode; + node = new QQuickCanvasNode(); if (d->smooth) node->setFiltering(QSGTexture::Linear); @@ -712,8 +709,15 @@ QSGNode *QQuickCanvasItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData d->context->flush(); } - node->setTexture(d->context->texture()); - node->markDirty(QSGNode::DirtyMaterial); + QQuickContext2D *ctx = qobject_cast<QQuickContext2D *>(d->context); + QQuickContext2DTexture *factory = ctx->texture(); + QSGTexture *texture = factory->textureForNextFrame(node->texture()); + if (!texture) { + delete node; + return 0; + } + + node->setTexture(texture); node->setRect(QRectF(QPoint(0, 0), d->canvasWindow.size())); return node; } @@ -916,7 +920,7 @@ void QQuickCanvasItem::loadImage(const QUrl& url) if (!d->pixmaps.contains(fullPathUrl)) { QQuickPixmap* pix = new QQuickPixmap(); QQmlRefPointer<QQuickCanvasPixmap> canvasPix; - canvasPix.take(new QQuickCanvasPixmap(pix, d->window)); + canvasPix.take(new QQuickCanvasPixmap(pix)); d->pixmaps.insert(fullPathUrl, canvasPix); pix->load(qmlEngine(this) diff --git a/src/quick/items/context2d/qquickcanvasitem_p.h b/src/quick/items/context2d/qquickcanvasitem_p.h index 2ec36cfe73..3baf68d418 100644 --- a/src/quick/items/context2d/qquickcanvasitem_p.h +++ b/src/quick/items/context2d/qquickcanvasitem_p.h @@ -52,17 +52,15 @@ QT_BEGIN_NAMESPACE class QQuickCanvasContext; class QQuickCanvasItemPrivate; -class QSGTexture; class QQuickPixmap; class QQuickCanvasPixmap : public QQmlRefCount { public: - QQuickCanvasPixmap(const QImage& image, QQuickWindow *window); - QQuickCanvasPixmap(QQuickPixmap *pixmap, QQuickWindow *window); + QQuickCanvasPixmap(const QImage& image); + QQuickCanvasPixmap(QQuickPixmap *pixmap); ~QQuickCanvasPixmap(); - QSGTexture *texture(); QImage image(); qreal width() const; @@ -73,8 +71,6 @@ public: private: QQuickPixmap *m_pixmap; QImage m_image; - QSGTexture *m_texture; - QQuickWindow *m_window; }; class QQuickCanvasItem : public QQuickItem diff --git a/src/quick/items/context2d/qquickcontext2d.cpp b/src/quick/items/context2d/qquickcontext2d.cpp index 2a9e0f2ac2..373efa3249 100644 --- a/src/quick/items/context2d/qquickcontext2d.cpp +++ b/src/quick/items/context2d/qquickcontext2d.cpp @@ -2985,14 +2985,14 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_drawImage(QV4::CallContext } else if (QQuickCanvasItem *canvas = qobject_cast<QQuickCanvasItem*>(qobjectWrapper->object())) { QImage img = canvas->toImage(); if (!img.isNull()) - pixmap.take(new QQuickCanvasPixmap(img, canvas->window())); + pixmap.take(new QQuickCanvasPixmap(img)); } else { V4THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "drawImage(), type mismatch"); } } else if (QV4::Referenced<QQuickJSContext2DImageData> imageData = arg->asRef<QQuickJSContext2DImageData>()) { QV4::Scoped<QQuickJSContext2DPixelData> pix(scope, imageData->pixelData.as<QQuickJSContext2DPixelData>()); if (pix && !pix->image.isNull()) { - pixmap.take(new QQuickCanvasPixmap(pix->image, r->context->canvas()->window())); + pixmap.take(new QQuickCanvasPixmap(pix->image)); } else { V4THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "drawImage(), type mismatch"); } @@ -4084,6 +4084,15 @@ void QQuickContext2D::init(QQuickCanvasItem *canvasItem, const QVariantMap &args QQuickWindow *window = canvasItem->window(); m_renderStrategy = canvasItem->renderStrategy(); +#ifdef Q_OS_WIN + if (m_renderTarget == QQuickCanvasItem::FramebufferObject + && (m_renderStrategy != QQuickCanvasItem::Cooperative)) { + // On windows a context needs to be unbound set up sharing, so + // for simplicity we disallow FBO + !coop here. + m_renderTarget = QQuickCanvasItem::Image; + } +#endif + switch (m_renderTarget) { case QQuickCanvasItem::Image: m_texture = new QQuickContext2DImageTexture; @@ -4099,6 +4108,7 @@ void QQuickContext2D::init(QQuickCanvasItem *canvasItem, const QVariantMap &args m_texture->setCanvasSize(canvasItem->canvasSize().toSize()); m_texture->setSmooth(canvasItem->smooth()); m_texture->setAntialiasing(canvasItem->antialiasing()); + m_texture->setOnCustomThread(m_renderStrategy == QQuickCanvasItem::Threaded); m_thread = QThread::currentThread(); QThread *renderThread = m_thread; @@ -4129,28 +4139,31 @@ void QQuickContext2D::init(QQuickCanvasItem *canvasItem, const QVariantMap &args void QQuickContext2D::prepare(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth, bool antialiasing) { - QMetaObject::invokeMethod(m_texture - , "canvasChanged" - , Qt::AutoConnection - , Q_ARG(QSize, canvasSize) - , Q_ARG(QSize, tileSize) - , Q_ARG(QRect, canvasWindow) - , Q_ARG(QRect, dirtyRect) - , Q_ARG(bool, smooth) - , Q_ARG(bool, antialiasing)); + if (m_texture->thread() == QThread::currentThread()) { + m_texture->canvasChanged(canvasSize, tileSize, canvasWindow, dirtyRect, smooth, antialiasing); + } else { + QEvent *e = new QQuickContext2DTexture::CanvasChangeEvent(canvasSize, + tileSize, + canvasWindow, + dirtyRect, + smooth, + antialiasing); + QCoreApplication::postEvent(m_texture, e); + } } void QQuickContext2D::flush() { - if (m_buffer) - QMetaObject::invokeMethod(m_texture, - "paint", - Qt::AutoConnection, - Q_ARG(QQuickContext2DCommandBuffer*, m_buffer)); + if (m_buffer) { + if (m_texture->thread() == QThread::currentThread()) + m_texture->paint(m_buffer); + else + QCoreApplication::postEvent(m_texture, new QQuickContext2DTexture::PaintEvent(m_buffer)); + } m_buffer = new QQuickContext2DCommandBuffer(); } -QSGDynamicTexture *QQuickContext2D::texture() const +QQuickContext2DTexture *QQuickContext2D::texture() const { return m_texture; } @@ -4164,6 +4177,7 @@ QImage QQuickContext2D::toImage(const QRectF& bounds) qWarning() << "Pixel readback is not supported in Cooperative mode, please try Threaded or Immediate mode"; return QImage(); } else { + QCoreApplication::postEvent(m_texture, new QEvent(QEvent::Type(QEvent::User + 10))); QMetaObject::invokeMethod(m_texture, "grabImage", Qt::BlockingQueuedConnection, diff --git a/src/quick/items/context2d/qquickcontext2d_p.h b/src/quick/items/context2d/qquickcontext2d_p.h index 4bef50d9cd..6399da3dee 100644 --- a/src/quick/items/context2d/qquickcontext2d_p.h +++ b/src/quick/items/context2d/qquickcontext2d_p.h @@ -74,6 +74,8 @@ class QOpenGLContext; class QQuickContext2D : public QQuickCanvasContext { + Q_OBJECT + public: Q_DISABLE_COPY(QQuickContext2D) @@ -171,8 +173,8 @@ public: void prepare(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth, bool antialiasing); void flush(); void sync(); - QThread *thread() const {return m_thread;} - QSGDynamicTexture *texture() const; + QThread *thread() const { return m_thread; } + QQuickContext2DTexture *texture() const; QImage toImage(const QRectF& bounds); QV4::ReturnedValue v4value() const; diff --git a/src/quick/items/context2d/qquickcontext2dcommandbuffer.cpp b/src/quick/items/context2d/qquickcontext2dcommandbuffer.cpp index d2f4e3317d..06a0713365 100644 --- a/src/quick/items/context2d/qquickcontext2dcommandbuffer.cpp +++ b/src/quick/items/context2d/qquickcontext2dcommandbuffer.cpp @@ -420,38 +420,8 @@ void QQuickContext2DCommandBuffer::replay(QPainter* p, QQuickContext2D::State& s Q_ASSERT(!pix.isNull()); const bool hasShadow = HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor); - if (p->paintEngine()->type() != QPaintEngine::OpenGL2 || hasShadow){ - //TODO: generate shadow blur with shaders - qt_drawImage(p, state, pix->image(), sr, dr, hasShadow); - } else if (pix->texture()){ - QSGTexture *tex = pix->texture(); - QSGDynamicTexture *dynamicTexture = qobject_cast<QSGDynamicTexture *>(tex); - if (dynamicTexture) - dynamicTexture->updateTexture(); - - if (tex->textureId()) { - - if (sr.width() < 0) - sr.setWidth(tex->textureSize().width()); - if (sr.height() < 0) - sr.setHeight(tex->textureSize().height()); - - if (dr.width() < 0) - dr.setWidth(sr.width()); - if (dr.height() < 0) - dr.setHeight(sr.height()); - - qreal srBottom = sr.bottom(); - sr.setBottom(sr.top()); - sr.setTop(srBottom); - - tex->bind(); - if (p->paintEngine()->type() == QPaintEngine::OpenGL2) { - QOpenGL2PaintEngineEx *engine = static_cast<QOpenGL2PaintEngineEx *>(p->paintEngine()); - engine->drawTexture(dr, tex->textureId(), tex->textureSize(), sr); - } - } - } + //TODO: generate shadow blur with shaders + qt_drawImage(p, state, pix->image(), sr, dr, hasShadow); break; } case QQuickContext2D::GetImageData: diff --git a/src/quick/items/context2d/qquickcontext2dcommandbuffer_p.h b/src/quick/items/context2d/qquickcontext2dcommandbuffer_p.h index 29cdc73708..9e79333a0c 100644 --- a/src/quick/items/context2d/qquickcontext2dcommandbuffer_p.h +++ b/src/quick/items/context2d/qquickcontext2dcommandbuffer_p.h @@ -58,8 +58,6 @@ public: void reset(); void clear(); - void lockQueue() { queueLock.lock(); } - void unlockQueue() { queueLock.unlock(); } inline int size() {return commands.size();} inline bool isEmpty() const {return commands.isEmpty(); } inline bool hasNext() const {return cmdIdx < commands.size(); } diff --git a/src/quick/items/context2d/qquickcontext2dtexture.cpp b/src/quick/items/context2d/qquickcontext2dtexture.cpp index 8dc9978089..8dd48b4988 100644 --- a/src/quick/items/context2d/qquickcontext2dtexture.cpp +++ b/src/quick/items/context2d/qquickcontext2dtexture.cpp @@ -50,6 +50,7 @@ #include <QOpenGLFramebufferObject> #include <QOpenGLFramebufferObjectFormat> #include <QtCore/QThread> +#include <QtGui/QGuiApplication> QT_BEGIN_NAMESPACE @@ -90,7 +91,6 @@ struct GLAcquireContext { QQuickContext2DTexture::QQuickContext2DTexture() : m_context(0) , m_item(0) - , m_dirtyCanvas(false) , m_canvasWindowChanged(false) , m_dirtyTexture(false) , m_smooth(true) @@ -105,23 +105,20 @@ QQuickContext2DTexture::~QQuickContext2DTexture() clearTiles(); } -QSize QQuickContext2DTexture::textureSize() const -{ - return m_canvasWindow.size(); -} - void QQuickContext2DTexture::markDirtyTexture() { + if (m_onCustomThread) + m_mutex.lock(); m_dirtyTexture = true; - updateTexture(); emit textureChanged(); + if (m_onCustomThread) + m_mutex.unlock(); } bool QQuickContext2DTexture::setCanvasSize(const QSize &size) { if (m_canvasSize != size) { m_canvasSize = size; - m_dirtyCanvas = true; return true; } return false; @@ -131,7 +128,6 @@ bool QQuickContext2DTexture::setTileSize(const QSize &size) { if (m_tileSize != size) { m_tileSize = size; - m_dirtyCanvas = true; return true; } return false; @@ -195,7 +191,6 @@ void QQuickContext2DTexture::canvasChanged(const QSize& canvasSize, const QSize& if (canvasSize == canvasWindow.size()) { m_tiledCanvas = false; - m_dirtyCanvas = false; } else { m_tiledCanvas = true; } @@ -309,7 +304,6 @@ QRect QQuickContext2DTexture::createTiles(const QRect& window) m_tiles.clear(); if (window.isEmpty()) { - m_dirtyCanvas = false; return QRect(); } @@ -351,7 +345,6 @@ QRect QQuickContext2DTexture::createTiles(const QRect& window) qDeleteAll(oldTiles); - m_dirtyCanvas = false; return r; } @@ -366,6 +359,20 @@ QSize QQuickContext2DTexture::adjustedTileSize(const QSize &ts) return ts; } +bool QQuickContext2DTexture::event(QEvent *e) +{ + if ((int) e->type() == QEvent::User + 1) { + PaintEvent *pe = static_cast<PaintEvent *>(e); + paint(pe->buffer); + return true; + } else if ((int) e->type() == QEvent::User + 2) { + CanvasChangeEvent *ce = static_cast<CanvasChangeEvent *>(e); + canvasChanged(ce->canvasSize, ce->tileSize, ce->canvasWindow, ce->dirtyRect, ce->smooth, ce->antialiasing); + return true; + } + return QObject::event(e); +} + static inline QSize npotAdjustedSize(const QSize &size) { static bool checked = false; @@ -391,6 +398,9 @@ QQuickContext2DFBOTexture::QQuickContext2DFBOTexture() , m_multisampledFbo(0) , m_paint_device(0) { + m_displayTextures[0] = 0; + m_displayTextures[1] = 0; + m_displayTexture = -1; } QQuickContext2DFBOTexture::~QQuickContext2DFBOTexture() @@ -403,17 +413,52 @@ QQuickContext2DFBOTexture::~QQuickContext2DFBOTexture() delete m_fbo; delete m_multisampledFbo; delete m_paint_device; + + glDeleteTextures(2, m_displayTextures); } -QSize QQuickContext2DFBOTexture::adjustedTileSize(const QSize &ts) +QSGTexture *QQuickContext2DFBOTexture::textureForNextFrame(QSGTexture *lastTexture) { - return npotAdjustedSize(ts); + QSGPlainTexture *texture = static_cast<QSGPlainTexture *>(lastTexture); + + if (m_onCustomThread) + m_mutex.lock(); + + if (m_fbo) { + if (!texture) { + texture = new QSGPlainTexture(); + texture->setHasMipmaps(false); + texture->setHasAlphaChannel(true); + texture->setOwnsTexture(false); + m_dirtyTexture = true; + } + + if (m_dirtyTexture) { + if (!m_context->glContext()) { + // on a rendering thread, use the fbo directly... + texture->setTextureId(m_fbo->texture()); + } else { + // on GUI or custom thread, use display textures... + m_displayTexture = m_displayTexture == 0 ? 1 : 0; + texture->setTextureId(m_displayTextures[m_displayTexture]); + } + texture->setTextureSize(m_fbo->size()); + m_dirtyTexture = false; + } + + } + + if (m_onCustomThread) { + m_condition.wakeOne(); + m_mutex.unlock(); + } + + return texture; } -void QQuickContext2DFBOTexture::bind() +QSize QQuickContext2DFBOTexture::adjustedTileSize(const QSize &ts) { - glBindTexture(GL_TEXTURE_2D, textureId()); - updateBindOptions(); + return npotAdjustedSize(ts); } QRectF QQuickContext2DFBOTexture::normalizedTextureSubRect() const @@ -424,20 +469,6 @@ QRectF QQuickContext2DFBOTexture::normalizedTextureSubRect() const , qreal(m_canvasWindow.height()) / m_fboSize.height()); } - -int QQuickContext2DFBOTexture::textureId() const -{ - return m_fbo? m_fbo->texture() : 0; -} - - -bool QQuickContext2DFBOTexture::updateTexture() -{ - bool textureUpdated = m_dirtyTexture; - m_dirtyTexture = false; - return textureUpdated; -} - QQuickContext2DTile* QQuickContext2DFBOTexture::createTile() const { return new QQuickContext2DFBOTile(); @@ -461,7 +492,6 @@ bool QQuickContext2DFBOTexture::doMultisampling() const void QQuickContext2DFBOTexture::grabImage(const QRectF& rf) { Q_ASSERT(rf.isValid()); - if (!m_fbo) { m_context->setGrabbedImage(QImage()); return; @@ -531,7 +561,6 @@ QPaintDevice* QQuickContext2DFBOTexture::beginPainting() } else { QOpenGLFramebufferObjectFormat format; format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); - m_fbo = new QOpenGLFramebufferObject(m_fboSize, format); } } @@ -541,7 +570,6 @@ QPaintDevice* QQuickContext2DFBOTexture::beginPainting() else m_fbo->bind(); - if (!m_paint_device) { QOpenGLPaintDevice *gl_device = new QOpenGLPaintDevice(m_fbo->size()); gl_device->setPaintFlipped(true); @@ -557,25 +585,47 @@ void QQuickContext2DFBOTexture::endPainting() QQuickContext2DTexture::endPainting(); if (m_multisampledFbo) QOpenGLFramebufferObject::blitFramebuffer(m_fbo, m_multisampledFbo); + + if (m_context->glContext()) { + /* When rendering happens on the render thread, the fbo's texture is + * used directly for display. If we are on the GUI thread or a + * dedicated Canvas render thread, we need to decouple the FBO from + * the texture we are displaying in the SG rendering thread to avoid + * stalls and read/write issues in the GL pipeline as the FBO's texture + * could then potentially be used in different threads. + * + * We could have gotten away with only one display texture, but this + * would have implied that beginPainting would have to wait for SG + * to release that texture. + */ + + if (m_onCustomThread) + m_mutex.lock(); + + if (m_displayTextures[0] == 0) { + m_displayTexture = 1; + glGenTextures(2, m_displayTextures); + } + + m_fbo->bind(); + GLuint target = m_displayTexture == 0 ? 1 : 0; + glBindTexture(GL_TEXTURE_2D, m_displayTextures[target]); + glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, m_fbo->width(), m_fbo->height(), 0); + + if (m_onCustomThread) + m_mutex.unlock(); + } + + m_fbo->bindDefault(); } QQuickContext2DImageTexture::QQuickContext2DImageTexture() : QQuickContext2DTexture() - , m_texture(0) { } QQuickContext2DImageTexture::~QQuickContext2DImageTexture() { - if (m_texture && m_texture->thread() != QThread::currentThread()) - m_texture->deleteLater(); - else - delete m_texture; -} - -int QQuickContext2DImageTexture::textureId() const -{ - return imageTexture()->textureId(); } QQuickCanvasItem::RenderTarget QQuickContext2DImageTexture::renderTarget() const @@ -583,22 +633,6 @@ QQuickCanvasItem::RenderTarget QQuickContext2DImageTexture::renderTarget() const return QQuickCanvasItem::Image; } -void QQuickContext2DImageTexture::bind() -{ - imageTexture()->setFiltering(filtering()); - imageTexture()->bind(); -} - -bool QQuickContext2DImageTexture::updateTexture() -{ - bool textureUpdated = m_dirtyTexture; - if (m_dirtyTexture) { - imageTexture()->setImage(m_image); - m_dirtyTexture = false; - } - return textureUpdated; -} - QQuickContext2DTile* QQuickContext2DImageTexture::createTile() const { return new QQuickContext2DImageTile(); @@ -608,19 +642,32 @@ void QQuickContext2DImageTexture::grabImage(const QRectF& rf) { Q_ASSERT(rf.isValid()); Q_ASSERT(m_context); - QImage grabbed = m_image.copy(rf.toRect()); + QImage grabbed = m_displayImage.copy(rf.toRect()); m_context->setGrabbedImage(grabbed); } -QSGPlainTexture *QQuickContext2DImageTexture::imageTexture() const +QSGTexture *QQuickContext2DImageTexture::textureForNextFrame(QSGTexture *last) { - if (!m_texture) { - QQuickContext2DImageTexture *that = const_cast<QQuickContext2DImageTexture *>(this); - that->m_texture = new QSGPlainTexture; - that->m_texture->setOwnsTexture(true); - that->m_texture->setHasMipmaps(false); + QSGPlainTexture *texture = static_cast<QSGPlainTexture *>(last); + + if (m_onCustomThread) + m_mutex.lock(); + + if (!texture) { + texture = new QSGPlainTexture(); + texture->setHasMipmaps(false); + texture->setHasAlphaChannel(true); + m_dirtyTexture = true; + } + if (m_dirtyTexture) { + texture->setImage(m_displayImage); + m_dirtyTexture = false; } - return m_texture; + + if (m_onCustomThread) + m_mutex.unlock(); + + return texture; } QPaintDevice* QQuickContext2DImageTexture::beginPainting() @@ -639,6 +686,16 @@ QPaintDevice* QQuickContext2DImageTexture::beginPainting() return &m_image; } +void QQuickContext2DImageTexture::endPainting() +{ + QQuickContext2DTexture::endPainting(); + if (m_onCustomThread) + m_mutex.lock(); + m_displayImage = m_image; + if (m_onCustomThread) + m_mutex.unlock(); +} + void QQuickContext2DImageTexture::compositeTile(QQuickContext2DTile* tile) { Q_ASSERT(!tile->dirty()); diff --git a/src/quick/items/context2d/qquickcontext2dtexture_p.h b/src/quick/items/context2d/qquickcontext2dtexture_p.h index 2a5b7a318a..cf0e8e3fa9 100644 --- a/src/quick/items/context2d/qquickcontext2dtexture_p.h +++ b/src/quick/items/context2d/qquickcontext2dtexture_p.h @@ -58,16 +58,44 @@ QT_BEGIN_NAMESPACE class QQuickContext2DTile; class QQuickContext2DCommandBuffer; -class QQuickContext2DTexture : public QSGDynamicTexture +class QQuickContext2DTexture : public QObject { Q_OBJECT public: + class PaintEvent : public QEvent { + public: + PaintEvent(QQuickContext2DCommandBuffer *b) : QEvent(QEvent::Type(QEvent::User + 1)), buffer(b) {} + QQuickContext2DCommandBuffer *buffer; + }; + + class CanvasChangeEvent : public QEvent { + public: + CanvasChangeEvent(const QSize &cSize, + const QSize &tSize, + const QRect &cWindow, + const QRect &dRect, + bool sm, + bool aa) + : QEvent(QEvent::Type(QEvent::User + 2)) + , canvasSize(cSize) + , tileSize(tSize) + , canvasWindow(cWindow) + , dirtyRect(dRect) + , smooth(sm) + , antialiasing(aa) + { + } + QSize canvasSize; + QSize tileSize; + QRect canvasWindow; + QRect dirtyRect; + bool smooth; + bool antialiasing; + }; + QQuickContext2DTexture(); ~QQuickContext2DTexture(); - virtual bool hasAlphaChannel() const {return true;} - virtual bool hasMipmaps() const {return false;} - virtual QSize textureSize() const; virtual QQuickCanvasItem::RenderTarget renderTarget() const = 0; static QRect tiledRect(const QRectF& window, const QSize& tileSize); @@ -78,15 +106,20 @@ public: void setAntialiasing(bool antialiasing); bool setDirtyRect(const QRect &dirtyRect); bool canvasDestroyed(); + void setOnCustomThread(bool is) { m_onCustomThread = is; } + + // Called during sync() on the scene graph thread while GUI is blocked. + virtual QSGTexture *textureForNextFrame(QSGTexture *lastFrame) = 0; + bool event(QEvent *e); Q_SIGNALS: void textureChanged(); public Q_SLOTS: - void markDirtyTexture(); - void setItem(QQuickCanvasItem* item); void canvasChanged(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth, bool antialiasing); void paint(QQuickContext2DCommandBuffer *ccb); + void markDirtyTexture(); + void setItem(QQuickCanvasItem* item); virtual void grabImage(const QRectF& region = QRectF()) = 0; protected: @@ -110,13 +143,16 @@ protected: QSize m_tileSize; QRect m_canvasWindow; - uint m_dirtyCanvas : 1; + QMutex m_mutex; + QWaitCondition m_condition; + uint m_canvasWindowChanged : 1; uint m_dirtyTexture : 1; uint m_smooth : 1; uint m_antialiasing : 1; uint m_tiledCanvas : 1; uint m_painting : 1; + uint m_onCustomThread : 1; // Not GUI and not SGRender }; class QQuickContext2DFBOTexture : public QQuickContext2DTexture @@ -126,17 +162,16 @@ class QQuickContext2DFBOTexture : public QQuickContext2DTexture public: QQuickContext2DFBOTexture(); ~QQuickContext2DFBOTexture(); - virtual int textureId() const; - virtual bool updateTexture(); virtual QQuickContext2DTile* createTile() const; virtual QPaintDevice* beginPainting(); virtual void endPainting(); QRectF normalizedTextureSubRect() const; virtual QQuickCanvasItem::RenderTarget renderTarget() const; virtual void compositeTile(QQuickContext2DTile* tile); - virtual void bind(); QSize adjustedTileSize(const QSize &ts); + QSGTexture *textureForNextFrame(QSGTexture *); + public Q_SLOTS: virtual void grabImage(const QRectF& region = QRectF()); @@ -144,10 +179,12 @@ private: bool doMultisampling() const; QOpenGLFramebufferObject *m_fbo; QOpenGLFramebufferObject *m_multisampledFbo; - QMutex m_mutex; - QWaitCondition m_condition; QSize m_fboSize; QPaintDevice *m_paint_device; + + + GLuint m_displayTextures[2]; + int m_displayTexture; }; class QSGPlainTexture; @@ -158,24 +195,23 @@ class QQuickContext2DImageTexture : public QQuickContext2DTexture public: QQuickContext2DImageTexture(); ~QQuickContext2DImageTexture(); - virtual int textureId() const; - virtual void bind(); virtual QQuickCanvasItem::RenderTarget renderTarget() const; - virtual bool updateTexture(); virtual QQuickContext2DTile* createTile() const; virtual QPaintDevice* beginPainting(); + virtual void endPainting(); virtual void compositeTile(QQuickContext2DTile* tile); + virtual QSGTexture *textureForNextFrame(QSGTexture *lastFrame); + public Q_SLOTS: virtual void grabImage(const QRectF& region = QRectF()); private: - QSGPlainTexture *imageTexture() const; QImage m_image; + QImage m_displayImage; QPainter m_painter; - QSGPlainTexture* m_texture; }; QT_END_NAMESPACE |