Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgrenderloadedtexture.cpp
Go to the documentation of this file.
1// Copyright (C) 2008-2012 NVIDIA Corporation.
2// Copyright (C) 2019 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4// Qt-Security score:critical reason:data-parser
5
6#include <QtQuick3DRuntimeRender/private/qssgrenderloadedtexture_p.h>
7#include <QtQuick3DRuntimeRender/private/qssgrendererutil_p.h>
8#include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h>
9#include <QtQuick3DRuntimeRender/private/qssgrendertexturedata_p.h>
10#include <QtGui/QImageReader>
11#include <QtGui/QColorSpace>
12#include <QtCore/qnumeric.h>
13#include <QtMath>
14
15#include <QtQuick3DUtils/private/qssgutils_p.h>
16
17#include <private/qtexturefilereader_p.h>
18
19#define TINYEXR_IMPLEMENTATION
20#define TINYEXR_USE_MINIZ 0
21#define TINYEXR_USE_THREAD 1
22#include <zlib.h>
23QT_WARNING_PUSH
24QT_WARNING_DISABLE_CLANG("-Wunused-parameter")
25QT_WARNING_DISABLE_CLANG("-Wunused-function")
26QT_WARNING_DISABLE_GCC("-Wunused-parameter")
27QT_WARNING_DISABLE_GCC("-Wunused-function")
28#include <tinyexr.h>
29QT_WARNING_POP
31
33
34
35QSharedPointer<QIODevice> QSSGInputUtil::getStreamForFile(const QString &inPath, bool inQuiet, QString *outPath)
36{
37 QFile *file = nullptr;
38 QString tryPath = inPath;
39 if (tryPath.startsWith(QLatin1String("qrc:/"))) {
40 tryPath = inPath.mid(3);
41 } else if (tryPath.startsWith(QLatin1String("file://"))) {
42 tryPath = inPath.mid(7);
43 }
44
45 QFileInfo fi(tryPath);
46 bool found = fi.exists();
47 if (!found && fi.isNativePath()) {
48 tryPath.prepend(QLatin1String(":/"));
49 fi.setFile(tryPath);
50 found = fi.exists();
51 }
52 if (found) {
53 QString filePath = fi.canonicalFilePath();
54 file = new QFile(filePath);
55 if (file->open(QIODevice::ReadOnly)) {
56 if (outPath)
57 *outPath = filePath;
58 } else {
59 delete file;
60 file = nullptr;
61 }
62 }
63 if (!file && !inQuiet)
64 qCWarning(WARNING, "Failed to find file: %s", qPrintable(inPath));
65 return QSharedPointer<QIODevice>(file);
66}
67
68QSharedPointer<QIODevice> QSSGInputUtil::getStreamForTextureFile(const QString &inPath, bool inQuiet,
69 QString *outPath, FileType *outFileType)
70{
71 static const QList<QByteArray> hdrFormats = QList<QByteArray>({ "hdr", "exr" });
72 static const QList<QByteArray> textureFormats = QTextureFileReader::supportedFileFormats();
73 static const QList<QByteArray> imageFormats = QImageReader::supportedImageFormats();
74 static const QList<QByteArray> allFormats = textureFormats + hdrFormats + imageFormats;
75
76 QString filePath;
77 QByteArray ext;
78 QSharedPointer<QIODevice> stream = getStreamForFile(inPath, true, &filePath);
79 if (stream) {
80 ext = QFileInfo(filePath).suffix().toLatin1().toLower();
81 } else {
82 for (const QByteArray &format : allFormats) {
83 QString tryName = inPath + QLatin1Char('.') + QLatin1String(format);
84 stream = getStreamForFile(tryName, true, &filePath);
85 if (stream) {
86 ext = format;
87 break;
88 }
89 }
90 }
91 if (stream) {
92 if (outPath)
93 *outPath = filePath;
94 if (outFileType) {
95 FileType type = UnknownFile;
96 if (hdrFormats.contains(ext))
97 type = HdrFile;
98 else if (textureFormats.contains(ext))
99 type = TextureFile;
100 else if (imageFormats.contains(ext))
101 type = ImageFile;
102 *outFileType = type;
103 }
104 } else if (!inQuiet) {
105 qCWarning(WARNING, "Failed to find texture file for: %s", qPrintable(inPath));
106 }
107 return stream;
108}
109
110static inline QSSGRenderTextureFormat fromGLtoTextureFormat(quint32 internalFormat)
111{
112 switch (internalFormat) {
113 case 0x8229:
114 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::R8);
115 case 0x822A:
116 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::R16);
117 case 0x822D:
118 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::R16F);
119 case 0x8235:
120 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::R32I);
121 case 0x8236:
122 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::R32UI);
123 case 0x822E:
124 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::R32F);
125 case 0x822B:
126 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RG8);
127 case 0x8058:
128 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA8);
129 case 0x8051:
130 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB8);
131 case 0x8C41:
132 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8);
133 case 0x8C43:
134 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8A8);
135 case 0x8D62:
136 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB565);
137 case 0x803C:
138 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::Alpha8);
139 case 0x8040:
140 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::Luminance8);
141 case 0x8042:
142 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::Luminance16);
143 case 0x8045:
144 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::LuminanceAlpha8);
145 case 0x881A:
146 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA16F);
147 case 0x822F:
148 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RG16F);
149 case 0x8230:
150 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RG32F);
151 case 0x8815:
152 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB32F);
153 case 0x8814:
154 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA32F);
155 case 0x8C3A:
156 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::R11G11B10);
157 case 0x8C3D:
158 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB9E5);
159 case 0x8059:
160 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB10_A2);
161 case 0x881B:
162 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB16F);
163 case 0x8D70:
164 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA32UI);
165 case 0x8D71:
166 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB32UI);
167 case 0x8D76:
168 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA16UI);
169 case 0x8D77:
170 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB16UI);
171 case 0x8D7C:
172 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA8UI);
173 case 0x8D7D:
174 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB8UI);
175 case 0x8D82:
176 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA32I);
177 case 0x8D83:
178 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB32I);
179 case 0x8D88:
180 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA16I);
181 case 0x8D89:
182 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB16I);
183 case 0x8D8E:
184 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA8I);
185 case 0x8D8F:
186 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB8I);
187 case 0x83F1:
188 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_DXT1);
189 case 0x83F0:
190 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB_DXT1);
191 case 0x83F2:
192 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_DXT3);
193 case 0x83F3:
194 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_DXT5);
195 case 0x9270:
196 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::R11_EAC_UNorm);
197 case 0x9271:
198 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::R11_EAC_SNorm);
199 case 0x9272:
200 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RG11_EAC_UNorm);
201 case 0x9273:
202 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RG11_EAC_SNorm);
203 case 0x9274:
204 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB8_ETC2);
205 case 0x9275:
206 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_ETC2);
207 case 0x9276:
208 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGB8_PunchThrough_Alpha1_ETC2);
209 case 0x9277:
210 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_PunchThrough_Alpha1_ETC2);
211 case 0x9278:
212 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA8_ETC2_EAC);
213 case 0x9279:
214 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ETC2_EAC);
215 case 0x93B0:
216 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_4x4);
217 case 0x93B1:
218 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_5x4);
219 case 0x93B2:
220 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_5x5);
221 case 0x93B3:
222 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_6x5);
223 case 0x93B4:
224 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_6x6);
225 case 0x93B5:
226 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_8x5);
227 case 0x93B6:
228 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_8x6);
229 case 0x93B7:
230 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_8x8);
231 case 0x93B8:
232 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_10x5);
233 case 0x93B9:
234 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_10x6);
235 case 0x93BA:
236 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_10x8);
237 case 0x93BB:
238 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_10x10);
239 case 0x93BC:
240 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_12x10);
241 case 0x93BD:
242 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::RGBA_ASTC_12x12);
243 case 0x93D0:
244 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_4x4);
245 case 0x93D1:
246 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_5x4);
247 case 0x93D2:
248 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_5x5);
249 case 0x93D3:
250 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_6x5);
251 case 0x93D4:
252 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_6x6);
253 case 0x93D5:
254 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_8x5);
255 case 0x93D6:
256 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_8x6);
257 case 0x93D7:
258 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_8x8);
259 case 0x93D8:
260 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_10x5);
261 case 0x93D9:
262 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_10x6);
263 case 0x93DA:
264 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_10x8);
265 case 0x93DB:
266 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_10x10);
267 case 0x93DC:
268 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_12x10);
269 case 0x93DD:
270 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_12x12);
271 case 0x81A5:
272 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::Depth16);
273 case 0x81A6:
274 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::Depth24);
275 case 0x81A7:
276 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::Depth32);
277 case 0x88F0:
278 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::Depth24Stencil8);
279 default:
280 return QSSGRenderTextureFormat(QSSGRenderTextureFormat::Unknown);
281 }
282}
283
284static QImage loadImage(const QString &inPath, bool flipVertical)
285{
286 QImage image(inPath);
287 if (image.isNull())
288 return image;
289 const QPixelFormat pixFormat = image.pixelFormat();
290 QImage::Format targetFormat = QImage::Format_RGBA8888_Premultiplied;
291 if (image.colorCount()) // a palleted image
292 targetFormat = QImage::Format_RGBA8888;
293 else if (pixFormat.channelCount() == 1)
294 targetFormat = QImage::Format_Grayscale8;
295 else if (pixFormat.alphaUsage() == QPixelFormat::IgnoresAlpha)
296 targetFormat = QImage::Format_RGBX8888;
297 else if (pixFormat.premultiplied() == QPixelFormat::NotPremultiplied)
298 targetFormat = QImage::Format_RGBA8888;
299
300 image.convertTo(targetFormat); // convert to a format mappable to QRhiTexture::Format
301 if (flipVertical)
302 image.flip(); // Flip vertically to the conventional Y-up orientation
303 return image;
304}
305
306QSSGLoadedTexture *QSSGLoadedTexture::loadQImage(const QString &inPath, qint32 flipVertical)
307{
308 QImage image = loadImage(inPath, flipVertical);
309 if (image.isNull())
310 return nullptr;
311 QSSGLoadedTexture *retval = new QSSGLoadedTexture;
312 retval->width = image.width();
313 retval->height = image.height();
314 retval->components = image.pixelFormat().channelCount();
315 retval->image = image;
316 retval->data = (void *)retval->image.bits();
317 retval->dataSizeInBytes = image.sizeInBytes();
318 retval->setFormatFromComponents();
319 // #TODO: This is a very crude way detect color space
320 retval->isSRGB = image.colorSpace().transferFunction() != QColorSpace::TransferFunction::Linear;
321
322 return retval;
323}
324
325QSSGLoadedTexture *QSSGLoadedTexture::loadCompressedImage(const QString &inPath)
326{
327 QSSGLoadedTexture *retval = nullptr;
328
329 // Open File
330 QFile imageFile(inPath);
331 if (!imageFile.open(QIODevice::ReadOnly)) {
332 qWarning() << "Could not open image file: " << inPath;
333 return retval;
334 }
335 auto reader = new QTextureFileReader(&imageFile, inPath);
336
337 if (!reader->canRead()) {
338 qWarning() << "Unable to read image file: " << inPath;
339 delete reader;
340 return retval;
341 }
342 retval = new QSSGLoadedTexture;
343 retval->textureFileData = reader->read();
344
345 // Fill out what makes sense, leave the rest at the default 0 and null.
346 retval->width = retval->textureFileData.size().width();
347 retval->height = retval->textureFileData.size().height();
348 auto glFormat = retval->textureFileData.glInternalFormat()
349 ? retval->textureFileData.glInternalFormat()
350 : retval->textureFileData.glFormat();
351 retval->format = fromGLtoTextureFormat(glFormat);
352
353 // SRGB8 ASTC formats are always sRGB
354 if ((retval->format.format >= QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_4x4) &&
355 (retval->format.format <= QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_12x12))
356 retval->isSRGB = true;
357
358 delete reader;
359 imageFile.close();
360
361 return retval;
362
363}
364
365namespace {
366typedef unsigned char RGBE[4];
367#define R 0
368#define G 1
369#define B 2
370#define E 3
371
372#define MINELEN 8 // minimum scanline length for encoding
373#define MAXELEN 0x7fff // maximum scanline length for encoding
374
375// Size in bytes of an image with the given geometry, or -1 if the result would
376// overflow or exceed what QSSGLoadedTexture::dataSizeInBytes can describe. The
377// dimensions come from image file headers, so they cannot be trusted.
378qsizetype calculateDataSize(qsizetype width, qsizetype height, qsizetype depth, qsizetype bytesPerPixel)
379{
380 if (width <= 0 || height <= 0 || depth < 0 || bytesPerPixel <= 0)
381 return -1;
382
383 qsizetype line = 0;
384 if (qMulOverflow(width, bytesPerPixel, &line))
385 return -1;
386 const qsizetype pitch = (line + 3) & ~qsizetype(3); // rows are 4-byte aligned
387
388 qsizetype dataSize = 0;
389 if (qMulOverflow(height, pitch, &dataSize))
390 return -1;
391 if (depth > 0 && qMulOverflow(dataSize, depth, &dataSize))
392 return -1;
393
394 if (dataSize > qsizetype(std::numeric_limits<quint32>::max()))
395 return -1;
396
397 return dataSize;
398}
399
400float convertComponent(int exponent, int val)
401{
402 float v = val / (256.0f);
403 float d = powf(2.0f, (float)exponent - 128.0f);
404 return v * d;
405}
406
407// Decodes one run length encoded scanline. Returns false, having written no
408// more than w pixels, if the data is malformed; every run has to be bounded
409// against the row, because the counts come from the file.
410bool decrunchScanline(const char *&p, const char *pEnd, RGBE *scanline, int w)
411{
412 if (pEnd - p < 4)
413 return false;
414
415 scanline[0][R] = *p++;
416 scanline[0][G] = *p++;
417 scanline[0][B] = *p++;
418 scanline[0][E] = *p++;
419
420 if (scanline[0][R] == 2 && scanline[0][G] == 2 && scanline[0][B] < 128) {
421 // new rle, the first pixel was a dummy
422 for (int channel = 0; channel < 4; ++channel) {
423 int x = 0;
424 while (x < w) {
425 if (p >= pEnd)
426 return false;
427 unsigned char c = *p++;
428 if (c > 128) { // run
429 const int repCount = c & 127;
430 if (p >= pEnd || repCount > w - x)
431 return false;
432 c = *p++;
433 for (int i = 0; i < repCount; ++i)
434 scanline[x++][channel] = c;
435 } else { // not a run
436 if (c > w - x || pEnd - p < c)
437 return false;
438 for (int i = 0; i < c; ++i)
439 scanline[x++][channel] = *p++;
440 }
441 }
442 }
443 } else {
444 // old rle
445 int bitshift = 0;
446 int x = 1;
447 while (x < w) {
448 if (pEnd - p < 4)
449 return false;
450 scanline[x][R] = *p++;
451 scanline[x][G] = *p++;
452 scanline[x][B] = *p++;
453 scanline[x][E] = *p++;
454
455 if (scanline[x][R] == 1 && scanline[x][G] == 1 && scanline[x][B] == 1) { // run
456 // A consecutive run shifts the count up by another byte, so
457 // beyond 24 the shift itself would be undefined, and the
458 // shifted count no longer fits in an int.
459 if (bitshift > 24 || scanline[x][E] > ((w - x) >> bitshift))
460 return false;
461 const int repCount = int(scanline[x][E]) << bitshift;
462 for (int i = 0; i < repCount; ++i, ++x)
463 memcpy(scanline[x], scanline[x - 1], 4);
464 bitshift += 8;
465 } else { // not a run
466 ++x;
467 bitshift = 0;
468 }
469 }
470 }
471
472 return true;
473}
474
475void decodeScanlineToTexture(RGBE *scanline, int width, void *outBuf, qsizetype offset, QSSGRenderTextureFormat inFormat)
476{
477 quint8 *target = reinterpret_cast<quint8 *>(outBuf);
478 target += offset;
479
480 if (inFormat == QSSGRenderTextureFormat::RGBE8) {
481 memcpy(target, scanline, size_t(width) * 4);
482 } else {
483 float rgbaF32[4];
484 for (int i = 0; i < width; ++i) {
485 rgbaF32[R] = convertComponent(scanline[i][E], scanline[i][R]);
486 rgbaF32[G] = convertComponent(scanline[i][E], scanline[i][G]);
487 rgbaF32[B] = convertComponent(scanline[i][E], scanline[i][B]);
488 rgbaF32[3] = 1.0f;
489
490 inFormat.encodeToPixel(rgbaF32, target, i * inFormat.getSizeofFormat());
491 }
492 }
493}
494
495QSSGLoadedTexture *loadRadianceHdr(const QSharedPointer<QIODevice> &source, const QSSGRenderTextureFormat &format)
496{
497 QSSGLoadedTexture *imageData = nullptr;
498
499 char sig[11];
500 source->seek(0);
501 if (source->read(sig, sizeof(sig)) == sizeof(sig) && !memcmp(sig, "#?RADIANCE\n", sizeof(sig))) {
502 QByteArray buf = source->readAll();
503 const char *p = buf.constData();
504 const char *pEnd = p + buf.size();
505
506 // Process lines until the empty one.
507 QByteArray line;
508 while (p < pEnd) {
509 char c = *p++;
510 if (c == '\n') {
511 if (line.isEmpty())
512 break;
513 if (line.startsWith(QByteArrayLiteral("FORMAT="))) {
514 const QByteArray format = line.mid(7).trimmed();
515 if (format != QByteArrayLiteral("32-bit_rle_rgbe")) {
516 qWarning("HDR format '%s' is not supported", format.constData());
517 return imageData;
518 }
519 }
520 line.clear();
521 } else {
522 line.append(c);
523 }
524 }
525 if (p == pEnd) {
526 qWarning("Malformed HDR image data at property strings");
527 return imageData;
528 }
529
530 // Get the resolution string.
531 while (p < pEnd) {
532 char c = *p++;
533 if (c == '\n')
534 break;
535 line.append(c);
536 }
537 if (p == pEnd) {
538 qWarning("Malformed HDR image data at resolution string");
539 return imageData;
540 }
541
542 int width = 0;
543 int height = 0;
544 // We only care about the standard orientation.
545#ifdef Q_CC_MSVC
546 if (!sscanf_s(line.constData(), "-Y %d +X %d", &height, &width)) {
547#else
548 if (!sscanf(line.constData(), "-Y %d +X %d", &height, &width)) {
549#endif
550 qWarning("Unsupported HDR resolution string '%s'", line.constData());
551 return imageData;
552 }
553 if (width <= 0 || height <= 0 || width > 65536 || height > 65536) {
554 qWarning("Invalid HDR resolution %dx%d", width, height);
555 return imageData;
556 }
557
558 const int bytesPerPixel = format.getSizeofFormat();
559 const qsizetype dataSize = calculateDataSize(width, height, 0, bytesPerPixel);
560 if (dataSize < 0) {
561 qWarning("HDR image dimensions %dx%d are too large", width, height);
562 return imageData;
563 }
564 void *data = ::calloc(1, size_t(dataSize));
565 if (!data) {
566 qWarning("Failed to allocate %lld bytes for HDR image", qlonglong(dataSize));
567 return imageData;
568 }
569 imageData = new QSSGLoadedTexture;
570 imageData->dataSizeInBytes = quint32(dataSize);
571 imageData->data = data;
572 imageData->width = width;
573 imageData->height = height;
574 imageData->format = format;
575 imageData->components = format.getNumberOfComponent();
576 imageData->isSRGB = false;
577
578 // Allocate a scanline worth of RGBE data
579 RGBE *scanline = new RGBE[width];
580
581 // Note we are writing to the data buffer from bottom to top
582 // to correct for -Y orientation
583 for (int y = 0; y < height; ++y) {
584 const qsizetype byteOffset = qsizetype(height - 1 - y) * width * bytesPerPixel;
585 if (!decrunchScanline(p, pEnd, scanline, width)) {
586 qWarning("Malformed HDR scanline data at row %d of %d", y, height);
587 break;
588 }
589 decodeScanlineToTexture(scanline, width, imageData->data, byteOffset, format);
590 }
591
592 delete[] scanline;
593 }
594
595 return imageData;
596}
597
598QSSGLoadedTexture *loadExr(const QSharedPointer<QIODevice> &source, const QSSGRenderTextureFormat format)
599{
600 QSSGLoadedTexture *imageData = nullptr;
601
602 char versionBuffer[tinyexr::kEXRVersionSize];
603 source->seek(0);
604 auto size = source->read(versionBuffer, tinyexr::kEXRVersionSize);
605 // Check if file is big enough
606 if (size != tinyexr::kEXRVersionSize)
607 return imageData;
608 // Try to load the Version
609 EXRVersion exrVersion;
610 if (ParseEXRVersionFromMemory(&exrVersion, reinterpret_cast<unsigned char *>(versionBuffer), tinyexr::kEXRVersionSize) != TINYEXR_SUCCESS)
611 return imageData;
612
613 // Check that the file is not a multipart file
614 if (exrVersion.multipart)
615 return imageData;
616
617 // If we get here, than this is an EXR file
618 source->seek(0);
619 QByteArray buf = source->readAll();
620 const char *err = nullptr;
621 // Header
622 EXRHeader exrHeader;
623 InitEXRHeader(&exrHeader);
624 if (ParseEXRHeaderFromMemory(&exrHeader,
625 &exrVersion,
626 reinterpret_cast<const unsigned char *>(buf.constData()),
627 buf.size(),
628 &err) != TINYEXR_SUCCESS) {
629 qWarning("Failed to parse EXR Header with error: '%s'", err);
630 FreeEXRErrorMessage(err);
631 return imageData;
632 }
633
634 // Make sure we get floats instead of half floats
635 for (int i = 0; i < exrHeader.num_channels; i++) {
636 if (exrHeader.pixel_types[i] == TINYEXR_PIXELTYPE_HALF)
637 exrHeader.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
638 }
639
640 // Image
641 EXRImage exrImage;
642
643 InitEXRImage(&exrImage);
644 if (LoadEXRImageFromMemory(&exrImage,
645 &exrHeader,
646 reinterpret_cast<const unsigned char *>(buf.constData()),
647 buf.size(),
648 &err) != TINYEXR_SUCCESS) {
649 qWarning("Failed to load EXR Image with error: '%s'", err);
650 FreeEXRHeader(&exrHeader);
651 FreeEXRErrorMessage(err);
652 return imageData;
653 }
654
655 // Convert data
656 // RGBA
657 int idxR = -1;
658 int idxG = -1;
659 int idxB = -1;
660 int idxA = -1;
661 for (int c = 0; c < exrHeader.num_channels; c++) {
662 if (strcmp(exrHeader.channels[c].name, "R") == 0)
663 idxR = c;
664 else if (strcmp(exrHeader.channels[c].name, "G") == 0)
665 idxG = c;
666 else if (strcmp(exrHeader.channels[c].name, "B") == 0)
667 idxB = c;
668 else if (strcmp(exrHeader.channels[c].name, "A") == 0)
669 idxA = c;
670 }
671 const bool isSingleChannel = exrHeader.num_channels == 1;
672
673 // Anything but a lone channel is read as R, G and B, so all three have to be
674 // present. Without this a file whose channels are named something else (the
675 // luminance/chroma Y, BY, RY convention, for example) would index the plane
676 // array with -1.
677 if (!isSingleChannel && (idxR == -1 || idxG == -1 || idxB == -1)) {
678 qWarning("EXR image has no R, G and B channels");
679 FreeEXRImage(&exrImage);
680 FreeEXRHeader(&exrHeader);
681 return imageData;
682 }
683
684 // Setup Output container
685 const int bytesPerPixel = format.getSizeofFormat();
686 const qsizetype dataSize = calculateDataSize(exrImage.width, exrImage.height, 0, bytesPerPixel);
687 if (dataSize < 0) {
688 qWarning("EXR image dimensions %dx%d are too large", exrImage.width, exrImage.height);
689 FreeEXRImage(&exrImage);
690 FreeEXRHeader(&exrHeader);
691 return imageData;
692 }
693 void *data = ::malloc(size_t(dataSize));
694 if (!data) {
695 qWarning("Failed to allocate %lld bytes for EXR image", qlonglong(dataSize));
696 FreeEXRImage(&exrImage);
697 FreeEXRHeader(&exrHeader);
698 return imageData;
699 }
700 imageData = new QSSGLoadedTexture;
701 imageData->dataSizeInBytes = quint32(dataSize);
702 imageData->data = data;
703 imageData->width = exrImage.width;
704 imageData->height = exrImage.height;
705 imageData->format = format;
706 imageData->components = format.getNumberOfComponent();
707 imageData->isSRGB = false;
708
709 quint8 *target = reinterpret_cast<quint8 *>(imageData->data);
710
711 float rgbaF32[4];
712
713 if (exrHeader.tiled) {
714 for (int it = 0; it < exrImage.num_tiles; it++) {
715 for (int j = 0; j < exrHeader.tile_size_y; j++)
716 for (int i = 0; i < exrHeader.tile_size_x; i++) {
717 const int ii =
718 exrImage.tiles[it].offset_x * exrHeader.tile_size_x + i;
719 const int jj =
720 exrImage.tiles[it].offset_y * exrHeader.tile_size_y + j;
721 const int inverseJJ = std::abs(jj - (exrImage.height - 1));
722 const int idx = ii + inverseJJ * exrImage.width;
723
724 // out of region check.
725 if (ii >= exrImage.width) {
726 continue;
727 }
728 if (jj >= exrImage.height) {
729 continue;
730 }
731 const int srcIdx = i + j * exrHeader.tile_size_x;
732 unsigned char **src = exrImage.tiles[it].images;
733 if (isSingleChannel) {
734 rgbaF32[R] = reinterpret_cast<float **>(src)[0][srcIdx];
735 rgbaF32[G] = rgbaF32[R];
736 rgbaF32[B] = rgbaF32[R];
737 rgbaF32[3] = rgbaF32[R];
738 } else {
739 rgbaF32[R] = reinterpret_cast<float **>(src)[idxR][srcIdx];
740 rgbaF32[G] = reinterpret_cast<float **>(src)[idxG][srcIdx];
741 rgbaF32[B] = reinterpret_cast<float **>(src)[idxB][srcIdx];
742 if (idxA != -1)
743 rgbaF32[3] = reinterpret_cast<float **>(src)[idxA][srcIdx];
744 else
745 rgbaF32[3] = 1.0f;
746 }
747 format.encodeToPixel(rgbaF32, target + qsizetype(idx) * bytesPerPixel, 0);
748 }
749 }
750 } else {
751 int idx = 0;
752 for (int y = exrImage.height - 1; y >= 0; --y) {
753 for (int x = 0; x < exrImage.width; x++) {
754 const int i = y * exrImage.width + x;
755 if (isSingleChannel) {
756 rgbaF32[R] = reinterpret_cast<float **>(exrImage.images)[0][i];
757 rgbaF32[G] = rgbaF32[R];
758 rgbaF32[B] = rgbaF32[R];
759 rgbaF32[3] = rgbaF32[R];
760 } else {
761 rgbaF32[R] = reinterpret_cast<float **>(exrImage.images)[idxR][i];
762 rgbaF32[G] = reinterpret_cast<float **>(exrImage.images)[idxG][i];
763 rgbaF32[B] = reinterpret_cast<float **>(exrImage.images)[idxB][i];
764 if (idxA != -1)
765 rgbaF32[3] = reinterpret_cast<float **>(exrImage.images)[idxA][i];
766 else
767 rgbaF32[3] = 1.0f;
768 }
769 format.encodeToPixel(rgbaF32, target + qsizetype(idx) * bytesPerPixel, 0);
770 ++idx;
771 }
772 }
773 }
774
775 // Cleanup
776 FreeEXRImage(&exrImage);
777 FreeEXRHeader(&exrHeader);
778
779 return imageData;
780
781}
782}
783
784QSSGLoadedTexture *QSSGLoadedTexture::loadLightmapImage(const QString &inPath, const QSSGRenderTextureFormat &inFormat, const QString &key)
785{
786 if (auto stream = QSSGInputUtil::getStreamForFile(inPath))
787 return QSSGLightmapLoader::createTexture(stream, inFormat, key);
788 return nullptr;
789}
790
791QSSGLoadedTexture *QSSGLoadedTexture::loadHdrImage(const QSharedPointer<QIODevice> &source, const QSSGRenderTextureFormat &inFormat)
792{
793 QSSGLoadedTexture *imageData = nullptr;
794 // We need to do a sanity check on the inFormat
795 QSSGRenderTextureFormat format = inFormat;
796 if (format.format == QSSGRenderTextureFormat::Unknown) {
797 // Loading HDR images for use outside of lightProbes will end up here
798 // The renderer doesn't understand RGBE8 textures outside of lightProbes
799 // So this needs to be a "real" format
800 // TODO: This is a fallback, but there is no way of telling here what formats are supported
801 format = QSSGRenderTextureFormat::RGBA16F;
802 }
803
804 // .hdr Files
805 imageData = loadRadianceHdr(source, format);
806
807 // .exr Files
808 if (!imageData)
809 imageData = loadExr(source, format);
810
811 return imageData;
812}
813
814// Size in bytes of tightly packed texture data, or -1 if the result would
815// overflow or exceed what dataSizeInBytes can describe. The upload path assumes
816// rows with no padding, so calculateDataSize()'s 4-byte aligned pitch must not
817// be used here.
818static qsizetype packedDataSize(const QSize &size, int depth, int bytesPerPixel)
819{
820 if (size.width() <= 0 || size.height() <= 0 || depth < 0 || bytesPerPixel <= 0)
821 return -1;
822
823 qsizetype dataSize = bytesPerPixel;
824 if (qMulOverflow(dataSize, qsizetype(size.width()), &dataSize)
825 || qMulOverflow(dataSize, qsizetype(size.height()), &dataSize)
826 || qMulOverflow(dataSize, qsizetype(qMax(depth, 1)), &dataSize)) {
827 return -1;
828 }
829
830 if (dataSize > qsizetype(std::numeric_limits<quint32>::max()))
831 return -1;
832
833 return dataSize;
834}
835
836QSSGLoadedTexture *QSSGLoadedTexture::loadTextureData(QSSGRenderTextureData *textureData)
837{
838 QSSGLoadedTexture *imageData = new QSSGLoadedTexture;
839
840 if (!textureData->format().isCompressedTextureFormat()) {
841 const int bytesPerPixel = textureData->format().getSizeofFormat();
842 const qsizetype dataSize = packedDataSize(textureData->size(), textureData->depth(), bytesPerPixel);
843 if (dataSize < 0) {
844 qWarning("TextureData dimensions %dx%dx%d are not usable",
845 textureData->size().width(),
846 textureData->size().height(),
847 textureData->depth());
848 return imageData;
849 }
850 // The size only describes what the data is meant to be; the upload
851 // reads that many bytes from it either way.
852 if (dataSize > textureData->textureData().size()) {
853 qWarning("TextureData holds %lld bytes, but %dx%dx%d at %d bytes per pixel needs %lld",
854 qlonglong(textureData->textureData().size()),
855 textureData->size().width(),
856 textureData->size().height(),
857 textureData->depth(),
858 bytesPerPixel,
859 qlonglong(dataSize));
860 return imageData;
861 }
862 imageData->dataSizeInBytes = quint32(dataSize);
863 // We won't modifiy the data, but that is a nasty cast...
864 imageData->data = const_cast<void*>(reinterpret_cast<const void*>(textureData->textureData().data()));
865 imageData->width = textureData->size().width();
866 imageData->height = textureData->size().height();
867 imageData->depth = textureData->depth();
868 imageData->format = textureData->format();
869 imageData->components = textureData->format().getNumberOfComponent();
870 } else {
871 // Compressed Textures work a bit differently
872 // Fill out what makes sense, leave the rest at the default 0 and null.
873 const qsizetype dataSize = textureData->textureData().size();
874 if (dataSize > qsizetype(std::numeric_limits<quint32>::max())) {
875 qWarning("TextureData holds %lld bytes, which exceeds the 4GB limit",
876 qlonglong(dataSize));
877 return imageData;
878 }
879 imageData->data = const_cast<void *>(reinterpret_cast<const void *>(textureData->textureData().data()));
880 imageData->dataSizeInBytes = quint32(dataSize);
881 // When we use depth we need to do slicing per layer for the uploads, but right now there it is non-trivial
882 // to determine the size of each "pixel" for compressed formats, so we don't support it for now.
883 // TODO: We need to force depth to 0 for now, as we don't support compressed 3D textures from texureData
884 imageData->width = textureData->size().width();
885 imageData->height = textureData->size().height();
886 imageData->format = textureData->format();
887 }
888
889 // #TODO: add an API to make this explicit
890 // For now we assume HDR formats are linear and everything else
891 // is sRGB, which is not ideal but so far this is only used by
892 // the environment mapper code
893 if (imageData->format == QSSGRenderTextureFormat::RGBE8 ||
894 imageData->format == QSSGRenderTextureFormat::RGBA16F ||
895 imageData->format == QSSGRenderTextureFormat::RGBA32F ||
896 imageData->format == QSSGRenderTextureFormat::BC6H)
897 imageData->isSRGB = false;
898 else
899 imageData->isSRGB = true;
900
901 return imageData;
902}
903
904namespace {
905
906bool scanImageForAlpha(const void *inData, quint32 inWidth, quint32 inHeight, quint32 inPixelSizeInBytes, quint8 inAlphaSizeInBits)
907{
908 const quint8 *rowPtr = reinterpret_cast<const quint8 *>(inData);
909 bool hasAlpha = false;
910 if (inAlphaSizeInBits == 0)
911 return hasAlpha;
912 if (inPixelSizeInBytes != 2 && inPixelSizeInBytes != 4) {
913 Q_ASSERT(false);
914 return false;
915 }
916 if (inAlphaSizeInBits > 8) {
917 Q_ASSERT(false);
918 return false;
919 }
920
921 quint32 alphaRightShift = inPixelSizeInBytes * 8 - inAlphaSizeInBits;
922 quint32 maxAlphaValue = (1 << inAlphaSizeInBits) - 1;
923
924 for (quint32 rowIdx = 0; rowIdx < inHeight && !hasAlpha; ++rowIdx) {
925 for (quint32 idx = 0; idx < inWidth && !hasAlpha; ++idx, rowPtr += inPixelSizeInBytes) {
926 quint32 pixelValue = 0;
927 if (inPixelSizeInBytes == 2)
928 pixelValue = *(reinterpret_cast<const quint16 *>(rowPtr));
929 else
930 pixelValue = *(reinterpret_cast<const quint32 *>(rowPtr));
931 pixelValue = pixelValue >> alphaRightShift;
932 if (pixelValue < maxAlphaValue)
933 hasAlpha = true;
934 }
935 }
936 return hasAlpha;
937}
938}
939
940QSSGLoadedTexture::~QSSGLoadedTexture()
941{
942 if (data && image.sizeInBytes() <= 0 && ownsData)
943 ::free(data);
944}
945
946bool QSSGLoadedTexture::scanForTransparency() const
947{
948 switch (format.format) {
949 case QSSGRenderTextureFormat::SRGB8A8:
950 case QSSGRenderTextureFormat::RGBA8:
951 if (!data) // dds
952 return true;
953
954 return scanImageForAlpha(data, width, height, 4, 8);
955 // Scan the image.
956 case QSSGRenderTextureFormat::SRGB8:
957 case QSSGRenderTextureFormat::RGB8:
958 case QSSGRenderTextureFormat::RGBE8:
959 return false;
960 case QSSGRenderTextureFormat::RGB565:
961 return false;
962 case QSSGRenderTextureFormat::RGBA5551:
963 if (!data) { // dds
964 return true;
965 } else {
966 return scanImageForAlpha(data, width, height, 2, 1);
967 }
968 case QSSGRenderTextureFormat::Alpha8:
969 return true;
970 case QSSGRenderTextureFormat::R8:
971 case QSSGRenderTextureFormat::Luminance8:
972 case QSSGRenderTextureFormat::RG8:
973 return false;
974 case QSSGRenderTextureFormat::LuminanceAlpha8:
975 if (!data) // dds
976 return true;
977
978 return scanImageForAlpha(data, width, height, 2, 8);
979 case QSSGRenderTextureFormat::RGB_DXT1:
980 return false;
981 case QSSGRenderTextureFormat::RGBA_DXT3:
982 case QSSGRenderTextureFormat::RGBA_DXT1:
983 case QSSGRenderTextureFormat::RGBA_DXT5:
984 return false;
985 case QSSGRenderTextureFormat::RGB9E5:
986 return false;
987 case QSSGRenderTextureFormat::RG32F:
988 case QSSGRenderTextureFormat::RGB32F:
989 case QSSGRenderTextureFormat::RGBA16F:
990 case QSSGRenderTextureFormat::RGBA32F:
991 // TODO : For now, since IBL will be the main consumer, we'll just
992 // pretend there's no alpha. Need to do a proper scan down the line,
993 // but doing it for floats is a little different from integer scans.
994 return false;
995 default:
996 break;
997 }
998 Q_ASSERT(false);
999 return false;
1000}
1001
1002static bool isCompatible(const QImage &img1, const QImage &img2)
1003{
1004 if (img1.size() != img2.size())
1005 return false;
1006 if (img1.pixelFormat().channelCount() != img2.pixelFormat().channelCount())
1007 return false;
1008
1009 return true;
1010}
1011
1012static QSSGLoadedTexture *loadCubeMap(const QString &inPath, bool flipY)
1013{
1014 QStringList fileNames;
1015 if (inPath.contains(QStringLiteral("%p"))) {
1016 fileNames.reserve(6);
1017 const char *faces[6] = { "posx", "negx", "posy", "negy", "posz", "negz" };
1018 for (const auto face : faces) {
1019 QString fileName = inPath;
1020 fileName.replace(QStringLiteral("%p"), QLatin1StringView(face));
1021 fileNames << fileName;
1022 }
1023
1024 } else if (inPath.contains(QStringLiteral(";"))) {
1025 fileNames = inPath.split(QChar(u';'));
1026 }
1027 if (fileNames.size() != 6)
1028 return nullptr; // TODO: allow sparse cube maps (with some faces missing)
1029 std::unique_ptr<QTextureFileData> textureFileData = std::make_unique<QTextureFileData>(QTextureFileData::ImageMode);
1030 textureFileData->setNumFaces(6);
1031 textureFileData->setNumLevels(1);
1032 textureFileData->setLogName(inPath.toUtf8());
1033 QImage prevImage;
1034 for (int i = 0; i < 6; ++i) {
1035 QString searchName = fileNames[i];
1036 QString filePath;
1037 auto stream = QSSGInputUtil::getStreamForFile(searchName, true, &filePath);
1038 if (!stream)
1039 return nullptr;
1040
1041 QImage face = loadImage(filePath, !flipY); // Cube maps are flipped the other way
1042 if (face.isNull() || (!prevImage.isNull() && !isCompatible(prevImage, face))) {
1043 return nullptr;
1044 }
1045 textureFileData->setData(face, 0, i);
1046 textureFileData->setSize(face.size());
1047 prevImage = face;
1048 }
1049
1050 const qsizetype dataSize = prevImage.sizeInBytes();
1051 if (dataSize > qsizetype(std::numeric_limits<quint32>::max())) {
1052 qWarning("Cube map face %dx%d is too large to describe",
1053 prevImage.width(),
1054 prevImage.height());
1055 return nullptr;
1056 }
1057
1058 QSSGLoadedTexture *retval = new QSSGLoadedTexture;
1059
1060 retval->textureFileData = *textureFileData;
1061
1062 retval->width = prevImage.width();
1063 retval->height = prevImage.height();
1064 retval->components = prevImage.pixelFormat().channelCount();
1065 retval->image = prevImage;
1066 retval->data = (void *)retval->image.bits();
1067 retval->dataSizeInBytes = quint32(dataSize);
1068 retval->setFormatFromComponents();
1069 // #TODO: This is a very crude way detect color space
1070 retval->isSRGB = prevImage.colorSpace().transferFunction() != QColorSpace::TransferFunction::Linear;
1071
1072 return retval;
1073}
1074
1075QSSGLoadedTexture *QSSGLoadedTexture::load(const QString &inPath,
1076 const QSSGRenderTextureFormat &inFormat,
1077 bool inFlipY)
1078{
1079 if (inPath.isEmpty())
1080 return nullptr;
1081
1082 QSSGLoadedTexture *theLoadedImage = nullptr;
1083 QString fileName;
1084 QSSGInputUtil::FileType fileType = QSSGInputUtil::UnknownFile;
1085 QSharedPointer<QIODevice> theStream =
1086 QSSGInputUtil::getStreamForTextureFile(inPath, true, &fileName, &fileType);
1087
1088 if (theStream) {
1089 switch (fileType) {
1090 case QSSGInputUtil::HdrFile:
1091 // inFormat is a suggestion that's only relevant for HDR images
1092 // (tells if we want want RGBA16F or RGBE-on-RGBA8)
1093 theLoadedImage = loadHdrImage(theStream, inFormat);
1094 break;
1095 case QSSGInputUtil::TextureFile:
1096 theLoadedImage = loadCompressedImage(fileName); // no choice but to ignore inFlipY here
1097 break;
1098 default:
1099 theLoadedImage = loadQImage(fileName, inFlipY);
1100 break;
1101 }
1102 } else {
1103 // Check to see if we can find a cubemap
1104 return loadCubeMap(inPath, inFlipY);
1105 }
1106 return theLoadedImage;
1107}
1108
1109QT_END_NAMESPACE
Combined button and popup list for selecting options.
static qsizetype packedDataSize(const QSize &size, int depth, int bytesPerPixel)
static bool isCompatible(const QImage &img1, const QImage &img2)
static QSSGRenderTextureFormat fromGLtoTextureFormat(quint32 internalFormat)
static QImage loadImage(const QString &inPath, bool flipVertical)
static QSSGLoadedTexture * loadCubeMap(const QString &inPath, bool flipY)