diff options
Diffstat (limited to 'src/gui/opengl/qopenglframebufferobject.cpp')
-rw-r--r-- | src/gui/opengl/qopenglframebufferobject.cpp | 215 |
1 files changed, 150 insertions, 65 deletions
diff --git a/src/gui/opengl/qopenglframebufferobject.cpp b/src/gui/opengl/qopenglframebufferobject.cpp index 3102e1ecd2..0c05b61e76 100644 --- a/src/gui/opengl/qopenglframebufferobject.cpp +++ b/src/gui/opengl/qopenglframebufferobject.cpp @@ -88,6 +88,10 @@ QT_BEGIN_NAMESPACE #define GL_DEPTH_COMPONENT24 0x81A6 #endif +#ifndef GL_DEPTH_COMPONENT24_OES +#define GL_DEPTH_COMPONENT24_OES 0x81A6 +#endif + #ifndef GL_READ_FRAMEBUFFER #define GL_READ_FRAMEBUFFER 0x8CA8 #endif @@ -96,6 +100,23 @@ QT_BEGIN_NAMESPACE #define GL_DRAW_FRAMEBUFFER 0x8CA9 #endif +#ifndef GL_RGB8 +#define GL_RGB8 0x8051 +#endif + +#ifndef GL_RGBA8 +#define GL_RGBA8 0x8058 +#endif + +#ifndef GL_BGRA +#define GL_BGRA 0x80E1 +#endif + +#ifndef GL_UNSIGNED_INT_8_8_8_8_REV +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#endif + + /*! \class QOpenGLFramebufferObjectFormat \brief The QOpenGLFramebufferObjectFormat class specifies the format of an OpenGL @@ -423,18 +444,21 @@ void QOpenGLFramebufferObjectPrivate::init(QOpenGLFramebufferObject *, const QSi if (!funcs.hasOpenGLFeature(QOpenGLFunctions::Framebuffers)) return; - // Fall back to using a normal non-msaa FBO if we don't have support for MSAA if (!funcs.hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample) || !funcs.hasOpenGLExtension(QOpenGLExtensions::FramebufferBlit)) { samples = 0; } -#ifndef QT_OPENGL_ES_2 - GLint maxSamples; - funcs.glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); - samples = qBound(0, int(samples), int(maxSamples)); -#endif + // On GLES 2.0 multisampled framebuffers are available through vendor-specific extensions + const bool msaaES2 = ctx->isOpenGLES() && (ctx->hasExtension("GL_ANGLE_framebuffer_multisample") + || ctx->hasExtension("GL_NV_framebuffer_multisample")); + + if (!ctx->isOpenGLES() || msaaES2) { + GLint maxSamples; + funcs.glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); + samples = qBound(0, int(samples), int(maxSamples)); + } samples = qMax(0, samples); requestedSamples = samples; @@ -456,11 +480,9 @@ void QOpenGLFramebufferObjectPrivate::init(QOpenGLFramebufferObject *, const QSi } else { GLenum storageFormat = internal_format; #ifdef GL_RGBA8_OES - // Correct the internal format used by the render buffer when using ANGLE - if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES && internal_format == GL_RGBA - && strstr((const char *)funcs.glGetString(GL_RENDERER), "ANGLE") != 0) { + // Correct the internal format used by the render buffer when using ES with extensions + if (msaaES2 && internal_format == GL_RGBA) storageFormat = GL_RGBA8_OES; - } #endif mipmap = false; @@ -488,7 +510,6 @@ void QOpenGLFramebufferObjectPrivate::init(QOpenGLFramebufferObject *, const QSi initAttachments(ctx, attachment); - funcs.glBindFramebuffer(GL_FRAMEBUFFER, ctx->d_func()->current_fbo); if (valid) { fbo_guard = new QOpenGLSharedResourceGuard(ctx, fbo, freeFramebufferFunc); } else { @@ -975,8 +996,6 @@ bool QOpenGLFramebufferObject::bind() 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; } @@ -1004,10 +1023,8 @@ bool QOpenGLFramebufferObject::release() qWarning("QOpenGLFramebufferObject::release() called from incompatible context"); #endif - if (current) { - current->d_func()->current_fbo = current->defaultFramebufferObject(); - d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, current->d_func()->current_fbo); - } + if (current) + d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, current->defaultFramebufferObject()); return true; } @@ -1054,7 +1071,7 @@ GLuint QOpenGLFramebufferObject::takeTexture() 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()) + if (current && current->shareGroup() == d->fbo_guard->group() && isBound()) release(); id = d->texture_guard->id(); // Do not call free() on texture_guard, just null it out. @@ -1097,47 +1114,101 @@ QOpenGLFramebufferObjectFormat QOpenGLFramebufferObject::format() const return d->format; } -Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha) +static inline QImage qt_gl_read_framebuffer_rgba8(const QSize &size, bool include_alpha, QOpenGLContext *context) { - int w = size.width(); - int h = size.height(); - - QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); - while (funcs->glGetError()); + QOpenGLFunctions *funcs = context->functions(); + const int w = size.width(); + const int h = size.height(); + bool isOpenGL12orBetter = !context->isOpenGLES() && (context->format().majorVersion() >= 2 || context->format().minorVersion() >= 2); + if (isOpenGL12orBetter) { + QImage img(size, include_alpha ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32); + funcs->glReadPixels(0, 0, w, h, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, img.bits()); + return img; + } #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - QImage img(size, (alpha_format && include_alpha) ? QImage::Format_ARGB32_Premultiplied - : QImage::Format_RGB32); -#ifdef QT_OPENGL_ES - GLint fmt = GL_BGRA_EXT; -#else - GLint fmt = GL_BGRA; -#endif - funcs->glReadPixels(0, 0, w, h, fmt, GL_UNSIGNED_BYTE, img.bits()); - if (!funcs->glGetError()) - return img.mirrored(); + // Without GL_UNSIGNED_INT_8_8_8_8_REV, GL_BGRA only makes sense on little endian. + const bool supports_bgra = context->isOpenGLES() + ? context->hasExtension(QByteArrayLiteral("GL_EXT_read_format_bgra")) + : context->hasExtension(QByteArrayLiteral("GL_EXT_bgra")); + if (supports_bgra) { + QImage img(size, include_alpha ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32); + funcs->glReadPixels(0, 0, w, h, GL_BGRA, GL_UNSIGNED_BYTE, img.bits()); + return img; + } #endif - - QImage rgbaImage(size, (alpha_format && include_alpha) ? QImage::Format_RGBA8888_Premultiplied - : QImage::Format_RGBX8888); + QImage rgbaImage(size, include_alpha ? QImage::Format_RGBA8888_Premultiplied : QImage::Format_RGBX8888); funcs->glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, rgbaImage.bits()); - return rgbaImage.mirrored(); + return rgbaImage; +} + +static QImage qt_gl_read_framebuffer(const QSize &size, GLenum internal_format, bool include_alpha, bool flip) +{ + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + QOpenGLFunctions *funcs = ctx->functions(); + while (funcs->glGetError()); + + switch (internal_format) { + case GL_RGB: + case GL_RGB8: + return qt_gl_read_framebuffer_rgba8(size, false, ctx).mirrored(false, flip); + case GL_RGBA: + case GL_RGBA8: + default: + return qt_gl_read_framebuffer_rgba8(size, include_alpha, ctx).mirrored(false, flip); + } + + Q_UNREACHABLE(); + return QImage(); +} + +Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha) +{ + return qt_gl_read_framebuffer(size, alpha_format ? GL_RGBA : GL_RGB, include_alpha, true); } /*! - \fn QImage QOpenGLFramebufferObject::toImage() const + \fn QImage QOpenGLFramebufferObject::toImage(bool flipped) const Returns the contents of this framebuffer object as a QImage. + If \a flipped is true the image is flipped from OpenGL coordinates to raster coordinates. + If used together with QOpenGLPaintDevice, \a flipped should be the opposite of the value + of QOpenGLPaintDevice::paintFlipped(). + Will try to return a premultiplied ARBG32 or RGB32 image. Since 5.2 it will fall back to a premultiplied RGBA8888 or RGBx8888 image when reading to ARGB32 is not supported. + + For multisampled framebuffer objects the samples are resolved using the + \c{GL_EXT_framebuffer_blit} extension. If the extension is not available, the contents + of the returned image is undefined. + + For singlesampled framebuffers the contents is retrieved via \c glReadPixels. This is + a potentially expensive and inefficient operation. Therefore it is recommended that + this function is used as seldom as possible. + + \sa QOpenGLPaintDevice::paintFlipped() */ -QImage QOpenGLFramebufferObject::toImage() const + +QImage QOpenGLFramebufferObject::toImage(bool flipped) const { Q_D(const QOpenGLFramebufferObject); if (!d->valid) return QImage(); + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + if (!ctx) { + qWarning("QOpenGLFramebufferObject::toImage() called without a current context"); + return QImage(); + } + + GLuint prevFbo = 0; + ctx->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *) &prevFbo); + + if (prevFbo != d->fbo()) + const_cast<QOpenGLFramebufferObject *>(this)->bind(); + + QImage image; // qt_gl_read_framebuffer doesn't work on a multisample FBO if (format().samples() != 0) { QOpenGLFramebufferObject temp(size(), QOpenGLFramebufferObjectFormat()); @@ -1145,20 +1216,31 @@ QImage QOpenGLFramebufferObject::toImage() const QRect rect(QPoint(0, 0), size()); blitFramebuffer(&temp, rect, const_cast<QOpenGLFramebufferObject *>(this), rect); - return temp.toImage(); + image = temp.toImage(flipped); + } else { + image = qt_gl_read_framebuffer(d->size, format().internalTextureFormat(), true, flipped); } - bool wasBound = isBound(); - if (!wasBound) - const_cast<QOpenGLFramebufferObject *>(this)->bind(); - QImage image = qt_gl_read_framebuffer(d->size, format().internalTextureFormat() != GL_RGB, true); - if (!wasBound) - const_cast<QOpenGLFramebufferObject *>(this)->release(); + if (prevFbo != d->fbo()) + ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, prevFbo); return image; } /*! + \fn QImage QOpenGLFramebufferObject::toImage() const + \overload + + Returns the contents of this framebuffer object as a QImage. This method flips + the image from OpenGL coordinates to raster coordinates. +*/ +// ### Qt 6: Remove this method and make it a default argument instead. +QImage QOpenGLFramebufferObject::toImage() const +{ + return toImage(true); +} + +/*! \fn bool QOpenGLFramebufferObject::bindDefault() Switches rendering back to the default, windowing system provided @@ -1170,16 +1252,13 @@ QImage QOpenGLFramebufferObject::toImage() const bool QOpenGLFramebufferObject::bindDefault() { QOpenGLContext *ctx = const_cast<QOpenGLContext *>(QOpenGLContext::currentContext()); - QOpenGLFunctions functions(ctx); - if (ctx) { - ctx->d_func()->current_fbo = ctx->defaultFramebufferObject(); - functions.glBindFramebuffer(GL_FRAMEBUFFER, ctx->d_func()->current_fbo); + if (ctx) + ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); #ifdef QT_DEBUG - } else { + else qWarning("QOpenGLFramebufferObject::bindDefault() called without current context."); #endif - } return ctx != 0; } @@ -1192,7 +1271,7 @@ bool QOpenGLFramebufferObject::bindDefault() */ bool QOpenGLFramebufferObject::hasOpenGLFramebufferObjects() { - return QOpenGLFunctions(QOpenGLContext::currentContext()).hasOpenGLFeature(QOpenGLFunctions::Framebuffers); + return QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(QOpenGLFunctions::Framebuffers); } /*! @@ -1228,6 +1307,8 @@ QOpenGLFramebufferObject::Attachment QOpenGLFramebufferObject::attachment() cons This can be used to free or reattach the depth and stencil buffer attachments as needed. + + \note This function alters the current framebuffer binding. */ void QOpenGLFramebufferObject::setAttachment(QOpenGLFramebufferObject::Attachment attachment) { @@ -1243,20 +1324,21 @@ void QOpenGLFramebufferObject::setAttachment(QOpenGLFramebufferObject::Attachmen #endif d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, d->fbo()); d->initAttachments(current, attachment); - if (current->d_func()->current_fbo != d->fbo()) - d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, current->d_func()->current_fbo); } /*! - Returns \c true if the framebuffer object is currently bound to a context, + Returns \c true if the framebuffer object is currently bound to the current context, otherwise false is returned. */ - bool QOpenGLFramebufferObject::isBound() const { Q_D(const QOpenGLFramebufferObject); - QOpenGLContext *current = QOpenGLContext::currentContext(); - return current ? current->d_func()->current_fbo == d->fbo() : false; + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + if (!ctx) + return false; + GLint fbo = 0; + ctx->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo); + return GLuint(fbo) == d->fbo(); } /*! @@ -1310,6 +1392,9 @@ void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target, If \a source or \a target is 0, the default framebuffer will be used instead of a framebuffer object as source or target respectively. + This function will have no effect unless hasOpenGLFramebufferBlit() returns + true. + The \a buffers parameter should be a mask consisting of any combination of \c GL_COLOR_BUFFER_BIT, \c GL_DEPTH_BUFFER_BIT, and \c GL_STENCIL_BUFFER_BIT. Any buffer type that is not present both @@ -1326,10 +1411,7 @@ void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target, have different sizes. The sizes must also be the same if any of the framebuffer objects are multisample framebuffers. - Note that the scissor test will restrict the blit area if enabled. - - This function will have no effect unless hasOpenGLFramebufferBlit() returns - true. + \note The scissor test will restrict the blit area if enabled. \sa hasOpenGLFramebufferBlit() */ @@ -1346,6 +1428,9 @@ void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target, if (!extensions.hasOpenGLExtension(QOpenGLExtensions::FramebufferBlit)) return; + GLuint prevFbo = 0; + ctx->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *) &prevFbo); + const int sx0 = sourceRect.left(); const int sx1 = sourceRect.left() + sourceRect.width(); const int sy0 = sourceRect.top(); @@ -1363,7 +1448,7 @@ void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target, tx0, ty0, tx1, ty1, buffers, filter); - extensions.glBindFramebuffer(GL_FRAMEBUFFER, ctx->d_func()->current_fbo); + ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, prevFbo); // sets both READ and DRAW } QT_END_NAMESPACE |