aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/items/context2d/qquickcontext2dtexture.cpp
diff options
context:
space:
mode:
authorGunnar Sletta <gunnar.sletta@digia.com>2013-11-27 17:04:06 +0100
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-12-03 18:06:36 +0100
commit9ad9615d0003c9fb84255152f0cbb473ee2a7a70 (patch)
treec4aba681ddacd7eadb4defe7312e9020b43407c2 /src/quick/items/context2d/qquickcontext2dtexture.cpp
parent9e77d6c31b406e5941a2d287f3c8842954815db4 (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.cpp193
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());