diff options
Diffstat (limited to 'src/opengl/qpixmapdata_gl.cpp')
-rw-r--r-- | src/opengl/qpixmapdata_gl.cpp | 510 |
1 files changed, 396 insertions, 114 deletions
diff --git a/src/opengl/qpixmapdata_gl.cpp b/src/opengl/qpixmapdata_gl.cpp index edf7eb8..ae616a8 100644 --- a/src/opengl/qpixmapdata_gl.cpp +++ b/src/opengl/qpixmapdata_gl.cpp @@ -1,7 +1,6 @@ /**************************************************************************** ** ** 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. @@ -21,9 +20,10 @@ ** 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. +** 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. @@ -40,6 +40,7 @@ ****************************************************************************/ #include "qpixmap.h" +#include "qglframebufferobject.h" #include <private/qpaintengine_raster_p.h> @@ -48,117 +49,217 @@ #include <private/qgl_p.h> #include <private/qdrawhelper_p.h> +#include <private/qpaintengineex_opengl2_p.h> + QT_BEGIN_NAMESPACE extern QGLWidget* qt_gl_share_widget(); -class QGLShareContextScope +/*! + \class QGLFramebufferObjectPool + \since 4.6 + + \brief The QGLFramebufferObject class provides a pool of framebuffer + objects for offscreen rendering purposes. + + When requesting an FBO of a given size and format, an FBO of the same + format and a size at least as big as the requested size will be returned. + + \internal +*/ + +static inline int areaDiff(const QSize &size, const QGLFramebufferObject *fbo) { -public: - QGLShareContextScope(const QGLContext *ctx) - : m_oldContext(0) - , m_ctx(const_cast<QGLContext *>(ctx)) - { - const QGLContext *currentContext = QGLContext::currentContext(); - if (currentContext != ctx && !qgl_share_reg()->checkSharing(ctx, currentContext)) { - m_oldContext = const_cast<QGLContext *>(currentContext); - m_ctx->makeCurrent(); + return qAbs(size.width() * size.height() - fbo->width() * fbo->height()); +} + +QGLFramebufferObject *QGLFramebufferObjectPool::acquire(const QSize &requestSize, const QGLFramebufferObjectFormat &requestFormat) +{ + QGLFramebufferObject *chosen = 0; + QGLFramebufferObject *candidate = 0; + for (int i = 0; !chosen && i < m_fbos.size(); ++i) { + QGLFramebufferObject *fbo = m_fbos.at(i); + + if (fbo->format() == requestFormat) { + // choose the fbo with a matching format and the closest size + if (!candidate || areaDiff(requestSize, candidate) > areaDiff(requestSize, fbo)) + candidate = fbo; } - } - operator QGLContext *() - { - return m_ctx; + if (candidate) { + m_fbos.removeOne(candidate); + + const QSize fboSize = candidate->size(); + QSize sz = fboSize; + + if (sz.width() < requestSize.width()) + sz.setWidth(qMax(requestSize.width(), qRound(sz.width() * 1.5))); + if (sz.height() < requestSize.height()) + sz.setHeight(qMax(requestSize.height(), qRound(sz.height() * 1.5))); + + // wasting too much space? + if (sz.width() * sz.height() > requestSize.width() * requestSize.height() * 2.5) + sz = requestSize; + + if (sz != fboSize) { + delete candidate; + candidate = new QGLFramebufferObject(sz, requestFormat); + } + + chosen = candidate; + } } - QGLContext *operator->() - { - return m_ctx; + if (!chosen) { + chosen = new QGLFramebufferObject(requestSize, requestFormat); } - ~QGLShareContextScope() - { - if (m_oldContext) - m_oldContext->makeCurrent(); + if (!chosen->isValid()) { + delete chosen; + chosen = 0; } -private: - QGLContext *m_oldContext; - QGLContext *m_ctx; -}; + return chosen; +} + +void QGLFramebufferObjectPool::release(QGLFramebufferObject *fbo) +{ + m_fbos << fbo; +} + -void qt_gl_convertFromGLImage(QImage *img) +QPaintEngine* QGLPixmapGLPaintDevice::paintEngine() const { - const int w = img->width(); - const int h = img->height(); + return data->paintEngine(); +} - if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { - uint *p = (uint*)img->bits(); - uint *end = p + w*h; +void QGLPixmapGLPaintDevice::beginPaint() +{ + if (!data->isValid()) + return; - while (p < end) { - uint a = *p << 24; - *p = (*p >> 8) | a; - p++; - } + // QGLPaintDevice::beginPaint will store the current binding and replace + // it with m_thisFBO: + m_thisFBO = data->m_renderFbo->handle(); + QGLPaintDevice::beginPaint(); - *img = img->mirrored(); - } else { - // mirror image - uint *data = (uint *)img->bits(); + Q_ASSERT(data->paintEngine()->type() == QPaintEngine::OpenGL2); - const int mid = h/2; + // QPixmap::fill() is deferred until now, where we actually need to do the fill: + if (data->needsFill()) { + const QColor &c = data->fillColor(); + float alpha = c.alphaF(); + glClearColor(c.redF() * alpha, c.greenF() * alpha, c.blueF() * alpha, alpha); + glClear(GL_COLOR_BUFFER_BIT); + } + else if (!data->isUninitialized()) { + // If the pixmap (GL Texture) has valid content (it has been + // uploaded from an image or rendered into before), we need to + // copy it from the texture to the render FBO. - for (int y = 0; y < mid; ++y) { - uint *p = data + y * w; - uint *end = p + w; - uint *q = data + (h - y - 1) * w; + // Pass false to tell bind to _not_ copy the FBO into the texture! + GLuint texId = data->bind(false); - while (p < end) - qSwap(*p++, *q++); - } + QGL2PaintEngineEx* pe = static_cast<QGL2PaintEngineEx*>(data->paintEngine()); + QRect rect(0, 0, data->width(), data->height()); + pe->drawTexture(QRectF(rect), texId, rect.size(), QRectF(rect)); } } +void QGLPixmapGLPaintDevice::endPaint() +{ + if (!data->isValid()) + return; + + data->copyBackFromRenderFbo(false); + + data->m_renderFbo->release(); + qgl_fbo_pool()->release(data->m_renderFbo); + data->m_renderFbo = 0; + + // Base's endPaint will restore the previous FBO binding + QGLPaintDevice::endPaint(); +} + +QGLContext* QGLPixmapGLPaintDevice::context() const +{ + data->ensureCreated(); + return data->m_ctx; +} + +QSize QGLPixmapGLPaintDevice::size() const +{ + return data->size(); +} + +void QGLPixmapGLPaintDevice::setPixmapData(QGLPixmapData* d) +{ + data = d; +} static int qt_gl_pixmap_serial = 0; QGLPixmapData::QGLPixmapData(PixelType type) : QPixmapData(type, OpenGLClass) - , m_width(0) - , m_height(0) - , m_texture(0) + , m_renderFbo(0) + , m_engine(0) + , m_ctx(0) , m_dirty(false) + , m_hasFillColor(false) + , m_hasAlpha(false) { setSerialNumber(++qt_gl_pixmap_serial); + m_glDevice.setPixmapData(this); } QGLPixmapData::~QGLPixmapData() { - if (m_texture && qt_gl_share_widget()) { - QGLShareContextScope ctx(qt_gl_share_widget()->context()); - glDeleteTextures(1, &m_texture); + QGLWidget *shareWidget = qt_gl_share_widget(); + if (!shareWidget) + return; + + delete m_engine; + + if (m_texture.id) { + QGLShareContextScope ctx(shareWidget->context()); + glDeleteTextures(1, &m_texture.id); } } bool QGLPixmapData::isValid() const { - return m_width > 0 && m_height > 0; + return w > 0 && h > 0; } bool QGLPixmapData::isValidContext(const QGLContext *ctx) const { + if (ctx == m_ctx) + return true; + const QGLContext *share_ctx = qt_gl_share_widget()->context(); return ctx == share_ctx || qgl_share_reg()->checkSharing(ctx, share_ctx); } void QGLPixmapData::resize(int width, int height) { - if (width == m_width && height == m_height) + if (width == w && height == h) return; - m_width = width; - m_height = height; + if (width <= 0 || height <= 0) { + width = 0; + height = 0; + } + + w = width; + h = height; + is_null = (w <= 0 || h <= 0); + d = pixelType() == QPixmapData::PixmapType ? 32 : 1; + + if (m_texture.id) { + QGLShareContextScope ctx(qt_gl_share_widget()->context()); + glDeleteTextures(1, &m_texture.id); + m_texture.id = 0; + } m_source = QImage(); m_dirty = isValid(); @@ -173,36 +274,83 @@ void QGLPixmapData::ensureCreated() const m_dirty = false; QGLShareContextScope ctx(qt_gl_share_widget()->context()); + m_ctx = ctx; const GLenum format = qt_gl_preferredTextureFormat(); - const GLenum target = qt_gl_preferredTextureTarget(); - - if (!m_texture) - glGenTextures(1, &m_texture); - - glBindTexture(target, m_texture); + const GLenum target = GL_TEXTURE_2D; + + if (!m_texture.id) { + glGenTextures(1, &m_texture.id); + glBindTexture(target, m_texture.id); + GLenum format = m_hasAlpha ? GL_RGBA : GL_RGB; + glTexImage2D(target, 0, format, w, h, 0, + GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } - if (m_source.isNull()) { - glTexImage2D(target, 0, GL_RGBA, m_width, m_height, 0, format, GL_UNSIGNED_BYTE, 0); - } else { + if (!m_source.isNull()) { const QImage tx = ctx->d_func()->convertToGLFormat(m_source, true, format); - glBindTexture(target, m_texture); - glTexImage2D(target, 0, GL_RGBA, m_width, m_height, 0, format, - GL_UNSIGNED_BYTE, tx.bits()); + glBindTexture(target, m_texture.id); + glTexSubImage2D(target, 0, 0, 0, w, h, format, + GL_UNSIGNED_BYTE, tx.bits()); - m_source = QImage(); + if (useFramebufferObjects()) + m_source = QImage(); } + + m_texture.options &= ~QGLContext::MemoryManagedBindOption; } void QGLPixmapData::fromImage(const QImage &image, Qt::ImageConversionFlags) { - if (image.size() == QSize(m_width, m_height)) + if (image.size() == QSize(w, h)) setSerialNumber(++qt_gl_pixmap_serial); resize(image.width(), image.height()); - m_source = image; + + if (pixelType() == BitmapType) { + m_source = image.convertToFormat(QImage::Format_MonoLSB); + } else { + m_source = image.hasAlphaChannel() + ? image.convertToFormat(QImage::Format_ARGB32_Premultiplied) + : image.convertToFormat(QImage::Format_RGB32); + } + m_dirty = true; + m_hasFillColor = false; + + m_hasAlpha = image.hasAlphaChannel(); + w = image.width(); + h = image.height(); + is_null = (w <= 0 || h <= 0); + d = pixelType() == QPixmapData::PixmapType ? 32 : 1; + + if (m_texture.id) { + QGLShareContextScope ctx(qt_gl_share_widget()->context()); + glDeleteTextures(1, &m_texture.id); + m_texture.id = 0; + } +} + +bool QGLPixmapData::scroll(int dx, int dy, const QRect &rect) +{ + Q_UNUSED(dx); + Q_UNUSED(dy); + Q_UNUSED(rect); + return false; +} + +void QGLPixmapData::copy(const QPixmapData *data, const QRect &rect) +{ + if (data->classId() != QPixmapData::OpenGLClass) { + QPixmapData::copy(data, rect); + return; + } + + // can be optimized to do a framebuffer blit or similar ... + QPixmapData::copy(data, rect); } void QGLPixmapData::fill(const QColor &color) @@ -210,50 +358,131 @@ void QGLPixmapData::fill(const QColor &color) if (!isValid()) return; - if (!m_source.isNull()) { - m_source.fill(PREMUL(color.rgba())); - } else { - // ## TODO: improve performance here - QImage img(m_width, m_height, QImage::Format_ARGB32_Premultiplied); - img.fill(PREMUL(color.rgba())); + bool hasAlpha = color.alpha() != 255; + if (hasAlpha && !m_hasAlpha) { + if (m_texture.id) { + glDeleteTextures(1, &m_texture.id); + m_texture.id = 0; + m_dirty = true; + } + m_hasAlpha = color.alpha() != 255; + } - fromImage(img, 0); + if (useFramebufferObjects()) { + m_source = QImage(); + m_hasFillColor = true; + m_fillColor = color; + } else { + QImage image = fillImage(color); + fromImage(image, 0); } } bool QGLPixmapData::hasAlphaChannel() const { - return true; + return m_hasAlpha; } +QImage QGLPixmapData::fillImage(const QColor &color) const +{ + QImage img; + if (pixelType() == BitmapType) { + img = QImage(w, h, QImage::Format_MonoLSB); + img.setNumColors(2); + img.setColor(0, QColor(Qt::color0).rgba()); + img.setColor(1, QColor(Qt::color1).rgba()); + + int gray = qGray(color.rgba()); + if (qAbs(255 - gray) < gray) + img.fill(0); + else + img.fill(1); + } else { + img = QImage(w, h, + m_hasAlpha + ? QImage::Format_ARGB32_Premultiplied + : QImage::Format_RGB32); + img.fill(PREMUL(color.rgba())); + } + return img; +} + +extern QImage qt_gl_read_texture(const QSize &size, bool alpha_format, bool include_alpha); + QImage QGLPixmapData::toImage() const { if (!isValid()) return QImage(); - if (!m_source.isNull()) + if (m_renderFbo) { + copyBackFromRenderFbo(true); + } else if (!m_source.isNull()) { return m_source; - else if (m_dirty) - return QImage(m_width, m_height, QImage::Format_ARGB32_Premultiplied); + } else if (m_dirty || m_hasFillColor) { + return fillImage(m_fillColor); + } else { + ensureCreated(); + } + + QGLShareContextScope ctx(qt_gl_share_widget()->context()); + glBindTexture(GL_TEXTURE_2D, m_texture.id); + return qt_gl_read_texture(QSize(w, h), true, true); +} + +struct TextureBuffer +{ + QGLFramebufferObject *fbo; + QGL2PaintEngineEx *engine; +}; + +Q_GLOBAL_STATIC(QGLFramebufferObjectPool, _qgl_fbo_pool) +QGLFramebufferObjectPool* qgl_fbo_pool() +{ + return _qgl_fbo_pool(); +} + +void QGLPixmapData::copyBackFromRenderFbo(bool keepCurrentFboBound) const +{ + if (!isValid()) + return; + + m_hasFillColor = false; + + const QGLContext *share_ctx = qt_gl_share_widget()->context(); + QGLShareContextScope ctx(share_ctx); ensureCreated(); - QGLShareContextScope ctx(qt_gl_share_widget()->context()); - QImage img(m_width, m_height, QImage::Format_ARGB32_Premultiplied); + if (!ctx->d_ptr->fbo) + glGenFramebuffers(1, &ctx->d_ptr->fbo); - GLenum format = qt_gl_preferredTextureFormat(); - GLenum target = qt_gl_preferredTextureTarget(); + glBindFramebuffer(GL_FRAMEBUFFER_EXT, ctx->d_ptr->fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D, m_texture.id, 0); - glBindTexture(target, m_texture); -#ifndef QT_OPENGL_ES - glGetTexImage(target, 0, format, GL_UNSIGNED_BYTE, img.bits()); -#else - // XXX - cannot download textures this way on OpenGL/ES. -#endif + const int x0 = 0; + const int x1 = w; + const int y0 = 0; + const int y1 = h; - qt_gl_convertFromGLImage(&img); + glBindFramebuffer(GL_READ_FRAMEBUFFER_EXT, m_renderFbo->handle()); - return img; + glDisable(GL_SCISSOR_TEST); + + glBlitFramebufferEXT(x0, y0, x1, y1, + x0, y0, x1, y1, + GL_COLOR_BUFFER_BIT, + GL_NEAREST); + + if (keepCurrentFboBound) + glBindFramebuffer(GL_FRAMEBUFFER_EXT, ctx->d_ptr->current_fbo); +} + +bool QGLPixmapData::useFramebufferObjects() +{ + return QGLFramebufferObject::hasOpenGLFramebufferObjects() + && QGLFramebufferObject::hasOpenGLFramebufferBlit() + && qt_gl_preferGL2Engine(); } QPaintEngine* QGLPixmapData::paintEngine() const @@ -261,23 +490,68 @@ QPaintEngine* QGLPixmapData::paintEngine() const if (!isValid()) return 0; - m_source = toImage(); - m_dirty = true; + if (m_renderFbo) + return m_engine; + if (useFramebufferObjects()) { + extern QGLWidget* qt_gl_share_widget(); + + if (!QGLContext::currentContext()) + qt_gl_share_widget()->makeCurrent(); + QGLShareContextScope ctx(qt_gl_share_widget()->context()); + + QGLFramebufferObjectFormat format; + format.setAttachment(QGLFramebufferObject::CombinedDepthStencil); + format.setSamples(4); + format.setInternalTextureFormat(GLenum(m_hasAlpha ? GL_RGBA : GL_RGB)); + + m_renderFbo = qgl_fbo_pool()->acquire(size(), format); + + if (m_renderFbo) { + if (!m_engine) + m_engine = new QGL2PaintEngineEx; + return m_engine; + } + + qWarning() << "Failed to create pixmap texture buffer of size " << size() << ", falling back to raster paint engine"; + } + + m_dirty = true; + if (m_source.size() != size()) + m_source = QImage(size(), QImage::Format_ARGB32_Premultiplied); + if (m_hasFillColor) { + m_source.fill(PREMUL(m_fillColor.rgba())); + m_hasFillColor = false; + } return m_source.paintEngine(); } -GLuint QGLPixmapData::bind() const + +// If copyBack is true, bind will copy the contents of the render +// FBO to the texture (which is not bound to the texture, as it's +// a multisample FBO). +GLuint QGLPixmapData::bind(bool copyBack) const { - ensureCreated(); - glBindTexture(qt_gl_preferredTextureTarget(), m_texture); - return m_texture; + if (m_renderFbo && copyBack) { + copyBackFromRenderFbo(true); + } else { + if (m_hasFillColor) { + m_dirty = true; + m_source = QImage(w, h, QImage::Format_ARGB32_Premultiplied); + m_source.fill(PREMUL(m_fillColor.rgba())); + m_hasFillColor = false; + } + ensureCreated(); + } + + GLuint id = m_texture.id; + glBindTexture(GL_TEXTURE_2D, id); + return id; } -GLuint QGLPixmapData::textureId() const +QGLTexture* QGLPixmapData::texture() const { - ensureCreated(); - return m_texture; + return &m_texture; } extern int qt_defaultDpiX(); @@ -285,19 +559,22 @@ extern int qt_defaultDpiY(); int QGLPixmapData::metric(QPaintDevice::PaintDeviceMetric metric) const { + if (w == 0) + return 0; + switch (metric) { case QPaintDevice::PdmWidth: - return m_width; + return w; case QPaintDevice::PdmHeight: - return m_height; + return h; case QPaintDevice::PdmNumColors: return 0; case QPaintDevice::PdmDepth: - return pixelType() == QPixmapData::PixmapType ? 32 : 1; + return d; case QPaintDevice::PdmWidthMM: - return qRound(m_width * 25.4 / qt_defaultDpiX()); + return qRound(w * 25.4 / qt_defaultDpiX()); case QPaintDevice::PdmHeightMM: - return qRound(m_height * 25.4 / qt_defaultDpiY()); + return qRound(h * 25.4 / qt_defaultDpiY()); case QPaintDevice::PdmDpiX: case QPaintDevice::PdmPhysicalDpiX: return qt_defaultDpiX(); @@ -310,4 +587,9 @@ int QGLPixmapData::metric(QPaintDevice::PaintDeviceMetric metric) const } } +QGLPaintDevice *QGLPixmapData::glDevice() const +{ + return &m_glDevice; +} + QT_END_NAMESPACE |