| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtGui 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> |
| |
| #include <qtgui_tracepoints_p.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 QtGui |
| |
| \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 *qfbo, const QSize &size, |
| QOpenGLFramebufferObject::Attachment attachment, |
| GLenum texture_target, GLenum internal_format, |
| GLint samples, bool mipmap) |
| { |
| Q_TRACE_SCOPE(QOpenGLFramebufferObjectPrivate_init, qfbo, size, attachment, texture_target, internal_format, samples, mipmap); |
| Q_UNUSED(qfbo); |
| |
| 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 QtGui |
| |
| \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_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(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 |