src/opengl/qglframebufferobject.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
--- /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 <qdebug.h>
+#include <private/qgl_p.h>
+#if !defined(QT_OPENGL_ES_1) && !defined(QT_OPENGL_ES_1_CL)
+#include <private/qpaintengineex_opengl2_p.h>
+#endif
+
+#ifndef QT_OPENGL_ES_2
+#include <private/qpaintengine_opengl_p.h>
+#endif
+
+#include <qglframebufferobject.h>
+#include <qlibrary.h>
+#include <qimage.h>
+
+#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<QGLContext *>(fbo->d_ptr->fbo_guard.context());
+    QGLContext *currentContext = const_cast<QGLContext *>(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*>(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 *>(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<QGLFramebufferObject *>(this), rect);
+
+        return temp.toImage();
+    }
+
+    bool wasBound = isBound();
+    if (!wasBound)
+        const_cast<QGLFramebufferObject *>(this)->bind();
+    QImage image = qt_gl_read_framebuffer(d->size, format().internalTextureFormat() != GL_RGB, true);
+    if (!wasBound)
+        const_cast<QGLFramebufferObject *>(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 *>(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 *>(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 *>(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 *>(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