Support cubemap ktx files

Change-Id: I6905c393647d31fab958cdd9471bb0a6ffe83596
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
This commit is contained in:
Jonas Karlsson 2021-01-29 10:45:22 +01:00
parent 5caf4b074e
commit c5e9708ce6
5 changed files with 58 additions and 16 deletions

View File

@ -104,6 +104,13 @@ struct KTXMipmapLevel {
*/
};
// 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);
}
bool QKtxHandler::canRead(const QByteArray &suffix, const QByteArray &block)
{
Q_UNUSED(suffix);
@ -138,17 +145,25 @@ QTextureFileData QKtxHandler::read()
texData.setGLBaseInternalFormat(decode(header->glBaseInternalFormat));
texData.setNumLevels(decode(header->numberOfMipmapLevels));
texData.setNumFaces(1);
texData.setNumFaces(decode(header->numberOfFaces));
quint32 offset = headerSize + decode(header->bytesOfKeyValueData);
const int maxLevels = qMin(texData.numLevels(), 32); // Cap iterations in case of corrupt file.
for (int i = 0; i < maxLevels; i++) {
if (offset + sizeof(KTXMipmapLevel) > dataSize) // Corrupt file; avoid oob read
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
break;
const KTXMipmapLevel *level = reinterpret_cast<const KTXMipmapLevel *>(buf.constData() + offset);
quint32 levelLen = decode(level->imageSize);
texData.setDataOffset(offset + sizeof(KTXMipmapLevel::imageSize), i);
texData.setDataLength(levelLen, i);
offset += sizeof(KTXMipmapLevel::imageSize) + levelLen + (3 - ((levelLen + 3) % 4));
const quint32 imageSize = decode(qFromUnaligned<quint32>(buf.constData() + offset));
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);
}
}
if (!texData.isValid()) {
@ -176,9 +191,12 @@ bool QKtxHandler::checkHeader(const KTXHeader &header)
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));
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)));
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)));
qDebug(" pixelWidth: %u", decode(header.pixelWidth));
qDebug(" pixelHeight: %u", decode(header.pixelHeight));
qDebug(" pixelDepth: %u", decode(header.pixelDepth));
@ -187,10 +205,12 @@ bool QKtxHandler::checkHeader(const KTXHeader &header)
qDebug(" numberOfMipmapLevels: %u", decode(header.numberOfMipmapLevels));
qDebug(" bytesOfKeyValueData: %u", decode(header.bytesOfKeyValueData));
#endif
return ((decode(header.glType) == 0) &&
(decode(header.glFormat) == 0) &&
(decode(header.pixelDepth) == 0) &&
(decode(header.numberOfFaces) == 1));
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);
}
quint32 QKtxHandler::decode(quint32 val)

View File

@ -16,6 +16,7 @@ qt_internal_add_test(tst_qtexturefilereader
set(qtexturefilereader_resource_files
"texturefiles/car.ktx"
"texturefiles/car_mips.ktx"
"texturefiles/cubemap_float32_rgba.ktx"
"texturefiles/newlogo.astc"
"texturefiles/newlogo_srgb.astc"
"texturefiles/pattern.pkm"

View File

@ -1,6 +1,7 @@
<RCC>
<qresource prefix="/">
<file>texturefiles/car.ktx</file>
<file>texturefiles/cubemap_float32_rgba.ktx</file>
<file>texturefiles/pattern.pkm</file>
<file>texturefiles/car_mips.ktx</file>
<file>texturefiles/newlogo_srgb.astc</file>

View File

@ -46,6 +46,7 @@ void tst_qtexturefilereader::checkHandlers_data()
QTest::addColumn<quint32>("glInternalFormat");
QTest::addColumn<quint32>("glBaseInternalFormat");
QTest::addColumn<int>("levels");
QTest::addColumn<int>("faces");
QTest::addColumn<QList<int>>("dataOffsets");
QTest::addColumn<QList<int>>("dataLengths");
@ -56,6 +57,7 @@ void tst_qtexturefilereader::checkHandlers_data()
<< quint32(0x8d64)
<< quint32(0x0)
<< 1
<< 1
<< (QList<int>() << 16)
<< (QList<int>() << 2048);
@ -66,6 +68,7 @@ void tst_qtexturefilereader::checkHandlers_data()
<< quint32(0x9278)
<< quint32(0x1908)
<< 1
<< 1
<< (QList<int>() << 68)
<< (QList<int>() << 11840);
@ -76,9 +79,21 @@ void tst_qtexturefilereader::checkHandlers_data()
<< quint32(0x9274)
<< quint32(0x1907)
<< 8
<< 1
<< (QList<int>() << 68 << 5992 << 7516 << 7880 << 8004 << 8056 << 8068 << 8080)
<< (QList<int>() << 5920 << 1520 << 360 << 120 << 48 << 8 << 8 << 8);
QTest::addRow("cubemap_float32_rgba.ktx")
<< QStringLiteral(":/texturefiles/cubemap_float32_rgba.ktx")
<< QSize(16, 16)
<< quint32(0x1908)
<< quint32(0x8814)
<< quint32(0x1908)
<< 5
<< 6
<< (QList<int>() << 96 << 24676 << 30824 << 32364 << 32752)
<< (QList<int>() << 4096 << 1024 << 256 << 64 << 16);
QTest::addRow("newlogo.astc")
<< QStringLiteral(":/texturefiles/newlogo.astc")
<< QSize(111, 78)
@ -86,6 +101,7 @@ void tst_qtexturefilereader::checkHandlers_data()
<< quint32(0x93b9)
<< quint32(0x0)
<< 1
<< 1
<< (QList<int>() << 16)
<< (QList<int>() << 2496);
@ -96,6 +112,7 @@ void tst_qtexturefilereader::checkHandlers_data()
<< quint32(0x93d9)
<< quint32(0x0)
<< 1
<< 1
<< (QList<int>() << 16)
<< (QList<int>() << 2496);
}
@ -107,6 +124,7 @@ void tst_qtexturefilereader::checkHandlers()
QFETCH(quint32, glFormat);
QFETCH(quint32, glInternalFormat);
QFETCH(int, levels);
QFETCH(int, faces);
QFETCH(QList<int>, dataOffsets);
QFETCH(QList<int>, dataLengths);
@ -122,6 +140,8 @@ void tst_qtexturefilereader::checkHandlers()
QCOMPARE(tex.glFormat(), glFormat);
QCOMPARE(tex.glInternalFormat(), glInternalFormat);
QCOMPARE(tex.numLevels(), levels);
QCOMPARE(tex.numFaces(), faces);
for (int i = 0; i < tex.numLevels(); i++) {
QCOMPARE(tex.dataOffset(i), dataOffsets.at(i));
QCOMPARE(tex.dataLength(i), dataLengths.at(i));