diff options
author | Michael Brasser <michael.brasser@live.com> | 2017-11-07 20:07:34 -0600 |
---|---|---|
committer | Simon Hausmann <simon.hausmann@qt.io> | 2018-01-31 08:13:31 +0000 |
commit | 07f9ca1759e35f1eb497fe2f1ad4b7ba71b69b5c (patch) | |
tree | 5dd23f1f31ac3df199572c3add1f25972ec4ec69 /src | |
parent | eace041161a03a849d3896af65493b7885cecc04 (diff) |
Basic working compressed texture atlas
This adds experimental automatic atlasing of ETC-compressed textures
(to be expanded to additional formats), similar to existing atlas
support of QImages. It is off by default, and can be enabled with
QSG_ENABLE_COMPRESSED_ATLAS=1.
[ChangeLog] Add experimental automatic atlasing of ETC-compressed
textures (can be enabled with QSG_ENABLE_COMPRESSED_ATLAS=1)
Change-Id: Ia66971f51299d082a569bdfaadb662a3e522bd79
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/quick/scenegraph/compressedtexture/qsgcompressedatlastexture.cpp | 174 | ||||
-rw-r--r-- | src/quick/scenegraph/compressedtexture/qsgcompressedatlastexture_p.h | 119 | ||||
-rw-r--r-- | src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp | 9 | ||||
-rw-r--r-- | src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h | 5 | ||||
-rw-r--r-- | src/quick/scenegraph/qsgcontext.cpp | 14 | ||||
-rw-r--r-- | src/quick/scenegraph/qsgcontext_p.h | 2 | ||||
-rw-r--r-- | src/quick/scenegraph/qsgdefaultrendercontext.cpp | 9 | ||||
-rw-r--r-- | src/quick/scenegraph/qsgdefaultrendercontext_p.h | 1 | ||||
-rw-r--r-- | src/quick/scenegraph/scenegraph.pri | 2 | ||||
-rw-r--r-- | src/quick/scenegraph/util/qsgatlastexture.cpp | 322 | ||||
-rw-r--r-- | src/quick/scenegraph/util/qsgatlastexture_p.h | 88 |
11 files changed, 591 insertions, 154 deletions
diff --git a/src/quick/scenegraph/compressedtexture/qsgcompressedatlastexture.cpp b/src/quick/scenegraph/compressedtexture/qsgcompressedatlastexture.cpp new file mode 100644 index 0000000000..301f2826dc --- /dev/null +++ b/src/quick/scenegraph/compressedtexture/qsgcompressedatlastexture.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgcompressedatlastexture_p.h" + +#include <QtCore/QVarLengthArray> +#include <QtCore/QElapsedTimer> +#include <QtCore/QtMath> + +#include <QtGui/QOpenGLContext> +#include <QtGui/QGuiApplication> +#include <QtGui/QScreen> +#include <QtGui/QSurface> +#include <QtGui/QWindow> +#include <QtGui/QOpenGLFunctions> +#include <QtGui/QOpenGLTexture> +#include <QDebug> + +#include <private/qqmlglobal_p.h> +#include <private/qquickprofiler_p.h> +#include <private/qsgtexture_p.h> +#include <private/qsgcompressedtexture_p.h> +#include <private/qsgpkmhandler_p.h> + +QT_BEGIN_NAMESPACE + +static QElapsedTimer qsg_renderer_timer; + +namespace QSGCompressedAtlasTexture +{ + +Atlas::Atlas(const QSize &size, uint format) + : QSGAtlasTexture::AtlasBase(size) + , m_format(format) +{ +} + +Atlas::~Atlas() +{ +} + +Texture *Atlas::create(const QByteArray &data, int dataLength, int dataOffset, const QSize &size, const QSize &paddedSize) +{ + // No need to lock, as manager already locked it. + QRect rect = m_allocator.allocate(paddedSize); + if (rect.width() > 0 && rect.height() > 0) { + Texture *t = new Texture(this, rect, data, dataLength, dataOffset, size); + m_pending_uploads << t; + return t; + } + return 0; +} + +void Atlas::generateTexture() +{ + QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); + funcs->glCompressedTexImage2D(GL_TEXTURE_2D, 0, m_format, + m_size.width(), m_size.height(), 0, + (m_size.width() * m_size.height()) / 2, + 0); +} + +void Atlas::uploadPendingTexture(int i) +{ + Texture *texture = static_cast<Texture*>(m_pending_uploads.at(i)); + + const QRect &r = texture->atlasSubRect(); + + QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); + funcs->glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, + r.x(), r.y(), r.width(), r.height(), m_format, + texture->sizeInBytes(), + texture->data().constData() + texture->dataOffset()); + + qCDebug(QSG_LOG_TIME_TEXTURE).nospace() << "compressed atlastexture uploaded in: " << qsg_renderer_timer.elapsed() + << "ms (" << texture->textureSize().width() << "x" + << texture->textureSize().height() << ")"; + + // TODO: consider releasing the data (as is done in the regular atlas)? + // The advantage of keeping this data around is that it makes it much easier + // to remove the texture from the atlas +} + +Texture::Texture(Atlas *atlas, const QRect &textureRect, const QByteArray &data, int dataLength, int dataOffset, const QSize &size) + : QSGAtlasTexture::TextureBase(atlas, textureRect) + , m_nonatlas_texture(0) + , m_data(data) + , m_size(size) + , m_dataLength(dataLength) + , m_dataOffset(dataOffset) +{ + float w = atlas->size().width(); + float h = atlas->size().height(); + QRect nopad = atlasSubRect(); + // offset by half-pixel to prevent bleeding when scaling + m_texture_coords_rect = QRectF((nopad.x() + .5) / w, + (nopad.y() + .5) / h, + (nopad.width() - 1.) / w, + (nopad.height() - 1.) / h); +} + +Texture::~Texture() +{ + delete m_nonatlas_texture; +} + +bool Texture::hasAlphaChannel() const +{ + return QSGCompressedTexture::formatIsOpaque(static_cast<Atlas*>(m_atlas)->format()); +} + +QSGTexture *Texture::removedFromAtlas() const +{ + if (m_nonatlas_texture) { + m_nonatlas_texture->setMipmapFiltering(mipmapFiltering()); + m_nonatlas_texture->setFiltering(filtering()); + return m_nonatlas_texture; + } + + if (!m_data.isEmpty()) { + QSGCompressedTexture::DataPtr texData(QSGCompressedTexture::DataPtr::create()); + texData->data = m_data; + texData->size = m_size; + texData->format = static_cast<Atlas*>(m_atlas)->format(); + texData->hasAlpha = hasAlphaChannel(); + texData->dataLength = m_dataLength; + texData->dataOffset = m_dataOffset; + m_nonatlas_texture = new QSGCompressedTexture(texData); + m_nonatlas_texture->setMipmapFiltering(mipmapFiltering()); + m_nonatlas_texture->setFiltering(filtering()); + } + + return m_nonatlas_texture; +} + +} + +QT_END_NAMESPACE diff --git a/src/quick/scenegraph/compressedtexture/qsgcompressedatlastexture_p.h b/src/quick/scenegraph/compressedtexture/qsgcompressedatlastexture_p.h new file mode 100644 index 0000000000..59e935b623 --- /dev/null +++ b/src/quick/scenegraph/compressedtexture/qsgcompressedatlastexture_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGCOMPRESSEDATLASTEXTURE_P_H +#define QSGCOMPRESSEDATLASTEXTURE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/QSize> + +#include <QtGui/qopengl.h> + +#include <QtQuick/QSGTexture> +#include <QtQuick/private/qsgareaallocator_p.h> +#include <QtQuick/private/qsgatlastexture_p.h> + +QT_BEGIN_NAMESPACE + +class QSGCompressedTextureFactory; + +namespace QSGCompressedAtlasTexture { + +class Texture; + +class Atlas : public QSGAtlasTexture::AtlasBase +{ +public: + Atlas(const QSize &size, uint format); + ~Atlas(); + + void generateTexture() override; + void uploadPendingTexture(int i) override; + + Texture *create(const QByteArray &data, int dataLength, int dataOffset, const QSize &size, const QSize &paddedSize); + + uint format() const { return m_format; } + +private: + uint m_format; +}; + +class Texture : public QSGAtlasTexture::TextureBase +{ + Q_OBJECT +public: + Texture(Atlas *atlas, const QRect &textureRect, const QByteArray &data, int dataLength, int dataOffset, const QSize &size); + ~Texture(); + + QSize textureSize() const override { return m_size; } + bool hasAlphaChannel() const override; + bool hasMipmaps() const override { return false; } + + QRectF normalizedTextureSubRect() const override { return m_texture_coords_rect; } + + QSGTexture *removedFromAtlas() const override; + + const QByteArray &data() const { return m_data; } + int sizeInBytes() const { return m_dataLength; } + int dataOffset() const { return m_dataOffset; } + +private: + QRectF m_texture_coords_rect; + mutable QSGTexture *m_nonatlas_texture; + QByteArray m_data; + QSize m_size; + int m_dataLength; + int m_dataOffset; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp b/src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp index 6d51ed9d61..839c562989 100644 --- a/src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp +++ b/src/quick/scenegraph/compressedtexture/qsgcompressedtexture.cpp @@ -43,6 +43,7 @@ #include <QOpenGLTexture> #include <QOpenGLFunctions> #include <QDebug> +#include <QtQuick/private/qquickwindow_p.h> QT_BEGIN_NAMESPACE @@ -215,11 +216,17 @@ QSGCompressedTextureFactory::QSGCompressedTextureFactory(const QSGCompressedText { } -QSGTexture *QSGCompressedTextureFactory::createTexture(QQuickWindow *) const +QSGTexture *QSGCompressedTextureFactory::createTexture(QQuickWindow *window) const { if (!m_textureData || !m_textureData->isValid()) return nullptr; + // attempt to atlas the texture + QSGRenderContext *context = QQuickWindowPrivate::get(window)->context; + QSGTexture *t = context->compressedTextureForFactory(this); + if (t) + return t; + return new QSGCompressedTexture(m_textureData); } diff --git a/src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h b/src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h index dfedac5558..aa87316809 100644 --- a/src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h +++ b/src/quick/scenegraph/compressedtexture/qsgcompressedtexture_p.h @@ -103,6 +103,9 @@ protected: bool m_uploaded = false; }; +namespace QSGAtlasTexture { + class Manager; +} class Q_QUICK_PRIVATE_EXPORT QSGCompressedTextureFactory : public QQuickTextureFactory { @@ -114,6 +117,8 @@ public: protected: QSGCompressedTexture::DataPtr m_textureData; +private: + friend class QSGAtlasTexture::Manager; }; QT_END_NAMESPACE diff --git a/src/quick/scenegraph/qsgcontext.cpp b/src/quick/scenegraph/qsgcontext.cpp index fb66a6ebb1..e5334d59e1 100644 --- a/src/quick/scenegraph/qsgcontext.cpp +++ b/src/quick/scenegraph/qsgcontext.cpp @@ -404,6 +404,20 @@ void QSGRenderContext::textureFactoryDestroyed(QObject *o) m_mutex.unlock(); } +/*! + Return the texture corresponding to a texture factory. + + This may optionally manipulate the texture in some way; for example by returning + an atlased texture. + + This function is not a replacement for textureForFactory; both should be used + for a single texture (this might atlas, while the other might cache). +*/ +QSGTexture *QSGRenderContext::compressedTextureForFactory(const QSGCompressedTextureFactory *) const +{ + return nullptr; +} + #include "qsgcontext.moc" #include "moc_qsgcontext_p.cpp" diff --git a/src/quick/scenegraph/qsgcontext_p.h b/src/quick/scenegraph/qsgcontext_p.h index 84a2523f26..da0adcd5d7 100644 --- a/src/quick/scenegraph/qsgcontext_p.h +++ b/src/quick/scenegraph/qsgcontext_p.h @@ -78,6 +78,7 @@ class QSGMaterial; class QSGRenderLoop; class QSGLayer; class QQuickTextureFactory; +class QSGCompressedTextureFactory; class QSGContext; class QQuickPaintedItem; class QSGRendererInterface; @@ -173,6 +174,7 @@ public: virtual QSGTexture *createTexture(const QImage &image, uint flags = CreateTexture_Alpha) const = 0; virtual QSGRenderer *createRenderer() = 0; + virtual QSGTexture *compressedTextureForFactory(const QSGCompressedTextureFactory *) const; virtual void setAttachToGraphicsContext(bool attach) { Q_UNUSED(attach); } diff --git a/src/quick/scenegraph/qsgdefaultrendercontext.cpp b/src/quick/scenegraph/qsgdefaultrendercontext.cpp index 95f3555994..12357f12c7 100644 --- a/src/quick/scenegraph/qsgdefaultrendercontext.cpp +++ b/src/quick/scenegraph/qsgdefaultrendercontext.cpp @@ -44,6 +44,7 @@ #include <QtQuick/private/qsgbatchrenderer_p.h> #include <QtQuick/private/qsgrenderer_p.h> #include <QtQuick/private/qsgatlastexture_p.h> +#include <QtQuick/private/qsgcompressedtexture_p.h> #include <QtQuick/private/qsgdefaultdistancefieldglyphcache_p.h> QT_BEGIN_NAMESPACE @@ -243,6 +244,14 @@ QSGRenderer *QSGDefaultRenderContext::createRenderer() return new QSGBatchRenderer::Renderer(this); } +QSGTexture *QSGDefaultRenderContext::compressedTextureForFactory(const QSGCompressedTextureFactory *factory) const +{ + // The atlas implementation is only supported from the render thread + if (openglContext() && QThread::currentThread() == openglContext()->thread()) + return m_atlasManager->create(factory); + return nullptr; +} + /*! Compile \a shader, optionally using \a vertexCode and \a fragmentCode as replacement for the source code supplied by \a shader. diff --git a/src/quick/scenegraph/qsgdefaultrendercontext_p.h b/src/quick/scenegraph/qsgdefaultrendercontext_p.h index 2537a06988..68329256f1 100644 --- a/src/quick/scenegraph/qsgdefaultrendercontext_p.h +++ b/src/quick/scenegraph/qsgdefaultrendercontext_p.h @@ -84,6 +84,7 @@ public: QSGTexture *createTexture(const QImage &image, uint flags) const override; QSGRenderer *createRenderer() override; + QSGTexture *compressedTextureForFactory(const QSGCompressedTextureFactory *factory) const override; virtual void compileShader(QSGMaterialShader *shader, QSGMaterial *material, const char *vertexCode = 0, const char *fragmentCode = 0); virtual void initializeShader(QSGMaterialShader *shader); diff --git a/src/quick/scenegraph/scenegraph.pri b/src/quick/scenegraph/scenegraph.pri index 377a4647da..bdd694f95e 100644 --- a/src/quick/scenegraph/scenegraph.pri +++ b/src/quick/scenegraph/scenegraph.pri @@ -230,11 +230,13 @@ SOURCES += \ qtConfig(opengl(es1|es2)?) { HEADERS += \ + $$PWD/compressedtexture/qsgcompressedatlastexture_p.h \ $$PWD/compressedtexture/qsgcompressedtexture_p.h \ $$PWD/compressedtexture/qsgtexturefilehandler_p.h \ $$PWD/compressedtexture/qsgpkmhandler_p.h SOURCES += \ + $$PWD/compressedtexture/qsgcompressedatlastexture.cpp \ $$PWD/compressedtexture/qsgcompressedtexture.cpp \ $$PWD/compressedtexture/qsgpkmhandler.cpp } diff --git a/src/quick/scenegraph/util/qsgatlastexture.cpp b/src/quick/scenegraph/util/qsgatlastexture.cpp index 22f0b13f46..9d37746fff 100644 --- a/src/quick/scenegraph/util/qsgatlastexture.cpp +++ b/src/quick/scenegraph/util/qsgatlastexture.cpp @@ -44,6 +44,7 @@ #include <QtCore/QtMath> #include <QtGui/QOpenGLContext> +#include <QtGui/QOpenGLTexture> #include <QtGui/QOpenGLFunctions> #include <QtGui/QGuiApplication> #include <QtGui/QScreen> @@ -52,6 +53,8 @@ #include <QtGui/qpa/qplatformnativeinterface.h> #include <private/qsgtexture_p.h> +#include <private/qsgcompressedtexture_p.h> +#include <private/qsgcompressedatlastexture_p.h> #include <private/qquickprofiler_p.h> @@ -65,6 +68,8 @@ int qt_sg_envInt(const char *name, int defaultValue); static QElapsedTimer qsg_renderer_timer; +DEFINE_BOOL_CONFIG_OPTION(qsgEnableCompressedAtlas, QSG_ENABLE_COMPRESSED_ATLAS) + namespace QSGAtlasTexture { @@ -100,6 +105,7 @@ Manager::Manager() Manager::~Manager() { Q_ASSERT(m_atlas == 0); + Q_ASSERT(m_atlases.isEmpty()); } void Manager::invalidate() @@ -109,6 +115,14 @@ void Manager::invalidate() m_atlas->deleteLater(); m_atlas = 0; } + + QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*>::iterator i = m_atlases.begin(); + while (i != m_atlases.end()) { + i.value()->invalidate(); + i.value()->deleteLater(); + ++i; + } + m_atlases.clear(); } QSGTexture *Manager::create(const QImage &image, bool hasAlphaChannel) @@ -125,13 +139,147 @@ QSGTexture *Manager::create(const QImage &image, bool hasAlphaChannel) return t; } -Atlas::Atlas(const QSize &size) +QSGTexture *Manager::create(const QSGCompressedTextureFactory *factory) +{ + QSGTexture *t = 0; + if (!qsgEnableCompressedAtlas() || !factory->m_textureData || !factory->m_textureData->isValid()) + return t; + + // TODO: further abstract the atlas and remove this restriction + unsigned int format = factory->m_textureData->format; + switch (format) { + case QOpenGLTexture::RGB8_ETC1: + case QOpenGLTexture::RGB8_ETC2: + case QOpenGLTexture::RGBA8_ETC2_EAC: + case QOpenGLTexture::RGB8_PunchThrough_Alpha1_ETC2: + break; + default: + return t; + } + + QSize size = factory->m_textureData->size; + if (size.width() < m_atlas_size_limit && size.height() < m_atlas_size_limit) { + QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*>::iterator i = m_atlases.find(format); + if (i == m_atlases.end()) + i = m_atlases.insert(format, new QSGCompressedAtlasTexture::Atlas(m_atlas_size, format)); + // must be multiple of 4 + QSize paddedSize(((size.width() + 3) / 4) * 4, ((size.height() + 3) / 4) * 4); + QByteArray data = factory->m_textureData->data; + t = i.value()->create(data, factory->m_textureData->sizeInBytes(), factory->m_textureData->dataOffset, size, paddedSize); + } + return t; +} + +AtlasBase::AtlasBase(const QSize &size) : m_allocator(size) , m_texture_id(0) , m_size(size) - , m_atlas_transient_image_threshold(0) , m_allocated(false) { +} + +AtlasBase::~AtlasBase() +{ + Q_ASSERT(!m_texture_id); +} + +void AtlasBase::invalidate() +{ + if (m_texture_id && QOpenGLContext::currentContext()) + QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_texture_id); + m_texture_id = 0; +} + +int AtlasBase::textureId() const +{ + if (!m_texture_id) { + Q_ASSERT(QOpenGLContext::currentContext()); + QOpenGLContext::currentContext()->functions()->glGenTextures(1, &const_cast<AtlasBase *>(this)->m_texture_id); + } + + return m_texture_id; +} + +void AtlasBase::bind(QSGTexture::Filtering filtering) +{ + QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); + if (!m_allocated) { + m_allocated = true; + + while (funcs->glGetError() != GL_NO_ERROR) ; + + funcs->glGenTextures(1, &m_texture_id); + funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id); + funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#if !defined(QT_OPENGL_ES_2) + if (!QOpenGLContext::currentContext()->isOpenGLES()) + funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); +#endif + generateTexture(); + + GLenum errorCode = funcs->glGetError(); + if (errorCode == GL_OUT_OF_MEMORY) { + qDebug("QSGTextureAtlas: texture atlas allocation failed, out of memory"); + funcs->glDeleteTextures(1, &m_texture_id); + m_texture_id = 0; + } else if (errorCode != GL_NO_ERROR) { + qDebug("QSGTextureAtlas: texture atlas allocation failed, code=%x", errorCode); + funcs->glDeleteTextures(1, &m_texture_id); + m_texture_id = 0; + } + } else { + funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id); + } + + if (m_texture_id == 0) + return; + + // Upload all pending images.. + for (int i=0; i<m_pending_uploads.size(); ++i) { + + bool profileFrames = QSG_LOG_TIME_TEXTURE().isDebugEnabled(); + if (profileFrames) + qsg_renderer_timer.start(); + + Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphTexturePrepare); + + // Skip bind, convert, swizzle; they're irrelevant + Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphTexturePrepare, + QQuickProfiler::SceneGraphTexturePrepareStart, 3); + + uploadPendingTexture(i); + + Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphTexturePrepare, + QQuickProfiler::SceneGraphTexturePrepareUpload); + + // Skip mipmap; unused + Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphTexturePrepare, + QQuickProfiler::SceneGraphTexturePrepareUpload, 1); + Q_QUICK_SG_PROFILE_REPORT(QQuickProfiler::SceneGraphTexturePrepare, + QQuickProfiler::SceneGraphTexturePrepareMipmap); + } + + GLenum f = filtering == QSGTexture::Nearest ? GL_NEAREST : GL_LINEAR; + funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, f); + funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, f); + + m_pending_uploads.clear(); +} + +void AtlasBase::remove(TextureBase *t) +{ + QRect atlasRect = t->atlasSubRect(); + m_allocator.deallocate(atlasRect); + m_pending_uploads.removeOne(t); +} + +Atlas::Atlas(const QSize &size) + : AtlasBase(size) + , m_atlas_transient_image_threshold(0) +{ m_internalFormat = GL_RGBA; m_externalFormat = GL_BGRA; @@ -188,14 +336,6 @@ Atlas::Atlas(const QSize &size) Atlas::~Atlas() { - Q_ASSERT(!m_texture_id); -} - -void Atlas::invalidate() -{ - if (m_texture_id && QOpenGLContext::currentContext()) - QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_texture_id); - m_texture_id = 0; } Texture *Atlas::create(const QImage &image) @@ -210,17 +350,6 @@ Texture *Atlas::create(const QImage &image) return 0; } - -int Atlas::textureId() const -{ - if (!m_texture_id) { - Q_ASSERT(QOpenGLContext::currentContext()); - QOpenGLContext::currentContext()->functions()->glGenTextures(1, &const_cast<Atlas *>(this)->m_texture_id); - } - - return m_texture_id; -} - static void swizzleBGRAToRGBA(QImage *image) { const int width = image->width(); @@ -334,121 +463,68 @@ void Atlas::uploadBgra(Texture *texture) } } -void Atlas::bind(QSGTexture::Filtering filtering) +void Atlas::generateTexture() { QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); - if (!m_allocated) { - m_allocated = true; - - while (funcs->glGetError() != GL_NO_ERROR) ; - - funcs->glGenTextures(1, &m_texture_id); - funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id); - funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#if !defined(QT_OPENGL_ES_2) - if (!QOpenGLContext::currentContext()->isOpenGLES()) - funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); -#endif - funcs->glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_size.width(), m_size.height(), 0, m_externalFormat, GL_UNSIGNED_BYTE, 0); + funcs->glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_size.width(), m_size.height(), 0, m_externalFormat, GL_UNSIGNED_BYTE, 0); #if 0 - QImage pink(m_size.width(), m_size.height(), QImage::Format_ARGB32_Premultiplied); - pink.fill(0); - QPainter p(&pink); - QLinearGradient redGrad(0, 0, m_size.width(), 0); - redGrad.setColorAt(0, Qt::black); - redGrad.setColorAt(1, Qt::red); - p.fillRect(0, 0, m_size.width(), m_size.height(), redGrad); - p.setCompositionMode(QPainter::CompositionMode_Plus); - QLinearGradient blueGrad(0, 0, 0, m_size.height()); - blueGrad.setColorAt(0, Qt::black); - blueGrad.setColorAt(1, Qt::blue); - p.fillRect(0, 0, m_size.width(), m_size.height(), blueGrad); - p.end(); - - funcs->glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_size.width(), m_size.height(), 0, m_externalFormat, GL_UNSIGNED_BYTE, pink.constBits()); + QImage pink(m_size.width(), m_size.height(), QImage::Format_ARGB32_Premultiplied); + pink.fill(0); + QPainter p(&pink); + QLinearGradient redGrad(0, 0, m_size.width(), 0); + redGrad.setColorAt(0, Qt::black); + redGrad.setColorAt(1, Qt::red); + p.fillRect(0, 0, m_size.width(), m_size.height(), redGrad); + p.setCompositionMode(QPainter::CompositionMode_Plus); + QLinearGradient blueGrad(0, 0, 0, m_size.height()); + blueGrad.setColorAt(0, Qt::black); + blueGrad.setColorAt(1, Qt::blue); + p.fillRect(0, 0, m_size.width(), m_size.height(), blueGrad); + p.end(); + + funcs->glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_size.width(), m_size.height(), 0, m_externalFormat, GL_UNSIGNED_BYTE, pink.constBits()); #endif +} - GLenum errorCode = funcs->glGetError(); - if (errorCode == GL_OUT_OF_MEMORY) { - qDebug("QSGTextureAtlas: texture atlas allocation failed, out of memory"); - funcs->glDeleteTextures(1, &m_texture_id); - m_texture_id = 0; - } else if (errorCode != GL_NO_ERROR) { - qDebug("QSGTextureAtlas: texture atlas allocation failed, code=%x", errorCode); - funcs->glDeleteTextures(1, &m_texture_id); - m_texture_id = 0; - } +void Atlas::uploadPendingTexture(int i) +{ + Texture *t = static_cast<Texture*>(m_pending_uploads.at(i)); + if (m_externalFormat == GL_BGRA && + !m_use_bgra_fallback) { + uploadBgra(t); } else { - funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id); + upload(t); } - - if (m_texture_id == 0) - return; - - // Upload all pending images.. - for (int i=0; i<m_pending_uploads.size(); ++i) { - - bool profileFrames = QSG_LOG_TIME_TEXTURE().isDebugEnabled(); - if (profileFrames) - qsg_renderer_timer.start(); - - Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphTexturePrepare); - - // Skip bind, convert, swizzle; they're irrelevant - Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphTexturePrepare, - QQuickProfiler::SceneGraphTexturePrepareStart, 3); - - Texture *t = m_pending_uploads.at(i); - if (m_externalFormat == GL_BGRA && - !m_use_bgra_fallback) { - uploadBgra(t); - } else { - upload(t); - } - const QSize textureSize = t->textureSize(); - if (textureSize.width() > m_atlas_transient_image_threshold || - textureSize.height() > m_atlas_transient_image_threshold) - t->releaseImage(); - - qCDebug(QSG_LOG_TIME_TEXTURE).nospace() << "atlastexture uploaded in: " << qsg_renderer_timer.elapsed() - << "ms (" << t->textureSize().width() << "x" - << t->textureSize().height() << ")"; - - Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphTexturePrepare, - QQuickProfiler::SceneGraphTexturePrepareUpload); - - // Skip mipmap; unused - Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphTexturePrepare, - QQuickProfiler::SceneGraphTexturePrepareUpload, 1); - Q_QUICK_SG_PROFILE_REPORT(QQuickProfiler::SceneGraphTexturePrepare, - QQuickProfiler::SceneGraphTexturePrepareMipmap); - } - - GLenum f = filtering == QSGTexture::Nearest ? GL_NEAREST : GL_LINEAR; - funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, f); - funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, f); - - m_pending_uploads.clear(); + const QSize textureSize = t->textureSize(); + if (textureSize.width() > m_atlas_transient_image_threshold || + textureSize.height() > m_atlas_transient_image_threshold) + t->releaseImage(); + + qCDebug(QSG_LOG_TIME_TEXTURE).nospace() << "atlastexture uploaded in: " << qsg_renderer_timer.elapsed() + << "ms (" << t->textureSize().width() << "x" + << t->textureSize().height() << ")"; } -void Atlas::remove(Texture *t) +TextureBase::TextureBase(AtlasBase *atlas, const QRect &textureRect) + : m_allocated_rect(textureRect) + , m_atlas(atlas) { - QRect atlasRect = t->atlasSubRect(); - m_allocator.deallocate(atlasRect); - m_pending_uploads.removeOne(t); } +TextureBase::~TextureBase() +{ + m_atlas->remove(this); +} +void TextureBase::bind() +{ + m_atlas->bind(filtering()); +} Texture::Texture(Atlas *atlas, const QRect &textureRect, const QImage &image) - : QSGTexture() - , m_allocated_rect(textureRect) + : TextureBase(atlas, textureRect) , m_image(image) - , m_atlas(atlas) , m_nonatlas_texture(0) , m_has_alpha(image.hasAlphaChannel()) { @@ -463,16 +539,10 @@ Texture::Texture(Atlas *atlas, const QRect &textureRect, const QImage &image) Texture::~Texture() { - m_atlas->remove(this); if (m_nonatlas_texture) delete m_nonatlas_texture; } -void Texture::bind() -{ - m_atlas->bind(filtering()); -} - QSGTexture *Texture::removedFromAtlas() const { if (m_nonatlas_texture) { @@ -508,7 +578,7 @@ QSGTexture *Texture::removedFromAtlas() const QRect r = atlasSubRectWithoutPadding(); // and copy atlas into our texture. while (f->glGetError() != GL_NO_ERROR) ; - f->glCopyTexImage2D(GL_TEXTURE_2D, 0, m_atlas->internalFormat(), r.x(), r.y(), r.width(), r.height(), 0); + f->glCopyTexImage2D(GL_TEXTURE_2D, 0, static_cast<Atlas*>(m_atlas)->internalFormat(), r.x(), r.y(), r.width(), r.height(), 0); // BGRA may have been rejected by some GLES implementations if (f->glGetError() != GL_NO_ERROR) f->glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, r.x(), r.y(), r.width(), r.height(), 0); diff --git a/src/quick/scenegraph/util/qsgatlastexture_p.h b/src/quick/scenegraph/util/qsgatlastexture_p.h index 3dee539547..14dc8f7958 100644 --- a/src/quick/scenegraph/util/qsgatlastexture_p.h +++ b/src/quick/scenegraph/util/qsgatlastexture_p.h @@ -61,10 +61,16 @@ QT_BEGIN_NAMESPACE +namespace QSGCompressedAtlasTexture { + class Atlas; +} +class QSGCompressedTextureFactory; + namespace QSGAtlasTexture { class Texture; +class TextureBase; class Atlas; class Manager : public QObject @@ -76,93 +82,121 @@ public: ~Manager(); QSGTexture *create(const QImage &image, bool hasAlphaChannel); + QSGTexture *create(const QSGCompressedTextureFactory *factory); void invalidate(); private: Atlas *m_atlas; + // set of atlases for different compressed formats + QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*> m_atlases; QSize m_atlas_size; int m_atlas_size_limit; }; -class Atlas : public QObject +class AtlasBase : public QObject { + Q_OBJECT public: - Atlas(const QSize &size); - ~Atlas(); + AtlasBase(const QSize &size); + ~AtlasBase(); void invalidate(); int textureId() const; void bind(QSGTexture::Filtering filtering); + void remove(TextureBase *t); + + QSize size() const { return m_size; } + +protected: + virtual void generateTexture() = 0; + virtual void uploadPendingTexture(int i) = 0; + +protected: + QSGAreaAllocator m_allocator; + unsigned int m_texture_id; + QSize m_size; + QList<TextureBase *> m_pending_uploads; + +private: + bool m_allocated; +}; + +class Atlas : public AtlasBase +{ +public: + Atlas(const QSize &size); + ~Atlas(); + + void generateTexture() override; + void uploadPendingTexture(int i) override; + void upload(Texture *texture); void uploadBgra(Texture *texture); Texture *create(const QImage &image); - void remove(Texture *t); - - QSize size() const { return m_size; } uint internalFormat() const { return m_internalFormat; } uint externalFormat() const { return m_externalFormat; } private: - QSGAreaAllocator m_allocator; - unsigned int m_texture_id; - QSize m_size; - QList<Texture *> m_pending_uploads; - uint m_internalFormat; uint m_externalFormat; int m_atlas_transient_image_threshold; - uint m_allocated : 1; uint m_use_bgra_fallback: 1; - uint m_debug_overlay : 1; }; -class Texture : public QSGTexture +class TextureBase : public QSGTexture +{ + Q_OBJECT +public: + TextureBase(AtlasBase *atlas, const QRect &textureRect); + ~TextureBase(); + + int textureId() const override { return m_atlas->textureId(); } + bool isAtlasTexture() const override { return true; } + + QRect atlasSubRect() const { return m_allocated_rect; } + + void bind() override; + +protected: + QRect m_allocated_rect; + AtlasBase *m_atlas; +}; + +class Texture : public TextureBase { Q_OBJECT public: Texture(Atlas *atlas, const QRect &textureRect, const QImage &image); ~Texture(); - int textureId() const override { return m_atlas->textureId(); } QSize textureSize() const override { return atlasSubRectWithoutPadding().size(); } void setHasAlphaChannel(bool alpha) { m_has_alpha = alpha; } bool hasAlphaChannel() const override { return m_has_alpha; } bool hasMipmaps() const override { return false; } - bool isAtlasTexture() const override { return true; } QRectF normalizedTextureSubRect() const override { return m_texture_coords_rect; } QRect atlasSubRect() const { return m_allocated_rect; } QRect atlasSubRectWithoutPadding() const { return m_allocated_rect.adjusted(1, 1, -1, -1); } - bool isTexture() const { return true; } - QSGTexture *removedFromAtlas() const override; void releaseImage() { m_image = QImage(); } const QImage &image() const { return m_image; } - void bind() override; - private: - QRect m_allocated_rect; QRectF m_texture_coords_rect; - QImage m_image; - - Atlas *m_atlas; - mutable QSGPlainTexture *m_nonatlas_texture; - - uint m_has_alpha : 1; + bool m_has_alpha; }; } |