Make QOpenGLTextureCache::bindTexture upload efficiently

Currently QOpenGLTextureCache::bindTexture always convert any uploaded
image to RGBA8888 before uploading. This is quite inefficient when
OpenGL natively supports uploading formats in the original format.

This patch adds support for uploading a few native QImage formats. This
also get the performance of QOpenGLTextureCache::bindTexture on par with
QGLContext::bindTexture.

The texture brush used by QOpenGLPaintEngine is also converted to QImage,
since bindTexture will convert it to QImage anyway, and going over QPixmap
may cause an unnecessary conversion.

[ChangeLog][QtGui][QOpenGLTextureCache] Support uploading common QImage
formats directly to OpenGL when supported.

Change-Id: I828a763126441a98e4547c32ef52dddf7c129a32
Reviewed-by: Gunnar Sletta <gunnar.sletta@jollamobile.com>
This commit is contained in:
Allan Sandfeld Jensen 2014-07-03 13:36:01 +02:00
parent 0eefa785a0
commit 8a031696a1
5 changed files with 154 additions and 28 deletions

View File

@ -154,8 +154,8 @@ void QOpenGL2PaintEngineExPrivate::setBrush(const QBrush& brush)
Q_ASSERT(newStyle != Qt::NoBrush);
currentBrush = brush;
if (!currentBrushPixmap.isNull())
currentBrushPixmap = QPixmap();
if (!currentBrushImage.isNull())
currentBrushImage = QImage();
brushUniformsDirty = true; // All brushes have at least one uniform
if (newStyle > Qt::SolidPattern)
@ -214,11 +214,11 @@ void QOpenGL2PaintEngineExPrivate::updateBrushTexture()
updateTextureFilter(GL_TEXTURE_2D, GL_CLAMP_TO_EDGE, q->state()->renderHints & QPainter::SmoothPixmapTransform);
}
else if (style == Qt::TexturePattern) {
currentBrushPixmap = currentBrush.texture();
currentBrushImage = currentBrush.textureImage();
int max_texture_size = ctx->d_func()->maxTextureSize();
if (currentBrushPixmap.width() > max_texture_size || currentBrushPixmap.height() > max_texture_size)
currentBrushPixmap = currentBrushPixmap.scaled(max_texture_size, max_texture_size, Qt::KeepAspectRatio);
if (currentBrushImage.width() > max_texture_size || currentBrushImage.height() > max_texture_size)
currentBrushImage = currentBrushImage.scaled(max_texture_size, max_texture_size, Qt::KeepAspectRatio);
GLuint wrapMode = GL_REPEAT;
if (QOpenGLContext::currentContext()->isOpenGLES()) {
@ -229,7 +229,7 @@ void QOpenGL2PaintEngineExPrivate::updateBrushTexture()
}
funcs.glActiveTexture(GL_TEXTURE0 + QT_BRUSH_TEXTURE_UNIT);
QOpenGLTextureCache::cacheForContext(ctx)->bindTexture(ctx, currentBrushPixmap);
QOpenGLTextureCache::cacheForContext(ctx)->bindTexture(ctx, currentBrushImage);
updateTextureFilter(GL_TEXTURE_2D, wrapMode, q->state()->renderHints & QPainter::SmoothPixmapTransform);
textureInvertedY = false;
}

View File

@ -288,7 +288,7 @@ public:
QBrush currentBrush; // May not be the state's brush!
const QBrush noBrush;
QPixmap currentBrushPixmap;
QImage currentBrushImage;
QOpenGL2PEXVertexArray vertexCoordinateArray;
QOpenGL2PEXVertexArray textureCoordinateArray;

View File

