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