Add takeTexture() to QOpenGLFramebufferObject

Add an API that allows to retrieve and detach the texture from the
framebuffer object. The next bind() call will then create and attach a
new texture.

[ChangeLog][QtGui][QOpenGLFramebufferObject] Added takeTexture() for
retrieving and detaching the texture from the framebuffer object.

Task-number: QTBUG-35881
Change-Id: I2cca37f5872c1685b1238047f8b912e6534ab781
Reviewed-by: Gunnar Sletta <gunnar.sletta@jollamobile.com>
This commit is contained in:
Laszlo Agocs 2013-12-30 14:37:22 +01:00 committed by The Qt Project
parent b12b1ddf48
commit dcbb16a452
4 changed files with 147 additions and 41 deletions

View File

@ -446,42 +446,12 @@ void QOpenGLFramebufferObjectPrivate::init(QOpenGLFramebufferObject *, const QSi
funcs.glGenFramebuffers(1, &fbo);
funcs.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
GLuint texture = 0;
GLuint color_buffer = 0;
QT_CHECK_GLERROR();
// init texture
if (samples == 0) {
glGenTextures(1, &texture);
glBindTexture(target, texture);
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(target, 0, internal_format, size.width(), size.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
if (mipmap) {
int width = size.width();
int height = size.height();
int level = 0;
while (width > 1 || height > 1) {
width = qMax(1, width >> 1);
height = qMax(1, height >> 1);
++level;
glTexImage2D(target, level, internal_format, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
}
}
funcs.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
target, texture, 0);
QT_CHECK_GLERROR();
valid = checkFramebufferStatus(ctx);
glBindTexture(target, 0);
color_buffer = 0;
initTexture(texture_target, internal_format, size, mipmap);
} else {
mipmap = false;
funcs.glGenRenderbuffers(1, &color_buffer);
@ -492,8 +462,10 @@ void QOpenGLFramebufferObjectPrivate::init(QOpenGLFramebufferObject *, const QSi
QT_CHECK_GLERROR();
valid = checkFramebufferStatus(ctx);
if (valid)
if (valid) {
funcs.glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples);
color_buffer_guard = new QOpenGLSharedResourceGuard(ctx, color_buffer, freeRenderbufferFunc);
}
}
format.setTextureTarget(target);
@ -506,20 +478,59 @@ void QOpenGLFramebufferObjectPrivate::init(QOpenGLFramebufferObject *, const QSi
funcs.glBindFramebuffer(GL_FRAMEBUFFER, ctx->d_func()->current_fbo);
if (valid) {
fbo_guard = new QOpenGLSharedResourceGuard(ctx, fbo, freeFramebufferFunc);
if (color_buffer)
color_buffer_guard = new QOpenGLSharedResourceGuard(ctx, color_buffer, freeRenderbufferFunc);
else
texture_guard = new QOpenGLSharedResourceGuard(ctx, texture, freeTextureFunc);
} else {
if (color_buffer)
funcs.glDeleteRenderbuffers(1, &color_buffer);
else
glDeleteTextures(1, &texture);
if (color_buffer_guard) {
color_buffer_guard->free();
color_buffer_guard = 0;
} else if (texture_guard) {
texture_guard->free();
texture_guard = 0;
}
funcs.glDeleteFramebuffers(1, &fbo);
}
QT_CHECK_GLERROR();
}
void QOpenGLFramebufferObjectPrivate::initTexture(GLenum target, GLenum internal_format,
const QSize &size, bool mipmap)
{
QOpenGLContext *ctx = QOpenGLContext::currentContext();
GLuint texture = 0;
glGenTextures(1, &texture);
glBindTexture(target, texture);
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(target, 0, internal_format, size.width(), size.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
if (mipmap) {
int width = size.width();
int height = size.height();
int level = 0;
while (width > 1 || height > 1) {
width = qMax(1, width >> 1);
height = qMax(1, height >> 1);
++level;
glTexImage2D(target, level, internal_format, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
}
}
funcs.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
target, texture, 0);
QT_CHECK_GLERROR();
glBindTexture(target, 0);
valid = checkFramebufferStatus(ctx);
if (valid)
texture_guard = new QOpenGLSharedResourceGuard(ctx, texture, freeTextureFunc);
else
glDeleteTextures(1, &texture);
}
void QOpenGLFramebufferObjectPrivate::initAttachments(QOpenGLContext *ctx, QOpenGLFramebufferObject::Attachment attachment)
{
int samples = format.samples();
@ -905,6 +916,10 @@ bool QOpenGLFramebufferObject::isValid() const
framebuffer to this framebuffer object.
Returns \c true upon success, false otherwise.
\note If takeTexture() was called, a new texture is created and associated
with the framebuffer object. This is potentially expensive and changes the
context state (the currently bound texture).
\sa release()
*/
bool QOpenGLFramebufferObject::bind()
@ -920,7 +935,10 @@ bool QOpenGLFramebufferObject::bind()
qWarning("QOpenGLFramebufferObject::bind() called from incompatible context");
#endif
d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, d->fbo());
d->valid = d->checkFramebufferStatus(current);
if (d->texture_guard || d->format.samples() != 0)
d->valid = d->checkFramebufferStatus(current);
else
d->initTexture(d->format.textureTarget(), d->format.internalTextureFormat(), d->size, d->format.mipmap());
if (d->valid && current)
current->d_func()->current_fbo = d->fbo();
return d->valid;
@ -967,6 +985,8 @@ bool QOpenGLFramebufferObject::release()
If a multisample framebuffer object is used then the value returned
from this function will be invalid.
\sa takeTexture()
*/
GLuint QOpenGLFramebufferObject::texture() const
{
@ -974,6 +994,40 @@ GLuint QOpenGLFramebufferObject::texture() const
return d->texture_guard ? d->texture_guard->id() : 0;
}
/*!
\fn GLuint QOpenGLFramebufferObject::takeTexture()
Returns the texture id for the texture attached to this framebuffer
object. The ownership of the texture is transferred to the caller.
If the framebuffer object is currently bound, an implicit release()
will be done. During the next call to bind() a new texture will be
created.
If a multisample framebuffer object is used, then there is no
texture and the return value from this function will be invalid.
Similarly, incomplete framebuffer objects will also return 0.
\since 5.3
\sa texture(), bind(), release()
*/
GLuint QOpenGLFramebufferObject::takeTexture()
{
Q_D(QOpenGLFramebufferObject);
GLuint id = 0;
if (isValid() && d->texture_guard) {
QOpenGLContext *current = QOpenGLContext::currentContext();
if (current && current->shareGroup() == d->fbo_guard->group() && current->d_func()->current_fbo == d->fbo())
release();
id = d->texture_guard->id();
// Do not call free() on texture_guard, just null it out.
// This way the texture will not be deleted when the guard is destroyed.
d->texture_guard = 0;
}
return id;
}
/*!
\fn QSize QOpenGLFramebufferObject::size() const

View File

@ -97,6 +97,7 @@ public:
int height() const { return size().height(); }
GLuint texture() const;
GLuint takeTexture();
QSize size() const;
QImage toImage() const;
Attachment attachment() const;

View File

@ -116,6 +116,7 @@ public:
QOpenGLFramebufferObject::Attachment attachment,
GLenum internal_format, GLenum texture_target,
GLint samples = 0, bool mipmap = false);
void initTexture(GLenum target, GLenum internal_format, const QSize &size, bool mipmap);
void initAttachments(QOpenGLContext *ctx, QOpenGLFramebufferObject::Attachment attachment);
bool checkFramebufferStatus(QOpenGLContext *ctx) const;

View File

@ -66,6 +66,8 @@ private slots:
void multiGroupSharedResourceCleanupCustom();
void fboSimpleRendering_data();
void fboSimpleRendering();
void fboTextureOwnership_data();
void fboTextureOwnership();
void fboRendering_data();
void fboRendering();
void fboHandleNulledAfterContextDestroyed();
@ -429,6 +431,54 @@ void tst_QOpenGL::fboSimpleRendering()
delete fbo;
}
void tst_QOpenGL::fboTextureOwnership_data()
{
common_data();
}
void tst_QOpenGL::fboTextureOwnership()
{
QFETCH(int, surfaceClass);
QScopedPointer<QSurface> surface(createSurface(surfaceClass));
QOpenGLContext ctx;
QVERIFY(ctx.create());
ctx.makeCurrent(surface.data());
if (!QOpenGLFramebufferObject::hasOpenGLFramebufferObjects())
QSKIP("QOpenGLFramebufferObject not supported on this platform");
QOpenGLFramebufferObjectFormat fboFormat;
fboFormat.setAttachment(QOpenGLFramebufferObject::NoAttachment);
QOpenGLFramebufferObject *fbo = new QOpenGLFramebufferObject(200, 100, fboFormat);
QVERIFY(fbo->texture() != 0);
fbo->bind();
// pull out the texture
GLuint texture = fbo->takeTexture();
QVERIFY(texture != 0);
QVERIFY(fbo->texture() == 0);
// verify that the next bind() creates a new texture
fbo->bind();
QVERIFY(fbo->texture() != 0 && fbo->texture() != texture);
glClearColor(1.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glFinish();
QImage fb = fbo->toImage().convertToFormat(QImage::Format_RGB32);
QImage reference(fb.size(), QImage::Format_RGB32);
reference.fill(0xffff0000);
QFUZZY_COMPARE_IMAGES(fb, reference);
glDeleteTextures(1, &texture);
delete fbo;
}
void tst_QOpenGL::fboRendering_data()
{
common_data();