summaryrefslogtreecommitdiffstats
path: root/src/opengl/qpixmapdata_gl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/opengl/qpixmapdata_gl.cpp')
-rw-r--r--src/opengl/qpixmapdata_gl.cpp510
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