2018-07-03 10:30:46 +00:00
|
|
|
/****************************************************************************
|
|
|
|
**
|
|
|
|
** Copyright (C) 2017 The Qt Company Ltd.
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
**
|
|
|
|
** This file is part of the QtGui 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 "qktxhandler_p.h"
|
|
|
|
#include "qtexturefiledata_p.h"
|
|
|
|
#include <QtEndian>
|
|
|
|
#include <QSize>
|
2022-02-06 19:54:00 +00:00
|
|
|
#include <QMap>
|
2020-08-07 09:46:07 +00:00
|
|
|
#include <QtCore/qiodevice.h>
|
2018-07-03 10:30:46 +00:00
|
|
|
|
|
|
|
//#define KTX_DEBUG
|
2018-08-15 08:58:12 +00:00
|
|
|
#ifdef KTX_DEBUG
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QMetaEnum>
|
|
|
|
#include <QOpenGLTexture>
|
|
|
|
#endif
|
2018-07-03 10:30:46 +00:00
|
|
|
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
|
2022-04-11 12:04:17 +00:00
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
|
2018-07-03 10:30:46 +00:00
|
|
|
#define KTX_IDENTIFIER_LENGTH 12
|
|
|
|
static const char ktxIdentifier[KTX_IDENTIFIER_LENGTH] = { '\xAB', 'K', 'T', 'X', ' ', '1', '1', '\xBB', '\r', '\n', '\x1A', '\n' };
|
|
|
|
static const quint32 platformEndianIdentifier = 0x04030201;
|
|
|
|
static const quint32 inversePlatformEndianIdentifier = 0x01020304;
|
|
|
|
|
|
|
|
struct KTXHeader {
|
|
|
|
quint8 identifier[KTX_IDENTIFIER_LENGTH]; // Must match ktxIdentifier
|
|
|
|
quint32 endianness; // Either platformEndianIdentifier or inversePlatformEndianIdentifier, other values not allowed.
|
|
|
|
quint32 glType;
|
|
|
|
quint32 glTypeSize;
|
|
|
|
quint32 glFormat;
|
|
|
|
quint32 glInternalFormat;
|
|
|
|
quint32 glBaseInternalFormat;
|
|
|
|
quint32 pixelWidth;
|
|
|
|
quint32 pixelHeight;
|
|
|
|
quint32 pixelDepth;
|
|
|
|
quint32 numberOfArrayElements;
|
|
|
|
quint32 numberOfFaces;
|
|
|
|
quint32 numberOfMipmapLevels;
|
|
|
|
quint32 bytesOfKeyValueData;
|
|
|
|
};
|
|
|
|
|
2018-08-15 08:58:12 +00:00
|
|
|
static const quint32 headerSize = sizeof(KTXHeader);
|
2018-07-03 10:30:46 +00:00
|
|
|
|
|
|
|
// Currently unused, declared for future reference
|
|
|
|
struct KTXKeyValuePairItem {
|
|
|
|
quint32 keyAndValueByteSize;
|
|
|
|
/*
|
|
|
|
quint8 keyAndValue[keyAndValueByteSize];
|
|
|
|
quint8 valuePadding[3 - ((keyAndValueByteSize + 3) % 4)];
|
|
|
|
*/
|
|
|
|
};
|
|
|
|
|
|
|
|
struct KTXMipmapLevel {
|
|
|
|
quint32 imageSize;
|
|
|
|
/*
|
|
|
|
for each array_element in numberOfArrayElements*
|
|
|
|
for each face in numberOfFaces
|
|
|
|
for each z_slice in pixelDepth*
|
|
|
|
for each row or row_of_blocks in pixelHeight*
|
|
|
|
for each pixel or block_of_pixels in pixelWidth
|
|
|
|
Byte data[format-specific-number-of-bytes]**
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
Byte cubePadding[0-3]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
quint8 mipPadding[3 - ((imageSize + 3) % 4)]
|
|
|
|
*/
|
|
|
|
};
|
|
|
|
|
2021-01-29 09:45:22 +00:00
|
|
|
// Returns the nearest multiple of 'rounding' greater than or equal to 'value'
|
|
|
|
constexpr quint32 withPadding(quint32 value, quint32 rounding)
|
|
|
|
{
|
|
|
|
Q_ASSERT(rounding > 1);
|
|
|
|
return value + (rounding - 1) - ((value + (rounding - 1)) % rounding);
|
|
|
|
}
|
|
|
|
|
2022-02-11 16:03:48 +00:00
|
|
|
QKtxHandler::~QKtxHandler() = default;
|
|
|
|
|
2018-07-03 10:30:46 +00:00
|
|
|
bool QKtxHandler::canRead(const QByteArray &suffix, const QByteArray &block)
|
|
|
|
{
|
2020-06-27 12:18:09 +00:00
|
|
|
Q_UNUSED(suffix);
|
2018-07-03 10:30:46 +00:00
|
|
|
|
|
|
|
return (qstrncmp(block.constData(), ktxIdentifier, KTX_IDENTIFIER_LENGTH) == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
QTextureFileData QKtxHandler::read()
|
|
|
|
{
|
|
|
|
if (!device())
|
|
|
|
return QTextureFileData();
|
|
|
|
|
2022-04-08 22:06:22 +00:00
|
|
|
const QByteArray buf = device()->readAll();
|
2018-08-15 08:58:12 +00:00
|
|
|
const quint32 dataSize = quint32(buf.size());
|
|
|
|
if (dataSize < headerSize || !canRead(QByteArray(), buf)) {
|
2018-07-03 10:30:46 +00:00
|
|
|
qCDebug(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData());
|
|
|
|
return QTextureFileData();
|
|
|
|
}
|
|
|
|
|
2022-04-08 22:06:22 +00:00
|
|
|
const KTXHeader *header = reinterpret_cast<const KTXHeader *>(buf.data());
|
2018-07-03 10:30:46 +00:00
|
|
|
if (!checkHeader(*header)) {
|
|
|
|
qCDebug(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData());
|
|
|
|
return QTextureFileData();
|
|
|
|
}
|
|
|
|
|
|
|
|
QTextureFileData texData;
|
|
|
|
texData.setData(buf);
|
|
|
|
|
|
|
|
texData.setSize(QSize(decode(header->pixelWidth), decode(header->pixelHeight)));
|
|
|
|
texData.setGLFormat(decode(header->glFormat));
|
|
|
|
texData.setGLInternalFormat(decode(header->glInternalFormat));
|
|
|
|
texData.setGLBaseInternalFormat(decode(header->glBaseInternalFormat));
|
|
|
|
|
2018-08-15 08:58:12 +00:00
|
|
|
texData.setNumLevels(decode(header->numberOfMipmapLevels));
|
2021-01-29 09:45:22 +00:00
|
|
|
texData.setNumFaces(decode(header->numberOfFaces));
|
2021-02-04 14:37:53 +00:00
|
|
|
|
|
|
|
const quint32 bytesOfKeyValueData = decode(header->bytesOfKeyValueData);
|
2021-03-15 18:04:24 +00:00
|
|
|
if (headerSize + bytesOfKeyValueData < quint64(buf.length())) // oob check
|
2021-02-04 14:37:53 +00:00
|
|
|
texData.setKeyValueMetadata(
|
|
|
|
decodeKeyValues(QByteArrayView(buf.data() + headerSize, bytesOfKeyValueData)));
|
|
|
|
quint32 offset = headerSize + bytesOfKeyValueData;
|
2021-01-29 09:45:22 +00:00
|
|
|
|
|
|
|
constexpr int MAX_ITERATIONS = 32; // cap iterations in case of corrupt data
|
|
|
|
|
|
|
|
for (int level = 0; level < qMin(texData.numLevels(), MAX_ITERATIONS); level++) {
|
|
|
|
if (offset + sizeof(quint32) > dataSize) // Corrupt file; avoid oob read
|
2018-08-15 08:58:12 +00:00
|
|
|
break;
|
2021-01-29 09:45:22 +00:00
|
|
|
|
2022-04-08 22:06:22 +00:00
|
|
|
const quint32 imageSize = decode(qFromUnaligned<quint32>(buf.data() + offset));
|
2021-01-29 09:45:22 +00:00
|
|
|
offset += sizeof(quint32);
|
|
|
|
|
|
|
|
for (int face = 0; face < qMin(texData.numFaces(), MAX_ITERATIONS); face++) {
|
|
|
|
texData.setDataOffset(offset, level, face);
|
|
|
|
texData.setDataLength(imageSize, level, face);
|
|
|
|
|
|
|
|
// Add image data and padding to offset
|
|
|
|
offset += withPadding(imageSize, 4);
|
|
|
|
}
|
2018-07-03 10:30:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!texData.isValid()) {
|
|
|
|
qCDebug(lcQtGuiTextureIO, "Invalid values in header of KTX file %s", logName().constData());
|
|
|
|
return QTextureFileData();
|
|
|
|
}
|
|
|
|
|
|
|
|
texData.setLogName(logName());
|
|
|
|
|
|
|
|
#ifdef KTX_DEBUG
|
2018-08-15 08:58:12 +00:00
|
|
|
qDebug() << "KTX file handler read" << texData;
|
2018-07-03 10:30:46 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return texData;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QKtxHandler::checkHeader(const KTXHeader &header)
|
|
|
|
{
|
|
|
|
if (header.endianness != platformEndianIdentifier && header.endianness != inversePlatformEndianIdentifier)
|
|
|
|
return false;
|
|
|
|
inverseEndian = (header.endianness == inversePlatformEndianIdentifier);
|
|
|
|
#ifdef KTX_DEBUG
|
|
|
|
QMetaEnum tfme = QMetaEnum::fromType<QOpenGLTexture::TextureFormat>();
|
|
|
|
QMetaEnum ptme = QMetaEnum::fromType<QOpenGLTexture::PixelType>();
|
|
|
|
qDebug("Header of %s:", logName().constData());
|
|
|
|
qDebug(" glType: 0x%x (%s)", decode(header.glType), ptme.valueToKey(decode(header.glType)));
|
|
|
|
qDebug(" glTypeSize: %u", decode(header.glTypeSize));
|
2021-01-29 09:45:22 +00:00
|
|
|
qDebug(" glFormat: 0x%x (%s)", decode(header.glFormat),
|
|
|
|
tfme.valueToKey(decode(header.glFormat)));
|
|
|
|
qDebug(" glInternalFormat: 0x%x (%s)", decode(header.glInternalFormat),
|
|
|
|
tfme.valueToKey(decode(header.glInternalFormat)));
|
|
|
|
qDebug(" glBaseInternalFormat: 0x%x (%s)", decode(header.glBaseInternalFormat),
|
|
|
|
tfme.valueToKey(decode(header.glBaseInternalFormat)));
|
2018-07-03 10:30:46 +00:00
|
|
|
qDebug(" pixelWidth: %u", decode(header.pixelWidth));
|
|
|
|
qDebug(" pixelHeight: %u", decode(header.pixelHeight));
|
|
|
|
qDebug(" pixelDepth: %u", decode(header.pixelDepth));
|
|
|
|
qDebug(" numberOfArrayElements: %u", decode(header.numberOfArrayElements));
|
|
|
|
qDebug(" numberOfFaces: %u", decode(header.numberOfFaces));
|
|
|
|
qDebug(" numberOfMipmapLevels: %u", decode(header.numberOfMipmapLevels));
|
|
|
|
qDebug(" bytesOfKeyValueData: %u", decode(header.bytesOfKeyValueData));
|
|
|
|
#endif
|
2021-01-29 09:45:22 +00:00
|
|
|
const bool isCompressedImage = decode(header.glType) == 0 && decode(header.glFormat) == 0
|
|
|
|
&& decode(header.pixelDepth) == 0;
|
|
|
|
const bool isCubeMap = decode(header.numberOfFaces) == 6;
|
|
|
|
const bool is2D = decode(header.pixelDepth) == 0 && decode(header.numberOfArrayElements) == 0;
|
|
|
|
|
|
|
|
return is2D && (isCubeMap || isCompressedImage);
|
2018-07-03 10:30:46 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 14:37:53 +00:00
|
|
|
QMap<QByteArray, QByteArray> QKtxHandler::decodeKeyValues(QByteArrayView view) const
|
|
|
|
{
|
|
|
|
QMap<QByteArray, QByteArray> output;
|
|
|
|
quint32 offset = 0;
|
|
|
|
while (offset < view.size() + sizeof(quint32)) {
|
|
|
|
const quint32 keyAndValueByteSize =
|
|
|
|
decode(qFromUnaligned<quint32>(view.constData() + offset));
|
|
|
|
offset += sizeof(quint32);
|
|
|
|
|
2021-03-15 18:04:24 +00:00
|
|
|
if (offset + keyAndValueByteSize > quint64(view.size()))
|
2021-02-04 14:37:53 +00:00
|
|
|
break; // oob read
|
|
|
|
|
|
|
|
// 'key' is a UTF-8 string ending with a null terminator, 'value' is the rest.
|
|
|
|
// To separate the key and value we convert the complete data to utf-8 and find the first
|
|
|
|
// null terminator from the left, here we split the data into two.
|
|
|
|
const auto str = QString::fromUtf8(view.constData() + offset, keyAndValueByteSize);
|
2022-04-11 12:04:17 +00:00
|
|
|
const int idx = str.indexOf('\0'_L1);
|
2021-02-04 14:37:53 +00:00
|
|
|
if (idx == -1)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const QByteArray key = str.left(idx).toUtf8();
|
|
|
|
const size_t keySize = key.size() + 1; // Actual data size
|
|
|
|
const QByteArray value = QByteArray::fromRawData(view.constData() + offset + keySize,
|
|
|
|
keyAndValueByteSize - keySize);
|
|
|
|
|
|
|
|
offset = withPadding(offset + keyAndValueByteSize, 4);
|
|
|
|
output.insert(key, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
quint32 QKtxHandler::decode(quint32 val) const
|
2018-07-03 10:30:46 +00:00
|
|
|
{
|
|
|
|
return inverseEndian ? qbswap<quint32>(val) : val;
|
|
|
|
}
|
|
|
|
|
|
|
|
QT_END_NAMESPACE
|