1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qktxhandler_p.h"
41#include "qtexturefiledata_p.h"
42#include <QtEndian>
43#include <QSize>
44#include <QtCore/qiodevice.h>
45
46//#define KTX_DEBUG
47#ifdef KTX_DEBUG
48#include <QDebug>
49#include <QMetaEnum>
50#include <QOpenGLTexture>
51#endif
52
53QT_BEGIN_NAMESPACE
54
55#define KTX_IDENTIFIER_LENGTH 12
56static const char ktxIdentifier[KTX_IDENTIFIER_LENGTH] = { '\xAB', 'K', 'T', 'X', ' ', '1', '1', '\xBB', '\r', '\n', '\x1A', '\n' };
57static const quint32 platformEndianIdentifier = 0x04030201;
58static const quint32 inversePlatformEndianIdentifier = 0x01020304;
59
60struct KTXHeader {
61 quint8 identifier[KTX_IDENTIFIER_LENGTH]; // Must match ktxIdentifier
62 quint32 endianness; // Either platformEndianIdentifier or inversePlatformEndianIdentifier, other values not allowed.
63 quint32 glType;
64 quint32 glTypeSize;
65 quint32 glFormat;
66 quint32 glInternalFormat;
67 quint32 glBaseInternalFormat;
68 quint32 pixelWidth;
69 quint32 pixelHeight;
70 quint32 pixelDepth;
71 quint32 numberOfArrayElements;
72 quint32 numberOfFaces;
73 quint32 numberOfMipmapLevels;
74 quint32 bytesOfKeyValueData;
75};
76
77static const quint32 headerSize = sizeof(KTXHeader);
78
79// Currently unused, declared for future reference
80struct KTXKeyValuePairItem {
81 quint32 keyAndValueByteSize;
82 /*
83 quint8 keyAndValue[keyAndValueByteSize];
84 quint8 valuePadding[3 - ((keyAndValueByteSize + 3) % 4)];
85 */
86};
87
88struct KTXMipmapLevel {
89 quint32 imageSize;
90 /*
91 for each array_element in numberOfArrayElements*
92 for each face in numberOfFaces
93 for each z_slice in pixelDepth*
94 for each row or row_of_blocks in pixelHeight*
95 for each pixel or block_of_pixels in pixelWidth
96 Byte data[format-specific-number-of-bytes]**
97 end
98 end
99 end
100 Byte cubePadding[0-3]
101 end
102 end
103 quint8 mipPadding[3 - ((imageSize + 3) % 4)]
104 */
105};
106
107bool QKtxHandler::canRead(const QByteArray &suffix, const QByteArray &block)
108{
109 Q_UNUSED(suffix);
110
111 return (qstrncmp(block.constData(), ktxIdentifier, KTX_IDENTIFIER_LENGTH) == 0);
112}
113
114QTextureFileData QKtxHandler::read()
115{
116 if (!device())
117 return QTextureFileData();
118
119 QByteArray buf = device()->readAll();
120 const quint32 dataSize = quint32(buf.size());
121 if (dataSize < headerSize || !canRead(QByteArray(), buf)) {
122 qCDebug(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData());
123 return QTextureFileData();
124 }
125
126 const KTXHeader *header = reinterpret_cast<const KTXHeader *>(buf.constData());
127 if (!checkHeader(*header)) {
128 qCDebug(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData());
129 return QTextureFileData();
130 }
131
132 QTextureFileData texData;
133 texData.setData(buf);
134
135 texData.setSize(QSize(decode(header->pixelWidth), decode(header->pixelHeight)));
136 texData.setGLFormat(decode(header->glFormat));
137 texData.setGLInternalFormat(decode(header->glInternalFormat));
138 texData.setGLBaseInternalFormat(decode(header->glBaseInternalFormat));
139
140 texData.setNumLevels(decode(header->numberOfMipmapLevels));
141 quint32 offset = headerSize + decode(header->bytesOfKeyValueData);
142 const int maxLevels = qMin(texData.numLevels(), 32); // Cap iterations in case of corrupt file.
143 for (int i = 0; i < maxLevels; i++) {
144 if (offset + sizeof(KTXMipmapLevel) > dataSize) // Corrupt file; avoid oob read
145 break;
146 const KTXMipmapLevel *level = reinterpret_cast<const KTXMipmapLevel *>(buf.constData() + offset);
147 quint32 levelLen = decode(level->imageSize);
148 texData.setDataOffset(offset + sizeof(KTXMipmapLevel::imageSize), i);
149 texData.setDataLength(levelLen, i);
150 offset += sizeof(KTXMipmapLevel::imageSize) + levelLen + (3 - ((levelLen + 3) % 4));
151 }
152
153 if (!texData.isValid()) {
154 qCDebug(lcQtGuiTextureIO, "Invalid values in header of KTX file %s", logName().constData());
155 return QTextureFileData();
156 }
157
158 texData.setLogName(logName());
159
160#ifdef KTX_DEBUG
161 qDebug() << "KTX file handler read" << texData;
162#endif
163
164 return texData;
165}
166
167bool QKtxHandler::checkHeader(const KTXHeader &header)
168{
169 if (header.endianness != platformEndianIdentifier && header.endianness != inversePlatformEndianIdentifier)
170 return false;
171 inverseEndian = (header.endianness == inversePlatformEndianIdentifier);
172#ifdef KTX_DEBUG
173 QMetaEnum tfme = QMetaEnum::fromType<QOpenGLTexture::TextureFormat>();
174 QMetaEnum ptme = QMetaEnum::fromType<QOpenGLTexture::PixelType>();
175 qDebug("Header of %s:", logName().constData());
176 qDebug(" glType: 0x%x (%s)", decode(header.glType), ptme.valueToKey(decode(header.glType)));
177 qDebug(" glTypeSize: %u", decode(header.glTypeSize));
178 qDebug(" glFormat: 0x%x (%s)", decode(header.glFormat), tfme.valueToKey(decode(header.glFormat)));
179 qDebug(" glInternalFormat: 0x%x (%s)", decode(header.glInternalFormat), tfme.valueToKey(decode(header.glInternalFormat)));
180 qDebug(" glBaseInternalFormat: 0x%x (%s)", decode(header.glBaseInternalFormat), tfme.valueToKey(decode(header.glBaseInternalFormat)));
181 qDebug(" pixelWidth: %u", decode(header.pixelWidth));
182 qDebug(" pixelHeight: %u", decode(header.pixelHeight));
183 qDebug(" pixelDepth: %u", decode(header.pixelDepth));
184 qDebug(" numberOfArrayElements: %u", decode(header.numberOfArrayElements));
185 qDebug(" numberOfFaces: %u", decode(header.numberOfFaces));
186 qDebug(" numberOfMipmapLevels: %u", decode(header.numberOfMipmapLevels));
187 qDebug(" bytesOfKeyValueData: %u", decode(header.bytesOfKeyValueData));
188#endif
189 return ((decode(header.glType) == 0) &&
190 (decode(header.glFormat) == 0) &&
191 (decode(header.pixelDepth) == 0) &&
192 (decode(header.numberOfFaces) == 1));
193}
194
195quint32 QKtxHandler::decode(quint32 val)
196{
197 return inverseEndian ? qbswap<quint32>(val) : val;
198}
199
200QT_END_NAMESPACE
201