diff -r 000000000000 -r 1918ee327afb src/opengl/qglframebufferobject.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/opengl/qglframebufferobject.cpp Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,1298 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtOpenGL module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglframebufferobject.h" +#include "qglframebufferobject_p.h" + +#include +#include +#if !defined(QT_OPENGL_ES_1) && !defined(QT_OPENGL_ES_1_CL) +#include +#endif + +#ifndef QT_OPENGL_ES_2 +#include +#endif + +#include +#include +#include + +#ifdef QT_OPENGL_ES_1_CL +#include "qgl_cl_p.h" +#endif + +QT_BEGIN_NAMESPACE + +extern QImage qt_gl_read_framebuffer(const QSize&, bool, bool); + +#define QGL_FUNC_CONTEXT const QGLContext *ctx = d_ptr->fbo_guard.context(); +#define QGL_FUNCP_CONTEXT const QGLContext *ctx = fbo_guard.context(); + +#ifndef QT_NO_DEBUG +#define QT_RESET_GLERROR() \ +{ \ + while (glGetError() != GL_NO_ERROR) {} \ +} +#define QT_CHECK_GLERROR() \ +{ \ + GLenum err = glGetError(); \ + if (err != GL_NO_ERROR) { \ + qDebug("[%s line %d] GL Error: %d", \ + __FILE__, __LINE__, (int)err); \ + } \ +} +#else +#define QT_RESET_GLERROR() {} +#define QT_CHECK_GLERROR() {} +#endif + +/*! + \class QGLFramebufferObjectFormat + \brief The QGLFramebufferObjectFormat class specifies the format of an OpenGL + framebuffer object. + + \since 4.6 + + \ingroup painting-3D + + A framebuffer object has several characteristics: + \list + \i \link setSamples() Number of samples per pixels.\endlink + \i \link setAttachment() Depth and/or stencil attachments.\endlink + \i \link setTextureTarget() Texture target.\endlink + \i \link setInternalTextureFormat() Internal texture format.\endlink + \endlist + + Note that the desired attachments or number of samples per pixels might not + be supported by the hardware driver. Call QGLFramebufferObject::format() + after creating a QGLFramebufferObject to find the exact format that was + used to create the frame buffer object. + + \sa QGLFramebufferObject +*/ + +/*! + \internal +*/ +void QGLFramebufferObjectFormat::detach() +{ + if (d->ref != 1) { + QGLFramebufferObjectFormatPrivate *newd + = new QGLFramebufferObjectFormatPrivate(d); + if (!d->ref.deref()) + delete d; + d = newd; + } +} + +/*! + Creates a QGLFramebufferObjectFormat object for specifying + the format of an OpenGL framebuffer object. + + By default the format specifies a non-multisample framebuffer object with no + 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(), target(), internalTextureFormat() +*/ + +QGLFramebufferObjectFormat::QGLFramebufferObjectFormat() +{ + d = new QGLFramebufferObjectFormatPrivate; +} + +/*! + Constructs a copy of \a other. +*/ + +QGLFramebufferObjectFormat::QGLFramebufferObjectFormat(const QGLFramebufferObjectFormat &other) +{ + d = other.d; + d->ref.ref(); +} + +/*! + Assigns \a other to this object. +*/ + +QGLFramebufferObjectFormat &QGLFramebufferObjectFormat::operator=(const QGLFramebufferObjectFormat &other) +{ + if (d != other.d) { + other.d->ref.ref(); + if (!d->ref.deref()) + delete d; + d = other.d; + } + return *this; +} + +/*! + Destroys the QGLFramebufferObjectFormat. +*/ +QGLFramebufferObjectFormat::~QGLFramebufferObjectFormat() +{ + 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 can not 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 QGLFramebufferObjectFormat::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 QGLFramebufferObjectFormat::samples() const +{ + return d->samples; +} + +/*! + Sets the attachment configuration of a framebuffer object to \a attachment. + + \sa attachment() +*/ +void QGLFramebufferObjectFormat::setAttachment(QGLFramebufferObject::Attachment attachment) +{ + detach(); + d->attachment = attachment; +} + +/*! + Returns the configuration of the depth and stencil buffers attached to + a framebuffer object. The default is QGLFramebufferObject::NoAttachment. + + \sa setAttachment() +*/ +QGLFramebufferObject::Attachment QGLFramebufferObjectFormat::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 QGLFramebufferObjectFormat::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 QGLFramebufferObjectFormat::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 QGLFramebufferObjectFormat::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 QGLFramebufferObjectFormat::internalTextureFormat() const +{ + return d->internal_format; +} + +#ifdef Q_MAC_COMPAT_GL_FUNCTIONS +/*! \internal */ +void QGLFramebufferObjectFormat::setTextureTarget(QMacCompatGLenum target) +{ + detach(); + d->target = target; +} + +/*! \internal */ +void QGLFramebufferObjectFormat::setInternalTextureFormat(QMacCompatGLenum internalTextureFormat) +{ + detach(); + d->internal_format = internalTextureFormat; +} +#endif + +/*! + Returns true if all the options of this framebuffer object format + are the same as \a other; otherwise returns false. +*/ +bool QGLFramebufferObjectFormat::operator==(const QGLFramebufferObjectFormat& other) const +{ + if (d == other.d) + return true; + else + return d->equals(other.d); +} + +/*! + Returns false if all the options of this framebuffer object format + are the same as \a other; otherwise returns true. +*/ +bool QGLFramebufferObjectFormat::operator!=(const QGLFramebufferObjectFormat& other) const +{ + return !(*this == other); +} + +void QGLFBOGLPaintDevice::setFBO(QGLFramebufferObject* f, + QGLFramebufferObject::Attachment attachment) +{ + fbo = f; + m_thisFBO = fbo->d_func()->fbo(); // This shouldn't be needed + + // The context that the fbo was created in may not have depth + // and stencil buffers, but the fbo itself might. + fboFormat = QGLContext::currentContext()->format(); + if (attachment == QGLFramebufferObject::CombinedDepthStencil) { + fboFormat.setDepth(true); + fboFormat.setStencil(true); + } else if (attachment == QGLFramebufferObject::Depth) { + fboFormat.setDepth(true); + } +} + +QGLContext *QGLFBOGLPaintDevice::context() const +{ + QGLContext *fboContext = const_cast(fbo->d_ptr->fbo_guard.context()); + QGLContext *currentContext = const_cast(QGLContext::currentContext()); + + if (QGLContextPrivate::contextGroup(fboContext) == QGLContextPrivate::contextGroup(currentContext)) + return currentContext; + else + return fboContext; +} + +void QGLFBOGLPaintDevice::ensureActiveTarget() +{ + if (QGLContext::currentContext() != context()) + context()->makeCurrent(); + + QGLContext* ctx = const_cast(QGLContext::currentContext()); + Q_ASSERT(ctx); + const GLuint fboId = fbo->d_func()->fbo(); + if (ctx->d_func()->current_fbo != fboId) { + ctx->d_func()->current_fbo = fboId; + glBindFramebuffer(GL_FRAMEBUFFER_EXT, fboId); + } +} + +void QGLFBOGLPaintDevice::beginPaint() +{ + if (QGLContext::currentContext() != context()) + context()->makeCurrent(); + + // We let QFBO track the previously bound FBO rather than doing it + // ourselves here. This has the advantage that begin/release & bind/end + // work as expected. + wasBound = fbo->isBound(); + if (!wasBound) + fbo->bind(); +} + +void QGLFBOGLPaintDevice::endPaint() +{ + if (!wasBound) + fbo->release(); +} + +bool QGLFramebufferObjectPrivate::checkFramebufferStatus() const +{ + QGL_FUNCP_CONTEXT; + if (!ctx) + return false; // Context no longer exists. + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT); + switch(status) { + case GL_NO_ERROR: + case GL_FRAMEBUFFER_COMPLETE_EXT: + return true; + break; + case GL_FRAMEBUFFER_UNSUPPORTED_EXT: + qDebug("QGLFramebufferObject: Unsupported framebuffer format."); + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: + qDebug("QGLFramebufferObject: Framebuffer incomplete attachment."); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: + qDebug("QGLFramebufferObject: Framebuffer incomplete, missing attachment."); + break; +#ifdef GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT + case GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT: + qDebug("QGLFramebufferObject: Framebuffer incomplete, duplicate attachment."); + break; +#endif + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: + qDebug("QGLFramebufferObject: Framebuffer incomplete, attached images must have same dimensions."); + break; + case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: + qDebug("QGLFramebufferObject: Framebuffer incomplete, attached images must have same format."); + break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: + qDebug("QGLFramebufferObject: Framebuffer incomplete, missing draw buffer."); + break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: + qDebug("QGLFramebufferObject: Framebuffer incomplete, missing read buffer."); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT: + qDebug("QGLFramebufferObject: Framebuffer incomplete, attachments must have same number of samples per pixel."); + break; + default: + qDebug() <<"QGLFramebufferObject: An undefined error has occurred: "<< status; + break; + } + return false; +} + +void QGLFramebufferObjectPrivate::init(QGLFramebufferObject *q, const QSize &sz, + QGLFramebufferObject::Attachment attachment, + GLenum texture_target, GLenum internal_format, GLint samples) +{ + QGLContext *ctx = const_cast(QGLContext::currentContext()); + fbo_guard.setContext(ctx); + + bool ext_detected = (QGLExtensions::glExtensions & QGLExtensions::FramebufferObject); + if (!ext_detected || (ext_detected && !qt_resolve_framebufferobject_extensions(ctx))) + return; + + size = sz; + target = texture_target; + // texture dimensions + + QT_RESET_GLERROR(); // reset error state + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER_EXT, fbo); + fbo_guard.setId(fbo); + + glDevice.setFBO(q, attachment); + + QT_CHECK_GLERROR(); + // init texture + if (samples == 0) { + glGenTextures(1, &texture); + glBindTexture(target, texture); + glTexImage2D(target, 0, internal_format, size.width(), size.height(), 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); +#ifndef QT_OPENGL_ES + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#else + glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#endif + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + target, texture, 0); + + QT_CHECK_GLERROR(); + valid = checkFramebufferStatus(); + glBindTexture(target, 0); + + color_buffer = 0; + } else { + GLint maxSamples; + glGetIntegerv(GL_MAX_SAMPLES_EXT, &maxSamples); + + samples = qBound(1, int(samples), int(maxSamples)); + + glGenRenderbuffers(1, &color_buffer); + glBindRenderbuffer(GL_RENDERBUFFER_EXT, color_buffer); + if (glRenderbufferStorageMultisampleEXT) { + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, samples, + internal_format, size.width(), size.height()); + } else { + samples = 0; + glRenderbufferStorage(GL_RENDERBUFFER_EXT, internal_format, + size.width(), size.height()); + } + + glFramebufferRenderbuffer(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_RENDERBUFFER_EXT, color_buffer); + + QT_CHECK_GLERROR(); + valid = checkFramebufferStatus(); + + if (valid) + glGetRenderbufferParameteriv(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_SAMPLES_EXT, &samples); + } + + if (attachment == QGLFramebufferObject::CombinedDepthStencil + && (QGLExtensions::glExtensions & QGLExtensions::PackedDepthStencil)) { + // depth and stencil buffer needs another extension + glGenRenderbuffers(1, &depth_stencil_buffer); + Q_ASSERT(!glIsRenderbuffer(depth_stencil_buffer)); + glBindRenderbuffer(GL_RENDERBUFFER_EXT, depth_stencil_buffer); + Q_ASSERT(glIsRenderbuffer(depth_stencil_buffer)); + if (samples != 0 && glRenderbufferStorageMultisampleEXT) + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, samples, + GL_DEPTH24_STENCIL8_EXT, size.width(), size.height()); + else + glRenderbufferStorage(GL_RENDERBUFFER_EXT, + GL_DEPTH24_STENCIL8_EXT, size.width(), size.height()); + + GLint i = 0; + glGetRenderbufferParameteriv(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_DEPTH_SIZE_EXT, &i); + glFramebufferRenderbuffer(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, + GL_RENDERBUFFER_EXT, depth_stencil_buffer); + glFramebufferRenderbuffer(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, + GL_RENDERBUFFER_EXT, depth_stencil_buffer); + fbo_attachment = QGLFramebufferObject::CombinedDepthStencil; + + valid = checkFramebufferStatus(); + if (!valid) + glDeleteRenderbuffers(1, &depth_stencil_buffer); + } else if (attachment == QGLFramebufferObject::Depth + || attachment == QGLFramebufferObject::CombinedDepthStencil) + { + glGenRenderbuffers(1, &depth_stencil_buffer); + Q_ASSERT(!glIsRenderbuffer(depth_stencil_buffer)); + glBindRenderbuffer(GL_RENDERBUFFER_EXT, depth_stencil_buffer); + Q_ASSERT(glIsRenderbuffer(depth_stencil_buffer)); + if (samples != 0 && glRenderbufferStorageMultisampleEXT) { +#ifdef QT_OPENGL_ES +#define GL_DEPTH_COMPONENT16 0x81A5 + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, samples, + GL_DEPTH_COMPONENT16, size.width(), size.height()); +#else + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, samples, + GL_DEPTH_COMPONENT, size.width(), size.height()); +#endif + } else { +#ifdef QT_OPENGL_ES +#define GL_DEPTH_COMPONENT16 0x81A5 + glRenderbufferStorage(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT16, size.width(), size.height()); +#else + glRenderbufferStorage(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, size.width(), size.height()); +#endif + } + GLint i = 0; + glGetRenderbufferParameteriv(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_DEPTH_SIZE_EXT, &i); + glFramebufferRenderbuffer(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, + GL_RENDERBUFFER_EXT, depth_stencil_buffer); + fbo_attachment = QGLFramebufferObject::Depth; + valid = checkFramebufferStatus(); + if (!valid) + glDeleteRenderbuffers(1, &depth_stencil_buffer); + } else { + fbo_attachment = QGLFramebufferObject::NoAttachment; + } + + glBindFramebuffer(GL_FRAMEBUFFER_EXT, ctx->d_ptr->current_fbo); + if (!valid) { + if (color_buffer) + glDeleteRenderbuffers(1, &color_buffer); + else + glDeleteTextures(1, &texture); + glDeleteFramebuffers(1, &fbo); + fbo_guard.setId(0); + } + QT_CHECK_GLERROR(); + + format.setTextureTarget(target); + format.setSamples(int(samples)); + format.setAttachment(fbo_attachment); + format.setInternalTextureFormat(internal_format); +} + +/*! + \class QGLFramebufferObject + \brief The QGLFramebufferObject class encapsulates an OpenGL framebuffer object. + \since 4.2 + + \ingroup painting-3D + + The QGLFramebufferObject class encapsulates an OpenGL framebuffer + object, defined by the \c{GL_EXT_framebuffer_object} extension. In + addition it provides a rendering surface that can be painted on + with a QPainter, rendered to using native GL calls, or both. This + surface can be bound and used as a regular texture in your own GL + drawing code. By default, the QGLFramebufferObject class + generates a 2D GL texture (using the \c{GL_TEXTURE_2D} target), + which is used as the internal rendering target. + + \bold{It is important to have a current GL context when creating a + QGLFramebufferObject, otherwise initialization will fail.} + + OpenGL framebuffer objects and pbuffers (see + \l{QGLPixelBuffer}{QGLPixelBuffer}) can both be used to render to + offscreen surfaces, but there are a number of advantages with + using framebuffer objects instead of pbuffers: + + \list 1 + \o A framebuffer object does not require a separate rendering + context, so no context switching will occur when switching + rendering targets. There is an overhead involved in switching + targets, but in general it is cheaper than a context switch to a + pbuffer. + + \o Rendering to dynamic textures (i.e. render-to-texture + functionality) works on all platforms. No need to do explicit copy + calls from a render buffer into a texture, as was necessary on + systems that did not support the \c{render_texture} extension. + + \o It is possible to attach several rendering buffers (or texture + objects) to the same framebuffer object, and render to all of them + without doing a context switch. + + \o The OpenGL framebuffer extension is a pure GL extension with no + system dependant WGL, CGL, or GLX parts. This makes using + framebuffer objects more portable. + \endlist + + When using a QPainter to paint to a QGLFramebufferObject you should take + care that the QGLFramebufferObject is created with the CombinedDepthStencil + attachment for QPainter to be able to render correctly. + Note that you need to create a QGLFramebufferObject 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 QGLFramebufferObject parameter, and set the + QGLFramebufferObject::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. + + 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 QGLContext::blitFramebuffer(). + + \sa {Framebuffer Object Example} +*/ + + +/*! + \enum QGLFramebufferObject::Attachment + \since 4.3 + + 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() +*/ + + +/*! \fn QGLFramebufferObject::QGLFramebufferObject(const QSize &size, GLenum target) + + Constructs an OpenGL framebuffer object and binds a 2D GL 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 GL 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 GL context set when + creating the QGLFramebufferObject, otherwise the initialization + will fail. + + \sa size(), texture(), attachment() +*/ + +QGLFramebufferObject::QGLFramebufferObject(const QSize &size, GLenum target) + : d_ptr(new QGLFramebufferObjectPrivate) +{ + Q_D(QGLFramebufferObject); + d->init(this, size, NoAttachment, target, DEFAULT_FORMAT); +} + +#ifdef Q_MAC_COMPAT_GL_FUNCTIONS +/*! \internal */ +QGLFramebufferObject::QGLFramebufferObject(const QSize &size, QMacCompatGLenum target) + : d_ptr(new QGLFramebufferObjectPrivate) +{ + Q_D(QGLFramebufferObject); + d->init(this, size, NoAttachment, target, DEFAULT_FORMAT); +} +#endif + +/*! \overload + + Constructs an OpenGL framebuffer object and binds a 2D GL texture + to the buffer of the given \a width and \a height. + + \sa size(), texture() +*/ +QGLFramebufferObject::QGLFramebufferObject(int width, int height, GLenum target) + : d_ptr(new QGLFramebufferObjectPrivate) +{ + Q_D(QGLFramebufferObject); + d->init(this, QSize(width, height), NoAttachment, target, DEFAULT_FORMAT); +} + +/*! \overload + + Constructs an OpenGL framebuffer object of the given \a size based on the + supplied \a format. +*/ + +QGLFramebufferObject::QGLFramebufferObject(const QSize &size, const QGLFramebufferObjectFormat &format) + : d_ptr(new QGLFramebufferObjectPrivate) +{ + Q_D(QGLFramebufferObject); + d->init(this, size, format.attachment(), format.textureTarget(), format.internalTextureFormat(), + format.samples()); +} + +/*! \overload + + Constructs an OpenGL framebuffer object of the given \a width and \a height + based on the supplied \a format. +*/ + +QGLFramebufferObject::QGLFramebufferObject(int width, int height, const QGLFramebufferObjectFormat &format) + : d_ptr(new QGLFramebufferObjectPrivate) +{ + Q_D(QGLFramebufferObject); + d->init(this, QSize(width, height), format.attachment(), format.textureTarget(), + format.internalTextureFormat(), format.samples()); +} + +#ifdef Q_MAC_COMPAT_GL_FUNCTIONS +/*! \internal */ +QGLFramebufferObject::QGLFramebufferObject(int width, int height, QMacCompatGLenum target) + : d_ptr(new QGLFramebufferObjectPrivate) +{ + Q_D(QGLFramebufferObject); + d->init(this, QSize(width, height), NoAttachment, target, DEFAULT_FORMAT); +} +#endif + +/*! \overload + + 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 internal_format + 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() +*/ +QGLFramebufferObject::QGLFramebufferObject(int width, int height, Attachment attachment, + GLenum target, GLenum internal_format) + : d_ptr(new QGLFramebufferObjectPrivate) +{ + Q_D(QGLFramebufferObject); + d->init(this, QSize(width, height), attachment, target, internal_format); +} + +#ifdef Q_MAC_COMPAT_GL_FUNCTIONS +/*! \internal */ +QGLFramebufferObject::QGLFramebufferObject(int width, int height, Attachment attachment, + QMacCompatGLenum target, QMacCompatGLenum internal_format) + : d_ptr(new QGLFramebufferObjectPrivate) +{ + Q_D(QGLFramebufferObject); + d->init(this, QSize(width, height), attachment, target, internal_format); +} +#endif + +/*! \overload + + 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 internal_format + 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() +*/ +QGLFramebufferObject::QGLFramebufferObject(const QSize &size, Attachment attachment, + GLenum target, GLenum internal_format) + : d_ptr(new QGLFramebufferObjectPrivate) +{ + Q_D(QGLFramebufferObject); + d->init(this, size, attachment, target, internal_format); +} + +#ifdef Q_MAC_COMPAT_GL_FUNCTIONS +/*! \internal */ +QGLFramebufferObject::QGLFramebufferObject(const QSize &size, Attachment attachment, + QMacCompatGLenum target, QMacCompatGLenum internal_format) + : d_ptr(new QGLFramebufferObjectPrivate) +{ + Q_D(QGLFramebufferObject); + d->init(this, size, attachment, target, internal_format); +} +#endif + +/*! + \fn QGLFramebufferObject::~QGLFramebufferObject() + + Destroys the framebuffer object and frees any allocated resources. +*/ +QGLFramebufferObject::~QGLFramebufferObject() +{ + Q_D(QGLFramebufferObject); + QGL_FUNC_CONTEXT; + + delete d->engine; + + if (isValid() && ctx) { + QGLShareContextScope scope(ctx); + if (d->texture) + glDeleteTextures(1, &d->texture); + if (d->color_buffer) + glDeleteRenderbuffers(1, &d->color_buffer); + if (d->depth_stencil_buffer) + glDeleteRenderbuffers(1, &d->depth_stencil_buffer); + GLuint fbo = d->fbo(); + glDeleteFramebuffers(1, &fbo); + } +} + +/*! + \fn bool QGLFramebufferObject::isValid() const + + Returns 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 QGLContext that + the framebuffer was created within is destroyed and there are + no other shared contexts that can take over ownership of the + framebuffer. +*/ +bool QGLFramebufferObject::isValid() const +{ + Q_D(const QGLFramebufferObject); + return d->valid && d->fbo_guard.context(); +} + +/*! + \fn bool QGLFramebufferObject::bind() + + Switches rendering from the default, windowing system provided + framebuffer to this framebuffer object. + Returns true upon success, false otherwise. + + Since 4.6: if another QGLFramebufferObject instance was already bound + to the current context, then its handle() will be remembered and + automatically restored when release() is called. This allows multiple + framebuffer rendering targets to be stacked up. It is important that + release() is called on the stacked framebuffer objects in the reverse + order of the calls to bind(). + + \sa release() +*/ +bool QGLFramebufferObject::bind() +{ + if (!isValid()) + return false; + Q_D(QGLFramebufferObject); + QGL_FUNC_CONTEXT; + if (!ctx) + return false; // Context no longer exists. + glBindFramebuffer(GL_FRAMEBUFFER_EXT, d->fbo()); + d->valid = d->checkFramebufferStatus(); + const QGLContext *context = QGLContext::currentContext(); + if (d->valid && context) { + Q_ASSERT(QGLContextPrivate::contextGroup(context) == QGLContextPrivate::contextGroup(ctx)); + // Save the previous setting to automatically restore in release(). + if (context->d_ptr->current_fbo != d->fbo()) { + d->previous_fbo = context->d_ptr->current_fbo; + context->d_ptr->current_fbo = d->fbo(); + } + } + return d->valid; +} + +/*! + \fn bool QGLFramebufferObject::release() + + Switches rendering back to the default, windowing system provided + framebuffer. + Returns true upon success, false otherwise. + + Since 4.6: if another QGLFramebufferObject instance was already bound + to the current context when bind() was called, then this function will + automatically re-bind it to the current context. + + \sa bind() +*/ +bool QGLFramebufferObject::release() +{ + if (!isValid()) + return false; + Q_D(QGLFramebufferObject); + QGL_FUNC_CONTEXT; + if (!ctx) + return false; // Context no longer exists. + + const QGLContext *context = QGLContext::currentContext(); + if (context) { + Q_ASSERT(QGLContextPrivate::contextGroup(context) == QGLContextPrivate::contextGroup(ctx)); + // Restore the previous setting for stacked framebuffer objects. + if (d->previous_fbo != context->d_ptr->current_fbo) { + context->d_ptr->current_fbo = d->previous_fbo; + glBindFramebuffer(GL_FRAMEBUFFER_EXT, d->previous_fbo); + } + d->previous_fbo = 0; + } + + return true; +} + +/*! + \fn GLuint QGLFramebufferObject::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 GL code. + + If a multisample framebuffer object is used then the value returned + from this function will be invalid. +*/ +GLuint QGLFramebufferObject::texture() const +{ + Q_D(const QGLFramebufferObject); + return d->texture; +} + +/*! + \fn QSize QGLFramebufferObject::size() const + + Returns the size of the texture attached to this framebuffer + object. +*/ +QSize QGLFramebufferObject::size() const +{ + Q_D(const QGLFramebufferObject); + return d->size; +} + +/*! + Returns the format of this framebuffer object. +*/ +QGLFramebufferObjectFormat QGLFramebufferObject::format() const +{ + Q_D(const QGLFramebufferObject); + return d->format; +} + +/*! + \fn QImage QGLFramebufferObject::toImage() const + + Returns the contents of this framebuffer object as a QImage. +*/ +QImage QGLFramebufferObject::toImage() const +{ + Q_D(const QGLFramebufferObject); + if (!d->valid) + return QImage(); + + // qt_gl_read_framebuffer doesn't work on a multisample FBO + if (format().samples() != 0) { + QGLFramebufferObject temp(size(), QGLFramebufferObjectFormat()); + + QRect rect(QPoint(0, 0), size()); + blitFramebuffer(&temp, rect, const_cast(this), rect); + + return temp.toImage(); + } + + bool wasBound = isBound(); + if (!wasBound) + const_cast(this)->bind(); + QImage image = qt_gl_read_framebuffer(d->size, format().internalTextureFormat() != GL_RGB, true); + if (!wasBound) + const_cast(this)->release(); + + return image; +} + +#if !defined(QT_OPENGL_ES_1) && !defined(QT_OPENGL_ES_1_CL) +Q_GLOBAL_STATIC(QGL2PaintEngineEx, qt_buffer_2_engine) +#endif + +#ifndef QT_OPENGL_ES_2 +Q_GLOBAL_STATIC(QOpenGLPaintEngine, qt_buffer_engine) +#endif + +/*! \reimp */ +QPaintEngine *QGLFramebufferObject::paintEngine() const +{ + Q_D(const QGLFramebufferObject); + if (d->engine) + return d->engine; + +#if !defined(QT_OPENGL_ES_1) && !defined(QT_OPENGL_ES_1_CL) +#if !defined (QT_OPENGL_ES_2) + if (qt_gl_preferGL2Engine()) { +#endif + QPaintEngine *engine = qt_buffer_2_engine(); + if (engine->isActive() && engine->paintDevice() != this) { + d->engine = new QGL2PaintEngineEx; + return d->engine; + } + return engine; +#if !defined (QT_OPENGL_ES_2) + } +#endif +#endif + +#if !defined(QT_OPENGL_ES_2) + QPaintEngine *engine = qt_buffer_engine(); + if (engine->isActive() && engine->paintDevice() != this) { + d->engine = new QOpenGLPaintEngine; + return d->engine; + } + return engine; +#endif +} + +/*! + \fn bool QGLFramebufferObject::hasOpenGLFramebufferObjects() + + Returns true if the OpenGL \c{GL_EXT_framebuffer_object} extension + is present on this system; otherwise returns false. +*/ +bool QGLFramebufferObject::hasOpenGLFramebufferObjects() +{ + QGLExtensions::init(); + return (QGLExtensions::glExtensions & QGLExtensions::FramebufferObject); +} + +/*! + \since 4.4 + + Draws the given texture, \a textureId, to the given target rectangle, + \a target, in OpenGL model space. The \a textureTarget should be a 2D + texture target. + + The framebuffer object should be bound when calling this function. + + Equivalent to the corresponding QGLContext::drawTexture(). +*/ +void QGLFramebufferObject::drawTexture(const QRectF &target, GLuint textureId, GLenum textureTarget) +{ + const_cast(QGLContext::currentContext())->drawTexture(target, textureId, textureTarget); +} + +#ifdef Q_MAC_COMPAT_GL_FUNCTIONS +/*! \internal */ +void QGLFramebufferObject::drawTexture(const QRectF &target, QMacCompatGLuint textureId, QMacCompatGLenum textureTarget) +{ + const_cast(QGLContext::currentContext())->drawTexture(target, textureId, textureTarget); +} +#endif + +/*! + \since 4.4 + + Draws the given texture, \a textureId, at the given \a point in OpenGL + model space. The \a textureTarget should be a 2D texture target. + + The framebuffer object should be bound when calling this function. + + Equivalent to the corresponding QGLContext::drawTexture(). +*/ +void QGLFramebufferObject::drawTexture(const QPointF &point, GLuint textureId, GLenum textureTarget) +{ + const_cast(QGLContext::currentContext())->drawTexture(point, textureId, textureTarget); +} + +#ifdef Q_MAC_COMPAT_GL_FUNCTIONS +/*! \internal */ +void QGLFramebufferObject::drawTexture(const QPointF &point, QMacCompatGLuint textureId, QMacCompatGLenum textureTarget) +{ + const_cast(QGLContext::currentContext())->drawTexture(point, textureId, textureTarget); +} +#endif + +extern int qt_defaultDpiX(); +extern int qt_defaultDpiY(); + +/*! \reimp */ +int QGLFramebufferObject::metric(PaintDeviceMetric metric) const +{ + Q_D(const QGLFramebufferObject); + + float dpmx = qt_defaultDpiX()*100./2.54; + float dpmy = qt_defaultDpiY()*100./2.54; + int w = d->size.width(); + int h = d->size.height(); + switch (metric) { + case PdmWidth: + return w; + + case PdmHeight: + return h; + + case PdmWidthMM: + return qRound(w * 1000 / dpmx); + + case PdmHeightMM: + return qRound(h * 1000 / dpmy); + + case PdmNumColors: + return 0; + + case PdmDepth: + return 32;//d->depth; + + case PdmDpiX: + return qRound(dpmx * 0.0254); + + case PdmDpiY: + return qRound(dpmy * 0.0254); + + case PdmPhysicalDpiX: + return qRound(dpmx * 0.0254); + + case PdmPhysicalDpiY: + return qRound(dpmy * 0.0254); + + default: + qWarning("QGLFramebufferObject::metric(), Unhandled metric type: %d.\n", metric); + break; + } + return 0; +} + +/*! + \fn GLuint QGLFramebufferObject::handle() const + + Returns the GL 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 QGLFramebufferObject::handle() const +{ + Q_D(const QGLFramebufferObject); + return d->fbo(); +} + +/*! \fn int QGLFramebufferObject::devType() const + \internal +*/ + + +/*! + Returns the status of the depth and stencil buffers attached to + this framebuffer object. +*/ + +QGLFramebufferObject::Attachment QGLFramebufferObject::attachment() const +{ + Q_D(const QGLFramebufferObject); + if (d->valid) + return d->fbo_attachment; + return NoAttachment; +} + +/*! + \since 4.5 + + Returns true if the framebuffer object is currently bound to a context, + otherwise false is returned. +*/ + +bool QGLFramebufferObject::isBound() const +{ + Q_D(const QGLFramebufferObject); + const QGLContext *current = QGLContext::currentContext(); + return current ? current->d_ptr->current_fbo == d->fbo() : false; +} + +/*! + \fn bool QGLFramebufferObject::hasOpenGLFramebufferBlit() + + \since 4.6 + + Returns true if the OpenGL \c{GL_EXT_framebuffer_blit} extension + is present on this system; otherwise returns false. + + \sa blitFramebuffer() +*/ +bool QGLFramebufferObject::hasOpenGLFramebufferBlit() +{ + QGLExtensions::init(); + return (QGLExtensions::glExtensions & QGLExtensions::FramebufferBlit); +} + +/*! + \since 4.6 + + 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. + + 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 that the scissor test will restrict the blit area if enabled. + + This function will have no effect unless hasOpenGLFramebufferBlit() returns + true. + + \sa hasOpenGLFramebufferBlit() +*/ +void QGLFramebufferObject::blitFramebuffer(QGLFramebufferObject *target, const QRect &targetRect, + QGLFramebufferObject *source, const QRect &sourceRect, + GLbitfield buffers, + GLenum filter) +{ + if (!(QGLExtensions::glExtensions & QGLExtensions::FramebufferBlit)) + return; + + const QGLContext *ctx = QGLContext::currentContext(); + if (!ctx) + return; + + const int height = ctx->device()->height(); + + const int sh = source ? source->height() : height; + const int th = target ? target->height() : height; + + const int sx0 = sourceRect.left(); + const int sx1 = sourceRect.left() + sourceRect.width(); + const int sy0 = sh - (sourceRect.top() + sourceRect.height()); + const int sy1 = sh - sourceRect.top(); + + const int tx0 = targetRect.left(); + const int tx1 = targetRect.left() + targetRect.width(); + const int ty0 = th - (targetRect.top() + targetRect.height()); + const int ty1 = th - targetRect.top(); + + glBindFramebuffer(GL_READ_FRAMEBUFFER_EXT, source ? source->handle() : 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, target ? target->handle() : 0); + + glBlitFramebufferEXT(sx0, sy0, sx1, sy1, + tx0, ty0, tx1, ty1, + buffers, filter); + + glBindFramebuffer(GL_FRAMEBUFFER_EXT, ctx->d_ptr->current_fbo); +} + +QT_END_NAMESPACE