Fix for current_fbo getting out of sync in QtOpenGL

When using QGLWidget in combination with QOpenGLFramebufferObject from
QtGui, instead of QGLFramebufferObject from QtOpenGL, the current_fbo
variable doesn't get updated when framebuffer object bindings change.

To ensure that the QGLWidget correctly releases the currently bound
framebuffer object when using a QPainter, we keep track of whether
QOpenGLFramebufferObject has modified the current FBO binding, and if
that's the case we need to read the OpenGL state directly instead of
relying on a cached value.

Change-Id: If7e0bd936e202cad07365b5ce641ee01d2251930
Reviewed-by: Laszlo Agocs <laszlo.agocs@digia.com>
This commit is contained in:
Samuel Rødal 2014-10-31 16:56:25 +01:00
parent 459a32e39b
commit a4428d480b
9 changed files with 138 additions and 9 deletions

View File

@ -203,6 +203,7 @@ public:
, workaround_brokenTexSubImage(false) , workaround_brokenTexSubImage(false)
, workaround_missingPrecisionQualifiers(false) , workaround_missingPrecisionQualifiers(false)
, active_engine(0) , active_engine(0)
, qgl_current_fbo_invalid(false)
{ {
requestedFormat = QSurfaceFormat::defaultFormat(); requestedFormat = QSurfaceFormat::defaultFormat();
} }
@ -237,6 +238,8 @@ public:
QPaintEngineEx *active_engine; QPaintEngineEx *active_engine;
bool qgl_current_fbo_invalid;
QVariant nativeHandle; QVariant nativeHandle;
static QOpenGLContext *setCurrentContext(QOpenGLContext *context); static QOpenGLContext *setCurrentContext(QOpenGLContext *context);

View File

@ -469,6 +469,8 @@ void QOpenGLFramebufferObjectPrivate::init(QOpenGLFramebufferObject *, const QSi
funcs.glGenFramebuffers(1, &fbo); funcs.glGenFramebuffers(1, &fbo);
funcs.glBindFramebuffer(GL_FRAMEBUFFER, fbo); funcs.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
QOpenGLContextPrivate::get(ctx)->qgl_current_fbo_invalid = true;
GLuint color_buffer = 0; GLuint color_buffer = 0;
QT_CHECK_GLERROR(); QT_CHECK_GLERROR();
@ -997,7 +999,11 @@ bool QOpenGLFramebufferObject::bind()
if (current->shareGroup() != d->fbo_guard->group()) if (current->shareGroup() != d->fbo_guard->group())
qWarning("QOpenGLFramebufferObject::bind() called from incompatible context"); qWarning("QOpenGLFramebufferObject::bind() called from incompatible context");
#endif #endif
d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, d->fbo()); d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, d->fbo());
QOpenGLContextPrivate::get(current)->qgl_current_fbo_invalid = true;
if (d->texture_guard || d->format.samples() != 0) if (d->texture_guard || d->format.samples() != 0)
d->valid = d->checkFramebufferStatus(current); d->valid = d->checkFramebufferStatus(current);
else else
@ -1029,9 +1035,12 @@ bool QOpenGLFramebufferObject::release()
qWarning("QOpenGLFramebufferObject::release() called from incompatible context"); qWarning("QOpenGLFramebufferObject::release() called from incompatible context");
#endif #endif
if (current) if (current) {
d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, current->defaultFramebufferObject()); d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, current->defaultFramebufferObject());
QOpenGLContextPrivate::get(current)->qgl_current_fbo_invalid = true;
}
return true; return true;
} }
@ -1272,8 +1281,10 @@ bool QOpenGLFramebufferObject::bindDefault()
{ {
QOpenGLContext *ctx = const_cast<QOpenGLContext *>(QOpenGLContext::currentContext()); QOpenGLContext *ctx = const_cast<QOpenGLContext *>(QOpenGLContext::currentContext());
if (ctx) if (ctx) {
ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
QOpenGLContextPrivate::get(ctx)->qgl_current_fbo_invalid = true;
}
#ifdef QT_DEBUG #ifdef QT_DEBUG
else else
qWarning("QOpenGLFramebufferObject::bindDefault() called without current context."); qWarning("QOpenGLFramebufferObject::bindDefault() called without current context.");
@ -1342,6 +1353,7 @@ void QOpenGLFramebufferObject::setAttachment(QOpenGLFramebufferObject::Attachmen
qWarning("QOpenGLFramebufferObject::setAttachment() called from incompatible context"); qWarning("QOpenGLFramebufferObject::setAttachment() called from incompatible context");
#endif #endif
d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, d->fbo()); d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, d->fbo());
QOpenGLContextPrivate::get(current)->qgl_current_fbo_invalid = true;
d->initAttachments(current, attachment); d->initAttachments(current, attachment);
} }

