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 <QtMath>
13
14#include <QtQuick3DUtils/private/qssgutils_p.h>
15#include <QtQuick3DUtils/private/qssgassert_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
376
377inline int calculateLine(int width, int bitdepth) { return ((width * bitdepth) + 7) / 8; }
378
379inline int calculatePitch(int line) { return (line + 3) & ~3; }
380
381float convertComponent(int exponent, int val)
382{
383 float v = val / (256.0f);
384 float d = powf(2.0f, (float)exponent - 128.0f);
385 return v * d;
386}
387
388void decrunchScanline(const char *&p, const char *pEnd, RGBE *scanline, int w)
389{
390 scanline[0][R] = *p++;
391 scanline[0][G] = *p++;
392 scanline[0][B] = *p++;
393 scanline[0][E] = *p++;
394
395 if (scanline[0][R] == 2 && scanline[0][G] == 2 && scanline[0][B] < 128) {
396 // new rle, the first pixel was a dummy
397 for (int channel = 0; channel < 4; ++channel) {
398 for (int x = 0; x < w && p < pEnd; ) {
399 unsigned char c = *p++;
400 if (c > 128) { // run
401 if (p < pEnd) {
402 int repCount = c & 127;
403 c = *p++;
404 while (repCount--)
405 scanline[x++][channel] = c;
406 }
407 } else { // not a run
408 while (c-- && p < pEnd)
409 scanline[x++][channel] = *p++;
410 }
411 }
412 }
413 } else {
414 // old rle
415 scanline[0][R] = 2;
416 int bitshift = 0;
417 int x = 1;
418 while (x < w && pEnd - p >= 4) {
419 scanline[x][R] = *p++;
420 scanline[x][G] = *p++;
421 scanline[x][B] = *p++;
422 scanline[x][E] = *p++;
423
424 if (scanline[x][R] == 1 && scanline[x][G] == 1 && scanline[x][B] == 1) { // run
425 int repCount = scanline[x][3] << bitshift;
426 while (repCount--) {
427 memcpy(scanline[x], scanline[x - 1], 4);
428 ++x;
429 }
430 bitshift += 8;
431 } else { // not a run
432 ++x;
433 bitshift = 0;
434 }
435 }
436 }
437}
438
439void decodeScanlineToTexture(RGBE *scanline, int width, void *outBuf, quint32 offset, QSSGRenderTextureFormat inFormat)
440{
441 quint8 *target = reinterpret_cast<quint8 *>(outBuf);
442 target += offset;
443
444 if (inFormat == QSSGRenderTextureFormat::RGBE8) {
445 memcpy(target, scanline, size_t(width) * 4);
446 } else {
447 float rgbaF32[4];
448 for (int i = 0; i < width; ++i) {
449 rgbaF32[R] = convertComponent(scanline[i][E], scanline[i][R]);
450 rgbaF32[G] = convertComponent(scanline[i][E], scanline[i][G]);
451 rgbaF32[B] = convertComponent(scanline[i][E], scanline[i][B]);
452 rgbaF32[3] = 1.0f;
453
454 inFormat.encodeToPixel(rgbaF32, target, i * inFormat.getSizeofFormat());
455 }
456 }
457}
458
459QSSGLoadedTexture *loadRadianceHdr(const QSharedPointer<QIODevice> &source, const QSSGRenderTextureFormat &format)
460{
461 QSSGLoadedTexture *imageData = nullptr;
462
463 char sig[256];
464 source->seek(0);
465 source->read(sig, 11);
466 if (!strncmp(sig, "#?RADIANCE\n", 11)) {
467 QByteArray buf = source->readAll();
468 const char *p = buf.constData();
469 const char *pEnd = p + buf.size();
470
471 // Process lines until the empty one.
472 QByteArray line;
473 while (p < pEnd) {
474 char c = *p++;
475 if (c == '\n') {
476 if (line.isEmpty())
477 break;
478 if (line.startsWith(QByteArrayLiteral("FORMAT="))) {
479 const QByteArray format = line.mid(7).trimmed();
480 if (format != QByteArrayLiteral("32-bit_rle_rgbe")) {
481 qWarning("HDR format '%s' is not supported", format.constData());
482 return imageData;
483 }
484 }
485 line.clear();
486 } else {
487 line.append(c);
488 }
489 }
490 if (p == pEnd) {
491 qWarning("Malformed HDR image data at property strings");
492 return imageData;
493 }
494
495 // Get the resolution string.
496 while (p < pEnd) {
497 char c = *p++;
498 if (c == '\n')
499 break;
500 line.append(c);
501 }
502 if (p == pEnd) {
503 qWarning("Malformed HDR image data at resolution string");
504 return imageData;
505 }
506
507 int width = 0;
508 int height = 0;
509 // We only care about the standard orientation.
510#ifdef Q_CC_MSVC
511 if (!sscanf_s(line.constData(), "-Y %d +X %d", &height, &width)) {
512#else
513 if (!sscanf(line.constData(), "-Y %d +X %d", &height, &width)) {
514#endif
515 qWarning("Unsupported HDR resolution string '%s'", line.constData());
516 return imageData;
517 }
518 if (width <= 0 || height <= 0) {
519 qWarning("Invalid HDR resolution");
520 return imageData;
521 }
522
523 const int bytesPerPixel = format.getSizeofFormat();
524 const int bitCount = bytesPerPixel * 8;
525 const int pitch = calculatePitch(calculateLine(width, bitCount));
526 const size_t dataSize = height * pitch;
527 QSSG_CHECK_X(dataSize <= std::numeric_limits<quint32>::max(), "Requested data size exceeds 4GB limit!");
528 imageData = new QSSGLoadedTexture;
529 imageData->dataSizeInBytes = quint32(dataSize);
530 imageData->data = ::malloc(dataSize);
531 imageData->width = width;
532 imageData->height = height;
533 imageData->format = format;
534 imageData->components = format.getNumberOfComponent();
535 imageData->isSRGB = false;
536
537 // Allocate a scanline worth of RGBE data
538 RGBE *scanline = new RGBE[width];
539
540 // Note we are writing to the data buffer from bottom to top
541 // to correct for -Y orientation
542 for (int y = 0; y < height; ++y) {
543 quint32 byteOffset = quint32((height - 1 - y) * width * bytesPerPixel);
544 if (pEnd - p < 4) {
545 qWarning("Unexpected end of HDR data");
546 delete[] scanline;
547 return imageData;
548 }
549 decrunchScanline(p, pEnd, scanline, width);
550 decodeScanlineToTexture(scanline, width, imageData->data, byteOffset, format);
551 }
552
553 delete[] scanline;
554 }
555
556 return imageData;
557}
558
559QSSGLoadedTexture *loadExr(const QSharedPointer<QIODevice> &source, const QSSGRenderTextureFormat format)
560{
561 QSSGLoadedTexture *imageData = nullptr;
562
563 char versionBuffer[tinyexr::kEXRVersionSize];
564 source->seek(0);
565 auto size = source->read(versionBuffer, tinyexr::kEXRVersionSize);
566 // Check if file is big enough
567 if (size != tinyexr::kEXRVersionSize)
568 return imageData;
569 // Try to load the Version
570 EXRVersion exrVersion;
571 if (ParseEXRVersionFromMemory(&exrVersion, reinterpret_cast<unsigned char *>(versionBuffer), tinyexr::kEXRVersionSize) != TINYEXR_SUCCESS)
572 return imageData;
573
574 // Check that the file is not a multipart file
575 if (exrVersion.multipart)
576 return imageData;
577
578 // If we get here, than this is an EXR file
579 source->seek(0);
580 QByteArray buf = source->readAll();
581 const char *err = nullptr;
582 // Header
583 EXRHeader exrHeader;
584 InitEXRHeader(&exrHeader);
585 if (ParseEXRHeaderFromMemory(&exrHeader,
586 &exrVersion,
587 reinterpret_cast<const unsigned char *>(buf.constData()),
588 buf.size(),
589 &err) != TINYEXR_SUCCESS) {
590 qWarning("Failed to parse EXR Header with error: '%s'", err);
591 FreeEXRErrorMessage(err);
592 return imageData;
593 }
594
595 // Make sure we get floats instead of half floats
596 for (int i = 0; i < exrHeader.num_channels; i++) {
597 if (exrHeader.pixel_types[i] == TINYEXR_PIXELTYPE_HALF)
598 exrHeader.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
599 }
600
601 // Image
602 EXRImage exrImage;
603
604 InitEXRImage(&exrImage);
605 if (LoadEXRImageFromMemory(&exrImage,
606 &exrHeader,
607 reinterpret_cast<const unsigned char *>(buf.constData()),
608 buf.size(),
609 &err) != TINYEXR_SUCCESS) {
610 qWarning("Failed to load EXR Image with error: '%s'", err);
611 FreeEXRHeader(&exrHeader);
612 FreeEXRErrorMessage(err);
613 return imageData;
614 }
615
616 // Setup Output container
617 const int bytesPerPixel = format.getSizeofFormat();
618 const int bitCount = bytesPerPixel * 8;
619 const int pitch = calculatePitch(calculateLine(exrImage.width, bitCount));
620 const size_t dataSize = exrImage.height * pitch;
621 QSSG_CHECK_X(dataSize <= std::numeric_limits<quint32>::max(), "Requested data size exceeds 4GB limit!");
622 imageData = new QSSGLoadedTexture;
623 imageData->dataSizeInBytes = quint32(dataSize);
624 imageData->data = ::malloc(imageData->dataSizeInBytes);
625 imageData->width = exrImage.width;
626 imageData->height = exrImage.height;
627 imageData->format = format;
628 imageData->components = format.getNumberOfComponent();
629 imageData->isSRGB = false;
630
631 quint8 *target = reinterpret_cast<quint8 *>(imageData->data);
632
633 // Convert data
634 // RGBA
635 int idxR = -1;
636 int idxG = -1;
637 int idxB = -1;
638 int idxA = -1;
639 for (int c = 0; c < exrHeader.num_channels; c++) {
640 if (strcmp(exrHeader.channels[c].name, "R") == 0)
641 idxR = c;
642 else if (strcmp(exrHeader.channels[c].name, "G") == 0)
643 idxG = c;
644 else if (strcmp(exrHeader.channels[c].name, "B") == 0)
645 idxB = c;
646 else if (strcmp(exrHeader.channels[c].name, "A") == 0)
647 idxA = c;
648 }
649 const bool isSingleChannel = exrHeader.num_channels == 1;
650 float rgbaF32[4];
651
652 if (exrHeader.tiled) {
653 for (int it = 0; it < exrImage.num_tiles; it++) {
654 for (int j = 0; j < exrHeader.tile_size_y; j++)
655 for (int i = 0; i < exrHeader.tile_size_x; i++) {
656 const int ii =
657 exrImage.tiles[it].offset_x * exrHeader.tile_size_x + i;
658 const int jj =
659 exrImage.tiles[it].offset_y * exrHeader.tile_size_y + j;
660 const int inverseJJ = std::abs(jj - (exrImage.height - 1));
661 const int idx = ii + inverseJJ * exrImage.width;
662
663 // out of region check.
664 if (ii >= exrImage.width) {
665 continue;
666 }
667 if (jj >= exrImage.height) {
668 continue;
669 }
670 const int srcIdx = i + j * exrHeader.tile_size_x;
671 unsigned char **src = exrImage.tiles[it].images;
672 if (isSingleChannel) {
673 rgbaF32[R] = reinterpret_cast<float **>(src)[0][srcIdx];
674 rgbaF32[G] = rgbaF32[R];
675 rgbaF32[B] = rgbaF32[R];
676 rgbaF32[3] = rgbaF32[R];
677 } else {
678 rgbaF32[R] = reinterpret_cast<float **>(src)[idxR][srcIdx];
679 rgbaF32[G] = reinterpret_cast<float **>(src)[idxG][srcIdx];
680 rgbaF32[B] = reinterpret_cast<float **>(src)[idxB][srcIdx];
681 if (idxA != -1)
682 rgbaF32[3] = reinterpret_cast<float **>(src)[idxA][srcIdx];
683 else
684 rgbaF32[3] = 1.0f;
685 }
686 format.encodeToPixel(rgbaF32, target, idx * bytesPerPixel);
687 }
688 }
689 } else {
690 int idx = 0;
691 for (int y = exrImage.height - 1; y >= 0; --y) {
692 for (int x = 0; x < exrImage.width; x++) {
693 const int i = y * exrImage.width + x;
694 if (isSingleChannel) {
695 rgbaF32[R] = reinterpret_cast<float **>(exrImage.images)[0][i];
696 rgbaF32[G] = rgbaF32[R];
697 rgbaF32[B] = rgbaF32[R];
698 rgbaF32[3] = rgbaF32[R];
699 } else {
700 rgbaF32[R] = reinterpret_cast<float **>(exrImage.images)[idxR][i];
701 rgbaF32[G] = reinterpret_cast<float **>(exrImage.images)[idxG][i];
702 rgbaF32[B] = reinterpret_cast<float **>(exrImage.images)[idxB][i];
703 if (idxA != -1)
704 rgbaF32[3] = reinterpret_cast<float **>(exrImage.images)[idxA][i];
705 else
706 rgbaF32[3] = 1.0f;
707 }
708 format.encodeToPixel(rgbaF32, target, idx * bytesPerPixel);
709 ++idx;
710 }
711 }
712 }
713
714 // Cleanup
715 FreeEXRImage(&exrImage);
716 FreeEXRHeader(&exrHeader);
717
718 return imageData;
719
720}
721}
722
723QSSGLoadedTexture *QSSGLoadedTexture::loadLightmapImage(const QString &inPath, const QSSGRenderTextureFormat &inFormat, const QString &key)
724{
725 if (auto stream = QSSGInputUtil::getStreamForFile(inPath))
726 return QSSGLightmapLoader::createTexture(stream, inFormat, key);
727 return nullptr;
728}
729
730QSSGLoadedTexture *QSSGLoadedTexture::loadHdrImage(const QSharedPointer<QIODevice> &source, const QSSGRenderTextureFormat &inFormat)
731{
732 QSSGLoadedTexture *imageData = nullptr;
733 // We need to do a sanity check on the inFormat
734 QSSGRenderTextureFormat format = inFormat;
735 if (format.format == QSSGRenderTextureFormat::Unknown) {
736 // Loading HDR images for use outside of lightProbes will end up here
737 // The renderer doesn't understand RGBE8 textures outside of lightProbes
738 // So this needs to be a "real" format
739 // TODO: This is a fallback, but there is no way of telling here what formats are supported
740 format = QSSGRenderTextureFormat::RGBA16F;
741 }
742
743 // .hdr Files
744 imageData = loadRadianceHdr(source, format);
745
746 // .exr Files
747 if (!imageData)
748 imageData = loadExr(source, format);
749
750 return imageData;
751}
752
753QSSGLoadedTexture *QSSGLoadedTexture::loadTextureData(QSSGRenderTextureData *textureData)
754{
755 QSSGLoadedTexture *imageData = new QSSGLoadedTexture;
756
757 if (!textureData->format().isCompressedTextureFormat()) {
758 const int bytesPerPixel = textureData->format().getSizeofFormat();
759 const int bitCount = bytesPerPixel * 8;
760 const int pitch = calculatePitch(calculateLine(textureData->size().width(), bitCount));
761 size_t dataSize = size_t(textureData->size().height()) * pitch;
762 if (textureData->depth() > 0)
763 dataSize *= textureData->depth();
764 QSSG_CHECK_X(dataSize <= std::numeric_limits<quint32>::max(), "Requested data size exceeds 4GB limit!");
765 imageData->dataSizeInBytes = quint32(dataSize);
766 // We won't modifiy the data, but that is a nasty cast...
767 imageData->data = const_cast<void*>(reinterpret_cast<const void*>(textureData->textureData().data()));
768 imageData->width = textureData->size().width();
769 imageData->height = textureData->size().height();
770 imageData->depth = textureData->depth();
771 imageData->format = textureData->format();
772 imageData->components = textureData->format().getNumberOfComponent();
773 } else {
774 // Compressed Textures work a bit differently
775 // Fill out what makes sense, leave the rest at the default 0 and null.
776 imageData->data = const_cast<void*>(reinterpret_cast<const void*>(textureData->textureData().data()));
777 const size_t dataSize = textureData->textureData().size();
778 QSSG_CHECK_X(dataSize <= std::numeric_limits<quint32>::max(), "Requested data size exceeds 4GB limit!");
779 imageData->dataSizeInBytes = quint32(dataSize);
780 // When we use depth we need to do slicing per layer for the uploads, but right now there it is non-trivial
781 // to determine the size of each "pixel" for compressed formats, so we don't support it for now.
782 // TODO: We need to force depth to 0 for now, as we don't support compressed 3D textures from texureData
783 imageData->width = textureData->size().width();
784 imageData->height = textureData->size().height();
785 imageData->format = textureData->format();
786 }
787
788 // #TODO: add an API to make this explicit
789 // For now we assume HDR formats are linear and everything else
790 // is sRGB, which is not ideal but so far this is only used by
791 // the environment mapper code
792 if (imageData->format == QSSGRenderTextureFormat::RGBE8 ||
793 imageData->format == QSSGRenderTextureFormat::RGBA16F ||
794 imageData->format == QSSGRenderTextureFormat::RGBA32F ||
795 imageData->format == QSSGRenderTextureFormat::BC6H)
796 imageData->isSRGB = false;
797 else
798 imageData->isSRGB = true;
799
800 return imageData;
801}
802
803namespace {
804
805bool scanImageForAlpha(const void *inData, quint32 inWidth, quint32 inHeight, quint32 inPixelSizeInBytes, quint8 inAlphaSizeInBits)
806{
807 const quint8 *rowPtr = reinterpret_cast<const quint8 *>(inData);
808 bool hasAlpha = false;
809 if (inAlphaSizeInBits == 0)
810 return hasAlpha;
811 if (inPixelSizeInBytes != 2 && inPixelSizeInBytes != 4) {
812 Q_ASSERT(false);
813 return false;
814 }
815 if (inAlphaSizeInBits > 8) {
816 Q_ASSERT(false);
817 return false;
818 }
819
820 quint32 alphaRightShift = inPixelSizeInBytes * 8 - inAlphaSizeInBits;
821 quint32 maxAlphaValue = (1 << inAlphaSizeInBits) - 1;
822
823 for (quint32 rowIdx = 0; rowIdx < inHeight && !hasAlpha; ++rowIdx) {
824 for (quint32 idx = 0; idx < inWidth && !hasAlpha; ++idx, rowPtr += inPixelSizeInBytes) {
825 quint32 pixelValue = 0;
826 if (inPixelSizeInBytes == 2)
827 pixelValue = *(reinterpret_cast<const quint16 *>(rowPtr));
828 else
829 pixelValue = *(reinterpret_cast<const quint32 *>(rowPtr));
830 pixelValue = pixelValue >> alphaRightShift;
831 if (pixelValue < maxAlphaValue)
832 hasAlpha = true;
833 }
834 }
835 return hasAlpha;
836}
837}
838
839QSSGLoadedTexture::~QSSGLoadedTexture()
840{
841 if (data && image.sizeInBytes() <= 0 && ownsData)
842 ::free(data);
843}
844
845bool QSSGLoadedTexture::scanForTransparency() const
846{
847 switch (format.format) {
848 case QSSGRenderTextureFormat::SRGB8A8:
849 case QSSGRenderTextureFormat::RGBA8:
850 if (!data) // dds
851 return true;
852
853 return scanImageForAlpha(data, width, height, 4, 8);
854 // Scan the image.
855 case QSSGRenderTextureFormat::SRGB8:
856 case QSSGRenderTextureFormat::RGB8:
857 case QSSGRenderTextureFormat::RGBE8:
858 return false;
859 case QSSGRenderTextureFormat::RGB565:
860 return false;
861 case QSSGRenderTextureFormat::RGBA5551:
862 if (!data) { // dds
863 return true;
864 } else {
865 return scanImageForAlpha(data, width, height, 2, 1);
866 }
867 case QSSGRenderTextureFormat::Alpha8:
868 return true;
869 case QSSGRenderTextureFormat::R8:
870 case QSSGRenderTextureFormat::Luminance8:
871 case QSSGRenderTextureFormat::RG8:
872 return false;
873 case QSSGRenderTextureFormat::LuminanceAlpha8:
874 if (!data) // dds
875 return true;
876
877 return scanImageForAlpha(data, width, height, 2, 8);
878 case QSSGRenderTextureFormat::RGB_DXT1:
879 return false;
880 case QSSGRenderTextureFormat::RGBA_DXT3:
881 case QSSGRenderTextureFormat::RGBA_DXT1:
882 case QSSGRenderTextureFormat::RGBA_DXT5:
883 return false;
884 case QSSGRenderTextureFormat::RGB9E5:
885 return false;
886 case QSSGRenderTextureFormat::RG32F:
887 case QSSGRenderTextureFormat::RGB32F:
888 case QSSGRenderTextureFormat::RGBA16F:
889 case QSSGRenderTextureFormat::RGBA32F:
890 // TODO : For now, since IBL will be the main consumer, we'll just
891 // pretend there's no alpha. Need to do a proper scan down the line,
892 // but doing it for floats is a little different from integer scans.
893 return false;
894 default:
895 break;
896 }
897 Q_ASSERT(false);
898 return false;
899}
900
901static bool isCompatible(const QImage &img1, const QImage &img2)
902{
903 if (img1.size() != img2.size())
904 return false;
905 if (img1.pixelFormat().channelCount() != img2.pixelFormat().channelCount())
906 return false;
907
908 return true;
909}
910
911static QSSGLoadedTexture *loadCubeMap(const QString &inPath, bool flipY)
912{
913 QStringList fileNames;
914 if (inPath.contains(QStringLiteral("%p"))) {
915 fileNames.reserve(6);
916 const char *faces[6] = { "posx", "negx", "posy", "negy", "posz", "negz" };
917 for (const auto face : faces) {
918 QString fileName = inPath;
919 fileName.replace(QStringLiteral("%p"), QLatin1StringView(face));
920 fileNames << fileName;
921 }
922
923 } else if (inPath.contains(QStringLiteral(";"))) {
924 fileNames = inPath.split(QChar(u';'));
925 }
926 if (fileNames.size() != 6)
927 return nullptr; // TODO: allow sparse cube maps (with some faces missing)
928 std::unique_ptr<QTextureFileData> textureFileData = std::make_unique<QTextureFileData>(QTextureFileData::ImageMode);
929 textureFileData->setNumFaces(6);
930 textureFileData->setNumLevels(1);
931 textureFileData->setLogName(inPath.toUtf8());
932 QImage prevImage;
933 for (int i = 0; i < 6; ++i) {
934 QString searchName = fileNames[i];
935 QString filePath;
936 auto stream = QSSGInputUtil::getStreamForFile(searchName, true, &filePath);
937 if (!stream)
938 return nullptr;
939
940 QImage face = loadImage(filePath, !flipY); // Cube maps are flipped the other way
941 if (face.isNull() || (!prevImage.isNull() && !isCompatible(prevImage, face))) {
942 return nullptr;
943 }
944 textureFileData->setData(face, 0, i);
945 textureFileData->setSize(face.size());
946 prevImage = face;
947 }
948
949 QSSGLoadedTexture *retval = new QSSGLoadedTexture;
950
951 retval->textureFileData = *textureFileData;
952
953 retval->width = prevImage.width();
954 retval->height = prevImage.height();
955 retval->components = prevImage.pixelFormat().channelCount();
956 retval->image = prevImage;
957 retval->data = (void *)retval->image.bits();
958 const size_t dataSize = prevImage.sizeInBytes();
959 QSSG_CHECK_X(dataSize <= std::numeric_limits<quint32>::max(), "Requested data size exceeds 4GB limit!");
960 retval->dataSizeInBytes = quint32(dataSize);
961 retval->setFormatFromComponents();
962 // #TODO: This is a very crude way detect color space
963 retval->isSRGB = prevImage.colorSpace().transferFunction() != QColorSpace::TransferFunction::Linear;
964
965 return retval;
966}
967
968QSSGLoadedTexture *QSSGLoadedTexture::load(const QString &inPath,
969 const QSSGRenderTextureFormat &inFormat,
970 bool inFlipY)
971{
972 if (inPath.isEmpty())
973 return nullptr;
974
975 QSSGLoadedTexture *theLoadedImage = nullptr;
976 QString fileName;
977 QSSGInputUtil::FileType fileType = QSSGInputUtil::UnknownFile;
978 QSharedPointer<QIODevice> theStream =
979 QSSGInputUtil::getStreamForTextureFile(inPath, true, &fileName, &fileType);
980
981 if (theStream) {
982 switch (fileType) {
983 case QSSGInputUtil::HdrFile:
984 // inFormat is a suggestion that's only relevant for HDR images
985 // (tells if we want want RGBA16F or RGBE-on-RGBA8)
986 theLoadedImage = loadHdrImage(theStream, inFormat);
987 break;
988 case QSSGInputUtil::TextureFile:
989 theLoadedImage = loadCompressedImage(fileName); // no choice but to ignore inFlipY here
990 break;
991 default:
992 theLoadedImage = loadQImage(fileName, inFlipY);
993 break;
994 }
995 } else {
996 // Check to see if we can find a cubemap
997 return loadCubeMap(inPath, inFlipY);
998 }
999 return theLoadedImage;
1000}
1001
1002QT_END_NAMESPACE
Combined button and popup list for selecting options.
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)