blob: f79cc30255e2809f63cb56073eaf32502803f2f5 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011 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$
** GNU Lesser General Public License Usage
** 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.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qpixmap.h"
#include "qglframebufferobject.h"
#include <private/qpaintengine_raster_p.h>
#include "qpixmapdata_gl_p.h"
#include <private/qgl_p.h>
#include <private/qdrawhelper_p.h>
#include <private/qimage_p.h>
#include <private/qpaintengineex_opengl2_p.h>
#include <qdesktopwidget.h>
#include <qfile.h>
#include <qimagereader.h>
#include <qbuffer.h>
QT_BEGIN_NAMESPACE
Q_OPENGL_EXPORT extern QGLWidget* qt_gl_share_widget();
/*!
\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)
{
return qAbs(size.width() * size.height() - fbo->width() * fbo->height());
}
extern int qt_next_power_of_two(int v);
static inline QSize maybeRoundToNextPowerOfTwo(const QSize &sz)
{
#ifdef QT_OPENGL_ES_2
QSize rounded(qt_next_power_of_two(sz.width()), qt_next_power_of_two(sz.height()));
if (rounded.width() * rounded.height() < 1.20 * sz.width() * sz.height())
return rounded;
#endif
return sz;
}
QGLFramebufferObject *QGLFramebufferObjectPool::acquire(const QSize &requestSize, const QGLFramebufferObjectFormat &requestFormat, bool strictSize)
{
QGLFramebufferObject *chosen = 0;
QGLFramebufferObject *candidate = 0;
for (int i = 0; !chosen && i < m_fbos.size(); ++i) {
QGLFramebufferObject *fbo = m_fbos.at(i);
if (strictSize) {
if (fbo->size() == requestSize && fbo->format() == requestFormat) {
chosen = fbo;
break;
} else {
continue;
}
}
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;
}
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() * 4)
sz = requestSize;
if (sz != fboSize) {
delete candidate;
candidate = new QGLFramebufferObject(maybeRoundToNextPowerOfTwo(sz), requestFormat);
}
chosen = candidate;
}
}
if (!chosen) {
if (strictSize)
chosen = new QGLFramebufferObject(requestSize, requestFormat);
else
chosen = new QGLFramebufferObject(maybeRoundToNextPowerOfTwo(requestSize), requestFormat);
}
if (!chosen->isValid()) {
delete chosen;
chosen = 0;
}
return chosen;
}
void QGLFramebufferObjectPool::release(QGLFramebufferObject *fbo)
{
if (fbo)
m_fbos << fbo;
}
QPaintEngine* QGLPixmapGLPaintDevice::paintEngine() const
{
return data->paintEngine();
}
void QGLPixmapGLPaintDevice::beginPaint()
{
if (!data->isValid())
return;
// QGLPaintDevice::beginPaint will store the current binding and replace
// it with m_thisFBO:
m_thisFBO = data->m_renderFbo->handle();
QGLPaintDevice::beginPaint();
Q_ASSERT(data->paintEngine()->type() == QPaintEngine::OpenGL2);
// 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();
glDisable(GL_SCISSOR_TEST);
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.
glDisable(GL_DEPTH_TEST);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
#if !defined(QT_OPENGL_ES_2)
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, data->width(), data->height(), 0, -999999, 999999);
#endif
glViewport(0, 0, data->width(), data->height());
// Pass false to bind so it doesn't copy the FBO into the texture!
context()->drawTexture(QRect(0, 0, data->width(), data->height()), data->bind(false));
}
}
void QGLPixmapGLPaintDevice::endPaint()
{
if (!data->isValid())
return;
data->copyBackFromRenderFbo(false);
// Base's endPaint will restore the previous FBO binding
QGLPaintDevice::endPaint();
qgl_fbo_pool()->release(data->m_renderFbo);
data->m_renderFbo = 0;
}
QGLContext* QGLPixmapGLPaintDevice::context() const
{
data->ensureCreated();
return data->m_ctx;
}
QSize QGLPixmapGLPaintDevice::size() const
{
return data->size();
}
bool QGLPixmapGLPaintDevice::alphaRequested() const
{
return data->m_hasAlpha;
}
void QGLPixmapGLPaintDevice::setPixmapData(QGLPixmapData* d)
{
data = d;
}
static int qt_gl_pixmap_serial = 0;
QGLPixmapData::QGLPixmapData(PixelType type)
: QPixmapData(type, OpenGLClass)
, 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()
{
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);
}
}
QPixmapData *QGLPixmapData::createCompatiblePixmapData() const
{
return new QGLPixmapData(pixelType());
}
bool QGLPixmapData::isValid() const
{
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 || QGLContext::areSharing(ctx, share_ctx);
}
void QGLPixmapData::resize(int width, int height)
{
if (width == w && height == h)
return;
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();
setSerialNumber(++qt_gl_pixmap_serial);
}
void QGLPixmapData::ensureCreated() const
{
if (!m_dirty)
return;
m_dirty = false;
QGLShareContextScope ctx(qt_gl_share_widget()->context());
m_ctx = ctx;
const GLenum internal_format = m_hasAlpha ? GL_RGBA : GL_RGB;
#ifdef QT_OPENGL_ES_2
const GLenum external_format = internal_format;
#else
const GLenum external_format = qt_gl_preferredTextureFormat();
#endif
const GLenum target = GL_TEXTURE_2D;
if (!m_texture.id) {
glGenTextures(1, &m_texture.id);
glBindTexture(target, m_texture.id);
glTexImage2D(target, 0, internal_format, w, h, 0, external_format, 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()) {
if (external_format == GL_RGB) {
const QImage tx = m_source.convertToFormat(QImage::Format_RGB888).mirrored(false, true);
glBindTexture(target, m_texture.id);
glTexSubImage2D(target, 0, 0, 0, w, h, external_format,
GL_UNSIGNED_BYTE, tx.bits());
} else {
const QImage tx = ctx->d_func()->convertToGLFormat(m_source, true, external_format);
glBindTexture(target, m_texture.id);
glTexSubImage2D(target, 0, 0, 0, w, h, external_format,
GL_UNSIGNED_BYTE, tx.bits());
}
if (useFramebufferObjects())
m_source = QImage();
}
m_texture.options &= ~QGLContext::MemoryManagedBindOption;
}
void QGLPixmapData::fromImage(const QImage &image,
Qt::ImageConversionFlags flags)
{
QImage img = image;
createPixmapForImage(img, flags, false);
}
void QGLPixmapData::fromImageReader(QImageReader *imageReader,
Qt::ImageConversionFlags flags)
{
QImage image = imageReader->read();
if (image.isNull())
return;
createPixmapForImage(image, flags, true);
}
bool QGLPixmapData::fromFile(const QString &filename, const char *format,
Qt::ImageConversionFlags flags)
{
if (pixelType() == QPixmapData::BitmapType)
return QPixmapData::fromFile(filename, format, flags);
QFile file(filename);
if (file.open(QIODevice::ReadOnly)) {
QByteArray data = file.peek(64);
bool alpha;
if (m_texture.canBindCompressedTexture
(data.constData(), data.size(), format, &alpha)) {
resize(0, 0);
data = file.readAll();
file.close();
QGLShareContextScope ctx(qt_gl_share_widget()->context());
QSize size = m_texture.bindCompressedTexture
(data.constData(), data.size(), format);
if (!size.isEmpty()) {
w = size.width();
h = size.height();
is_null = false;
d = 32;
m_hasAlpha = alpha;
m_source = QImage();
m_dirty = isValid();
return true;
}
return false;
}
}
QImage image = QImageReader(filename, format).read();
if (image.isNull())
return false;
createPixmapForImage(image, flags, true);
return !isNull();
}
bool QGLPixmapData::fromData(const uchar *buffer, uint len, const char *format,
Qt::ImageConversionFlags flags)
{
bool alpha;
const char *buf = reinterpret_cast<const char *>(buffer);
if (m_texture.canBindCompressedTexture(buf, int(len), format, &alpha)) {
resize(0, 0);
QGLShareContextScope ctx(qt_gl_share_widget()->context());
QSize size = m_texture.bindCompressedTexture(buf, int(len), format);
if (!size.isEmpty()) {
w = size.width();
h = size.height();
is_null = false;
d = 32;
m_hasAlpha = alpha;
m_source = QImage();
m_dirty = isValid();
return true;
}
}
QByteArray a = QByteArray::fromRawData(reinterpret_cast<const char *>(buffer), len);
QBuffer b(&a);
b.open(QIODevice::ReadOnly);
QImage image = QImageReader(&b, format).read();
if (image.isNull())
return false;
createPixmapForImage(image, flags, true);
return !isNull();
}
/*!
out-of-place conversion (inPlace == false) will always detach()
*/
void QGLPixmapData::createPixmapForImage(QImage &image, Qt::ImageConversionFlags flags, bool inPlace)
{
if (image.size() == QSize(w, h))
setSerialNumber(++qt_gl_pixmap_serial);
resize(image.width(), image.height());
if (pixelType() == BitmapType) {
m_source = image.convertToFormat(QImage::Format_MonoLSB);
} else {
QImage::Format format = QImage::Format_RGB32;
if (qApp->desktop()->depth() == 16)
format = QImage::Format_RGB16;
if (image.hasAlphaChannel()
&& ((flags & Qt::NoOpaqueDetection)
|| const_cast<QImage &>(image).data_ptr()->checkForAlphaPixels()))
format = QImage::Format_ARGB32_Premultiplied;;
if (inPlace && image.data_ptr()->convertInPlace(format, flags)) {
m_source = image;
} else {
m_source = image.convertToFormat(format);
// convertToFormat won't detach the image if format stays the same.
if (image.format() == format)
m_source.detach();
}
}
m_dirty = true;
m_hasFillColor = false;
m_hasAlpha = m_source.hasAlphaChannel();
w = image.width();
h = image.height();
is_null = (w <= 0 || h <= 0);
d = m_source.depth();
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 || !static_cast<const QGLPixmapData *>(data)->useFramebufferObjects()) {
QPixmapData::copy(data, rect);
return;
}
const QGLPixmapData *other = static_cast<const QGLPixmapData *>(data);
if (other->m_renderFbo) {
QGLShareContextScope ctx(qt_gl_share_widget()->context());
resize(rect.width(), rect.height());
m_hasAlpha = other->m_hasAlpha;
ensureCreated();
if (!ctx->d_ptr->fbo)
glGenFramebuffers(1, &ctx->d_ptr->fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, ctx->d_ptr->fbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, m_texture.id, 0);
if (!other->m_renderFbo->isBound())
glBindFramebuffer(GL_READ_FRAMEBUFFER_EXT, other->m_renderFbo->handle());
glDisable(GL_SCISSOR_TEST);
if (ctx->d_ptr->active_engine && ctx->d_ptr->active_engine->type() == QPaintEngine::OpenGL2)
static_cast<QGL2PaintEngineEx *>(ctx->d_ptr->active_engine)->invalidateState();
glBlitFramebufferEXT(rect.x(), rect.y(), rect.x() + rect.width(), rect.y() + rect.height(),
0, 0, w, h,
GL_COLOR_BUFFER_BIT,
GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER_EXT, ctx->d_ptr->current_fbo);
} else {
QPixmapData::copy(data, rect);
}
}
void QGLPixmapData::fill(const QColor &color)
{
if (!isValid())
return;
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;
}
if (useFramebufferObjects()) {
m_source = QImage();
m_hasFillColor = true;
m_fillColor = color;
} else {
if (m_source.isNull()) {
m_fillColor = color;
m_hasFillColor = true;
} else if (m_source.depth() == 32) {
m_source.fill(PREMUL(color.rgba()));
} else if (m_source.depth() == 1) {
if (color == Qt::color1)
m_source.fill(1);
else
m_source.fill(0);
}
}
}
bool QGLPixmapData::hasAlphaChannel() const
{
return m_hasAlpha;
}
QImage QGLPixmapData::fillImage(const QColor &color) const
{
QImage img;
if (pixelType() == BitmapType) {
img = QImage(w, h, QImage::Format_MonoLSB);
img.setColorCount(2);
img.setColor(0, QColor(Qt::color0).rgba());
img.setColor(1, QColor(Qt::color1).rgba());
if (color == Qt::color1)
img.fill(1);
else
img.fill(0);
} 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_renderFbo) {
copyBackFromRenderFbo(true);
} else if (!m_source.isNull()) {
QImageData *data = const_cast<QImage &>(m_source).data_ptr();
if (data->paintEngine && data->paintEngine->isActive()
&& data->paintEngine->paintDevice() == &m_source)
{
return m_source.copy();
}
return m_source;
} 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();
if (!ctx->d_ptr->fbo)
glGenFramebuffers(1, &ctx->d_ptr->fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, ctx->d_ptr->fbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, m_texture.id, 0);
const int x0 = 0;
const int x1 = w;
const int y0 = 0;
const int y1 = h;
if (!m_renderFbo->isBound())
glBindFramebuffer(GL_READ_FRAMEBUFFER_EXT, m_renderFbo->handle());
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);
} else {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, m_renderFbo->handle());
ctx->d_ptr->current_fbo = m_renderFbo->handle();
}
}
bool QGLPixmapData::useFramebufferObjects() const
{
return QGLFramebufferObject::hasOpenGLFramebufferObjects()
&& QGLFramebufferObject::hasOpenGLFramebufferBlit()
&& qt_gl_preferGL2Engine()
&& (w * h > 32*32); // avoid overhead of FBOs for small pixmaps
}
QPaintEngine* QGLPixmapData::paintEngine() const
{
if (!isValid())
return 0;
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();
}
extern QRgb qt_gl_convertToGLFormat(QRgb src_pixel, GLenum texture_format);
// 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
{
if (m_renderFbo && copyBack) {
copyBackFromRenderFbo(true);
} else {
ensureCreated();
}
GLuint id = m_texture.id;
glBindTexture(GL_TEXTURE_2D, id);
if (m_hasFillColor) {
if (!useFramebufferObjects()) {
m_source = QImage(w, h, QImage::Format_ARGB32_Premultiplied);
m_source.fill(PREMUL(m_fillColor.rgba()));
}
m_hasFillColor = false;
GLenum format = qt_gl_preferredTextureFormat();
QImage tx(w, h, QImage::Format_ARGB32_Premultiplied);
tx.fill(qt_gl_convertToGLFormat(m_fillColor.rgba(), format));
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, format, GL_UNSIGNED_BYTE, tx.bits());
}
return id;
}
QGLTexture* QGLPixmapData::texture() const
{
return &m_texture;
}
Q_GUI_EXPORT int qt_defaultDpiX();
Q_GUI_EXPORT int qt_defaultDpiY();
int QGLPixmapData::metric(QPaintDevice::PaintDeviceMetric metric) const
{
if (w == 0)
return 0;
switch (metric) {
case QPaintDevice::PdmWidth:
return w;
case QPaintDevice::PdmHeight:
return h;
case QPaintDevice::PdmNumColors:
return 0;
case QPaintDevice::PdmDepth:
return d;
case QPaintDevice::PdmWidthMM:
return qRound(w * 25.4 / qt_defaultDpiX());
case QPaintDevice::PdmHeightMM:
return qRound(h * 25.4 / qt_defaultDpiY());
case QPaintDevice::PdmDpiX:
case QPaintDevice::PdmPhysicalDpiX:
return qt_defaultDpiX();
case QPaintDevice::PdmDpiY:
case QPaintDevice::PdmPhysicalDpiY:
return qt_defaultDpiY();
default:
qWarning("QGLPixmapData::metric(): Invalid metric");
return 0;
}
}
QGLPaintDevice *QGLPixmapData::glDevice() const
{
return &m_glDevice;
}
QT_END_NAMESPACE