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