View File

@ -167,6 +167,8 @@ void QGLTextureGlyphCache::resizeTextureData(int width, int height)
// ### the QTextureGlyphCache API needs to be reworked to allow // ### the QTextureGlyphCache API needs to be reworked to allow
// ### resizeTextureData to fail // ### resizeTextureData to fail
ctx->d_ptr->refreshCurrentFbo();
funcs->glBindFramebuffer(GL_FRAMEBUFFER, m_textureResource->m_fbo); funcs->glBindFramebuffer(GL_FRAMEBUFFER, m_textureResource->m_fbo);
GLuint tmp_texture; GLuint tmp_texture;

View File

@ -510,6 +510,35 @@ void QGLContextPrivate::setupSharing() {
} }
} }
void QGLContextPrivate::refreshCurrentFbo()
{
QOpenGLContextPrivate *guiGlContextPrivate =
guiGlContext ? QOpenGLContextPrivate::get(guiGlContext) : 0;
// if QOpenGLFramebufferObjects have been used in the mean-time, we've lost our cached value
if (guiGlContextPrivate && guiGlContextPrivate->qgl_current_fbo_invalid) {
GLint current;
QOpenGLFunctions *funcs = qgl_functions();
funcs->glGetIntegerv(GL_FRAMEBUFFER_BINDING, &current);
current_fbo = current;
guiGlContextPrivate->qgl_current_fbo_invalid = false;
}
}
void QGLContextPrivate::setCurrentFbo(GLuint fbo)
{
current_fbo = fbo;
QOpenGLContextPrivate *guiGlContextPrivate =
guiGlContext ? QOpenGLContextPrivate::get(guiGlContext) : 0;
if (guiGlContextPrivate)
guiGlContextPrivate->qgl_current_fbo_invalid = false;
}
/*! /*!
\fn bool QGLFormat::doubleBuffer() const \fn bool QGLFormat::doubleBuffer() const

View File

@ -238,6 +238,8 @@ public:
bool ownContext; bool ownContext;
void setupSharing(); void setupSharing();
void refreshCurrentFbo();
void setCurrentFbo(GLuint fbo);
QGLFormat glFormat; QGLFormat glFormat;
QGLFormat reqFormat; QGLFormat reqFormat;

View File

@ -472,6 +472,8 @@ void QGLFramebufferObjectPrivate::init(QGLFramebufferObject *q, const QSize &sz,
if (!funcs.hasOpenGLFeature(QOpenGLFunctions::Framebuffers)) if (!funcs.hasOpenGLFeature(QOpenGLFunctions::Framebuffers))
return; return;
ctx->d_ptr->refreshCurrentFbo();
size = sz; size = sz;
target = texture_target; target = texture_target;
// texture dimensions // texture dimensions
@ -1027,7 +1029,7 @@ bool QGLFramebufferObject::bind()
d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, d->fbo()); d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, d->fbo());
d->valid = d->checkFramebufferStatus(); d->valid = d->checkFramebufferStatus();
if (d->valid && current) if (d->valid && current)
current->d_ptr->current_fbo = d->fbo(); current->d_ptr->setCurrentFbo(d->fbo());
return d->valid; return d->valid;
} }
@ -1060,7 +1062,7 @@ bool QGLFramebufferObject::release()
#endif #endif
if (current) { if (current) {
current->d_ptr->current_fbo = current->d_ptr->default_fbo; current->d_ptr->setCurrentFbo(current->d_ptr->default_fbo);
d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, current->d_ptr->default_fbo); d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, current->d_ptr->default_fbo);
} }
@ -1173,7 +1175,7 @@ bool QGLFramebufferObject::bindDefault()
if (!functions.hasOpenGLFeature(QOpenGLFunctions::Framebuffers)) if (!functions.hasOpenGLFeature(QOpenGLFunctions::Framebuffers))
return false; return false;
ctx->d_ptr->current_fbo = ctx->d_ptr->default_fbo; ctx->d_ptr->setCurrentFbo(ctx->d_ptr->default_fbo);
functions.glBindFramebuffer(GL_FRAMEBUFFER, ctx->d_ptr->default_fbo); functions.glBindFramebuffer(GL_FRAMEBUFFER, ctx->d_ptr->default_fbo);
#ifdef QT_DEBUG #ifdef QT_DEBUG
} else { } else {
@ -1320,7 +1322,12 @@ bool QGLFramebufferObject::isBound() const
{ {
Q_D(const QGLFramebufferObject); Q_D(const QGLFramebufferObject);
const QGLContext *current = QGLContext::currentContext(); const QGLContext *current = QGLContext::currentContext();
return current ? current->d_ptr->current_fbo == d->fbo() : false; if (current) {
current->d_ptr->refreshCurrentFbo();
return current->d_ptr->current_fbo == d->fbo();
}
return false;
} }
/*! /*!
@ -1400,6 +1407,8 @@ void QGLFramebufferObject::blitFramebuffer(QGLFramebufferObject *target, const Q
const int ty0 = th - (targetRect.top() + targetRect.height()); const int ty0 = th - (targetRect.top() + targetRect.height());
const int ty1 = th - targetRect.top(); const int ty1 = th - targetRect.top();
ctx->d_ptr->refreshCurrentFbo();
functions.glBindFramebuffer(GL_READ_FRAMEBUFFER, source ? source->handle() : 0); functions.glBindFramebuffer(GL_READ_FRAMEBUFFER, source ? source->handle() : 0);
functions.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target ? target->handle() : 0); functions.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target ? target->handle() : 0);

View File

@ -74,6 +74,8 @@ void QGLPaintDevice::beginPaint()
QGLContext *ctx = context(); QGLContext *ctx = context();
ctx->makeCurrent(); ctx->makeCurrent();
ctx->d_func()->refreshCurrentFbo();
// Record the currently bound FBO so we can restore it again // Record the currently bound FBO so we can restore it again
// in endPaint() and bind this device's FBO // in endPaint() and bind this device's FBO
// //
@ -85,7 +87,7 @@ void QGLPaintDevice::beginPaint()
m_previousFBO = ctx->d_func()->current_fbo; m_previousFBO = ctx->d_func()->current_fbo;
if (m_previousFBO != m_thisFBO) { if (m_previousFBO != m_thisFBO) {
ctx->d_ptr->current_fbo = m_thisFBO; ctx->d_func()->setCurrentFbo(m_thisFBO);
ctx->contextHandle()->functions()->glBindFramebuffer(GL_FRAMEBUFFER, m_thisFBO); ctx->contextHandle()->functions()->glBindFramebuffer(GL_FRAMEBUFFER, m_thisFBO);
} }
@ -102,8 +104,10 @@ void QGLPaintDevice::ensureActiveTarget()
if (ctx != QGLContext::currentContext()) if (ctx != QGLContext::currentContext())
ctx->makeCurrent(); ctx->makeCurrent();
ctx->d_func()->refreshCurrentFbo();
if (ctx->d_ptr->current_fbo != m_thisFBO) { if (ctx->d_ptr->current_fbo != m_thisFBO) {
ctx->d_ptr->current_fbo = m_thisFBO; ctx->d_func()->setCurrentFbo(m_thisFBO);
ctx->contextHandle()->functions()->glBindFramebuffer(GL_FRAMEBUFFER, m_thisFBO); ctx->contextHandle()->functions()->glBindFramebuffer(GL_FRAMEBUFFER, m_thisFBO);
} }
@ -114,8 +118,11 @@ void QGLPaintDevice::endPaint()
{ {
// Make sure the FBO bound at beginPaint is re-bound again here: // Make sure the FBO bound at beginPaint is re-bound again here:
QGLContext *ctx = context(); QGLContext *ctx = context();
ctx->d_func()->refreshCurrentFbo();
if (m_previousFBO != ctx->d_func()->current_fbo) { if (m_previousFBO != ctx->d_func()->current_fbo) {
ctx->d_ptr->current_fbo = m_previousFBO; ctx->d_func()->setCurrentFbo(m_previousFBO);
ctx->contextHandle()->functions()->glBindFramebuffer(GL_FRAMEBUFFER, m_previousFBO); ctx->contextHandle()->functions()->glBindFramebuffer(GL_FRAMEBUFFER, m_previousFBO);
} }

View File

@ -344,6 +344,8 @@ void QGLPixelBuffer::updateDynamicTexture(GLuint texture_id) const
QOpenGLExtensions extensions(ctx->contextHandle()); QOpenGLExtensions extensions(ctx->contextHandle());
ctx->d_ptr->refreshCurrentFbo();
if (d->blit_fbo) { if (d->blit_fbo) {
QOpenGLFramebufferObject::blitFramebuffer(d->blit_fbo, d->fbo); QOpenGLFramebufferObject::blitFramebuffer(d->blit_fbo, d->fbo);
extensions.glBindFramebuffer(GL_READ_FRAMEBUFFER, d->blit_fbo->handle()); extensions.glBindFramebuffer(GL_READ_FRAMEBUFFER, d->blit_fbo->handle());

View File

@ -42,6 +42,8 @@
#include <qglcolormap.h> #include <qglcolormap.h>
#include <qpaintengine.h> #include <qpaintengine.h>
#include <qopenglfunctions.h> #include <qopenglfunctions.h>
#include <qopenglframebufferobject.h>
#include <qopenglpaintdevice.h>
#include <QGraphicsView> #include <QGraphicsView>
#include <QGraphicsProxyWidget> #include <QGraphicsProxyWidget>
@ -78,6 +80,7 @@ private slots:
void glWidgetRendering(); void glWidgetRendering();
void glFBOSimpleRendering(); void glFBOSimpleRendering();
void glFBORendering(); void glFBORendering();
void currentFboSync();
void multipleFBOInterleavedRendering(); void multipleFBOInterleavedRendering();
void glFBOUseInGLWidget(); void glFBOUseInGLWidget();
void glPBufferRendering(); void glPBufferRendering();
@ -1138,6 +1141,66 @@ void tst_QGL::glFBORendering()
qt_opengl_check_test_pattern(fb); qt_opengl_check_test_pattern(fb);
} }
class QOpenGLFramebufferObjectPaintDevice : public QOpenGLPaintDevice
{
public:
QOpenGLFramebufferObjectPaintDevice(int width, int height)
: QOpenGLPaintDevice(width, height)
, m_fbo(width, height, QOpenGLFramebufferObject::CombinedDepthStencil)
{
}
void ensureActiveTarget()
{
m_fbo.bind();
}
QImage toImage() const
{
return m_fbo.toImage();
}
private:
QOpenGLFramebufferObject m_fbo;
};
void tst_QGL::currentFboSync()
{
if (!QGLFramebufferObject::hasOpenGLFramebufferObjects())
QSKIP("QGLFramebufferObject not supported on this platform");
#if defined(Q_OS_QNX)
QSKIP("Reading the QGLFramebufferObject is unsupported on this platform");
#endif
QGLWidget glw;
glw.makeCurrent();
{
QGLFramebufferObject fbo1(256, 256, QGLFramebufferObject::CombinedDepthStencil);
QOpenGLFramebufferObjectPaintDevice fbo2(256, 256);
QImage sourceImage(256, 256, QImage::Format_ARGB32_Premultiplied);
QPainter sourcePainter(&sourceImage);
qt_opengl_draw_test_pattern(&sourcePainter, 256, 256);
QPainter fbo1Painter(&fbo1);
QPainter fbo2Painter(&fbo2);
fbo2Painter.drawImage(0, 0, sourceImage);
fbo2Painter.end();
QImage fbo2Image = fbo2.toImage();
fbo1Painter.drawImage(0, 0, sourceImage);
fbo1Painter.end();
QGLFramebufferObject::bindDefault();
QCOMPARE(fbo1.toImage(), fbo2Image);
}
}
// Tests multiple QPainters active on different FBOs at the same time, with // Tests multiple QPainters active on different FBOs at the same time, with
// interleaving painting. Performance-wise, this is sub-optimal, but it still // interleaving painting. Performance-wise, this is sub-optimal, but it still