10#include <QtCore/qiodevice.h>
16#include <QOpenGLTexture>
21using namespace Qt::StringLiterals;
23#define KTX_IDENTIFIER_LENGTH 12
24static const char ktxIdentifier[
KTX_IDENTIFIER_LENGTH] = {
'\xAB',
'K',
'T',
'X',
' ',
'1',
'1',
'\xBB',
'\r',
'\n',
'\x1A',
'\n' };
59
60
61
62
63
64
65
66
67
68
69
70
71
72
78 constexpr quint32 rounding = 4;
80 if (qAddOverflow(value, rounding - 1, &result))
82 result &= ~(rounding - 1);
90 if (qAddOverflow(start, length, &end) || end > quint32(view.length()))
92 return view.sliced(start, length);
97bool QKtxHandler::canRead(
const QByteArray &suffix,
const QByteArray &block)
106 return QTextureFileData();
108 const QByteArray buf = device()->readAll();
109 if (
static_cast<size_t>(buf.size()) > std::numeric_limits<quint32>::max()) {
110 qCWarning(lcQtGuiTextureIO,
"Too big KTX file %s", logName().constData());
111 return QTextureFileData();
114 if (!canRead(QByteArray(), buf)) {
115 qCWarning(lcQtGuiTextureIO,
"Invalid KTX file %s", logName().constData());
116 return QTextureFileData();
119 if (buf.size() < qsizetype(qktxh_headerSize)) {
120 qCWarning(lcQtGuiTextureIO,
"Invalid KTX header size in %s", logName().constData());
121 return QTextureFileData();
125 memcpy(&header, buf.data(), qktxh_headerSize);
126 if (!checkHeader(header)) {
127 qCWarning(lcQtGuiTextureIO,
"Unsupported KTX file format in %s", logName().constData());
128 return QTextureFileData();
132 texData.setData(buf);
134 texData.setSize(QSize(decode(header.pixelWidth), decode(header.pixelHeight)));
135 texData.setGLFormat(decode(header.glFormat));
136 texData.setGLInternalFormat(decode(header.glInternalFormat));
137 texData.setGLBaseInternalFormat(decode(header.glBaseInternalFormat));
139 texData.setNumLevels(decode(header.numberOfMipmapLevels));
140 texData.setNumFaces(decode(header.numberOfFaces));
142 const quint32 bytesOfKeyValueData = decode(header.bytesOfKeyValueData);
143 quint32 headerKeyValueSize;
144 if (qAddOverflow(qktxh_headerSize, bytesOfKeyValueData, &headerKeyValueSize)) {
145 qCWarning(lcQtGuiTextureIO,
"Overflow in size of key value data in header of KTX file %s",
146 logName().constData());
147 return QTextureFileData();
150 if (headerKeyValueSize >= quint32(buf.size())) {
151 qCWarning(lcQtGuiTextureIO,
"OOB request in KTX file %s", logName().constData());
152 return QTextureFileData();
156 if (bytesOfKeyValueData > 0) {
157 auto keyValueDataView = safeView(buf, qktxh_headerSize, bytesOfKeyValueData);
158 if (keyValueDataView.isEmpty()) {
159 qCWarning(lcQtGuiTextureIO,
"Invalid view in KTX file %s", logName().constData());
160 return QTextureFileData();
163 auto keyValues = decodeKeyValues(keyValueDataView);
165 qCWarning(lcQtGuiTextureIO,
"Could not parse key values in KTX file %s",
166 logName().constData());
167 return QTextureFileData();
170 texData.setKeyValueMetadata(*keyValues);
176 const int maxLevels = (
sizeof(quint32) * 8)
177 - qCountLeadingZeroBits(std::max(
178 { header.pixelWidth, header.pixelHeight, header.pixelDepth }));
180 if (texData.numLevels() > maxLevels) {
181 qCWarning(lcQtGuiTextureIO,
"Too many levels in KTX file %s", logName().constData());
182 return QTextureFileData();
185 if (texData.numFaces() != 1 && texData.numFaces() != 6) {
186 qCWarning(lcQtGuiTextureIO,
"Invalid number of faces in KTX file %s", logName().constData());
187 return QTextureFileData();
190 quint32 offset = headerKeyValueSize;
191 for (
int level = 0; level < texData.numLevels(); level++) {
192 const auto imageSizeView = safeView(buf, offset,
sizeof(quint32));
193 if (imageSizeView.isEmpty()) {
194 qCWarning(lcQtGuiTextureIO,
"OOB request in KTX file %s", logName().constData());
195 return QTextureFileData();
198 const quint32 imageSize = decode(qFromUnaligned<quint32>(imageSizeView.data()));
199 offset +=
sizeof(quint32);
201 for (
int face = 0; face < texData.numFaces(); face++) {
202 texData.setDataOffset(offset, level, face);
203 texData.setDataLength(imageSize, level, face);
206 const auto padded = nearestMultipleOf4(imageSize);
208 qCWarning(lcQtGuiTextureIO,
"Overflow in KTX file %s", logName().constData());
209 return QTextureFileData();
213 if (qAddOverflow(offset, *padded, &offsetNext)) {
214 qCWarning(lcQtGuiTextureIO,
"OOB request in KTX file %s", logName().constData());
215 return QTextureFileData();
222 if (!texData.isValid()) {
223 qCWarning(lcQtGuiTextureIO,
"Invalid values in header of KTX file %s",
224 logName().constData());
225 return QTextureFileData();
228 texData.setLogName(logName());
231 qDebug() <<
"KTX file handler read" << texData;
239 if (header.endianness != platformEndianIdentifier && header.endianness != inversePlatformEndianIdentifier)
241 inverseEndian = (header.endianness == inversePlatformEndianIdentifier);
243 QMetaEnum tfme = QMetaEnum::fromType<QOpenGLTexture::TextureFormat>();
244 QMetaEnum ptme = QMetaEnum::fromType<QOpenGLTexture::PixelType>();
245 qDebug(
"Header of %s:", logName().constData());
246 qDebug(
" glType: 0x%x (%s)", decode(header.glType), ptme.valueToKey(decode(header.glType)));
247 qDebug(
" glTypeSize: %u", decode(header.glTypeSize));
248 qDebug(
" glFormat: 0x%x (%s)", decode(header.glFormat),
249 tfme.valueToKey(decode(header.glFormat)));
250 qDebug(
" glInternalFormat: 0x%x (%s)", decode(header.glInternalFormat),
251 tfme.valueToKey(decode(header.glInternalFormat)));
252 qDebug(
" glBaseInternalFormat: 0x%x (%s)", decode(header.glBaseInternalFormat),
253 tfme.valueToKey(decode(header.glBaseInternalFormat)));
254 qDebug(
" pixelWidth: %u", decode(header.pixelWidth));
255 qDebug(
" pixelHeight: %u", decode(header.pixelHeight));
256 qDebug(
" pixelDepth: %u", decode(header.pixelDepth));
257 qDebug(
" numberOfArrayElements: %u", decode(header.numberOfArrayElements));
258 qDebug(
" numberOfFaces: %u", decode(header.numberOfFaces));
259 qDebug(
" numberOfMipmapLevels: %u", decode(header.numberOfMipmapLevels));
260 qDebug(
" bytesOfKeyValueData: %u", decode(header.bytesOfKeyValueData));
262 const bool isCompressedImage = decode(header.glType) == 0 && decode(header.glFormat) == 0
263 && decode(header.pixelDepth) == 0;
264 const bool isCubeMap = decode(header.numberOfFaces) == 6;
265 const bool is2D = decode(header.pixelDepth) == 0 && decode(header.numberOfArrayElements) == 0;
267 return is2D && (isCubeMap || isCompressedImage);
270std::optional<QMap<QByteArray, QByteArray>>
QKtxHandler::decodeKeyValues(QByteArrayView view)
const
272 QMap<QByteArray, QByteArray> output;
274 while (offset < quint32(view.size())) {
275 const auto keyAndValueByteSizeView = safeView(view, offset,
sizeof(quint32));
276 if (keyAndValueByteSizeView.isEmpty()) {
277 qCWarning(lcQtGuiTextureIO,
"Invalid view in KTX key-value");
281 const quint32 keyAndValueByteSize =
282 decode(qFromUnaligned<quint32>(keyAndValueByteSizeView.data()));
284 quint32 offsetKeyAndValueStart;
285 if (qAddOverflow(offset, quint32(
sizeof(quint32)), &offsetKeyAndValueStart)) {
286 qCWarning(lcQtGuiTextureIO,
"Overflow in KTX key-value");
290 quint32 offsetKeyAndValueEnd;
291 if (qAddOverflow(offsetKeyAndValueStart, keyAndValueByteSize, &offsetKeyAndValueEnd)) {
292 qCWarning(lcQtGuiTextureIO,
"Overflow in KTX key-value");
296 const auto keyValueView = safeView(view, offsetKeyAndValueStart, keyAndValueByteSize);
297 if (keyValueView.isEmpty()) {
298 qCWarning(lcQtGuiTextureIO,
"Invalid view in KTX key-value");
306 const int idx = keyValueView.indexOf(
'\0');
308 qCWarning(lcQtGuiTextureIO,
"Invalid key in KTX key-value");
312 const QByteArrayView keyView = safeView(view, offsetKeyAndValueStart, idx);
313 if (keyView.isEmpty()) {
314 qCWarning(lcQtGuiTextureIO,
"Overflow in KTX key-value");
318 const quint32 keySize = idx + 1;
320 quint32 offsetValueStart;
321 if (qAddOverflow(offsetKeyAndValueStart, keySize, &offsetValueStart)) {
322 qCWarning(lcQtGuiTextureIO,
"Overflow in KTX key-value");
327 if (qSubOverflow(keyAndValueByteSize, keySize, &valueSize)) {
328 qCWarning(lcQtGuiTextureIO,
"Underflow in KTX key-value");
332 const QByteArrayView valueView = safeView(view, offsetValueStart, valueSize);
333 if (valueView.isEmpty()) {
334 qCWarning(lcQtGuiTextureIO,
"Invalid view in KTX key-value");
338 output.insert(keyView.toByteArray(), valueView.toByteArray());
340 const auto offsetNext = nearestMultipleOf4(offsetKeyAndValueEnd);
342 qCWarning(lcQtGuiTextureIO,
"Overflow in KTX key-value");
346 offset = *offsetNext;
354 return inverseEndian ? qbswap<quint32>(val) : val;
QTextureFileData read() override
static const char ktxIdentifier[KTX_IDENTIFIER_LENGTH]
static const std::optional< quint32 > nearestMultipleOf4(quint32 value)
static constexpr quint32 qktxh_headerSize
static QByteArrayView safeView(QByteArrayView view, quint32 start, quint32 length)
#define KTX_IDENTIFIER_LENGTH
static const quint32 platformEndianIdentifier
static const quint32 inversePlatformEndianIdentifier
quint32 keyAndValueByteSize