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