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/items/context2d/qquickcontext2dtexture.cpp | |
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/items/context2d/qquickcontext2dtexture.cpp')
-rw-r--r-- | src/quick/items/context2d/qquickcontext2dtexture.cpp | 193 |
1 files changed, 125 insertions, 68 deletions
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()); |