From f2bbc9c69f5265986dd5574a790e189a7a126c61 Mon Sep 17 00:00:00 2001 From: Johan Klokkhammer Helsing Date: Fri, 17 Jan 2020 15:51:29 +0100 Subject: Move QOpenGLFrameBufferObject from QtGui to QtOpenGL Task-number: QTBUG-74409 Change-Id: I817ea6f052fc61a6465d443450c8017ac5d0c0e9 Reviewed-by: Laszlo Agocs --- src/opengl/CMakeLists.txt | 1 + src/opengl/opengl.pro | 3 + src/opengl/qopenglframebufferobject.cpp | 1857 +++++++++++++++++++++++++++++++ src/opengl/qopenglframebufferobject.h | 196 ++++ src/opengl/qopenglframebufferobject_p.h | 153 +++ src/opengl/qopenglwidget.cpp | 5 +- src/opengl/qopenglwindow.cpp | 6 +- 7 files changed, 2215 insertions(+), 6 deletions(-) create mode 100644 src/opengl/qopenglframebufferobject.cpp create mode 100644 src/opengl/qopenglframebufferobject.h create mode 100644 src/opengl/qopenglframebufferobject_p.h (limited to 'src/opengl') 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 +#include +#include +#include +#include + +#include +#include +#include + +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 QOpenGLFramebufferObject::textures() const +{ + Q_D(const QOpenGLFramebufferObject); + QVector 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 QOpenGLFramebufferObject::sizes() const +{ + Q_D(const QOpenGLFramebufferObject); + QVector 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(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(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(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::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 + +#include +#include + +#include + +#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 textures() const; + + GLuint takeTexture(); + GLuint takeTexture(int colorAttachmentIndex); + + QSize size() const; + QVector 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 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 +#include +#include + +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 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 -#include #include #include #include @@ -47,12 +46,14 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -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 #include #include #include #include #include #include + +#include +#include #include #include @@ -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. -- cgit v1.2.3