Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qktxhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qktxhandler_p.h"
6#include <QtEndian>
7#include <QSize>
8#include <QMap>
9#include <QtCore/qiodevice.h>
10
11//#define KTX_DEBUG
12#ifdef KTX_DEBUG
13#include <QDebug>
14#include <QMetaEnum>
15#include <QOpenGLTexture>
16#endif
17
19
20using namespace Qt::StringLiterals;
21
22#define KTX_IDENTIFIER_LENGTH 12
23static const char ktxIdentifier[KTX_IDENTIFIER_LENGTH] = { '\xAB', 'K', 'T', 'X', ' ', '1', '1', '\xBB', '\r', '\n', '\x1A', '\n' };
24static const quint32 platformEndianIdentifier = 0x04030201;
25static const quint32 inversePlatformEndianIdentifier = 0x01020304;
26
43
44static constexpr quint32 qktxh_headerSize = sizeof(KTXHeader);
45
46// Currently unused, declared for future reference
49 /*
50 quint8 keyAndValue[keyAndValueByteSize];
51 quint8 valuePadding[3 - ((keyAndValueByteSize + 3) % 4)];
52 */
53};
54
57 /*
58 for each array_element in numberOfArrayElements*
59 for each face in numberOfFaces
60 for each z_slice in pixelDepth*
61 for each row or row_of_blocks in pixelHeight*
62 for each pixel or block_of_pixels in pixelWidth
63 Byte data[format-specific-number-of-bytes]**
64 end
65 end
66 end
67 Byte cubePadding[0-3]
68 end
69 end
70 quint8 mipPadding[3 - ((imageSize + 3) % 4)]
71 */
72};
73
74// Returns the nearest multiple of 4 greater than or equal to 'value'
75static const std::optional<quint32> nearestMultipleOf4(quint32 value)
76{
77 constexpr quint32 rounding = 4;
78 quint32 result = 0;
79 if (qAddOverflow(value, rounding - 1, &result))
80 return std::nullopt;
81 result &= ~(rounding - 1);
82 return result;
83}
84
85// Returns a view with prechecked bounds
87{
88 quint32 end = 0;
89 if (qAddOverflow(start, length, &end) || end > quint32(view.length()))
90 return {};
91 return view.sliced(start, length);
92}
93
95
96bool QKtxHandler::canRead(const QByteArray &suffix, const QByteArray &block)
97{
98 Q_UNUSED(suffix);
99 return block.startsWith(ktxIdentifier);
100}
101
103{
104 if (!device())
105 return QTextureFileData();
106
107 const QByteArray buf = device()->readAll();
108 if (static_cast<size_t>(buf.size()) > std::numeric_limits<quint32>::max()) {
109 qWarning(lcQtGuiTextureIO, "Too big KTX file %s", logName().constData());
110 return QTextureFileData();
111 }
112
113 if (!canRead(QByteArray(), buf)) {
114 qWarning(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData());
115 return QTextureFileData();
116 }
117
118 if (buf.size() < qsizetype(qktxh_headerSize)) {
119 qWarning(lcQtGuiTextureIO, "Invalid KTX header size in %s", logName().constData());
120 return QTextureFileData();
121 }
122
124 memcpy(&header, buf.data(), qktxh_headerSize);
125 if (!checkHeader(header)) {
126 qWarning(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData());
127 return QTextureFileData();
128 }
129
130 QTextureFileData texData;
131 texData.setData(buf);
132
133 texData.setSize(QSize(decode(header.pixelWidth), decode(header.pixelHeight)));
134 texData.setGLFormat(decode(header.glFormat));
135 texData.setGLInternalFormat(decode(header.glInternalFormat));
136 texData.setGLBaseInternalFormat(decode(header.glBaseInternalFormat));
137
138 texData.setNumLevels(decode(header.numberOfMipmapLevels));
139 texData.setNumFaces(decode(header.numberOfFaces));
140
141 const quint32 bytesOfKeyValueData = decode(header.bytesOfKeyValueData);
142 quint32 headerKeyValueSize;
143 if (qAddOverflow(qktxh_headerSize, bytesOfKeyValueData, &headerKeyValueSize)) {
144 qWarning(lcQtGuiTextureIO, "Overflow in size of key value data in header of KTX file %s",
145 logName().constData());
146 return QTextureFileData();
147 }
148
149 if (headerKeyValueSize >= quint32(buf.size())) {
150 qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData());
151 return QTextureFileData();
152 }
153
154 // File contains key/values
155 if (bytesOfKeyValueData > 0) {
156 auto keyValueDataView = safeView(buf, qktxh_headerSize, bytesOfKeyValueData);
157 if (keyValueDataView.isEmpty()) {
158 qWarning(lcQtGuiTextureIO, "Invalid view in KTX file %s", logName().constData());
159 return QTextureFileData();
160 }
161
162 auto keyValues = decodeKeyValues(keyValueDataView);
163 if (!keyValues) {
164 qWarning(lcQtGuiTextureIO, "Could not parse key values in KTX file %s",
165 logName().constData());
166 return QTextureFileData();
167 }
168
169 texData.setKeyValueMetadata(*keyValues);
170 }
171
172 // Technically, any number of levels is allowed but if the value is bigger than
173 // what is possible in KTX V2 (and what makes sense) we return an error.
174 // maxLevels = log2(max(width, height, depth))
175 const int maxLevels = (sizeof(quint32) * 8)
176 - qCountLeadingZeroBits(std::max(
177 { header.pixelWidth, header.pixelHeight, header.pixelDepth }));
178
179 if (texData.numLevels() > maxLevels) {
180 qWarning(lcQtGuiTextureIO, "Too many levels in KTX file %s", logName().constData());
181 return QTextureFileData();
182 }
183
184 if (texData.numFaces() != 1 && texData.numFaces() != 6) {
185 qWarning(lcQtGuiTextureIO, "Invalid number of faces in KTX file %s", logName().constData());
186 return QTextureFileData();
187 }
188
189 quint32 offset = headerKeyValueSize;
190 for (int level = 0; level < texData.numLevels(); level++) {
191 const auto imageSizeView = safeView(buf, offset, sizeof(quint32));
192 if (imageSizeView.isEmpty()) {
193 qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData());
194 return QTextureFileData();
195 }
196
197 const quint32 imageSize = decode(qFromUnaligned<quint32>(imageSizeView.data()));
198 offset += sizeof(quint32); // overflow checked indirectly above
199
200 for (int face = 0; face < texData.numFaces(); face++) {
201 texData.setDataOffset(offset, level, face);
202 texData.setDataLength(imageSize, level, face);
203
204 // Add image data and padding to offset
205 const auto padded = nearestMultipleOf4(imageSize);
206 if (!padded) {
207 qWarning(lcQtGuiTextureIO, "Overflow in KTX file %s", logName().constData());
208 return QTextureFileData();
209 }
210
211 quint32 offsetNext;
212 if (qAddOverflow(offset, *padded, &offsetNext)) {
213 qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData());
214 return QTextureFileData();
215 }
216
217 offset = offsetNext;
218 }
219 }
220
221 if (!texData.isValid()) {
222 qWarning(lcQtGuiTextureIO, "Invalid values in header of KTX file %s",
223 logName().constData());
224 return QTextureFileData();
225 }
226
227 texData.setLogName(logName());
228
229#ifdef KTX_DEBUG
230 qDebug() << "KTX file handler read" << texData;
231#endif
232
233 return texData;
234}
235
236bool QKtxHandler::checkHeader(const KTXHeader &header)
237{
239 return false;
240 inverseEndian = (header.endianness == inversePlatformEndianIdentifier);
241#ifdef KTX_DEBUG
242 QMetaEnum tfme = QMetaEnum::fromType<QOpenGLTexture::TextureFormat>();
243 QMetaEnum ptme = QMetaEnum::fromType<QOpenGLTexture::PixelType>();
244 qDebug("Header of %s:", logName().constData());
245 qDebug(" glType: 0x%x (%s)", decode(header.glType), ptme.valueToKey(decode(header.glType)));
246 qDebug(" glTypeSize: %u", decode(header.glTypeSize));
247 qDebug(" glFormat: 0x%x (%s)", decode(header.glFormat),
248 tfme.valueToKey(decode(header.glFormat)));
249 qDebug(" glInternalFormat: 0x%x (%s)", decode(header.glInternalFormat),
250 tfme.valueToKey(decode(header.glInternalFormat)));
251 qDebug(" glBaseInternalFormat: 0x%x (%s)", decode(header.glBaseInternalFormat),
252 tfme.valueToKey(decode(header.glBaseInternalFormat)));
253 qDebug(" pixelWidth: %u", decode(header.pixelWidth));
254 qDebug(" pixelHeight: %u", decode(header.pixelHeight));
255 qDebug(" pixelDepth: %u", decode(header.pixelDepth));
256 qDebug(" numberOfArrayElements: %u", decode(header.numberOfArrayElements));
257 qDebug(" numberOfFaces: %u", decode(header.numberOfFaces));
258 qDebug(" numberOfMipmapLevels: %u", decode(header.numberOfMipmapLevels));
259 qDebug(" bytesOfKeyValueData: %u", decode(header.bytesOfKeyValueData));
260#endif
261 const bool isCompressedImage = decode(header.glType) == 0 && decode(header.glFormat) == 0
262 && decode(header.pixelDepth) == 0;
263 const bool isCubeMap = decode(header.numberOfFaces) == 6;
264 const bool is2D = decode(header.pixelDepth) == 0 && decode(header.numberOfArrayElements) == 0;
265
266 return is2D && (isCubeMap || isCompressedImage);
267}
268
269std::optional<QMap<QByteArray, QByteArray>> QKtxHandler::decodeKeyValues(QByteArrayView view) const
270{
271 QMap<QByteArray, QByteArray> output;
272 quint32 offset = 0;
273 while (offset < quint32(view.size())) {
274 const auto keyAndValueByteSizeView = safeView(view, offset, sizeof(quint32));
275 if (keyAndValueByteSizeView.isEmpty()) {
276 qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value");
277 return std::nullopt;
278 }
279
280 const quint32 keyAndValueByteSize =
281 decode(qFromUnaligned<quint32>(keyAndValueByteSizeView.data()));
282
283 quint32 offsetKeyAndValueStart;
284 if (qAddOverflow(offset, quint32(sizeof(quint32)), &offsetKeyAndValueStart)) {
285 qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
286 return std::nullopt;
287 }
288
289 quint32 offsetKeyAndValueEnd;
290 if (qAddOverflow(offsetKeyAndValueStart, keyAndValueByteSize, &offsetKeyAndValueEnd)) {
291 qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
292 return std::nullopt;
293 }
294
295 const auto keyValueView = safeView(view, offsetKeyAndValueStart, keyAndValueByteSize);
296 if (keyValueView.isEmpty()) {
297 qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value");
298 return std::nullopt;
299 }
300
301 // 'key' is a UTF-8 string ending with a null terminator, 'value' is the rest.
302 // To separate the key and value we convert the complete data to utf-8 and find the first
303 // null terminator from the left, here we split the data into two.
304
305 const int idx = keyValueView.indexOf('\0');
306 if (idx == -1) {
307 qWarning(lcQtGuiTextureIO, "Invalid key in KTX key-value");
308 return std::nullopt;
309 }
310
311 const QByteArrayView keyView = safeView(view, offsetKeyAndValueStart, idx);
312 if (keyView.isEmpty()) {
313 qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
314 return std::nullopt;
315 }
316
317 const quint32 keySize = idx + 1; // Actual data size
318
319 quint32 offsetValueStart;
320 if (qAddOverflow(offsetKeyAndValueStart, keySize, &offsetValueStart)) {
321 qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
322 return std::nullopt;
323 }
324
325 quint32 valueSize;
326 if (qSubOverflow(keyAndValueByteSize, keySize, &valueSize)) {
327 qWarning(lcQtGuiTextureIO, "Underflow in KTX key-value");
328 return std::nullopt;
329 }
330
331 const QByteArrayView valueView = safeView(view, offsetValueStart, valueSize);
332 if (valueView.isEmpty()) {
333 qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value");
334 return std::nullopt;
335 }
336
337 output.insert(keyView.toByteArray(), valueView.toByteArray());
338
339 const auto offsetNext = nearestMultipleOf4(offsetKeyAndValueEnd);
340 if (!offsetNext) {
341 qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
342 return std::nullopt;
343 }
344
345 offset = *offsetNext;
346 }
347
348 return output;
349}
350
351quint32 QKtxHandler::decode(quint32 val) const
352{
353 return inverseEndian ? qbswap<quint32>(val) : val;
354}
355
\inmodule QtCore
Definition qbytearray.h:57
bool startsWith(QByteArrayView bv) const
Definition qbytearray.h:223
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
static bool canRead(const QByteArray &suffix, const QByteArray &block)
~QKtxHandler() override
QTextureFileData read() override
\inmodule QtCore
\inmodule QtCore
Definition qsize.h:25
void setData(const QByteArray &data)
QIODevice * device() const
QByteArray logName() const
Combined button and popup list for selecting options.
QT_POPCOUNT_RELAXED_CONSTEXPR uint qCountLeadingZeroBits(quint32 v) noexcept
static QString header(const QString &name)
static bool isCubeMap(const DDSHeader &dds)
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
static const quint32 platformEndianIdentifier
static const std::optional< quint32 > nearestMultipleOf4(quint32 value)
static QByteArrayView safeView(QByteArrayView view, quint32 start, quint32 length)
#define KTX_IDENTIFIER_LENGTH
static constexpr quint32 qktxh_headerSize
static const char ktxIdentifier[KTX_IDENTIFIER_LENGTH]
static const quint32 inversePlatformEndianIdentifier
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
std::enable_if_t< std::is_unsigned_v< T >, bool > qAddOverflow(T v1, T v2, T *r)
Definition qnumeric.h:113
std::enable_if_t< std::is_unsigned_v< T >, bool > qSubOverflow(T v1, T v2, T *r)
Definition qnumeric.h:153
GLenum GLuint GLint level
GLuint GLuint end
GLenum GLuint GLenum GLsizei length
GLenum face
GLenum GLuint GLenum GLsizei const GLchar * buf
GLuint start
GLenum GLuint GLintptr offset
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei imageSize
GLuint GLfloat * val
GLuint64EXT * result
[6]
#define Q_UNUSED(x)
unsigned int quint32
Definition qtypes.h:50
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned char quint8
Definition qtypes.h:46
#define decode(x)
QT_BEGIN_NAMESPACE typedef uchar * output
QQuickView * view
[0]
quint32 endianness
quint32 bytesOfKeyValueData
quint32 glTypeSize
quint32 numberOfArrayElements
quint32 pixelHeight
quint32 numberOfMipmapLevels
quint32 pixelDepth
quint32 glType
quint8 identifier[KTX_IDENTIFIER_LENGTH]
quint32 glFormat
quint32 numberOfFaces
quint32 glBaseInternalFormat
quint32 glInternalFormat
quint32 pixelWidth