@ -43,11 +43,24 @@
#include <qmath.h>
#include <qopenglfunctions.h>
#include <private/qopenglcontext_p.h>
#include <private/qopenglextensions_p.h>
#include <private/qimagepixmapcleanuphooks_p.h>
#include <qpa/qplatformpixmap.h>
QT_BEGIN_NAMESPACE
#ifndef GL_BGR
#define GL_BGR 0x80E0
#endif
#ifndef GL_BGRA
#define GL_BGRA 0x80E1
#endif
#ifndef GL_UNSIGNED_INT_8_8_8_8_REV
#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
#endif
class QOpenGLTextureCacheWrapper
{
public:
@ -107,7 +120,7 @@ QOpenGLTextureCache::~QOpenGLTextureCache()
{
}
GLuint QOpenGLTextureCache::bindTexture(QOpenGLContext *context, const QPixmap &pixmap)
GLuint QOpenGLTextureCache::bindTexture(QOpenGLContext *context, const QPixmap &pixmap, BindOptions options)
{
if (pixmap.isNull())
return 0;
@ -117,20 +130,20 @@ GLuint QOpenGLTextureCache::bindTexture(QOpenGLContext *context, const QPixmap &
// A QPainter is active on the image - take the safe route and replace the texture.
if (!pixmap.paintingActive()) {
QOpenGLCachedTexture *entry = m_cache.object(key);
if (entry) {
if (entry && entry->options() == options) {
context->functions()->glBindTexture(GL_TEXTURE_2D, entry->id());
return entry->id();
}
}
GLuint id = bindTexture(context, key, pixmap.toImage());
GLuint id = bindTexture(context, key, pixmap.toImage(), options);
if (id > 0)
QImagePixmapCleanupHooks::enableCleanupHooks(pixmap);
return id;
}
GLuint QOpenGLTextureCache::bindTexture(QOpenGLContext *context, const QImage &image)
GLuint QOpenGLTextureCache::bindTexture(QOpenGLContext *context, const QImage &image, BindOptions options)
{
if (image.isNull())
return 0;
@ -140,7 +153,7 @@ GLuint QOpenGLTextureCache::bindTexture(QOpenGLContext *context, const QImage &i
// A QPainter is active on the image - take the safe route and replace the texture.
if (!image.paintingActive()) {
QOpenGLCachedTexture *entry = m_cache.object(key);
if (entry) {
if (entry && entry->options() == options) {
context->functions()->glBindTexture(GL_TEXTURE_2D, entry->id());
return entry->id();
}
@ -158,26 +171,119 @@ GLuint QOpenGLTextureCache::bindTexture(QOpenGLContext *context, const QImage &i
}
}
GLuint id = bindTexture(context, key, img);
GLuint id = bindTexture(context, key, img, options);
if (id > 0)
QImagePixmapCleanupHooks::enableCleanupHooks(image);
return id;
}
GLuint QOpenGLTextureCache::bindTexture(QOpenGLContext *context, qint64 key, const QImage &image)
GLuint QOpenGLTextureCache::bindTexture(QOpenGLContext *context, qint64 key, const QImage &image, BindOptions options)
{
GLuint id;
QOpenGLFunctions *funcs = context->functions();
funcs->glGenTextures(1, &id);
funcs->glBindTexture(GL_TEXTURE_2D, id);
QImage tx = image.convertToFormat(QImage::Format_RGBA8888_Premultiplied);
QImage tx;
GLenum externalFormat;
GLenum internalFormat;
GLuint pixelType;
QImage::Format targetFormat = QImage::Format_Invalid;
const bool isOpenGL12orBetter = !context->isOpenGLES() && (context->format().majorVersion() >= 2 || context->format().minorVersion() >= 2);
funcs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tx.width(), tx.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, const_cast<const QImage &>(tx).bits());
switch (image.format()) {
case QImage::Format_RGB32:
case QImage::Format_ARGB32:
case QImage::Format_ARGB32_Premultiplied:
if (isOpenGL12orBetter) {
externalFormat = GL_BGRA;
internalFormat = GL_RGBA;
pixelType = GL_UNSIGNED_INT_8_8_8_8_REV;
} else {
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
// Without GL_UNSIGNED_INT_8_8_8_8_REV, BGRA only matches ARGB on little endian.
break;
#endif
if (static_cast<QOpenGLExtensions*>(context->functions())->hasOpenGLExtension(QOpenGLExtensions::BGRATextureFormat)) {
// GL_EXT_bgra or GL_EXT_texture_format_BGRA8888 extensions.
if (context->isOpenGLES()) {
// The GL_EXT_texture_format_BGRA8888 extension requires the internal format to match the external.
externalFormat = internalFormat = GL_BGRA;
} else {
// OpenGL BGRA/BGR format is not allowed as an internal format
externalFormat = GL_BGRA;
internalFormat = GL_RGBA;
}
pixelType = GL_UNSIGNED_BYTE;
} else if (context->isOpenGLES() && context->hasExtension(QByteArrayLiteral("GL_APPLE_texture_format_BGRA8888"))) {
// Is only allowed as an external format like OpenGL.
externalFormat = GL_BGRA;
internalFormat = GL_RGBA;
pixelType = GL_UNSIGNED_BYTE;
} else {
// No support for direct ARGB32 upload.
break;
}
}
targetFormat = image.format();
break;
case QImage::Format_RGB444:
case QImage::Format_RGB555:
case QImage::Format_RGB16:
if (isOpenGL12orBetter || context->isOpenGLES()) {
externalFormat = internalFormat = GL_RGB;
pixelType = GL_UNSIGNED_SHORT_5_6_5;
targetFormat = QImage::Format_RGB16;
}
break;
case QImage::Format_RGB666:
case QImage::Format_RGB888:
externalFormat = internalFormat = GL_RGB;
pixelType = GL_UNSIGNED_BYTE;
targetFormat = QImage::Format_RGB888;
break;
case QImage::Format_RGBX8888:
case QImage::Format_RGBA8888:
case QImage::Format_RGBA8888_Premultiplied:
externalFormat = internalFormat = GL_RGBA;
pixelType = GL_UNSIGNED_BYTE;
targetFormat = image.format();
break;
default:
break;
}
int cost = tx.width() * tx.height() * 4 / 1024;
m_cache.insert(key, new QOpenGLCachedTexture(id, context), cost);
if (targetFormat == QImage::Format_Invalid) {
externalFormat = internalFormat = GL_RGBA;
pixelType = GL_UNSIGNED_BYTE;
if (!image.hasAlphaChannel())
targetFormat = QImage::Format_RGBX8888;
else
targetFormat = QImage::Format_RGBA8888;
}
if (options & PremultipliedAlphaBindOption) {
if (targetFormat == QImage::Format_ARGB32)
targetFormat = QImage::Format_ARGB32_Premultiplied;
else if (targetFormat == QImage::Format_RGBA8888)
targetFormat = QImage::Format_RGBA8888_Premultiplied;
} else {
if (targetFormat == QImage::Format_ARGB32_Premultiplied)
targetFormat = QImage::Format_ARGB32;
else if (targetFormat == QImage::Format_RGBA8888_Premultiplied)
targetFormat = QImage::Format_RGBA8888;
}
if (image.format() != targetFormat)
tx = image.convertToFormat(targetFormat);
else
tx = image;
funcs->glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, tx.width(), tx.height(), 0, externalFormat, pixelType, const_cast<const QImage &>(tx).bits());
int cost = tx.width() * tx.height() * tx.depth() / (1024 * 8);
m_cache.insert(key, new QOpenGLCachedTexture(id, options, context), cost);
return id;
}
@ -203,7 +309,7 @@ static void freeTexture(QOpenGLFunctions *funcs, GLuint id)
funcs->glDeleteTextures(1, &id);
}
QOpenGLCachedTexture::QOpenGLCachedTexture(GLuint id, QOpenGLContext *context)
QOpenGLCachedTexture::QOpenGLCachedTexture(GLuint id, int options, QOpenGLContext *context) : m_options(options)
{
m_resource = new QOpenGLSharedResourceGuard(context, id, freeTexture);
}

View File

@ -64,13 +64,15 @@ QT_BEGIN_NAMESPACE
class QOpenGLCachedTexture
{
public:
QOpenGLCachedTexture(GLuint id, QOpenGLContext *context);
QOpenGLCachedTexture(GLuint id, int options, QOpenGLContext *context);
~QOpenGLCachedTexture() { m_resource->free(); }
GLuint id() const { return m_resource->id(); }
int options() const { return m_options; }
private:
QOpenGLSharedResourceGuard *m_resource;
int m_options;
};
class Q_GUI_EXPORT QOpenGLTextureCache : public QOpenGLSharedResource
@ -81,8 +83,14 @@ public:
QOpenGLTextureCache(QOpenGLContext *);
~QOpenGLTextureCache();
GLuint bindTexture(QOpenGLContext *context, const QPixmap &pixmap);
GLuint bindTexture(QOpenGLContext *context, const QImage &image);
enum BindOption {
NoBindOption = 0x0000,
PremultipliedAlphaBindOption = 0x0001,
};
Q_DECLARE_FLAGS(BindOptions, BindOption)
GLuint bindTexture(QOpenGLContext *context, const QPixmap &pixmap, QOpenGLTextureCache::BindOptions options = PremultipliedAlphaBindOption);
GLuint bindTexture(QOpenGLContext *context, const QImage &image, QOpenGLTextureCache::BindOptions options = PremultipliedAlphaBindOption);
void invalidate(qint64 key);
@ -90,12 +98,14 @@ public:
void freeResource(QOpenGLContext *ctx);
private:
GLuint bindTexture(QOpenGLContext *context, qint64 key, const QImage &image);
GLuint bindTexture(QOpenGLContext *context, qint64 key, const QImage &image, QOpenGLTextureCache::BindOptions options);
QMutex m_mutex;
QCache<quint64, QOpenGLCachedTexture> m_cache;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QOpenGLTextureCache::BindOptions)
QT_END_NAMESPACE
#endif

View File

@ -65,6 +65,8 @@
#include <QtPlatformHeaders/QGLXNativeContext>
#endif
Q_DECLARE_METATYPE(QImage::Format)
class tst_QOpenGL : public QObject
{
Q_OBJECT
@ -605,7 +607,14 @@ void tst_QOpenGL::fboHandleNulledAfterContextDestroyed()
void tst_QOpenGL::openGLPaintDevice_data()
{
common_data();
QTest::addColumn<int>("surfaceClass");
QTest::addColumn<QImage::Format>("imageFormat");
QTest::newRow("Using QWindow - RGB32") << int(QSurface::Window) << QImage::Format_RGB32;
QTest::newRow("Using QOffscreenSurface - RGB32") << int(QSurface::Offscreen) << QImage::Format_RGB32;
QTest::newRow("Using QOffscreenSurface - RGBx8888") << int(QSurface::Offscreen) << QImage::Format_RGBX8888;
QTest::newRow("Using QOffscreenSurface - RGB888") << int(QSurface::Offscreen) << QImage::Format_RGB888;
QTest::newRow("Using QOffscreenSurface - RGB16") << int(QSurface::Offscreen) << QImage::Format_RGB16;
}
void tst_QOpenGL::openGLPaintDevice()
@ -615,6 +624,7 @@ void tst_QOpenGL::openGLPaintDevice()
#endif
QFETCH(int, surfaceClass);
QFETCH(QImage::Format, imageFormat);
QScopedPointer<QSurface> surface(createSurface(surfaceClass));
QOpenGLContext ctx;
@ -627,7 +637,7 @@ void tst_QOpenGL::openGLPaintDevice()
const QSize size(128, 128);
QImage image(size, QImage::Format_RGB32);
QImage image(size, imageFormat);
QPainter p(&image);
p.fillRect(0, 0, image.width() / 2, image.height() / 2, Qt::red);
p.fillRect(image.width() / 2, 0, image.width() / 2, image.height() / 2, Qt::green);
@ -646,7 +656,7 @@ void tst_QOpenGL::openGLPaintDevice()
p.fillRect(0, image.height() / 2, image.width() / 2, image.height() / 2, Qt::white);
p.end();
QImage actual = fbo.toImage().convertToFormat(QImage::Format_RGB32);
QImage actual = fbo.toImage().convertToFormat(imageFormat);
QCOMPARE(image.size(), actual.size());
QCOMPARE(image, actual);
@ -655,7 +665,7 @@ void tst_QOpenGL::openGLPaintDevice()
p.drawImage(0, 0, image);
p.end();
actual = fbo.toImage().convertToFormat(QImage::Format_RGB32);
actual = fbo.toImage().convertToFormat(imageFormat);
QCOMPARE(image.size(), actual.size());
QCOMPARE(image, actual);
@ -664,7 +674,7 @@ void tst_QOpenGL::openGLPaintDevice()
p.fillRect(0, 0, image.width(), image.height(), QBrush(image));
p.end();
actual = fbo.toImage().convertToFormat(QImage::Format_RGB32);
actual = fbo.toImage().convertToFormat(imageFormat);
QCOMPARE(image.size(), actual.size());
QCOMPARE(image, actual);
}