summaryrefslogtreecommitdiffstats
path: root/src/opengl
diff options
context:
space:
mode:
Diffstat (limited to 'src/opengl')
-rw-r--r--src/opengl/CMakeLists.txt1
-rw-r--r--src/opengl/opengl.pro3
-rw-r--r--src/opengl/qopenglframebufferobject.cpp1857
-rw-r--r--src/opengl/qopenglframebufferobject.h196
-rw-r--r--src/opengl/qopenglframebufferobject_p.h153
-rw-r--r--src/opengl/qopenglwidget.cpp5
-rw-r--r--src/opengl/qopenglwindow.cpp6
7 files changed, 2215 insertions, 6 deletions
diff --git a/src/opengl/CMakeLists.txt b/src/opengl/CMakeLists.txt
index acad98a681..6fa346ff89 100644
--- a/src/opengl/CMakeLists.txt
+++ b/src/opengl/CMakeLists.txt
@@ -11,6 +11,7 @@ qt_add_module(OpenGL
qopengldebug.cpp qopengldebug.h
qopenglengineshadermanager.cpp qopenglengineshadermanager_p.h
qopenglengineshadersource_p.h
+ qopenglframebufferobject.cpp qopenglframebufferobject.h qopenglframebufferobject_p.h
qopenglgradientcache.cpp qopenglgradientcache_p.h
qopenglpaintdevice.cpp qopenglpaintdevice.h qopenglpaintdevice_p.h
qopenglpaintengine.cpp qopenglpaintengine_p.h
diff --git a/src/opengl/opengl.pro b/src/opengl/opengl.pro
index 2a5d7edbee..f7224da13b 100644
--- a/src/opengl/opengl.pro
+++ b/src/opengl/opengl.pro
@@ -15,6 +15,8 @@ HEADERS += \
qopengldebug.h \
qopenglengineshadermanager_p.h \
qopenglengineshadersource_p.h \
+ qopenglframebufferobject.h \
+ qopenglframebufferobject_p.h \
qopenglgradientcache_p.h \
qopenglpaintdevice.h \
qopenglpaintdevice_p.h \
@@ -35,6 +37,7 @@ SOURCES += \
qopengl2pexvertexarray.cpp \
qopenglcustomshaderstage.cpp \
qopenglengineshadermanager.cpp \
+ qopenglframebufferobject.cpp \
qopenglgradientcache.cpp \
qopenglpaintdevice.cpp \
qopenglpaintengine.cpp \
diff --git a/src/opengl/qopenglframebufferobject.cpp b/src/opengl/qopenglframebufferobject.cpp
new file mode 100644
index 0000000000..d39f5b5b75
--- /dev/null
+++ b/src/opengl/qopenglframebufferobject.cpp
@@ -0,0 +1,1857 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qopenglframebufferobject.h"
+#include "qopenglframebufferobject_p.h"
+
+#include <qdebug.h>
+#include <private/qopengl_p.h>
+#include <private/qopenglcontext_p.h>
+#include <private/qopenglextensions_p.h>
+#include <private/qfont_p.h>
+
+#include <qwindow.h>
+#include <qimage.h>
+#include <QtCore/qbytearray.h>
+
+QT_BEGIN_NAMESPACE
+
+#ifndef QT_NO_DEBUG
+#define QT_RESET_GLERROR() \
+{ \
+ while (true) {\
+ GLenum error = QOpenGLContext::currentContext()->functions()->glGetError(); \
+ if (error == GL_NO_ERROR || error == GL_CONTEXT_LOST) \
+ break; \
+ } \
+}
+#define QT_CHECK_GLERROR() \
+{ \
+ GLenum err = QOpenGLContext::currentContext()->functions()->glGetError(); \
+ if (err != GL_NO_ERROR && err != GL_CONTEXT_LOST) { \
+ qDebug("[%s line %d] OpenGL Error: %d", \
+ __FILE__, __LINE__, (int)err); \
+ } \
+}
+#else
+#define QT_RESET_GLERROR() {}
+#define QT_CHECK_GLERROR() {}
+#endif
+
+#ifndef GL_MAX_SAMPLES
+#define GL_MAX_SAMPLES 0x8D57
+#endif
+
+#ifndef GL_RENDERBUFFER_SAMPLES
+#define GL_RENDERBUFFER_SAMPLES 0x8CAB
+#endif
+
+#ifndef GL_DEPTH24_STENCIL8
+#define GL_DEPTH24_STENCIL8 0x88F0
+#endif
+
+#ifndef GL_DEPTH_COMPONENT24
+#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
+
+#ifndef GL_DRAW_FRAMEBUFFER
+#define GL_DRAW_FRAMEBUFFER 0x8CA9
+#endif
+
+#ifndef GL_RGB8
+#define GL_RGB8 0x8051
+#endif
+
+#ifndef GL_RGB10
+#define GL_RGB10 0x8052
+#endif
+
+#ifndef GL_RGB16
+#define GL_RGB16 0x8054
+#endif
+
+#ifndef GL_RGBA8
+#define GL_RGBA8 0x8058
+#endif
+
+#ifndef GL_RGB10_A2
+#define GL_RGB10_A2 0x8059
+#endif
+
+#ifndef GL_RGBA16
+#define GL_RGBA16 0x805B
+#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
+
+#ifndef GL_UNSIGNED_INT_2_10_10_10_REV
+#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368
+#endif
+
+#ifndef GL_CONTEXT_LOST
+#define GL_CONTEXT_LOST 0x0507
+#endif
+
+#ifndef GL_DEPTH_STENCIL_ATTACHMENT
+#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A
+#endif
+
+#ifndef GL_DEPTH_STENCIL
+#define GL_DEPTH_STENCIL 0x84F9
+#endif
+
+
+
+/*!
+ \class QOpenGLFramebufferObjectFormat
+ \brief The QOpenGLFramebufferObjectFormat class specifies the format of an OpenGL
+ framebuffer object.
+ \inmodule QtOpenGL
+
+ \since 5.0
+
+ \ingroup painting-3D
+
+ A framebuffer object has several characteristics:
+ \list
+ \li \l{setSamples()}{Number of samples per pixels.}
+ \li \l{setAttachment()}{Depth and/or stencil attachments.}
+ \li \l{setTextureTarget()}{Texture target.}
+ \li \l{setInternalTextureFormat()}{Internal texture format.}
+ \endlist
+
+ Note that the desired attachments or number of samples per pixels might not
+ be supported by the hardware driver. Call QOpenGLFramebufferObject::format()
+ after creating a QOpenGLFramebufferObject to find the exact format that was
+ used to create the frame buffer object.
+
+ \sa QOpenGLFramebufferObject
+*/
+
+/*!
+ \internal
+*/
+void QOpenGLFramebufferObjectFormat::detach()
+{
+ if (d->ref.loadRelaxed() != 1) {
+ QOpenGLFramebufferObjectFormatPrivate *newd
+ = new QOpenGLFramebufferObjectFormatPrivate(d);
+ if (!d->ref.deref())
+ delete d;
+ d = newd;
+ }
+}
+
+/*!
+ Creates a QOpenGLFramebufferObjectFormat object for specifying
+ the format of an OpenGL framebuffer object.
+
+ By default the format specifies a non-multisample framebuffer object with no
+ depth/stencil attachments, texture target \c GL_TEXTURE_2D, and internal format \c GL_RGBA8.
+ On OpenGL/ES systems, the default internal format is \c GL_RGBA.
+
+ \sa samples(), attachment(), internalTextureFormat()
+*/
+
+QOpenGLFramebufferObjectFormat::QOpenGLFramebufferObjectFormat()
+{
+ d = new QOpenGLFramebufferObjectFormatPrivate;
+}
+
+/*!
+ Constructs a copy of \a other.
+*/
+
+QOpenGLFramebufferObjectFormat::QOpenGLFramebufferObjectFormat(const QOpenGLFramebufferObjectFormat &other)
+{
+ d = other.d;
+ d->ref.ref();
+}
+
+/*!
+ Assigns \a other to this object.
+*/
+
+QOpenGLFramebufferObjectFormat &QOpenGLFramebufferObjectFormat::operator=(const QOpenGLFramebufferObjectFormat &other)
+{
+ if (d != other.d) {
+ other.d->ref.ref();
+ if (!d->ref.deref())
+ delete d;
+ d = other.d;
+ }
+ return *this;
+}
+
+/*!
+ Destroys the QOpenGLFramebufferObjectFormat.
+*/
+QOpenGLFramebufferObjectFormat::~QOpenGLFramebufferObjectFormat()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ Sets the number of samples per pixel for a multisample framebuffer object
+ to \a samples. The default sample count of 0 represents a regular
+ non-multisample framebuffer object.
+
+ If the desired amount of samples per pixel is not supported by the hardware
+ then the maximum number of samples per pixel will be used. Note that
+ multisample framebuffer objects cannot be bound as textures. Also, the
+ \c{GL_EXT_framebuffer_multisample} extension is required to create a
+ framebuffer with more than one sample per pixel.
+
+ \sa samples()
+*/
+void QOpenGLFramebufferObjectFormat::setSamples(int samples)
+{
+ detach();
+ d->samples = samples;
+}
+
+/*!
+ Returns the number of samples per pixel if a framebuffer object
+ is a multisample framebuffer object. Otherwise, returns 0.
+ The default value is 0.
+
+ \sa setSamples()
+*/
+int QOpenGLFramebufferObjectFormat::samples() const
+{
+ return d->samples;
+}
+
+/*!
+ Enables mipmapping if \a enabled is true; otherwise disables it.
+
+ Mipmapping is disabled by default.
+
+ If mipmapping is enabled, additional memory will be allocated for
+ the mipmap levels. The mipmap levels can be updated by binding the
+ texture and calling glGenerateMipmap(). Mipmapping cannot be enabled
+ for multisampled framebuffer objects.
+
+ \sa mipmap(), QOpenGLFramebufferObject::texture()
+*/
+void QOpenGLFramebufferObjectFormat::setMipmap(bool enabled)
+{
+ detach();
+ d->mipmap = enabled;
+}
+
+/*!
+ Returns \c true if mipmapping is enabled.
+
+ \sa setMipmap()
+*/
+bool QOpenGLFramebufferObjectFormat::mipmap() const
+{
+ return d->mipmap;
+}
+
+/*!
+ Sets the attachment configuration of a framebuffer object to \a attachment.
+
+ \sa attachment()
+*/
+void QOpenGLFramebufferObjectFormat::setAttachment(QOpenGLFramebufferObject::Attachment attachment)
+{
+ detach();
+ d->attachment = attachment;
+}
+
+/*!
+ Returns the configuration of the depth and stencil buffers attached to
+ a framebuffer object. The default is QOpenGLFramebufferObject::NoAttachment.
+
+ \sa setAttachment()
+*/
+QOpenGLFramebufferObject::Attachment QOpenGLFramebufferObjectFormat::attachment() const
+{
+ return d->attachment;
+}
+
+/*!
+ Sets the texture target of the texture attached to a framebuffer object to
+ \a target. Ignored for multisample framebuffer objects.
+
+ \sa textureTarget(), samples()
+*/
+void QOpenGLFramebufferObjectFormat::setTextureTarget(GLenum target)
+{
+ detach();
+ d->target = target;
+}
+
+/*!
+ Returns the texture target of the texture attached to a framebuffer object.
+ Ignored for multisample framebuffer objects. The default is
+ \c GL_TEXTURE_2D.
+
+ \sa setTextureTarget(), samples()
+*/
+GLenum QOpenGLFramebufferObjectFormat::textureTarget() const
+{
+ return d->target;
+}
+
+/*!
+ Sets the internal format of a framebuffer object's texture or
+ multisample framebuffer object's color buffer to
+ \a internalTextureFormat.
+
+ \sa internalTextureFormat()
+*/
+void QOpenGLFramebufferObjectFormat::setInternalTextureFormat(GLenum internalTextureFormat)
+{
+ detach();
+ d->internal_format = internalTextureFormat;
+}
+
+/*!
+ Returns the internal format of a framebuffer object's texture or
+ multisample framebuffer object's color buffer. The default is
+ \c GL_RGBA8 on desktop OpenGL systems, and \c GL_RGBA on
+ OpenGL/ES systems.
+
+ \sa setInternalTextureFormat()
+*/
+GLenum QOpenGLFramebufferObjectFormat::internalTextureFormat() const
+{
+ return d->internal_format;
+}
+
+/*!
+ Returns \c true if all the options of this framebuffer object format
+ are the same as \a other; otherwise returns \c false.
+*/
+bool QOpenGLFramebufferObjectFormat::operator==(const QOpenGLFramebufferObjectFormat& other) const
+{
+ if (d == other.d)
+ return true;
+ else
+ return d->equals(other.d);
+}
+
+/*!
+ Returns \c false if all the options of this framebuffer object format
+ are the same as \a other; otherwise returns \c true.
+*/
+bool QOpenGLFramebufferObjectFormat::operator!=(const QOpenGLFramebufferObjectFormat& other) const
+{
+ return !(*this == other);
+}
+
+bool QOpenGLFramebufferObjectPrivate::checkFramebufferStatus(QOpenGLContext *ctx) const
+{
+ if (!ctx)
+ return false; // Context no longer exists.
+ GLenum status = ctx->functions()->glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ switch(status) {
+ case GL_NO_ERROR:
+ case GL_FRAMEBUFFER_COMPLETE:
+ return true;
+ case GL_FRAMEBUFFER_UNSUPPORTED:
+ qDebug("QOpenGLFramebufferObject: Unsupported framebuffer format.");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+ qDebug("QOpenGLFramebufferObject: Framebuffer incomplete attachment.");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+ qDebug("QOpenGLFramebufferObject: Framebuffer incomplete, missing attachment.");
+ break;
+#ifdef GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT
+ case GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT:
+ qDebug("QOpenGLFramebufferObject: Framebuffer incomplete, duplicate attachment.");
+ break;
+#endif
+#ifdef GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS
+ case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
+ qDebug("QOpenGLFramebufferObject: Framebuffer incomplete, attached images must have same dimensions.");
+ break;
+#endif
+#ifdef GL_FRAMEBUFFER_INCOMPLETE_FORMATS
+ case GL_FRAMEBUFFER_INCOMPLETE_FORMATS:
+ qDebug("QOpenGLFramebufferObject: Framebuffer incomplete, attached images must have same format.");
+ break;
+#endif
+#ifdef GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
+ qDebug("QOpenGLFramebufferObject: Framebuffer incomplete, missing draw buffer.");
+ break;
+#endif
+#ifdef GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER
+ case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
+ qDebug("QOpenGLFramebufferObject: Framebuffer incomplete, missing read buffer.");
+ break;
+#endif
+#ifdef GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE
+ case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
+ qDebug("QOpenGLFramebufferObject: Framebuffer incomplete, attachments must have same number of samples per pixel.");
+ break;
+#endif
+ default:
+ qDebug() <<"QOpenGLFramebufferObject: An undefined error has occurred: "<< status;
+ break;
+ }
+ return false;
+}
+
+namespace
+{
+ void freeFramebufferFunc(QOpenGLFunctions *funcs, GLuint id)
+ {
+ funcs->glDeleteFramebuffers(1, &id);
+ }
+
+ void freeRenderbufferFunc(QOpenGLFunctions *funcs, GLuint id)
+ {
+ funcs->glDeleteRenderbuffers(1, &id);
+ }
+
+ void freeTextureFunc(QOpenGLFunctions *funcs, GLuint id)
+ {
+ funcs->glDeleteTextures(1, &id);
+ }
+}
+
+void QOpenGLFramebufferObjectPrivate::init(QOpenGLFramebufferObject *, const QSize &size,
+ QOpenGLFramebufferObject::Attachment attachment,
+ GLenum texture_target, GLenum internal_format,
+ GLint samples, bool mipmap)
+{
+ QOpenGLContext *ctx = QOpenGLContext::currentContext();
+
+ funcs.initializeOpenGLFunctions();
+
+ 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;
+ } else if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
+ GLint maxSamples;
+ funcs.glGetIntegerv(GL_MAX_SAMPLES, &maxSamples);
+ samples = qBound(0, int(samples), int(maxSamples));
+ }
+
+ colorAttachments.append(ColorAttachment(size, internal_format));
+
+ dsSize = size;
+
+ samples = qMax(0, samples);
+ requestedSamples = samples;
+
+ target = texture_target;
+
+ QT_RESET_GLERROR(); // reset error state
+ GLuint fbo = 0;
+
+ funcs.glGenFramebuffers(1, &fbo);
+ funcs.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+ QOpenGLContextPrivate::get(ctx)->qgl_current_fbo_invalid = true;
+
+ QT_CHECK_GLERROR();
+
+ format.setTextureTarget(target);
+ format.setInternalTextureFormat(internal_format);
+ format.setMipmap(mipmap);
+
+ if (samples == 0)
+ initTexture(0);
+ else
+ initColorBuffer(0, &samples);
+
+ format.setSamples(int(samples));
+
+ initDepthStencilAttachments(ctx, attachment);
+
+ if (valid)
+ fbo_guard = new QOpenGLSharedResourceGuard(ctx, fbo, freeFramebufferFunc);
+ else
+ funcs.glDeleteFramebuffers(1, &fbo);
+
+ QT_CHECK_GLERROR();
+}
+
+void QOpenGLFramebufferObjectPrivate::initTexture(int idx)
+{
+ QOpenGLContext *ctx = QOpenGLContext::currentContext();
+ GLuint texture = 0;
+
+ funcs.glGenTextures(1, &texture);
+ funcs.glBindTexture(target, texture);
+
+ funcs.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ funcs.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ funcs.glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ funcs.glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ ColorAttachment &color(colorAttachments[idx]);
+
+ GLuint pixelType = GL_UNSIGNED_BYTE;
+ if (color.internalFormat == GL_RGB10_A2 || color.internalFormat == GL_RGB10)
+ pixelType = GL_UNSIGNED_INT_2_10_10_10_REV;
+ else if (color.internalFormat == GL_RGB16 || color.internalFormat == GL_RGBA16)
+ pixelType = GL_UNSIGNED_SHORT;
+
+ funcs.glTexImage2D(target, 0, color.internalFormat, color.size.width(), color.size.height(), 0,
+ GL_RGBA, pixelType, nullptr);
+ if (format.mipmap()) {
+ int width = color.size.width();
+ int height = color.size.height();
+ int level = 0;
+ while (width > 1 || height > 1) {
+ width = qMax(1, width >> 1);
+ height = qMax(1, height >> 1);
+ ++level;
+ funcs.glTexImage2D(target, level, color.internalFormat, width, height, 0,
+ GL_RGBA, pixelType, nullptr);
+ }
+ }
+ funcs.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + idx,
+ target, texture, 0);
+
+ QT_CHECK_GLERROR();
+ funcs.glBindTexture(target, 0);
+ valid = checkFramebufferStatus(ctx);
+ if (valid) {
+ color.guard = new QOpenGLSharedResourceGuard(ctx, texture, freeTextureFunc);
+ } else {
+ funcs.glDeleteTextures(1, &texture);
+ }
+}
+
+void QOpenGLFramebufferObjectPrivate::initColorBuffer(int idx, GLint *samples)
+{
+ QOpenGLContext *ctx = QOpenGLContext::currentContext();
+ GLuint color_buffer = 0;
+
+ ColorAttachment &color(colorAttachments[idx]);
+
+ GLenum storageFormat = color.internalFormat;
+ // ES requires a sized format. The older desktop extension does not. Correct the format on ES.
+ if (ctx->isOpenGLES()) {
+ if (color.internalFormat == GL_RGBA) {
+ if (funcs.hasOpenGLExtension(QOpenGLExtensions::Sized8Formats))
+ storageFormat = GL_RGBA8;
+ else
+ storageFormat = GL_RGBA4;
+ } else if (color.internalFormat == GL_RGB10) {
+ // GL_RGB10 is not allowed in ES for glRenderbufferStorage.
+ storageFormat = GL_RGB10_A2;
+ }
+ }
+
+ funcs.glGenRenderbuffers(1, &color_buffer);
+ funcs.glBindRenderbuffer(GL_RENDERBUFFER, color_buffer);
+ funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, *samples, storageFormat, color.size.width(), color.size.height());
+ funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + idx,
+ GL_RENDERBUFFER, color_buffer);
+
+ QT_CHECK_GLERROR();
+ valid = checkFramebufferStatus(ctx);
+ if (valid) {
+ // Query the actual number of samples. This can be greater than the requested
+ // value since the typically supported values are 0, 4, 8, ..., and the
+ // requests are mapped to the next supported value.
+ funcs.glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, samples);
+ color.guard = new QOpenGLSharedResourceGuard(ctx, color_buffer, freeRenderbufferFunc);
+ } else {
+ funcs.glDeleteRenderbuffers(1, &color_buffer);
+ }
+}
+
+void QOpenGLFramebufferObjectPrivate::initDepthStencilAttachments(QOpenGLContext *ctx,
+ QOpenGLFramebufferObject::Attachment attachment)
+{
+ // Use the same sample count for all attachments. format.samples() already contains
+ // the actual number of samples for the color attachment and is not suitable. Use
+ // requestedSamples instead.
+ const int samples = requestedSamples;
+
+ // free existing attachments
+ if (depth_buffer_guard) {
+#ifdef Q_OS_WASM
+ funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
+#else
+ funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
+#endif
+ depth_buffer_guard->free();
+ }
+ if (stencil_buffer_guard) {
+ funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
+ if (stencil_buffer_guard != depth_buffer_guard)
+ stencil_buffer_guard->free();
+ }
+
+ depth_buffer_guard = nullptr;
+ stencil_buffer_guard = nullptr;
+
+ GLuint depth_buffer = 0;
+ GLuint stencil_buffer = 0;
+
+ // In practice, a combined depth-stencil buffer is supported by all desktop platforms, while a
+ // separate stencil buffer is not. On embedded devices however, a combined depth-stencil buffer
+ // might not be supported while separate buffers are, according to QTBUG-12861.
+#ifdef Q_OS_WASM
+ // WebGL doesn't allow separately attach buffers to
+ // STENCIL_ATTACHMENT and DEPTH_ATTACHMENT
+ // QTBUG-69913
+ if (attachment == QOpenGLFramebufferObject::CombinedDepthStencil) {
+ funcs.glGenRenderbuffers(1, &depth_buffer);
+ funcs.glBindRenderbuffer(GL_RENDERBUFFER, depth_buffer);
+ Q_ASSERT(funcs.glIsRenderbuffer(depth_buffer));
+
+ if (samples != 0 ) {
+ funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples,
+ GL_DEPTH24_STENCIL8, dsSize.width(), dsSize.height());
+ } else {
+ funcs.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL,
+ dsSize.width(), dsSize.height());
+ }
+
+ funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, depth_buffer);
+
+ valid = checkFramebufferStatus(ctx);
+ if (!valid) {
+ funcs.glDeleteRenderbuffers(1, &depth_buffer);
+ depth_buffer = 0;
+ }
+ }
+#else
+ if (attachment == QOpenGLFramebufferObject::CombinedDepthStencil
+ && funcs.hasOpenGLExtension(QOpenGLExtensions::PackedDepthStencil))
+ {
+ // depth and stencil buffer needs another extension
+ funcs.glGenRenderbuffers(1, &depth_buffer);
+ funcs.glBindRenderbuffer(GL_RENDERBUFFER, depth_buffer);
+ Q_ASSERT(funcs.glIsRenderbuffer(depth_buffer));
+ if (samples != 0 && funcs.hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample))
+ funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples,
+ GL_DEPTH24_STENCIL8, dsSize.width(), dsSize.height());
+ else
+ funcs.glRenderbufferStorage(GL_RENDERBUFFER,
+ GL_DEPTH24_STENCIL8, dsSize.width(), dsSize.height());
+
+ stencil_buffer = depth_buffer;
+ funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, depth_buffer);
+ funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, stencil_buffer);
+
+ valid = checkFramebufferStatus(ctx);
+ if (!valid) {
+ funcs.glDeleteRenderbuffers(1, &depth_buffer);
+ stencil_buffer = depth_buffer = 0;
+ }
+ }
+
+ if (depth_buffer == 0 && (attachment == QOpenGLFramebufferObject::CombinedDepthStencil
+ || (attachment == QOpenGLFramebufferObject::Depth)))
+ {
+ funcs.glGenRenderbuffers(1, &depth_buffer);
+ funcs.glBindRenderbuffer(GL_RENDERBUFFER, depth_buffer);
+ Q_ASSERT(funcs.glIsRenderbuffer(depth_buffer));
+ if (samples != 0 && funcs.hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)) {
+ if (ctx->isOpenGLES()) {
+ if (funcs.hasOpenGLExtension(QOpenGLExtensions::Depth24))
+ funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples,
+ GL_DEPTH_COMPONENT24, dsSize.width(), dsSize.height());
+ else
+ funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples,
+ GL_DEPTH_COMPONENT16, dsSize.width(), dsSize.height());
+ } else {
+ funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples,
+ GL_DEPTH_COMPONENT, dsSize.width(), dsSize.height());
+ }
+ } else {
+ if (ctx->isOpenGLES()) {
+ if (funcs.hasOpenGLExtension(QOpenGLExtensions::Depth24)) {
+ funcs.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24,
+ dsSize.width(), dsSize.height());
+ } else {
+ funcs.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16,
+ dsSize.width(), dsSize.height());
+ }
+ } else {
+ funcs.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, dsSize.width(), dsSize.height());
+ }
+ }
+ funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, depth_buffer);
+ valid = checkFramebufferStatus(ctx);
+ if (!valid) {
+ funcs.glDeleteRenderbuffers(1, &depth_buffer);
+ depth_buffer = 0;
+ }
+ }
+
+ if (stencil_buffer == 0 && (attachment == QOpenGLFramebufferObject::CombinedDepthStencil)) {
+ funcs.glGenRenderbuffers(1, &stencil_buffer);
+ funcs.glBindRenderbuffer(GL_RENDERBUFFER, stencil_buffer);
+ Q_ASSERT(funcs.glIsRenderbuffer(stencil_buffer));
+
+#ifdef QT_OPENGL_ES
+ GLenum storage = GL_STENCIL_INDEX8;
+#else
+ GLenum storage = ctx->isOpenGLES() ? GL_STENCIL_INDEX8 : GL_STENCIL_INDEX;
+#endif
+
+ if (samples != 0 && funcs.hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample))
+ funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, storage, dsSize.width(), dsSize.height());
+ else
+ funcs.glRenderbufferStorage(GL_RENDERBUFFER, storage, dsSize.width(), dsSize.height());
+
+ funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, stencil_buffer);
+ valid = checkFramebufferStatus(ctx);
+ if (!valid) {
+ funcs.glDeleteRenderbuffers(1, &stencil_buffer);
+ stencil_buffer = 0;
+ }
+ }
+#endif //Q_OS_WASM
+
+ // The FBO might have become valid after removing the depth or stencil buffer.
+ valid = checkFramebufferStatus(ctx);
+
+#ifdef Q_OS_WASM
+ if (depth_buffer) {
+#else
+ if (depth_buffer && stencil_buffer) {
+#endif
+ fbo_attachment = QOpenGLFramebufferObject::CombinedDepthStencil;
+ } else if (depth_buffer) {
+ fbo_attachment = QOpenGLFramebufferObject::Depth;
+ } else {
+ fbo_attachment = QOpenGLFramebufferObject::NoAttachment;
+ }
+
+ if (valid) {
+ if (depth_buffer)
+ depth_buffer_guard = new QOpenGLSharedResourceGuard(ctx, depth_buffer, freeRenderbufferFunc);
+ if (stencil_buffer) {
+ if (stencil_buffer == depth_buffer)
+ stencil_buffer_guard = depth_buffer_guard;
+ else
+ stencil_buffer_guard = new QOpenGLSharedResourceGuard(ctx, stencil_buffer, freeRenderbufferFunc);
+ }
+ } else {
+ if (depth_buffer)
+ funcs.glDeleteRenderbuffers(1, &depth_buffer);
+ if (stencil_buffer && depth_buffer != stencil_buffer)
+ funcs.glDeleteRenderbuffers(1, &stencil_buffer);
+ }
+ QT_CHECK_GLERROR();
+
+ format.setAttachment(fbo_attachment);
+}
+
+/*!
+ \class QOpenGLFramebufferObject
+ \brief The QOpenGLFramebufferObject class encapsulates an OpenGL framebuffer object.
+ \since 5.0
+ \inmodule QtOpenGL
+
+ \ingroup painting-3D
+
+ The QOpenGLFramebufferObject class encapsulates an OpenGL framebuffer
+ object, defined by the \c{GL_EXT_framebuffer_object} extension. It provides
+ a rendering surface that can be painted on with a QPainter with the help of
+ QOpenGLPaintDevice, or rendered to using native OpenGL calls. This surface
+ can be bound and used as a regular texture in your own OpenGL drawing code.
+ By default, the QOpenGLFramebufferObject class generates a 2D OpenGL
+ texture (using the \c{GL_TEXTURE_2D} target), which is used as the internal
+ rendering target.
+
+ \b{It is important to have a current OpenGL context when creating a
+ QOpenGLFramebufferObject, otherwise initialization will fail.}
+
+ Create the QOpenGLFrameBufferObject instance with the CombinedDepthStencil
+ attachment if you want QPainter to render correctly. Note that you need to
+ create a QOpenGLFramebufferObject with more than one sample per pixel for
+ primitives to be antialiased when drawing using a QPainter. To create a
+ multisample framebuffer object you should use one of the constructors that
+ take a QOpenGLFramebufferObjectFormat parameter, and set the
+ QOpenGLFramebufferObjectFormat::samples() property to a non-zero value.
+
+ For multisample framebuffer objects a color render buffer is created,
+ otherwise a texture with the specified texture target is created.
+ The color render buffer or texture will have the specified internal
+ format, and will be bound to the \c GL_COLOR_ATTACHMENT0
+ attachment in the framebuffer object.
+
+ Multiple render targets are also supported, in case the OpenGL
+ implementation supports this. Here there will be multiple textures (or, in
+ case of multisampling, renderbuffers) present and each of them will get
+ attached to \c GL_COLOR_ATTACHMENT0, \c 1, \c 2, ...
+
+ If you want to use a framebuffer object with multisampling enabled
+ as a texture, you first need to copy from it to a regular framebuffer
+ object using QOpenGLContext::blitFramebuffer().
+
+ It is possible to draw into a QOpenGLFramebufferObject using QPainter and
+ QOpenGLPaintDevice in a separate thread.
+*/
+
+
+/*!
+ \enum QOpenGLFramebufferObject::Attachment
+
+ This enum type is used to configure the depth and stencil buffers
+ attached to the framebuffer object when it is created.
+
+ \value NoAttachment No attachment is added to the framebuffer object. Note that the
+ OpenGL depth and stencil tests won't work when rendering to a
+ framebuffer object without any depth or stencil buffers.
+ This is the default value.
+
+ \value CombinedDepthStencil If the \c GL_EXT_packed_depth_stencil extension is present,
+ a combined depth and stencil buffer is attached.
+ If the extension is not present, only a depth buffer is attached.
+
+ \value Depth A depth buffer is attached to the framebuffer object.
+
+ \sa attachment()
+*/
+
+static inline GLenum effectiveInternalFormat(GLenum internalFormat)
+{
+ if (!internalFormat)
+#ifdef QT_OPENGL_ES_2
+ internalFormat = GL_RGBA;
+#else
+ internalFormat = QOpenGLContext::currentContext()->isOpenGLES() ? GL_RGBA : GL_RGBA8;
+#endif
+ return internalFormat;
+}
+
+/*!
+
+ Constructs an OpenGL framebuffer object and binds a 2D OpenGL texture
+ to the buffer of the size \a size. The texture is bound to the
+ \c GL_COLOR_ATTACHMENT0 target in the framebuffer object.
+
+ The \a target parameter is used to specify the OpenGL texture
+ target. The default target is \c GL_TEXTURE_2D. Keep in mind that
+ \c GL_TEXTURE_2D textures must have a power of 2 width and height
+ (e.g. 256x512), unless you are using OpenGL 2.0 or higher.
+
+ By default, no depth and stencil buffers are attached. This behavior
+ can be toggled using one of the overloaded constructors.
+
+ The default internal texture format is \c GL_RGBA8 for desktop
+ OpenGL, and \c GL_RGBA for OpenGL/ES.
+
+ It is important that you have a current OpenGL context set when
+ creating the QOpenGLFramebufferObject, otherwise the initialization
+ will fail.
+
+ \sa size(), texture(), attachment()
+*/
+
+QOpenGLFramebufferObject::QOpenGLFramebufferObject(const QSize &size, GLenum target)
+ : d_ptr(new QOpenGLFramebufferObjectPrivate)
+{
+ Q_D(QOpenGLFramebufferObject);
+ d->init(this, size, NoAttachment, target, effectiveInternalFormat(0));
+}
+
+/*!
+
+ Constructs an OpenGL framebuffer object and binds a 2D OpenGL texture
+ to the buffer of the given \a width and \a height.
+
+ \sa size(), texture()
+*/
+QOpenGLFramebufferObject::QOpenGLFramebufferObject(int width, int height, GLenum target)
+ : QOpenGLFramebufferObject(QSize(width, height), target)
+{
+}
+
+/*!
+
+ Constructs an OpenGL framebuffer object of the given \a size based on the
+ supplied \a format.
+*/
+
+QOpenGLFramebufferObject::QOpenGLFramebufferObject(const QSize &size, const QOpenGLFramebufferObjectFormat &format)
+ : d_ptr(new QOpenGLFramebufferObjectPrivate)
+{
+ Q_D(QOpenGLFramebufferObject);
+ d->init(this, size, format.attachment(), format.textureTarget(), format.internalTextureFormat(),
+ format.samples(), format.mipmap());
+}
+
+/*!
+
+ Constructs an OpenGL framebuffer object of the given \a width and \a height
+ based on the supplied \a format.
+*/
+
+QOpenGLFramebufferObject::QOpenGLFramebufferObject(int width, int height, const QOpenGLFramebufferObjectFormat &format)
+ : QOpenGLFramebufferObject(QSize(width, height), format)
+{
+}
+
+/*!
+
+ Constructs an OpenGL framebuffer object and binds a texture to the
+ buffer of the given \a width and \a height.
+
+ The \a attachment parameter describes the depth/stencil buffer
+ configuration, \a target the texture target and \a internalFormat
+ the internal texture format. The default texture target is \c
+ GL_TEXTURE_2D, while the default internal format is \c GL_RGBA8
+ for desktop OpenGL and \c GL_RGBA for OpenGL/ES.
+
+ \sa size(), texture(), attachment()
+*/
+QOpenGLFramebufferObject::QOpenGLFramebufferObject(int width, int height, Attachment attachment,
+ GLenum target, GLenum internalFormat)
+ : d_ptr(new QOpenGLFramebufferObjectPrivate)
+{
+ Q_D(QOpenGLFramebufferObject);
+ d->init(this, QSize(width, height), attachment, target, effectiveInternalFormat(internalFormat));
+}
+
+/*!
+
+ Constructs an OpenGL framebuffer object and binds a texture to the
+ buffer of the given \a size.
+
+ The \a attachment parameter describes the depth/stencil buffer
+ configuration, \a target the texture target and \a internalFormat
+ the internal texture format. The default texture target is \c
+ GL_TEXTURE_2D, while the default internal format is \c GL_RGBA8
+ for desktop OpenGL and \c GL_RGBA for OpenGL/ES.
+
+ \sa size(), texture(), attachment()
+*/
+QOpenGLFramebufferObject::QOpenGLFramebufferObject(const QSize &size, Attachment attachment,
+ GLenum target, GLenum internalFormat)
+ : d_ptr(new QOpenGLFramebufferObjectPrivate)
+{
+ Q_D(QOpenGLFramebufferObject);
+ d->init(this, size, attachment, target, effectiveInternalFormat(internalFormat));
+}
+
+/*!
+
+ Destroys the framebuffer object and frees any allocated resources.
+*/
+QOpenGLFramebufferObject::~QOpenGLFramebufferObject()
+{
+ Q_D(QOpenGLFramebufferObject);
+ if (isBound())
+ release();
+
+ for (const auto &color : qAsConst(d->colorAttachments)) {
+ if (color.guard)
+ color.guard->free();
+ }
+ d->colorAttachments.clear();
+
+ if (d->depth_buffer_guard)
+ d->depth_buffer_guard->free();
+ if (d->stencil_buffer_guard && d->stencil_buffer_guard != d->depth_buffer_guard)
+ d->stencil_buffer_guard->free();
+ if (d->fbo_guard)
+ d->fbo_guard->free();
+
+ QOpenGLContextPrivate *contextPrv = QOpenGLContextPrivate::get(QOpenGLContext::currentContext());
+ if (contextPrv && contextPrv->qgl_current_fbo == this) {
+ contextPrv->qgl_current_fbo_invalid = true;
+ contextPrv->qgl_current_fbo = nullptr;
+ }
+}
+
+/*!
+ Creates and attaches an additional texture or renderbuffer of \a size width
+ and height.
+
+ There is always an attachment at GL_COLOR_ATTACHMENT0. Call this function
+ to set up additional attachments at GL_COLOR_ATTACHMENT1,
+ GL_COLOR_ATTACHMENT2, ...
+
+ When \a internalFormat is not \c 0, it specifies the internal format of the
+ texture or renderbuffer. Otherwise a default of GL_RGBA or GL_RGBA8 is
+ used.
+
+ \note This is only functional when multiple render targets are supported by
+ the OpenGL implementation. When that is not the case, the function will not
+ add any additional color attachments. Call
+ QOpenGLFunctions::hasOpenGLFeature() with
+ QOpenGLFunctions::MultipleRenderTargets at runtime to check if MRT is
+ supported.
+
+ \note The internal format of the color attachments may differ but there may
+ be limitations on the supported combinations, depending on the drivers.
+
+ \note The size of the color attachments may differ but rendering is limited
+ to the area that fits all the attachments, according to the OpenGL
+ specification. Some drivers may not be fully conformant in this respect,
+ however.
+
+ \since 5.6
+ */
+void QOpenGLFramebufferObject::addColorAttachment(const QSize &size, GLenum internalFormat)
+{
+ Q_D(QOpenGLFramebufferObject);
+
+ if (!QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(QOpenGLFunctions::MultipleRenderTargets)) {
+ qWarning("Multiple render targets not supported, ignoring extra color attachment request");
+ return;
+ }
+
+ QOpenGLFramebufferObjectPrivate::ColorAttachment color(size, effectiveInternalFormat(internalFormat));
+ d->colorAttachments.append(color);
+ const int idx = d->colorAttachments.count() - 1;
+
+ if (d->requestedSamples == 0) {
+ d->initTexture(idx);
+ } else {
+ GLint samples = d->requestedSamples;
+ d->initColorBuffer(idx, &samples);
+ }
+}
+
+/*! \overload
+
+ Creates and attaches an additional texture or renderbuffer of size \a width and \a height.
+
+ When \a internalFormat is not \c 0, it specifies the internal format of the texture or
+ renderbuffer. Otherwise a default of GL_RGBA or GL_RGBA8 is used.
+
+ \since 5.6
+ */
+void QOpenGLFramebufferObject::addColorAttachment(int width, int height, GLenum internalFormat)
+{
+ addColorAttachment(QSize(width, height), internalFormat);
+}
+
+/*!
+ \fn bool QOpenGLFramebufferObject::isValid() const
+
+ Returns \c true if the framebuffer object is valid.
+
+ The framebuffer can become invalid if the initialization process
+ fails, the user attaches an invalid buffer to the framebuffer
+ object, or a non-power of two width/height is specified as the
+ texture size if the texture target is \c{GL_TEXTURE_2D}.
+ The non-power of two limitation does not apply if the OpenGL version
+ is 2.0 or higher, or if the GL_ARB_texture_non_power_of_two extension
+ is present.
+
+ The framebuffer can also become invalid if the QOpenGLContext that
+ the framebuffer was created within is destroyed and there are
+ no other shared contexts that can take over ownership of the
+ framebuffer.
+*/
+bool QOpenGLFramebufferObject::isValid() const
+{
+ Q_D(const QOpenGLFramebufferObject);
+ return d->valid && d->fbo_guard && d->fbo_guard->id();
+}
+
+/*!
+ \fn bool QOpenGLFramebufferObject::bind()
+
+ Switches rendering from the default, windowing system provided
+ 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()
+{
+ if (!isValid())
+ return false;
+ Q_D(QOpenGLFramebufferObject);
+ QOpenGLContext *current = QOpenGLContext::currentContext();
+ if (!current)
+ return false;
+#ifdef QT_DEBUG
+ if (current->shareGroup() != d->fbo_guard->group())
+ qWarning("QOpenGLFramebufferObject::bind() called from incompatible context");
+#endif
+
+ d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, d->fbo());
+
+ QOpenGLContextPrivate::get(current)->qgl_current_fbo_invalid = true;
+ QOpenGLContextPrivate::get(current)->qgl_current_fbo = this;
+
+ if (d->format.samples() == 0) {
+ // Create new textures to replace the ones stolen via takeTexture().
+ for (int i = 0; i < d->colorAttachments.count(); ++i) {
+ if (!d->colorAttachments.at(i).guard)
+ d->initTexture(i);
+ }
+ }
+
+ return d->valid;
+}
+
+/*!
+ \fn bool QOpenGLFramebufferObject::release()
+
+ Switches rendering back to the default, windowing system provided
+ framebuffer.
+ Returns \c true upon success, false otherwise.
+
+ \sa bind()
+*/
+bool QOpenGLFramebufferObject::release()
+{
+ if (!isValid())
+ return false;
+
+ QOpenGLContext *current = QOpenGLContext::currentContext();
+ if (!current)
+ return false;
+
+ Q_D(QOpenGLFramebufferObject);
+#ifdef QT_DEBUG
+ if (current->shareGroup() != d->fbo_guard->group())
+ qWarning("QOpenGLFramebufferObject::release() called from incompatible context");
+#endif
+
+ if (current) {
+ d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, current->defaultFramebufferObject());
+
+ QOpenGLContextPrivate *contextPrv = QOpenGLContextPrivate::get(current);
+ contextPrv->qgl_current_fbo_invalid = true;
+ contextPrv->qgl_current_fbo = nullptr;
+ }
+
+ return true;
+}
+
+/*!
+ \fn GLuint QOpenGLFramebufferObject::texture() const
+
+ Returns the texture id for the texture attached as the default
+ rendering target in this framebuffer object. This texture id can
+ be bound as a normal texture in your own OpenGL code.
+
+ If a multisample framebuffer object is used then the value returned
+ from this function will be invalid.
+
+ When multiple textures are attached, the return value is the ID of
+ the first one.
+
+ \sa takeTexture(), textures()
+*/
+GLuint QOpenGLFramebufferObject::texture() const
+{
+ Q_D(const QOpenGLFramebufferObject);
+ return d->colorAttachments[0].guard ? d->colorAttachments[0].guard->id() : 0;
+}
+
+/*!
+ Returns the texture id for all attached textures.
+
+ If a multisample framebuffer object is used, then an empty vector is returned.
+
+ \since 5.6
+
+ \sa takeTexture(), texture()
+*/
+QVector<GLuint> QOpenGLFramebufferObject::textures() const
+{
+ Q_D(const QOpenGLFramebufferObject);
+ QVector<GLuint> ids;
+ if (d->format.samples() != 0)
+ return ids;
+ ids.reserve(d->colorAttachments.count());
+ for (const auto &color : d->colorAttachments)
+ ids.append(color.guard ? color.guard->id() : 0);
+ return ids;
+}
+
+/*!
+ \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()
+{
+ return takeTexture(0);
+}
+
+/*! \overload
+
+ Returns the texture id for the texture attached to the color attachment of
+ index \a colorAttachmentIndex of this framebuffer object. The ownership of
+ the texture is transferred to the caller.
+
+ When \a colorAttachmentIndex is \c 0, the behavior is identical to the
+ parameter-less variant of this function.
+
+ 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.6
+ */
+GLuint QOpenGLFramebufferObject::takeTexture(int colorAttachmentIndex)
+{
+ Q_D(QOpenGLFramebufferObject);
+ GLuint id = 0;
+ if (isValid() && d->format.samples() == 0 && d->colorAttachments.count() > colorAttachmentIndex) {
+ QOpenGLContext *current = QOpenGLContext::currentContext();
+ if (current && current->shareGroup() == d->fbo_guard->group() && isBound())
+ release();
+ auto &guard = d->colorAttachments[colorAttachmentIndex].guard;
+ id = guard ? guard->id() : 0;
+ // Do not call free() on texture_guard, just null it out.
+ // This way the texture will not be deleted when the guard is destroyed.
+ guard = nullptr;
+ }
+ return id;
+}
+
+/*!
+ \return the size of the color and depth/stencil attachments attached to
+ this framebuffer object.
+*/
+QSize QOpenGLFramebufferObject::size() const
+{
+ Q_D(const QOpenGLFramebufferObject);
+ return d->dsSize;
+}
+
+/*!
+ \return the sizes of all color attachments attached to this framebuffer
+ object.
+
+ \since 5.6
+*/
+QVector<QSize> QOpenGLFramebufferObject::sizes() const
+{
+ Q_D(const QOpenGLFramebufferObject);
+ QVector<QSize> sz;
+ sz.reserve(d->colorAttachments.size());
+ for (const auto &color : d->colorAttachments)
+ sz.append(color.size);
+ return sz;
+}
+
+/*!
+ \fn int QOpenGLFramebufferObject::width() const
+
+ Returns the width of the framebuffer object attachments.
+*/
+
+/*!
+ \fn int QOpenGLFramebufferObject::height() const
+
+ Returns the height of the framebuffer object attachments.
+*/
+
+/*!
+ Returns the format of this framebuffer object.
+*/
+QOpenGLFramebufferObjectFormat QOpenGLFramebufferObject::format() const
+{
+ Q_D(const QOpenGLFramebufferObject);
+ return d->format;
+}
+
+static inline QImage qt_gl_read_framebuffer_rgba8(const QSize &size, bool include_alpha, QOpenGLContext *context)
+{
+ 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;
+ }
+
+ // For OpenGL ES stick with the byte ordered format / RGBA readback format
+ // since that is the only spec mandated way. (also, skip the
+ // GL_IMPLEMENTATION_COLOR_READ_FORMAT mess since there is nothing saying a
+ // BGRA capable impl would return BGRA from there)
+
+ 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;
+}
+
+static inline QImage qt_gl_read_framebuffer_rgb10a2(const QSize &size, bool include_alpha, QOpenGLContext *context)
+{
+ // We assume OpenGL 1.2+ or ES 3.0+ here.
+ QImage img(size, include_alpha ? QImage::Format_A2BGR30_Premultiplied : QImage::Format_BGR30);
+ context->functions()->glReadPixels(0, 0, size.width(), size.height(), GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, img.bits());
+ return img;
+}
+
+static inline QImage qt_gl_read_framebuffer_rgba16(const QSize &size, bool include_alpha, QOpenGLContext *context)
+{
+ // We assume OpenGL 1.2+ or ES 3.0+ here.
+ QImage img(size, include_alpha ? QImage::Format_RGBA64_Premultiplied : QImage::Format_RGBX64);
+ context->functions()->glReadPixels(0, 0, size.width(), size.height(), GL_RGBA, GL_UNSIGNED_SHORT, img.bits());
+ return img;
+}
+
+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 (true) {
+ GLenum error = funcs->glGetError();
+ if (error == GL_NO_ERROR || error == GL_CONTEXT_LOST)
+ break;
+ }
+ switch (internal_format) {
+ case GL_RGB:
+ case GL_RGB8:
+ return qt_gl_read_framebuffer_rgba8(size, false, ctx).mirrored(false, flip);
+ case GL_RGB10:
+ return qt_gl_read_framebuffer_rgb10a2(size, false, ctx).mirrored(false, flip);
+ case GL_RGB10_A2:
+ return qt_gl_read_framebuffer_rgb10a2(size, include_alpha, ctx).mirrored(false, flip);
+ case GL_RGB16:
+ return qt_gl_read_framebuffer_rgba16(size, false, ctx).mirrored(false, flip);
+ case GL_RGBA16:
+ return qt_gl_read_framebuffer_rgba16(size, include_alpha, 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_OPENGL_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(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().
+
+ The returned image has a format of premultiplied ARGB32 or RGB32. The latter
+ is used only when internalTextureFormat() is set to \c GL_RGB. Since Qt 5.2
+ the function will fall back to premultiplied RGBA8888 or RGBx8888 when
+ reading to (A)RGB32 is not supported, and this includes OpenGL ES. Since Qt
+ 5.4 an A2BGR30 image is returned if the internal format is RGB10_A2, and since
+ Qt 5.12 a RGBA64 image is return if the internal format is RGBA16.
+
+ If the rendering in the framebuffer was not done with premultiplied alpha in mind,
+ create a wrapper QImage with a non-premultiplied format. This is necessary before
+ performing operations like QImage::save() because otherwise the image data would get
+ unpremultiplied, even though it was not premultiplied in the first place. To create
+ such a wrapper without performing a copy of the pixel data, do the following:
+
+ \code
+ QImage fboImage(fbo.toImage());
+ QImage image(fboImage.constBits(), fboImage.width(), fboImage.height(), QImage::Format_ARGB32);
+ \endcode
+
+ 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(bool flipped) const
+{
+ return toImage(flipped, 0);
+}
+
+/*!
+ \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, 0);
+}
+
+/*! \overload
+
+ Returns the contents of the color attachment of index \a
+ colorAttachmentIndex of this framebuffer object as a QImage. This method
+ flips the image from OpenGL coordinates to raster coordinates when \a
+ flipped is set to \c true.
+
+ \note This overload is only fully functional when multiple render targets are
+ supported by the OpenGL implementation. When that is not the case, only one
+ color attachment will be set up.
+
+ \since 5.6
+*/
+QImage QOpenGLFramebufferObject::toImage(bool flipped, int colorAttachmentIndex) 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();
+ }
+
+ if (d->colorAttachments.count() <= colorAttachmentIndex) {
+ qWarning("QOpenGLFramebufferObject::toImage() called for missing color attachment");
+ return QImage();
+ }
+
+ GLuint prevFbo = 0;
+ ctx->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *) &prevFbo);
+
+ if (prevFbo != d->fbo())
+ const_cast<QOpenGLFramebufferObject *>(this)->bind();
+
+ QImage image;
+ QOpenGLExtraFunctions *extraFuncs = ctx->extraFunctions();
+ // qt_gl_read_framebuffer doesn't work on a multisample FBO
+ if (format().samples() != 0) {
+ QRect rect(QPoint(0, 0), size());
+ QOpenGLFramebufferObjectFormat fmt;
+ if (extraFuncs->hasOpenGLFeature(QOpenGLFunctions::MultipleRenderTargets)) {
+ fmt.setInternalTextureFormat(d->colorAttachments[colorAttachmentIndex].internalFormat);
+ QOpenGLFramebufferObject temp(d->colorAttachments[colorAttachmentIndex].size, fmt);
+ blitFramebuffer(&temp, rect, const_cast<QOpenGLFramebufferObject *>(this), rect,
+ GL_COLOR_BUFFER_BIT, GL_NEAREST,
+ colorAttachmentIndex, 0);
+ image = temp.toImage(flipped);
+ } else {
+ fmt.setInternalTextureFormat(d->colorAttachments[0].internalFormat);
+ QOpenGLFramebufferObject temp(size(), fmt);
+ blitFramebuffer(&temp, rect, const_cast<QOpenGLFramebufferObject *>(this), rect);
+ image = temp.toImage(flipped);
+ }
+ } else {
+ if (extraFuncs->hasOpenGLFeature(QOpenGLFunctions::MultipleRenderTargets)) {
+ extraFuncs->glReadBuffer(GL_COLOR_ATTACHMENT0 + colorAttachmentIndex);
+ image = qt_gl_read_framebuffer(d->colorAttachments[colorAttachmentIndex].size,
+ d->colorAttachments[colorAttachmentIndex].internalFormat,
+ true, flipped);
+ extraFuncs->glReadBuffer(GL_COLOR_ATTACHMENT0);
+ } else {
+ image = qt_gl_read_framebuffer(d->colorAttachments[0].size,
+ d->colorAttachments[0].internalFormat,
+ true, flipped);
+ }
+ }
+
+ if (prevFbo != d->fbo())
+ ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, prevFbo);
+
+ return image;
+}
+
+/*!
+ \fn bool QOpenGLFramebufferObject::bindDefault()
+
+ Switches rendering back to the default, windowing system provided
+ framebuffer.
+ Returns \c true upon success, false otherwise.
+
+ \sa bind(), release()
+*/
+bool QOpenGLFramebufferObject::bindDefault()
+{
+ QOpenGLContext *ctx = const_cast<QOpenGLContext *>(QOpenGLContext::currentContext());
+
+ if (ctx) {
+ ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
+ QOpenGLContextPrivate::get(ctx)->qgl_current_fbo_invalid = true;
+ QOpenGLContextPrivate::get(ctx)->qgl_current_fbo = nullptr;
+ }
+#ifdef QT_DEBUG
+ else
+ qWarning("QOpenGLFramebufferObject::bindDefault() called without current context.");
+#endif
+
+ return ctx != nullptr;
+}
+
+/*!
+ \fn bool QOpenGLFramebufferObject::hasOpenGLFramebufferObjects()
+
+ Returns \c true if the OpenGL \c{GL_EXT_framebuffer_object} extension
+ is present on this system; otherwise returns \c false.
+*/
+bool QOpenGLFramebufferObject::hasOpenGLFramebufferObjects()
+{
+ return QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(QOpenGLFunctions::Framebuffers);
+}
+
+/*!
+ \fn GLuint QOpenGLFramebufferObject::handle() const
+
+ Returns the OpenGL framebuffer object handle for this framebuffer
+ object (returned by the \c{glGenFrameBuffersEXT()} function). This
+ handle can be used to attach new images or buffers to the
+ framebuffer. The user is responsible for cleaning up and
+ destroying these objects.
+*/
+GLuint QOpenGLFramebufferObject::handle() const
+{
+ Q_D(const QOpenGLFramebufferObject);
+ return d->fbo();
+}
+
+/*!
+ Returns the status of the depth and stencil buffers attached to
+ this framebuffer object.
+*/
+
+QOpenGLFramebufferObject::Attachment QOpenGLFramebufferObject::attachment() const
+{
+ Q_D(const QOpenGLFramebufferObject);
+ if (d->valid)
+ return d->fbo_attachment;
+ return NoAttachment;
+}
+
+/*!
+ Sets the attachments of the framebuffer object to \a attachment.
+
+ 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)
+{
+ Q_D(QOpenGLFramebufferObject);
+ if (attachment == d->fbo_attachment || !isValid())
+ return;
+ QOpenGLContext *current = QOpenGLContext::currentContext();
+ if (!current)
+ return;
+#ifdef QT_DEBUG
+ if (current->shareGroup() != d->fbo_guard->group())
+ qWarning("QOpenGLFramebufferObject::setAttachment() called from incompatible context");
+#endif
+ d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, d->fbo());
+ QOpenGLContextPrivate::get(current)->qgl_current_fbo_invalid = true;
+ d->initDepthStencilAttachments(current, attachment);
+}
+
+/*!
+ 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 *ctx = QOpenGLContext::currentContext();
+ if (!ctx)
+ return false;
+ GLint fbo = 0;
+ ctx->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo);
+ return GLuint(fbo) == d->fbo();
+}
+
+/*!
+ \fn bool QOpenGLFramebufferObject::hasOpenGLFramebufferBlit()
+
+ Returns \c true if the OpenGL \c{GL_EXT_framebuffer_blit} extension
+ is present on this system; otherwise returns \c false.
+
+ \sa blitFramebuffer()
+*/
+bool QOpenGLFramebufferObject::hasOpenGLFramebufferBlit()
+{
+ return QOpenGLExtensions(QOpenGLContext::currentContext()).hasOpenGLExtension(QOpenGLExtensions::FramebufferBlit);
+}
+
+
+/*!
+ \overload
+
+ Convenience overload to blit between two framebuffer objects.
+*/
+void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target,
+ QOpenGLFramebufferObject *source,
+ GLbitfield buffers, GLenum filter)
+{
+ if (!target && !source)
+ return;
+
+ QSize targetSize;
+ QSize sourceSize;
+
+ if (target)
+ targetSize = target->size();
+ if (source)
+ sourceSize = source->size();
+
+ if (targetSize.isEmpty())
+ targetSize = sourceSize;
+ else if (sourceSize.isEmpty())
+ sourceSize = targetSize;
+
+ blitFramebuffer(target, QRect(QPoint(0, 0), targetSize),
+ source, QRect(QPoint(0, 0), sourceSize),
+ buffers, filter);
+}
+
+/*! \overload
+ *
+ Convenience overload to blit between two framebuffer objects.
+*/
+void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target, const QRect &targetRect,
+ QOpenGLFramebufferObject *source, const QRect &sourceRect,
+ GLbitfield buffers,
+ GLenum filter)
+{
+ blitFramebuffer(target, targetRect, source, sourceRect, buffers, filter, 0, 0);
+}
+
+/*!
+ \enum QOpenGLFramebufferObject::FramebufferRestorePolicy
+ \since 5.7
+
+ This enum type is used to configure the behavior related to restoring
+ framebuffer bindings when calling blitFramebuffer().
+
+ \value DontRestoreFramebufferBinding Do not restore the previous framebuffer binding.
+ The caller is responsible for tracking and setting
+ the framebuffer binding as needed.
+
+ \value RestoreFramebufferBindingToDefault After the blit operation, bind the default
+ framebuffer.
+
+ \value RestoreFrameBufferBinding Restore the previously bound framebuffer. This is
+ potentially expensive because of the need to
+ query the currently bound framebuffer.
+
+ \sa blitFramebuffer()
+*/
+
+/*!
+ \since 5.7
+
+ Blits from the \a sourceRect rectangle in the \a source framebuffer
+ object to the \a targetRect rectangle in the \a target framebuffer object.
+
+ 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
+ in the source and target buffers is ignored.
+
+ The \a sourceRect and \a targetRect rectangles may have different sizes;
+ in this case \a buffers should not contain \c GL_DEPTH_BUFFER_BIT or
+ \c GL_STENCIL_BUFFER_BIT. The \a filter parameter should be set to
+ \c GL_LINEAR or \c GL_NEAREST, and specifies whether linear or nearest
+ interpolation should be used when scaling is performed.
+
+ If \a source equals \a target a copy is performed within the same buffer.
+ Results are undefined if the source and target rectangles overlap and
+ have different sizes. The sizes must also be the same if any of the
+ framebuffer objects are multisample framebuffers.
+
+ \note The scissor test will restrict the blit area if enabled.
+
+ When multiple render targets are in use, \a readColorAttachmentIndex and \a
+ drawColorAttachmentIndex specify the index of the color attachments in the
+ source and destination framebuffers.
+
+ The \a restorePolicy determines if the framebuffer that was bound prior to
+ calling this function should be restored, or if the default framebuffer
+ should be bound before returning, of if the caller is responsible for
+ tracking and setting the bound framebuffer. Restoring the previous
+ framebuffer can be relatively expensive due to the call to \c{glGetIntegerv}
+ which on some OpenGL drivers may imply a pipeline stall.
+
+ \sa hasOpenGLFramebufferBlit()
+*/
+void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target, const QRect &targetRect,
+ QOpenGLFramebufferObject *source, const QRect &sourceRect,
+ GLbitfield buffers,
+ GLenum filter,
+ int readColorAttachmentIndex,
+ int drawColorAttachmentIndex,
+ QOpenGLFramebufferObject::FramebufferRestorePolicy restorePolicy)
+{
+ QOpenGLContext *ctx = QOpenGLContext::currentContext();
+ if (!ctx)
+ return;
+
+ QOpenGLExtensions extensions(ctx);
+ if (!extensions.hasOpenGLExtension(QOpenGLExtensions::FramebufferBlit))
+ return;
+
+ GLuint prevFbo = 0;
+ if (restorePolicy == RestoreFrameBufferBinding)
+ 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();
+ const int sy1 = sourceRect.top() + sourceRect.height();
+
+ const int tx0 = targetRect.left();
+ const int tx1 = targetRect.left() + targetRect.width();
+ const int ty0 = targetRect.top();
+ const int ty1 = targetRect.top() + targetRect.height();
+
+ const GLuint defaultFboId = ctx->defaultFramebufferObject();
+
+ extensions.glBindFramebuffer(GL_READ_FRAMEBUFFER, source ? source->handle() : defaultFboId);
+ extensions.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target ? target->handle() : defaultFboId);
+
+ const bool supportsMRT = extensions.hasOpenGLFeature(QOpenGLFunctions::MultipleRenderTargets);
+ if (supportsMRT) {
+ extensions.glReadBuffer(GL_COLOR_ATTACHMENT0 + readColorAttachmentIndex);
+ if (target) {
+ GLenum drawBuf = GL_COLOR_ATTACHMENT0 + drawColorAttachmentIndex;
+ extensions.glDrawBuffers(1, &drawBuf);
+ }
+ }
+
+ extensions.glBlitFramebuffer(sx0, sy0, sx1, sy1,
+ tx0, ty0, tx1, ty1,
+ buffers, filter);
+
+ if (supportsMRT)
+ extensions.glReadBuffer(GL_COLOR_ATTACHMENT0);
+
+ switch (restorePolicy) {
+ case RestoreFrameBufferBinding:
+ ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, prevFbo); // sets both READ and DRAW
+ break;
+
+ case RestoreFramebufferBindingToDefault:
+ ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); // sets both READ and DRAW
+ break;
+
+ case DontRestoreFramebufferBinding:
+ break;
+ }
+}
+
+/*!
+ \overload
+
+ Convenience overload to blit between two framebuffer objects and
+ to restore the previous framebuffer binding. Equivalent to calling
+ blitFramebuffer(target, targetRect, source, sourceRect, buffers, filter,
+ readColorAttachmentIndex, drawColorAttachmentIndex,
+ RestoreFrameBufferBinding).
+*/
+void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target, const QRect &targetRect,
+ QOpenGLFramebufferObject *source, const QRect &sourceRect,
+ GLbitfield buffers,
+ GLenum filter,
+ int readColorAttachmentIndex,
+ int drawColorAttachmentIndex)
+{
+ blitFramebuffer(target, targetRect, source, sourceRect,
+ buffers, filter,
+ readColorAttachmentIndex,
+ drawColorAttachmentIndex,
+ RestoreFrameBufferBinding);
+}
+
+QT_END_NAMESPACE
diff --git a/src/opengl/qopenglframebufferobject.h b/src/opengl/qopenglframebufferobject.h
new file mode 100644
index 0000000000..da3347eaec
--- /dev/null
+++ b/src/opengl/qopenglframebufferobject.h
@@ -0,0 +1,196 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QOPENGLFRAMEBUFFEROBJECT_H
+#define QOPENGLFRAMEBUFFEROBJECT_H
+
+#include <QtOpenGL/qtopenglglobal.h>
+
+#include <QtGui/qopengl.h>
+#include <QtGui/qpaintdevice.h>
+
+#include <QtCore/qscopedpointer.h>
+
+#if defined(Q_CLANG_QDOC)
+#undef GLuint
+typedef unsigned int GLuint;
+#undef GLenum
+typedef unsigned int GLenum;
+#undef GL_TEXTURE_2D
+#define GL_TEXTURE_2D 0x0DE1
+#undef GLbitfield
+typedef unsigned int GLbitfield;
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QOpenGLFramebufferObjectPrivate;
+class QOpenGLFramebufferObjectFormat;
+
+class Q_OPENGL_EXPORT QOpenGLFramebufferObject
+{
+ Q_DECLARE_PRIVATE(QOpenGLFramebufferObject)
+public:
+ enum Attachment {
+ NoAttachment,
+ CombinedDepthStencil,
+ Depth
+ };
+
+ explicit QOpenGLFramebufferObject(const QSize &size, GLenum target = GL_TEXTURE_2D);
+ QOpenGLFramebufferObject(int width, int height, GLenum target = GL_TEXTURE_2D);
+
+ QOpenGLFramebufferObject(const QSize &size, Attachment attachment,
+ GLenum target = GL_TEXTURE_2D, GLenum internalFormat = 0);
+ QOpenGLFramebufferObject(int width, int height, Attachment attachment,
+ GLenum target = GL_TEXTURE_2D, GLenum internalFormat = 0);
+
+ QOpenGLFramebufferObject(const QSize &size, const QOpenGLFramebufferObjectFormat &format);
+ QOpenGLFramebufferObject(int width, int height, const QOpenGLFramebufferObjectFormat &format);
+
+ virtual ~QOpenGLFramebufferObject();
+
+ void addColorAttachment(const QSize &size, GLenum internalFormat = 0);
+ void addColorAttachment(int width, int height, GLenum internalFormat = 0);
+
+ QOpenGLFramebufferObjectFormat format() const;
+
+ bool isValid() const;
+ bool isBound() const;
+ bool bind();
+ bool release();
+
+ int width() const { return size().width(); }
+ int height() const { return size().height(); }
+
+ GLuint texture() const;
+ QVector<GLuint> textures() const;
+
+ GLuint takeTexture();
+ GLuint takeTexture(int colorAttachmentIndex);
+
+ QSize size() const;
+ QVector<QSize> sizes() const;
+
+ QImage toImage() const;
+ QImage toImage(bool flipped) const;
+ QImage toImage(bool flipped, int colorAttachmentIndex) const;
+
+ Attachment attachment() const;
+ void setAttachment(Attachment attachment);
+
+ GLuint handle() const;
+
+ static bool bindDefault();
+
+ static bool hasOpenGLFramebufferObjects();
+
+ static bool hasOpenGLFramebufferBlit();
+
+ enum FramebufferRestorePolicy {
+ DontRestoreFramebufferBinding,
+ RestoreFramebufferBindingToDefault,
+ RestoreFrameBufferBinding
+ };
+
+ static void blitFramebuffer(QOpenGLFramebufferObject *target, const QRect &targetRect,
+ QOpenGLFramebufferObject *source, const QRect &sourceRect,
+ GLbitfield buffers,
+ GLenum filter,
+ int readColorAttachmentIndex,
+ int drawColorAttachmentIndex,
+ FramebufferRestorePolicy restorePolicy);
+ static void blitFramebuffer(QOpenGLFramebufferObject *target, const QRect &targetRect,
+ QOpenGLFramebufferObject *source, const QRect &sourceRect,
+ GLbitfield buffers,
+ GLenum filter,
+ int readColorAttachmentIndex,
+ int drawColorAttachmentIndex);
+ static void blitFramebuffer(QOpenGLFramebufferObject *target, const QRect &targetRect,
+ QOpenGLFramebufferObject *source, const QRect &sourceRect,
+ GLbitfield buffers = GL_COLOR_BUFFER_BIT,
+ GLenum filter = GL_NEAREST);
+ static void blitFramebuffer(QOpenGLFramebufferObject *target,
+ QOpenGLFramebufferObject *source,
+ GLbitfield buffers = GL_COLOR_BUFFER_BIT,
+ GLenum filter = GL_NEAREST);
+
+private:
+ Q_DISABLE_COPY(QOpenGLFramebufferObject)
+ QScopedPointer<QOpenGLFramebufferObjectPrivate> d_ptr;
+ friend class QOpenGLPaintDevice;
+ friend class QOpenGLFBOGLPaintDevice;
+};
+
+class QOpenGLFramebufferObjectFormatPrivate;
+class Q_OPENGL_EXPORT QOpenGLFramebufferObjectFormat
+{
+public:
+ QOpenGLFramebufferObjectFormat();
+ QOpenGLFramebufferObjectFormat(const QOpenGLFramebufferObjectFormat &other);
+ QOpenGLFramebufferObjectFormat &operator=(const QOpenGLFramebufferObjectFormat &other);
+ ~QOpenGLFramebufferObjectFormat();
+
+ void setSamples(int samples);
+ int samples() const;
+
+ void setMipmap(bool enabled);
+ bool mipmap() const;
+
+ void setAttachment(QOpenGLFramebufferObject::Attachment attachment);
+ QOpenGLFramebufferObject::Attachment attachment() const;
+
+ void setTextureTarget(GLenum target);
+ GLenum textureTarget() const;
+
+ void setInternalTextureFormat(GLenum internalTextureFormat);
+ GLenum internalTextureFormat() const;
+
+ bool operator==(const QOpenGLFramebufferObjectFormat& other) const;
+ bool operator!=(const QOpenGLFramebufferObjectFormat& other) const;
+
+private:
+ QOpenGLFramebufferObjectFormatPrivate *d;
+
+ void detach();
+};
+
+QT_END_NAMESPACE
+
+#endif // QOPENGLFRAMEBUFFEROBJECT_H
diff --git a/src/opengl/qopenglframebufferobject_p.h b/src/opengl/qopenglframebufferobject_p.h
new file mode 100644
index 0000000000..df87734a58
--- /dev/null
+++ b/src/opengl/qopenglframebufferobject_p.h
@@ -0,0 +1,153 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtOpenGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QOPENGLFRAMEBUFFEROBJECT_P_H
+#define QOPENGLFRAMEBUFFEROBJECT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qopenglframebufferobject.h>
+#include <private/qopenglcontext_p.h>
+#include <private/qopenglextensions_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QOpenGLFramebufferObjectFormatPrivate
+{
+public:
+ QOpenGLFramebufferObjectFormatPrivate()
+ : ref(1),
+ samples(0),
+ attachment(QOpenGLFramebufferObject::NoAttachment),
+ target(GL_TEXTURE_2D),
+ mipmap(false)
+ {
+#ifndef QT_OPENGL_ES_2
+ // There is nothing that says QOpenGLFramebufferObjectFormat needs a current
+ // context, so we need a fallback just to be safe, even though in pratice there
+ // will usually be a context current.
+ QOpenGLContext *ctx = QOpenGLContext::currentContext();
+ const bool isES = ctx ? ctx->isOpenGLES() : QOpenGLContext::openGLModuleType() != QOpenGLContext::LibGL;
+ internal_format = isES ? GL_RGBA : GL_RGBA8;
+#else
+ internal_format = GL_RGBA;
+#endif
+ }
+ QOpenGLFramebufferObjectFormatPrivate
+ (const QOpenGLFramebufferObjectFormatPrivate *other)
+ : ref(1),
+ samples(other->samples),
+ attachment(other->attachment),
+ target(other->target),
+ internal_format(other->internal_format),
+ mipmap(other->mipmap)
+ {
+ }
+ bool equals(const QOpenGLFramebufferObjectFormatPrivate *other)
+ {
+ return samples == other->samples &&
+ attachment == other->attachment &&
+ target == other->target &&
+ internal_format == other->internal_format &&
+ mipmap == other->mipmap;
+ }
+
+ QAtomicInt ref;
+ int samples;
+ QOpenGLFramebufferObject::Attachment attachment;
+ GLenum target;
+ GLenum internal_format;
+ uint mipmap : 1;
+};
+
+class QOpenGLFramebufferObjectPrivate
+{
+public:
+ QOpenGLFramebufferObjectPrivate() : fbo_guard(nullptr), depth_buffer_guard(nullptr)
+ , stencil_buffer_guard(nullptr)
+ , valid(false) {}
+ ~QOpenGLFramebufferObjectPrivate() {}
+
+ void init(QOpenGLFramebufferObject *q, const QSize &size,
+ QOpenGLFramebufferObject::Attachment attachment,
+ GLenum texture_target, GLenum internal_format,
+ GLint samples = 0, bool mipmap = false);
+ void initTexture(int idx);
+ void initColorBuffer(int idx, GLint *samples);
+ void initDepthStencilAttachments(QOpenGLContext *ctx, QOpenGLFramebufferObject::Attachment attachment);
+
+ bool checkFramebufferStatus(QOpenGLContext *ctx) const;
+ QOpenGLSharedResourceGuard *fbo_guard;
+ QOpenGLSharedResourceGuard *depth_buffer_guard;
+ QOpenGLSharedResourceGuard *stencil_buffer_guard;
+ GLenum target;
+ QSize dsSize;
+ QOpenGLFramebufferObjectFormat format;
+ int requestedSamples;
+ uint valid : 1;
+ QOpenGLFramebufferObject::Attachment fbo_attachment;
+ QOpenGLExtensions funcs;
+
+ struct ColorAttachment {
+ ColorAttachment() : internalFormat(0), guard(nullptr) { }
+ ColorAttachment(const QSize &size, GLenum internalFormat)
+ : size(size), internalFormat(internalFormat), guard(nullptr) { }
+ QSize size;
+ GLenum internalFormat;
+ QOpenGLSharedResourceGuard *guard;
+ };
+ QVector<ColorAttachment> colorAttachments;
+
+ inline GLuint fbo() const { return fbo_guard ? fbo_guard->id() : 0; }
+};
+
+Q_OPENGL_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
+
+QT_END_NAMESPACE
+
+#endif // QOPENGLFRAMEBUFFEROBJECT_P_H
diff --git a/src/opengl/qopenglwidget.cpp b/src/opengl/qopenglwidget.cpp
index b334ba867b..cf22a5e2df 100644
--- a/src/opengl/qopenglwidget.cpp
+++ b/src/opengl/qopenglwidget.cpp
@@ -39,7 +39,6 @@
#include "qopenglwidget.h"
#include <QtGui/QOpenGLContext>
-#include <QtGui/QOpenGLFramebufferObject>
#include <QtGui/QOffscreenSurface>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QWindow>
@@ -47,12 +46,14 @@
#include <QtGui/QScreen>
#include <QtGui/qpa/qplatformwindow.h>
#include <QtGui/qpa/qplatformintegration.h>
+#include <QtOpenGL/QOpenGLFramebufferObject>
#include <QtOpenGL/QOpenGLPaintDevice>
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/private/qopenglextensions_p.h>
#include <QtGui/private/qfont_p.h>
#include <QtGui/private/qopenglcontext_p.h>
+#include <QtOpenGL/private/qopenglframebufferobject_p.h>
#include <QtOpenGL/private/qopenglpaintdevice_p.h>
#include <QtWidgets/private/qwidget_p.h>
@@ -930,8 +931,6 @@ void QOpenGLWidgetPrivate::invalidateFbo()
}
}
-extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
-
QImage QOpenGLWidgetPrivate::grabFramebuffer()
{
Q_Q(QOpenGLWidget);
diff --git a/src/opengl/qopenglwindow.cpp b/src/opengl/qopenglwindow.cpp
index 5da20dc559..f473d849cf 100644
--- a/src/opengl/qopenglwindow.cpp
+++ b/src/opengl/qopenglwindow.cpp
@@ -38,13 +38,15 @@
****************************************************************************/
#include "qopenglwindow.h"
-#include <QtGui/QOpenGLFramebufferObject>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/private/qpaintdevicewindow_p.h>
#include <QtGui/private/qopenglextensions_p.h>
#include <QtGui/private/qopenglcontext_p.h>
#include <QtGui/QMatrix4x4>
#include <QtGui/QOffscreenSurface>
+
+#include <QtOpenGL/private/qopenglframebufferobject_p.h>
+#include <QtOpenGL/QOpenGLFramebufferObject>
#include <QtOpenGL/QOpenGLTextureBlitter>
#include <QtOpenGL/QOpenGLPaintDevice>
@@ -508,8 +510,6 @@ GLuint QOpenGLWindow::defaultFramebufferObject() const
return 0;
}
-extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
-
/*!
Returns a copy of the framebuffer.