From 432e27ae092397cb2154f48103e729852c38cf2d Mon Sep 17 00:00:00 2001 From: Michael Brasser Date: Tue, 18 Apr 2017 09:07:07 -0500 Subject: Add very basic compressed texture support Allow direct loading of pkm texture files into Image. This can be extended to additional texture types, and then eventually turned into a full plugin architexture. [ChangeLog][Qt Quick] Allow direct loading of pkm texture files into Image. For example: Image { source: "myImage.pkm" } Change-Id: I1baed6c3e85a15752da8adc675482d874c9355ab Task-number: QTBUG-59872 Task-number: QTBUG-29451 Reviewed-by: Laszlo Agocs --- .../scenegraph/compressedtexture/qsgpkmhandler.cpp | 209 +++++++++++++++++++++ .../scenegraph/compressedtexture/qsgpkmhandler_p.h | 94 +++++++++ src/quick/scenegraph/scenegraph.pri | 15 ++ src/quick/scenegraph/util/qsgtexturereader.cpp | 82 ++++++++ src/quick/scenegraph/util/qsgtexturereader_p.h | 72 +++++++ src/quick/util/qquickpixmapcache.cpp | 45 ++++- 6 files changed, 510 insertions(+), 7 deletions(-) create mode 100644 src/quick/scenegraph/compressedtexture/qsgpkmhandler.cpp create mode 100644 src/quick/scenegraph/compressedtexture/qsgpkmhandler_p.h create mode 100644 src/quick/scenegraph/util/qsgtexturereader.cpp create mode 100644 src/quick/scenegraph/util/qsgtexturereader_p.h (limited to 'src') diff --git a/src/quick/scenegraph/compressedtexture/qsgpkmhandler.cpp b/src/quick/scenegraph/compressedtexture/qsgpkmhandler.cpp new file mode 100644 index 0000000000..1b8882e9a5 --- /dev/null +++ b/src/quick/scenegraph/compressedtexture/qsgpkmhandler.cpp @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 "qsgpkmhandler_p.h" + +#include +#include +#include +#include +#include + +//#define ETC_DEBUG + +#ifndef GL_ETC1_RGB8_OES + #define GL_ETC1_RGB8_OES 0x8d64 +#endif + +#ifndef GL_COMPRESSED_RGB8_ETC2 + #define GL_COMPRESSED_RGB8_ETC2 0x9274 +#endif + +#ifndef GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 + #define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 +#endif + +#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC + #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#endif + +QT_BEGIN_NAMESPACE + +static const int headerSize = 16; + +static unsigned int typeMap[5] = { + GL_ETC1_RGB8_OES, + GL_COMPRESSED_RGB8_ETC2, + 0, // unused + GL_COMPRESSED_RGBA8_ETC2_EAC, + GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 +}; + +EtcTexture::EtcTexture() + : m_texture_id(0), m_uploaded(false) +{ + initializeOpenGLFunctions(); +} + +EtcTexture::~EtcTexture() +{ + if (m_texture_id) + glDeleteTextures(1, &m_texture_id); +} + +int EtcTexture::textureId() const +{ + if (m_texture_id == 0) { + EtcTexture *texture = const_cast(this); + texture->glGenTextures(1, &texture->m_texture_id); + } + return m_texture_id; +} + +bool EtcTexture::hasAlphaChannel() const +{ + return m_type == GL_COMPRESSED_RGBA8_ETC2_EAC || + m_type == GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; +} + + +void EtcTexture::bind() +{ + if (m_uploaded && m_texture_id) { + glBindTexture(GL_TEXTURE_2D, m_texture_id); + return; + } + + if (m_texture_id == 0) + glGenTextures(1, &m_texture_id); + glBindTexture(GL_TEXTURE_2D, m_texture_id); + +#ifdef ETC_DEBUG + qDebug() << "glCompressedTexImage2D, width: " << m_size.width() << "height" << m_size.height() << + "paddedWidth: " << m_paddedSize.width() << "paddedHeight: " << m_paddedSize.height(); +#endif + +#ifndef QT_NO_DEBUG + while (glGetError() != GL_NO_ERROR) { } +#endif + + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + Q_ASSERT(ctx != 0); + ctx->functions()->glCompressedTexImage2D(GL_TEXTURE_2D, 0, m_type, + m_size.width(), m_size.height(), 0, + (m_paddedSize.width() * m_paddedSize.height()) / 2, + m_data.data() + headerSize); + +#ifndef QT_NO_DEBUG + // Gracefully fail in case of an error... + GLuint error = glGetError(); + if (error != GL_NO_ERROR) { + qDebug () << "glCompressedTexImage2D for compressed texture failed, error: " << error; + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &m_texture_id); + m_texture_id = 0; + return; + } +#endif + + m_uploaded = true; + updateBindOptions(true); +} + +class QEtcTextureFactory : public QQuickTextureFactory +{ +public: + QByteArray m_data; + QSize m_size; + QSize m_paddedSize; + unsigned int m_type; + + QSize textureSize() const { return m_size; } + int textureByteCount() const { return m_data.size(); } + + QSGTexture *createTexture(QQuickWindow *) const { + EtcTexture *texture = new EtcTexture; + texture->m_data = m_data; + texture->m_size = m_size; + texture->m_paddedSize = m_paddedSize; + texture->m_type = m_type; + return texture; + } +}; + +QQuickTextureFactory *QSGPkmHandler::read(QIODevice *device) +{ + QScopedPointer ret(new QEtcTextureFactory); + ret->m_data = device->readAll(); + if (ret->m_data.isEmpty() || ret->m_data.size() < headerSize) + return nullptr; + + const char *rawData = ret->m_data.constData(); + + // magic number + if (qstrncmp(rawData, "PKM ", 4) != 0) + return nullptr; + + // currently ignore version (rawData + 4) + + // texture type + quint16 type = qFromBigEndian(rawData + 6); + static int typeCount = sizeof(typeMap)/sizeof(typeMap[0]); + if (type >= typeCount) + return nullptr; + ret->m_type = typeMap[type]; + + // texture size + ret->m_paddedSize.setWidth(qFromBigEndian(rawData + 8)); + ret->m_paddedSize.setHeight(qFromBigEndian(rawData + 10)); + if ((ret->m_paddedSize.width() * ret->m_paddedSize.height()) / 2 > ret->m_data.size() - headerSize) + return nullptr; + ret->m_size.setWidth(qFromBigEndian(rawData + 12)); + ret->m_size.setHeight(qFromBigEndian(rawData + 14)); + if (ret->m_size.isEmpty()) + return nullptr; + +#ifdef ETC_DEBUG + qDebug() << "requestTexture returning: " << ret->m_data.length() << "bytes; width: " << ret->m_size.width() << ", height: " << ret->m_size.height(); +#endif + + return ret.take(); +} + +QT_END_NAMESPACE diff --git a/src/quick/scenegraph/compressedtexture/qsgpkmhandler_p.h b/src/quick/scenegraph/compressedtexture/qsgpkmhandler_p.h new file mode 100644 index 0000000000..77097cb80a --- /dev/null +++ b/src/quick/scenegraph/compressedtexture/qsgpkmhandler_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QSGPKMHANDLER_H +#define QSGPKMHANDLER_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 +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QSGPkmHandler +{ +public: + QSGPkmHandler() {} + + QQuickTextureFactory *read(QIODevice *device); +}; + +class EtcTexture : public QSGTexture, protected QOpenGLFunctions +{ + Q_OBJECT +public: + EtcTexture(); + ~EtcTexture(); + + void bind(); + + QSize textureSize() const { return m_size; } + int textureId() const; + + bool hasAlphaChannel() const; + bool hasMipmaps() const { return false; } + + QByteArray m_data; + QSize m_size; + QSize m_paddedSize; + GLuint m_texture_id; + GLenum m_type; + bool m_uploaded; +}; + +QT_END_NAMESPACE + +#endif // QSGPKMHANDLER_H diff --git a/src/quick/scenegraph/scenegraph.pri b/src/quick/scenegraph/scenegraph.pri index c6db3df158..b5c72f521c 100644 --- a/src/quick/scenegraph/scenegraph.pri +++ b/src/quick/scenegraph/scenegraph.pri @@ -220,3 +220,18 @@ qtConfig(opengl(es1|es2)?) { $$PWD/shaders/visualization.frag \ $$PWD/shaders/visualization.vert } + +# Compressed Texture API +HEADERS += \ + $$PWD/util/qsgtexturereader_p.h + +SOURCES += \ + $$PWD/util/qsgtexturereader.cpp + +qtConfig(opengl(es1|es2)?) { + HEADERS += \ + $$PWD/compressedtexture/qsgpkmhandler_p.h + + SOURCES += \ + $$PWD/compressedtexture/qsgpkmhandler.cpp +} diff --git a/src/quick/scenegraph/util/qsgtexturereader.cpp b/src/quick/scenegraph/util/qsgtexturereader.cpp new file mode 100644 index 0000000000..61729ada18 --- /dev/null +++ b/src/quick/scenegraph/util/qsgtexturereader.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 "qsgtexturereader_p.h" + +#include + +#if QT_CONFIG(opengl) +#include +#endif + +QT_BEGIN_NAMESPACE + +QSGTextureReader::QSGTextureReader() +{ + +} + +QQuickTextureFactory *QSGTextureReader::read(QIODevice *device, const QByteArray &format) +{ +#if QT_CONFIG(opengl) + if (format == QByteArrayLiteral("pkm")) { + QSGPkmHandler handler; + return handler.read(device); + } +#else + Q_UNUSED(device) + Q_UNUSED(format) +#endif + return nullptr; +} + +bool QSGTextureReader::isTexture(QIODevice *device, const QByteArray &format) +{ +#if QT_CONFIG(opengl) + if (format == QByteArrayLiteral("pkm")) { + return device->peek(4) == QByteArrayLiteral("PKM "); + } +#else + Q_UNUSED(device) + Q_UNUSED(format) +#endif + return false; +} + +QT_END_NAMESPACE diff --git a/src/quick/scenegraph/util/qsgtexturereader_p.h b/src/quick/scenegraph/util/qsgtexturereader_p.h new file mode 100644 index 0000000000..7d2fc314a6 --- /dev/null +++ b/src/quick/scenegraph/util/qsgtexturereader_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QSGTEXTUREREADER_H +#define QSGTEXTUREREADER_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 + +QT_BEGIN_NAMESPACE + +class QIODevice; +class QQuickTextureFactory; + +class QSGTextureReader +{ +public: + QSGTextureReader(); + + static QQuickTextureFactory *read(QIODevice *device, const QByteArray &format); + static bool isTexture(QIODevice *device, const QByteArray &format); +}; + +QT_END_NAMESPACE + +#endif // QSGTEXTUREREADER_H diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp index e026608150..e218b84fff 100644 --- a/src/quick/util/qquickpixmapcache.cpp +++ b/src/quick/util/qquickpixmapcache.cpp @@ -49,6 +49,7 @@ #include #include +#include #include #include @@ -771,8 +772,26 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u QFile f(localFile); QSize readSize; if (f.open(QIODevice::ReadOnly)) { - if (!readImage(url, &f, &image, &errorStr, &readSize, runningJob->requestSize, runningJob->providerOptions)) - errorCode = QQuickPixmapReply::Loading; + + // for now, purely use suffix information to determine whether we are working with a compressed texture + QByteArray suffix = QFileInfo(f).suffix().toLower().toLatin1(); + if (QSGTextureReader::isTexture(&f, suffix)) { + QQuickTextureFactory *factory = QSGTextureReader::read(&f, suffix); + if (factory) { + readSize = factory->textureSize(); + } else { + errorStr = QQuickPixmap::tr("Error decoding: %1").arg(url.toString()); + errorCode = QQuickPixmapReply::Decoding; + } + mutex.lock(); + if (!cancelled.contains(runningJob)) + runningJob->postReply(errorCode, errorStr, readSize, factory); + mutex.unlock(); + return; + } else { + if (!readImage(url, &f, &image, &errorStr, &readSize, runningJob->requestSize, runningJob->providerOptions)) + errorCode = QQuickPixmapReply::Loading; + } } else { errorStr = QQuickPixmap::tr("Cannot open: %1").arg(url.toString()); errorCode = QQuickPixmapReply::Loading; @@ -1233,11 +1252,23 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q QString errorString; if (f.open(QIODevice::ReadOnly)) { - QImage image; - QQuickImageProviderOptions::AutoTransform appliedTransform = providerOptions.autoTransform(); - if (readImage(url, &f, &image, &errorString, &readSize, requestSize, providerOptions, &appliedTransform)) { - *ok = true; - return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestSize, providerOptions, appliedTransform); + // for now, purely use suffix information to determine whether we are working with a compressed texture + QByteArray suffix = QFileInfo(f).suffix().toLower().toLatin1(); + if (QSGTextureReader::isTexture(&f, suffix)) { + QQuickTextureFactory *factory = QSGTextureReader::read(&f, suffix); + if (factory) { + *ok = true; + return new QQuickPixmapData(declarativePixmap, factory); + } else { + errorString = QQuickPixmap::tr("Error decoding: %1").arg(url.toString()); + } + } else { + QImage image; + QQuickImageProviderOptions::AutoTransform appliedTransform = providerOptions.autoTransform(); + if (readImage(url, &f, &image, &errorString, &readSize, requestSize, providerOptions, &appliedTransform)) { + *ok = true; + return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestSize, providerOptions, appliedTransform); + } } } else { errorString = QQuickPixmap::tr("Cannot open: %1").arg(url.toString()); -- cgit v1.2.3