| /**************************************************************************** |
| ** |
| ** 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$ |
| ** |
| ****************************************************************************/ |
| |
| /*! |
| \class QGLPixelBuffer |
| \inmodule QtOpenGL |
| \brief The QGLPixelBuffer class encapsulates an OpenGL pbuffer. |
| \since 4.1 |
| \obsolete |
| |
| \ingroup painting-3D |
| |
| Rendering into a pbuffer is normally done using full hardware |
| acceleration. This can be significantly faster than rendering |
| into a QPixmap. |
| |
| There are three approaches to using this class: |
| |
| \list 1 |
| \li \b{We can draw into the pbuffer and convert it to a QImage |
| using toImage().} This is normally much faster than calling |
| QGLWidget::renderPixmap(). |
| |
| \li \b{We can draw into the pbuffer and copy the contents into |
| an OpenGL texture using updateDynamicTexture().} This allows |
| us to create dynamic textures and works on all systems |
| with pbuffer support. |
| |
| \li \b{On systems that support it, we can bind the pbuffer to |
| an OpenGL texture.} The texture is then updated automatically |
| when the pbuffer contents change, eliminating the need for |
| additional copy operations. This is supported only on Windows |
| and \macos systems that provide the \c render_texture |
| extension. Note that under Windows, a multi-sampled pbuffer |
| can't be used in conjunction with the \c render_texture |
| extension. If a multi-sampled pbuffer is requested under |
| Windows, the \c render_texture extension is turned off for that |
| pbuffer. |
| |
| |
| \endlist |
| |
| \note This class has been deprecated, use QOpenGLFramebufferObject |
| for offscreen rendering. |
| |
| \section1 Threading |
| |
| As of Qt 4.8, it's possible to render into a QGLPixelBuffer using |
| a QPainter in a separate thread. Note that OpenGL 2.0 or OpenGL ES |
| 2.0 is required for this to work. |
| |
| Pbuffers are provided by the OpenGL \c pbuffer extension; call |
| hasOpenGLPbuffer() to find out if the system provides pbuffers. |
| */ |
| |
| #include <private/qopenglextensions_p.h> |
| |
| #include <QtCore/qglobal.h> |
| #include <QtGui/qopenglframebufferobject.h> |
| |
| #include "gl2paintengineex/qpaintengineex_opengl2_p.h" |
| |
| #include <qglframebufferobject.h> |
| #include <qglpixelbuffer.h> |
| #include <private/qglpixelbuffer_p.h> |
| #include <private/qfont_p.h> |
| #include <qimage.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| extern QImage qt_gl_read_frame_buffer(const QSize&, bool, bool); |
| |
| |
| QGLContext* QGLPBufferGLPaintDevice::context() const |
| { |
| return pbuf->d_func()->qctx; |
| } |
| |
| void QGLPBufferGLPaintDevice::beginPaint() |
| { |
| pbuf->makeCurrent(); |
| QGLPaintDevice::beginPaint(); |
| } |
| |
| void QGLPBufferGLPaintDevice::endPaint() |
| { |
| QOpenGLContext::currentContext()->functions()->glFlush(); |
| QGLPaintDevice::endPaint(); |
| } |
| |
| void QGLPBufferGLPaintDevice::setFbo(GLuint fbo) |
| { |
| m_thisFBO = fbo; |
| } |
| |
| void QGLPBufferGLPaintDevice::setPBuffer(QGLPixelBuffer* pb) |
| { |
| pbuf = pb; |
| } |
| |
| void QGLPixelBufferPrivate::common_init(const QSize &size, const QGLFormat &format, QGLWidget *shareWidget) |
| { |
| Q_Q(QGLPixelBuffer); |
| if(init(size, format, shareWidget)) { |
| req_size = size; |
| req_format = format; |
| req_shareWidget = shareWidget; |
| invalid = false; |
| glDevice.setPBuffer(q); |
| } |
| } |
| |
| /*! |
| Constructs an OpenGL pbuffer of the given \a size. If no \a |
| format is specified, the \l{QGLFormat::defaultFormat()}{default |
| format} is used. If the \a shareWidget parameter points to a |
| valid QGLWidget, the pbuffer will share its context with \a |
| shareWidget. |
| |
| If you intend to bind this pbuffer as a dynamic texture, the width |
| and height components of \c size must be powers of two (e.g., 512 |
| x 128). |
| |
| \sa size(), format() |
| */ |
| QGLPixelBuffer::QGLPixelBuffer(const QSize &size, const QGLFormat &format, QGLWidget *shareWidget) |
| : d_ptr(new QGLPixelBufferPrivate(this)) |
| { |
| Q_D(QGLPixelBuffer); |
| d->common_init(size, format, shareWidget); |
| } |
| |
| |
| /*! \overload |
| |
| Constructs an OpenGL pbuffer with the \a width and \a height. If |
| no \a format is specified, the |
| \l{QGLFormat::defaultFormat()}{default format} is used. If the \a |
| shareWidget parameter points to a valid QGLWidget, the pbuffer |
| will share its context with \a shareWidget. |
| |
| If you intend to bind this pbuffer as a dynamic texture, the width |
| and height components of \c size must be powers of two (e.g., 512 |
| x 128). |
| |
| \sa size(), format() |
| */ |
| QGLPixelBuffer::QGLPixelBuffer(int width, int height, const QGLFormat &format, QGLWidget *shareWidget) |
| : d_ptr(new QGLPixelBufferPrivate(this)) |
| { |
| Q_D(QGLPixelBuffer); |
| d->common_init(QSize(width, height), format, shareWidget); |
| } |
| |
| |
| /*! \fn QGLPixelBuffer::~QGLPixelBuffer() |
| |
| Destroys the pbuffer and frees any allocated resources. |
| */ |
| QGLPixelBuffer::~QGLPixelBuffer() |
| { |
| Q_D(QGLPixelBuffer); |
| |
| // defined in qpaintengine_opengl.cpp |
| QGLContext *current = const_cast<QGLContext *>(QGLContext::currentContext()); |
| if (current != d->qctx) |
| makeCurrent(); |
| d->cleanup(); |
| if (current && current != d->qctx) |
| current->makeCurrent(); |
| } |
| |
| /*! \fn bool QGLPixelBuffer::makeCurrent() |
| |
| Makes this pbuffer the current OpenGL rendering context. Returns |
| true on success; otherwise returns \c false. |
| |
| \sa QGLContext::makeCurrent(), doneCurrent() |
| */ |
| |
| bool QGLPixelBuffer::makeCurrent() |
| { |
| Q_D(QGLPixelBuffer); |
| if (d->invalid) |
| return false; |
| d->qctx->makeCurrent(); |
| if (!d->fbo) { |
| QOpenGLFramebufferObjectFormat format; |
| if (d->req_format.stencil()) |
| format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); |
| else if (d->req_format.depth()) |
| format.setAttachment(QOpenGLFramebufferObject::Depth); |
| if (d->req_format.sampleBuffers()) |
| format.setSamples(d->req_format.samples()); |
| d->fbo = new QOpenGLFramebufferObject(d->req_size, format); |
| d->fbo->bind(); |
| d->glDevice.setFbo(d->fbo->handle()); |
| QOpenGLContext::currentContext()->functions()->glViewport(0, 0, d->req_size.width(), d->req_size.height()); |
| } |
| return true; |
| } |
| |
| /*! \fn bool QGLPixelBuffer::doneCurrent() |
| |
| Makes no context the current OpenGL context. Returns \c true on |
| success; otherwise returns \c false. |
| */ |
| |
| bool QGLPixelBuffer::doneCurrent() |
| { |
| Q_D(QGLPixelBuffer); |
| if (d->invalid) |
| return false; |
| d->qctx->doneCurrent(); |
| return true; |
| } |
| |
| /*! |
| Returns the context of this pixelbuffer. |
| */ |
| QGLContext *QGLPixelBuffer::context() const |
| { |
| Q_D(const QGLPixelBuffer); |
| return d->qctx; |
| } |
| |
| /*! |
| \fn GLuint QGLPixelBuffer::generateDynamicTexture() const |
| |
| Generates and binds a 2D GL texture that is the same size as the |
| pbuffer, and returns the texture's ID. This can be used in |
| conjunction with bindToDynamicTexture() and |
| updateDynamicTexture(). |
| |
| \sa size() |
| */ |
| |
| /*! \fn bool QGLPixelBuffer::bindToDynamicTexture(GLuint texture_id) |
| |
| Binds the texture specified by \a texture_id to this pbuffer. |
| Returns \c true on success; otherwise returns \c false. |
| |
| The texture must be of the same size and format as the pbuffer. |
| |
| To unbind the texture, call releaseFromDynamicTexture(). While |
| the texture is bound, it is updated automatically when the |
| pbuffer contents change, eliminating the need for additional copy |
| operations. |
| |
| Example: |
| |
| \snippet code/src_opengl_qglpixelbuffer.cpp 0 |
| |
| \warning This function uses the \c {render_texture} extension, |
| which is currently not supported under X11. An alternative that |
| works on all systems (including X11) is to manually copy the |
| pbuffer contents to a texture using updateDynamicTexture(). |
| |
| \warning For the bindToDynamicTexture() call to succeed on the |
| \macos, the pbuffer needs a shared context, i.e. the |
| QGLPixelBuffer must be created with a share widget. |
| |
| \sa generateDynamicTexture(), releaseFromDynamicTexture() |
| */ |
| |
| /*! \fn void QGLPixelBuffer::releaseFromDynamicTexture() |
| |
| Releases the pbuffer from any previously bound texture. |
| |
| \sa bindToDynamicTexture() |
| */ |
| |
| /*! \fn bool QGLPixelBuffer::hasOpenGLPbuffers() |
| |
| Returns \c true if the OpenGL \c pbuffer extension is present on |
| this system; otherwise returns \c false. |
| */ |
| |
| /*! |
| Copies the pbuffer contents into the texture specified with \a |
| texture_id. |
| |
| The texture must be of the same size and format as the pbuffer. |
| |
| Example: |
| |
| \snippet code/src_opengl_qglpixelbuffer.cpp 1 |
| |
| An alternative on Windows and \macos systems that support the |
| \c render_texture extension is to use bindToDynamicTexture() to |
| get dynamic updates of the texture. |
| |
| \sa generateDynamicTexture(), bindToDynamicTexture() |
| */ |
| void QGLPixelBuffer::updateDynamicTexture(GLuint texture_id) const |
| { |
| Q_D(const QGLPixelBuffer); |
| if (d->invalid || !d->fbo) |
| return; |
| |
| const QGLContext *ctx = QGLContext::currentContext(); |
| if (!ctx) |
| return; |
| |
| #undef glBindFramebuffer |
| |
| #ifndef GL_READ_FRAMEBUFFER |
| #define GL_READ_FRAMEBUFFER 0x8CA8 |
| #endif |
| |
| #ifndef GL_DRAW_FRAMEBUFFER |
| #define GL_DRAW_FRAMEBUFFER 0x8CA9 |
| #endif |
| |
| QOpenGLExtensions extensions(ctx->contextHandle()); |
| |
| ctx->d_ptr->refreshCurrentFbo(); |
| |
| if (d->blit_fbo) { |
| QOpenGLFramebufferObject::blitFramebuffer(d->blit_fbo, d->fbo); |
| extensions.glBindFramebuffer(GL_READ_FRAMEBUFFER, d->blit_fbo->handle()); |
| } |
| |
| extensions.glBindTexture(GL_TEXTURE_2D, texture_id); |
| #ifndef QT_OPENGL_ES |
| GLenum format = ctx->contextHandle()->isOpenGLES() ? GL_RGBA : GL_RGBA8; |
| extensions.glCopyTexImage2D(GL_TEXTURE_2D, 0, format, 0, 0, d->req_size.width(), d->req_size.height(), 0); |
| #else |
| extensions.glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, d->req_size.width(), d->req_size.height(), 0); |
| #endif |
| |
| if (d->blit_fbo) |
| extensions.glBindFramebuffer(GL_READ_FRAMEBUFFER, ctx->d_func()->current_fbo); |
| } |
| |
| /*! |
| Returns the size of the pbuffer. |
| */ |
| QSize QGLPixelBuffer::size() const |
| { |
| Q_D(const QGLPixelBuffer); |
| return d->req_size; |
| } |
| |
| /*! |
| Returns the contents of the pbuffer as a QImage. |
| */ |
| QImage QGLPixelBuffer::toImage() const |
| { |
| Q_D(const QGLPixelBuffer); |
| if (d->invalid) |
| return QImage(); |
| |
| const_cast<QGLPixelBuffer *>(this)->makeCurrent(); |
| if (d->fbo) |
| d->fbo->bind(); |
| return qt_gl_read_frame_buffer(d->req_size, d->format.alpha(), true); |
| } |
| |
| /*! |
| Returns the native pbuffer handle. |
| */ |
| Qt::HANDLE QGLPixelBuffer::handle() const |
| { |
| Q_D(const QGLPixelBuffer); |
| if (d->invalid) |
| return 0; |
| return (Qt::HANDLE) d->pbuf; |
| } |
| |
| /*! |
| Returns \c true if this pbuffer is valid; otherwise returns \c false. |
| */ |
| bool QGLPixelBuffer::isValid() const |
| { |
| Q_D(const QGLPixelBuffer); |
| return !d->invalid; |
| } |
| |
| Q_GLOBAL_STATIC(QGLEngineThreadStorage<QGL2PaintEngineEx>, qt_buffer_2_engine) |
| |
| /*! \reimp */ |
| QPaintEngine *QGLPixelBuffer::paintEngine() const |
| { |
| return qt_buffer_2_engine()->engine(); |
| } |
| |
| /*! \reimp */ |
| int QGLPixelBuffer::metric(PaintDeviceMetric metric) const |
| { |
| Q_D(const QGLPixelBuffer); |
| |
| float dpmx = qt_defaultDpiX()*100./2.54; |
| float dpmy = qt_defaultDpiY()*100./2.54; |
| int w = d->req_size.width(); |
| int h = d->req_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); |
| |
| case QPaintDevice::PdmDevicePixelRatio: |
| return 1; |
| |
| case QPaintDevice::PdmDevicePixelRatioScaled: |
| return QPaintDevice::devicePixelRatioFScale(); |
| |
| default: |
| qWarning("QGLPixelBuffer::metric(), Unhandled metric type: %d\n", metric); |
| break; |
| } |
| return 0; |
| } |
| |
| /*! |
| Generates and binds a 2D GL texture to the current context, based |
| on \a image. The generated texture id is returned and can be used |
| in later glBindTexture() calls. |
| |
| The \a target parameter specifies the texture target. |
| |
| Equivalent to calling QGLContext::bindTexture(). |
| |
| \sa deleteTexture() |
| */ |
| GLuint QGLPixelBuffer::bindTexture(const QImage &image, GLenum target) |
| { |
| Q_D(QGLPixelBuffer); |
| #ifndef QT_OPENGL_ES |
| GLenum format = QOpenGLContext::currentContext()->isOpenGLES() ? GL_RGBA : GL_RGBA8; |
| return d->qctx->bindTexture(image, target, GLint(format)); |
| #else |
| return d->qctx->bindTexture(image, target, GL_RGBA); |
| #endif |
| } |
| |
| /*! \overload |
| |
| Generates and binds a 2D GL texture based on \a pixmap. |
| |
| Equivalent to calling QGLContext::bindTexture(). |
| |
| \sa deleteTexture() |
| */ |
| GLuint QGLPixelBuffer::bindTexture(const QPixmap &pixmap, GLenum target) |
| { |
| Q_D(QGLPixelBuffer); |
| #ifndef QT_OPENGL_ES |
| GLenum format = QOpenGLContext::currentContext()->isOpenGLES() ? GL_RGBA : GL_RGBA8; |
| return d->qctx->bindTexture(pixmap, target, GLint(format)); |
| #else |
| return d->qctx->bindTexture(pixmap, target, GL_RGBA); |
| #endif |
| } |
| |
| /*! \overload |
| |
| Reads the DirectDrawSurface (DDS) compressed file \a fileName and |
| generates a 2D GL texture from it. |
| |
| Equivalent to calling QGLContext::bindTexture(). |
| |
| \sa deleteTexture() |
| */ |
| GLuint QGLPixelBuffer::bindTexture(const QString &fileName) |
| { |
| Q_D(QGLPixelBuffer); |
| return d->qctx->bindTexture(fileName); |
| } |
| |
| /*! |
| Removes the texture identified by \a texture_id from the texture cache. |
| |
| Equivalent to calling QGLContext::deleteTexture(). |
| */ |
| void QGLPixelBuffer::deleteTexture(GLuint texture_id) |
| { |
| Q_D(QGLPixelBuffer); |
| d->qctx->deleteTexture(texture_id); |
| } |
| |
| /*! |
| \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. |
| |
| Equivalent to the corresponding QGLContext::drawTexture(). |
| */ |
| void QGLPixelBuffer::drawTexture(const QRectF &target, GLuint textureId, GLenum textureTarget) |
| { |
| Q_D(QGLPixelBuffer); |
| d->qctx->drawTexture(target, textureId, textureTarget); |
| } |
| |
| /*! |
| \since 4.4 |
| |
| Draws the given texture, \a textureId, at the given \a point in OpenGL model |
| space. The textureTarget parameter should be a 2D texture target. |
| |
| Equivalent to the corresponding QGLContext::drawTexture(). |
| */ |
| void QGLPixelBuffer::drawTexture(const QPointF &point, GLuint textureId, GLenum textureTarget) |
| { |
| Q_D(QGLPixelBuffer); |
| d->qctx->drawTexture(point, textureId, textureTarget); |
| } |
| |
| /*! |
| Returns the format of the pbuffer. The format may be different |
| from the one that was requested. |
| */ |
| QGLFormat QGLPixelBuffer::format() const |
| { |
| Q_D(const QGLPixelBuffer); |
| return d->format; |
| } |
| |
| /*! \fn int QGLPixelBuffer::devType() const |
| \internal |
| */ |
| |
| bool QGLPixelBufferPrivate::init(const QSize &, const QGLFormat &f, QGLWidget *shareWidget) |
| { |
| widget = new QGLWidget(f, 0, shareWidget); |
| widget->resize(1, 1); |
| qctx = const_cast<QGLContext *>(widget->context()); |
| return widget->isValid(); |
| } |
| |
| bool QGLPixelBufferPrivate::cleanup() |
| { |
| delete fbo; |
| fbo = 0; |
| delete blit_fbo; |
| blit_fbo = 0; |
| delete widget; |
| widget = 0; |
| return true; |
| } |
| |
| bool QGLPixelBuffer::bindToDynamicTexture(GLuint texture_id) |
| { |
| Q_UNUSED(texture_id); |
| return false; |
| } |
| |
| void QGLPixelBuffer::releaseFromDynamicTexture() |
| { |
| } |
| |
| GLuint QGLPixelBuffer::generateDynamicTexture() const |
| { |
| Q_D(const QGLPixelBuffer); |
| if (!d->fbo) |
| return 0; |
| |
| if (d->fbo->format().samples() > 0 |
| && QOpenGLExtensions(QOpenGLContext::currentContext()) |
| .hasOpenGLExtension(QOpenGLExtensions::FramebufferBlit)) |
| { |
| if (!d->blit_fbo) |
| const_cast<QOpenGLFramebufferObject *&>(d->blit_fbo) = new QOpenGLFramebufferObject(d->req_size); |
| } else { |
| return d->fbo->texture(); |
| } |
| |
| GLuint texture; |
| QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); |
| |
| funcs->glGenTextures(1, &texture); |
| funcs->glBindTexture(GL_TEXTURE_2D, texture); |
| |
| funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| |
| funcs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, d->req_size.width(), d->req_size.height(), 0, |
| GL_RGBA, GL_UNSIGNED_BYTE, 0); |
| |
| return texture; |
| } |
| |
| bool QGLPixelBuffer::hasOpenGLPbuffers() |
| { |
| return QGLFramebufferObject::hasOpenGLFramebufferObjects(); |
| } |
| |
| QT_END_NAMESPACE |