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:
parent
0eefa785a0
commit
8a031696a1
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user