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
qssgrenderbuffermanager.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
6
7#include <QtQuick3DRuntimeRender/private/qssgrenderloadedtexture_p.h>
8
9#include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h>
10#include <QtQuick3DUtils/private/qssgmeshbvhbuilder_p.h>
11#include <QtQuick3DUtils/private/qssgbounds3_p.h>
12#include <QtQuick3DUtils/private/qssgassert_p.h>
13
14#include <QtQuick/QSGTexture>
16#include <QtCore/QDir>
17#include <QtGui/private/qimage_p.h>
18#include <QtQuick/private/qsgtexture_p.h>
19#include <QtQuick/private/qsgcompressedtexture_p.h>
20#include <QBuffer>
21
22#include "../utils/qssgrenderbasetypes_p.h"
23#include <QtQuick3DRuntimeRender/private/qssgrendergeometry_p.h>
24#include <QtQuick3DRuntimeRender/private/qssgrendermodel_p.h>
25#include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h>
26#include <QtQuick3DRuntimeRender/private/qssgrendertexturedata_p.h>
27#include "../qssgrendercontextcore.h"
28#include <QtQuick3DRuntimeRender/private/qssglightmapper_p.h>
29#include <QtQuick3DRuntimeRender/private/qssgrenderresourceloader_p.h>
30#include <qtquick3d_tracepoints_p.h>
31#include "../extensionapi/qssgrenderextensions.h"
33
35
36using namespace Qt::StringLiterals;
37
39{
40enum class Level
41{
42 Debug = 0x1,
43 Usage = 0x2,
44 // 0x3 DebugAndUsage
45};
46
47#ifdef QT_DEBUG
48static bool enabled(Level level)
49{
51 static auto v = qEnvironmentVariableIntValue("QSSG_BUFFERMANAGER_DEBUG");
52 return v && Level{v} <= DebugAndUsage && (v & quint8(level));
53}
54#else
55static constexpr bool enabled(Level) { return false; }
56#endif
57
58}
59
76
82using AssetMeshMap = QHash<QString, MeshStorageRef>;
83
84Q_GLOBAL_STATIC(AssetMeshMap, g_assetMeshMap)
85
86// Returns !idx@asset_id
87QString QSSGBufferManager::runtimeMeshSourceName(const QString &assetId, qsizetype meshId)
88{
89 return QString::fromUtf16(u"!%1@%2").arg(QString::number(meshId), assetId);
90}
91
92using MeshIdxNamePair = QPair<qsizetype, QString>;
93static MeshIdxNamePair splitRuntimeMeshPath(const QSSGRenderPath &rpath)
94{
95 const auto &path = rpath.path();
96 Q_ASSERT(path.startsWith(u'!'));
97 const auto strings = path.mid(1).split(u'@');
98 const bool hasData = (strings.size() == 2) && !strings[0].isEmpty() && !strings[1].isEmpty();
99 qsizetype idx = -1;
100 bool ok = false;
101 if (hasData)
102 idx = strings.at(0).toLongLong(&ok);
103
104 return (ok) ? qMakePair(idx, strings.at(1)) : qMakePair(qsizetype(-1), QString());
105}
106
107namespace {
108struct PrimitiveEntry
109{
110 // Name of the primitive as it will be in e.g., the QML file
111 const char *primitive;
112 // Name of the primitive file on the filesystem
113 const char *file;
114};
115}
116
117static const int nPrimitives = 5;
118static const PrimitiveEntry primitives[nPrimitives] = {
119 {"#Rectangle", "/Rectangle.mesh"},
120 {"#Sphere","/Sphere.mesh"},
121 {"#Cube","/Cube.mesh"},
122 {"#Cone","/Cone.mesh"},
123 {"#Cylinder","/Cylinder.mesh"},
124};
125
126static const char *primitivesDirectory = "res//primitives";
127
128static constexpr QSize sizeForMipLevel(int mipLevel, const QSize &baseLevelSize)
129{
130 return QSize(qMax(1, baseLevelSize.width() >> mipLevel), qMax(1, baseLevelSize.height() >> mipLevel));
131}
132
133static QPair<QSSGMesh::Mesh, QString> loadFromLightmapFile(const QString &lightmapPath, const QString &lightmapKey)
134{
135 QPair<QSSGMesh::Mesh, QString> retVal;
136
137 if (lightmapPath.isEmpty() || lightmapKey.isEmpty())
138 return retVal;
139
140 if (const auto io = QSSGLightmapLoader::open(lightmapPath)) {
141 const QVariantMap metadata = io->readMap(lightmapKey, QSSGLightmapIODataTag::Metadata);
142 if (metadata.isEmpty())
143 return retVal;
144
145 const QString meshKey = metadata[QStringLiteral("mesh_key")].toString();
146 if (meshKey.isEmpty())
147 return retVal;
148
149 QByteArray meshData = io->readData(meshKey, QSSGLightmapIODataTag::Mesh);
150 if (meshData.isEmpty())
151 return retVal;
152
153 QBuffer buffer(&meshData);
154 buffer.open(QIODevice::ReadOnly);
155 retVal.first = QSSGMesh::Mesh::loadMesh(&buffer, 1);
156 retVal.second = QFileInfo(lightmapPath).fileName() + " ["_L1 + lightmapKey + u']';
157 }
158
159 return retVal;
160}
161
162QSSGBufferManager::QSSGBufferManager()
163{
164}
165
166QSSGBufferManager::~QSSGBufferManager()
167{
168 clear();
169 m_contextInterface = nullptr;
170}
171
172void QSSGBufferManager::setRenderContextInterface(QSSGRenderContextInterface *ctx)
173{
174 m_contextInterface = ctx;
175}
176
177void QSSGBufferManager::releaseCachedResources()
178{
179 clear();
180}
181
182void QSSGBufferManager::releaseResourcesForLayer(QSSGRenderLayer *layer)
183{
184 // frameResetIndex must be +1 since it's depending on being the
185 // next frame and this is the cleanup after the final frame as
186 // the layer is destroyed
187 resetUsageCounters(frameResetIndex + 1, layer);
188 cleanupUnreferencedBuffers(frameResetIndex + 1, layer);
189}
190
191QSSGRenderImageTexture QSSGBufferManager::loadRenderImage(const QSSGRenderImage *image,
192 MipMode inMipMode,
193 LoadRenderImageFlags flags)
194{
195 if (inMipMode == MipModeFollowRenderImage)
196 inMipMode = image->m_generateMipmaps ? MipModeEnable : MipModeDisable;
197
198 const auto &context = m_contextInterface->rhiContext();
199 QSSGRenderImageTexture result;
200 if (image->m_qsgTexture) {
201 QRhi *rhi = context->rhi();
202 QSGTexture *qsgTexture = image->m_qsgTexture;
203 QRhiTexture *rhiTex = qsgTexture->rhiTexture(); // this may not be valid until commit and that's ok
204 if (!rhiTex || rhiTex->rhi() == rhi) {
205 // A QSGTexture from a textureprovider that is not a QSGDynamicTexture
206 // needs to be pushed to get its content updated (or even to create a
207 // QRhiTexture in the first place).
208 QRhiResourceUpdateBatch *rub = rhi->nextResourceUpdateBatch();
209 if (qsgTexture->isAtlasTexture()) {
210 // This returns a non-atlased QSGTexture (or does nothing if the
211 // extraction has already been done), the ownership of which stays with
212 // the atlas. As we do not store (and do not own) qsgTexture below,
213 // apart from using it as a cache key and querying its QRhiTexture
214 // (which we again do not own), we can just pretend we got the
215 // non-atlased QSGTexture in the first place.
216 qsgTexture = qsgTexture->removedFromAtlas(rub);
217 }
218 qsgTexture->commitTextureOperations(rhi, rub);
219 context->commandBuffer()->resourceUpdate(rub);
220 auto theImage = qsgImageMap.find(qsgTexture);
221 if (theImage == qsgImageMap.end())
222 theImage = qsgImageMap.insert(qsgTexture, ImageData());
223 theImage.value().renderImageTexture.m_texture = qsgTexture->rhiTexture();
224 theImage.value().renderImageTexture.m_flags.setHasTransparency(qsgTexture->hasAlphaChannel());
225 theImage.value().usageCounts[currentLayer]++;
226 result = theImage.value().renderImageTexture;
227 // inMipMode is ignored completely when sourcing the texture from a
228 // QSGTexture. Mipmap generation is not supported, whereas
229 // attempting to use such a texture as a light probe will fail. (no
230 // mip levels, no pre-filtering) In the latter case, print a warning
231 // because that will definitely lead to visual problems in the result.
232 if (inMipMode == MipModeBsdf)
233 qWarning("Cannot use QSGTexture from Texture.sourceItem as light probe.");
234 } else {
235 qWarning("Cannot use QSGTexture (presumably from Texture.sourceItem) created in another "
236 "window that was using a different graphics device/context. "
237 "Avoid using View3D.importScene between multiple windows.");
238 }
239
240 } else if (image->m_rawTextureData) {
241 Q_TRACE_SCOPE(QSSG_textureLoad);
242 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DTextureLoad);
243 result = loadTextureData(image->m_rawTextureData, inMipMode);
244 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DTextureLoad, stats.imageDataSize, image->profilingId);
245 } else if (!image->m_imagePath.isEmpty()) {
246
247 const ImageCacheKey imageKey = { image->m_imagePath, inMipMode, int(image->type), QString() };
248 auto foundIt = imageMap.find(imageKey);
249 if (foundIt != imageMap.cend()) {
250 result = foundIt.value().renderImageTexture;
251 } else {
252 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DTextureLoad);
253 QScopedPointer<QSSGLoadedTexture> theLoadedTexture;
254 const auto &path = image->m_imagePath.path();
255 const bool flipY = flags.testFlag(LoadWithFlippedY);
256 Q_TRACE_SCOPE(QSSG_textureLoadPath, path);
257 theLoadedTexture.reset(QSSGLoadedTexture::load(path, image->m_format, flipY));
258 if (theLoadedTexture) {
259 foundIt = imageMap.insert(imageKey, ImageData());
260 CreateRhiTextureFlags rhiTexFlags = ScanForTransparency;
261 if (image->type == QSSGRenderGraphObject::Type::ImageCube)
262 rhiTexFlags |= CubeMap;
263 if (!setRhiTexture(foundIt.value().renderImageTexture, theLoadedTexture.data(), inMipMode, rhiTexFlags, QFileInfo(path).fileName())) {
264 foundIt.value() = ImageData();
265 } else if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug)) {
266 qDebug() << "+ uploadTexture: " << image->m_imagePath.path() << currentLayer;
267 }
268 result = foundIt.value().renderImageTexture;
269 increaseMemoryStat(result.m_texture);
270 } else {
271 // We want to make sure that bad path fails once and doesn't fail over and over
272 // again
273 // which could slow down the system quite a bit.
274 foundIt = imageMap.insert(imageKey, ImageData());
275 qCWarning(WARNING, "Failed to load image: %s", qPrintable(path));
276 }
277 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DTextureLoad, stats.imageDataSize, path.toUtf8());
278 }
279 foundIt.value().usageCounts[currentLayer]++;
280 } else if (image->m_extensionsSource) {
281 auto it = renderExtensionTexture.find(image->m_extensionsSource);
282 if (it != renderExtensionTexture.end()) {
283 it->usageCounts[currentLayer]++;
284 result = it->renderImageTexture;
285 }
286 }
287 return result;
288}
289
290QSSGRenderImageTexture QSSGBufferManager::loadTextureData(QSSGRenderTextureData *data, MipMode inMipMode)
291{
292 QSSG_ASSERT(data != nullptr, return {});
293
294 const CustomImageCacheKey imageKey = { data, data->size(), inMipMode };
295 auto theImageData = customTextureMap.find(imageKey);
296 if (theImageData == customTextureMap.end()) {
297 theImageData = customTextureMap.insert(imageKey, ImageData{{}, {}, data->version()});
298 } else if (data->version() == theImageData->version) {
299 // Return the currently loaded texture
300 theImageData.value().usageCounts[currentLayer]++;
301 return theImageData.value().renderImageTexture;
302 } else {
303 // Optimization: If only the version number has changed, we can attempt to reuse the texture.
304 // Just update the version number and let setRhiTexture handle the rest.
305 theImageData->version = data->version();
306 }
307
308 // Load the texture
309 QScopedPointer<QSSGLoadedTexture> theLoadedTexture;
310 if (!data->textureData().isNull()) {
311 theLoadedTexture.reset(QSSGLoadedTexture::loadTextureData(data));
312 theLoadedTexture->ownsData = false;
313 CreateRhiTextureFlags rhiTexFlags = {};
314 if (theLoadedTexture->depth > 0)
315 rhiTexFlags |= Texture3D;
316
317 bool wasTextureCreated = false;
318
319 if (setRhiTexture(theImageData.value().renderImageTexture, theLoadedTexture.data(), inMipMode, rhiTexFlags, data->debugObjectName, &wasTextureCreated)) {
320 if (wasTextureCreated) {
321 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
322 qDebug() << "+ uploadTexture: " << data << theImageData.value().renderImageTexture.m_texture << currentLayer;
323 increaseMemoryStat(theImageData.value().renderImageTexture.m_texture);
324 }
325 } else {
326 theImageData.value() = ImageData();
327 }
328 }
329
330 theImageData.value().usageCounts[currentLayer]++;
331 return theImageData.value().renderImageTexture;
332}
333
334QSSGRenderImageTexture QSSGBufferManager::loadLightmap(const QSSGRenderModel &model)
335{
336 Q_ASSERT(currentLayer);
337
338 if (model.lightmapKey.isEmpty() || currentlyLightmapBaking || !validateLightmap())
339 return {};
340
341 Q_ASSERT(!lightmapSource.isEmpty());
342 static const QSSGRenderTextureFormat format = QSSGRenderTextureFormat::RGBA16F;
343 QSSGRenderImageTexture result;
344 const ImageCacheKey imageKey = { QSSGRenderPath(lightmapSource), MipModeDisable, int(QSSGRenderGraphObject::Type::Image2D), model.lightmapKey };
345 auto foundIt = imageMap.find(imageKey);
346 if (foundIt != imageMap.end()) {
347 result = foundIt.value().renderImageTexture;
348 } else {
349 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DTextureLoad);
350 Q_TRACE_SCOPE(QSSG_textureLoadPath, lightmapSource);
351 QScopedPointer<QSSGLoadedTexture> theLoadedTexture;
352 theLoadedTexture.reset(QSSGLoadedTexture::loadLightmapImage(lightmapSource, format, model.lightmapKey));
353 if (!theLoadedTexture) {
354 qCWarning(WARNING, "Failed to load lightmap image for %s", qPrintable(model.lightmapKey));
355 }
356 foundIt = imageMap.insert(imageKey, ImageData());
357 if (theLoadedTexture) {
358 const QString debugOjbectName = lightmapSource + QStringLiteral(" [%1]").arg(model.lightmapKey);
359 if (!setRhiTexture(foundIt.value().renderImageTexture, theLoadedTexture.data(), MipModeDisable, {}, debugOjbectName))
360 foundIt.value() = ImageData();
361 else if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
362 qDebug() << "+ uploadTexture: " << debugOjbectName << currentLayer;
363 result = foundIt.value().renderImageTexture;
364 }
365 increaseMemoryStat(result.m_texture);
366 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DTextureLoad, stats.imageDataSize, lightmapSource.toUtf8());
367 }
368 foundIt.value().usageCounts[currentLayer]++;
369 return result;
370}
371
372QSSGRenderImageTexture QSSGBufferManager::loadSkinmap(QSSGRenderTextureData *skin)
373{
374 return loadTextureData(skin, MipModeDisable);
375}
376
377QSSGRenderMesh *QSSGBufferManager::getMeshForPicking(const QSSGRenderModel &model) const
378{
379 if (!model.meshPath.isNull()) {
380 const auto foundIt = meshMap.constFind(model.meshPath);
381 if (foundIt != meshMap.constEnd())
382 return foundIt->mesh;
383 }
384
385 if (model.geometry) {
386 const auto foundIt = customMeshMap.constFind(model.geometry);
387 if (foundIt != customMeshMap.constEnd())
388 return foundIt->mesh;
389 }
390
391 return nullptr;
392}
393
394QRhiTexture::Format QSSGBufferManager::toRhiFormat(const QSSGRenderTextureFormat format)
395{
396 switch (format.format) {
397
398 case QSSGRenderTextureFormat::RGBA8:
399 return QRhiTexture::RGBA8;
400 case QSSGRenderTextureFormat::R8:
401 return QRhiTexture::R8;
402 case QSSGRenderTextureFormat::Luminance16: //???
403 case QSSGRenderTextureFormat::R16:
404 return QRhiTexture::R16;
405 case QSSGRenderTextureFormat::LuminanceAlpha8:
406 case QSSGRenderTextureFormat::Luminance8:
407 case QSSGRenderTextureFormat::Alpha8:
408 return QRhiTexture::RED_OR_ALPHA8;
409 case QSSGRenderTextureFormat::RGBA16F:
410 return QRhiTexture::RGBA16F;
411 case QSSGRenderTextureFormat::RGBA32F:
412 return QRhiTexture::RGBA32F;
413 case QSSGRenderTextureFormat::R16F:
414 return QRhiTexture::R16F;
415 case QSSGRenderTextureFormat::R32F:
416 return QRhiTexture::R32F;
417 case QSSGRenderTextureFormat::RGBE8:
418 return QRhiTexture::RGBA8;
419 case QSSGRenderTextureFormat::R32UI:
420 return QRhiTexture::R32UI;
421 case QSSGRenderTextureFormat::RGBA32UI:
422 return QRhiTexture::RGBA32UI;
423 case QSSGRenderTextureFormat::RGB_DXT1:
424 return QRhiTexture::BC1;
425 case QSSGRenderTextureFormat::RGBA_DXT3:
426 return QRhiTexture::BC2;
427 case QSSGRenderTextureFormat::RGBA_DXT5:
428 return QRhiTexture::BC3;
429 case QSSGRenderTextureFormat::RGBA8_ETC2_EAC:
430 return QRhiTexture::ETC2_RGBA8;
431 case QSSGRenderTextureFormat::RGBA_ASTC_4x4:
432 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_4x4:
433 return QRhiTexture::ASTC_4x4;
434 case QSSGRenderTextureFormat::RGBA_ASTC_5x4:
435 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_5x4:
436 return QRhiTexture::ASTC_5x4;
437 case QSSGRenderTextureFormat::RGBA_ASTC_5x5:
438 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_5x5:
439 return QRhiTexture::ASTC_5x5;
440 case QSSGRenderTextureFormat::RGBA_ASTC_6x5:
441 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_6x5:
442 return QRhiTexture::ASTC_6x5;
443 case QSSGRenderTextureFormat::RGBA_ASTC_6x6:
444 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_6x6:
445 return QRhiTexture::ASTC_6x6;
446 case QSSGRenderTextureFormat::RGBA_ASTC_8x5:
447 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_8x5:
448 return QRhiTexture::ASTC_8x5;
449 case QSSGRenderTextureFormat::RGBA_ASTC_8x6:
450 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_8x6:
451 return QRhiTexture::ASTC_8x6;
452 case QSSGRenderTextureFormat::RGBA_ASTC_8x8:
453 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_8x8:
454 return QRhiTexture::ASTC_8x8;
455 case QSSGRenderTextureFormat::RGBA_ASTC_10x5:
456 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_10x5:
457 return QRhiTexture::ASTC_10x5;
458 case QSSGRenderTextureFormat::RGBA_ASTC_10x6:
459 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_10x6:
460 return QRhiTexture::ASTC_10x6;
461 case QSSGRenderTextureFormat::RGBA_ASTC_10x8:
462 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_10x8:
463 return QRhiTexture::ASTC_10x8;
464 case QSSGRenderTextureFormat::RGBA_ASTC_10x10:
465 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_10x10:
466 return QRhiTexture::ASTC_10x10;
467 case QSSGRenderTextureFormat::RGBA_ASTC_12x10:
468 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_12x10:
469 return QRhiTexture::ASTC_12x10;
470 case QSSGRenderTextureFormat::RGBA_ASTC_12x12:
471 case QSSGRenderTextureFormat::SRGB8_Alpha8_ASTC_12x12:
472 return QRhiTexture::ASTC_12x12;
473
474
475 case QSSGRenderTextureFormat::SRGB8A8:
476 return QRhiTexture::RGBA8; // Note: user must keep track of color space manually
477
478 default:
479 qWarning() << "Unsupported texture format" << format.format;
480 return QRhiTexture::UnknownFormat;
481 }
482
483}
484
485// Vertex data for rendering environment cube map
486static const float cube[] = {
487 -1.0f,-1.0f,-1.0f, // -X side
488 -1.0f,-1.0f, 1.0f,
489 -1.0f, 1.0f, 1.0f,
490 -1.0f, 1.0f, 1.0f,
491 -1.0f, 1.0f,-1.0f,
492 -1.0f,-1.0f,-1.0f,
493
494 -1.0f,-1.0f,-1.0f, // -Z side
495 1.0f, 1.0f,-1.0f,
496 1.0f,-1.0f,-1.0f,
497 -1.0f,-1.0f,-1.0f,
498 -1.0f, 1.0f,-1.0f,
499 1.0f, 1.0f,-1.0f,
500
501 -1.0f,-1.0f,-1.0f, // -Y side
502 1.0f,-1.0f,-1.0f,
503 1.0f,-1.0f, 1.0f,
504 -1.0f,-1.0f,-1.0f,
505 1.0f,-1.0f, 1.0f,
506 -1.0f,-1.0f, 1.0f,
507
508 -1.0f, 1.0f,-1.0f, // +Y side
509 -1.0f, 1.0f, 1.0f,
510 1.0f, 1.0f, 1.0f,
511 -1.0f, 1.0f,-1.0f,
512 1.0f, 1.0f, 1.0f,
513 1.0f, 1.0f,-1.0f,
514
515 1.0f, 1.0f,-1.0f, // +X side
516 1.0f, 1.0f, 1.0f,
517 1.0f,-1.0f, 1.0f,
518 1.0f,-1.0f, 1.0f,
519 1.0f,-1.0f,-1.0f,
520 1.0f, 1.0f,-1.0f,
521
522 -1.0f, 1.0f, 1.0f, // +Z side
523 -1.0f,-1.0f, 1.0f,
524 1.0f, 1.0f, 1.0f,
525 -1.0f,-1.0f, 1.0f,
526 1.0f,-1.0f, 1.0f,
527 1.0f, 1.0f, 1.0f,
528
529 0.0f, 1.0f, // -X side
530 1.0f, 1.0f,
531 1.0f, 0.0f,
532 1.0f, 0.0f,
533 0.0f, 0.0f,
534 0.0f, 1.0f,
535
536 1.0f, 1.0f, // -Z side
537 0.0f, 0.0f,
538 0.0f, 1.0f,
539 1.0f, 1.0f,
540 1.0f, 0.0f,
541 0.0f, 0.0f,
542
543 1.0f, 0.0f, // -Y side
544 1.0f, 1.0f,
545 0.0f, 1.0f,
546 1.0f, 0.0f,
547 0.0f, 1.0f,
548 0.0f, 0.0f,
549
550 1.0f, 0.0f, // +Y side
551 0.0f, 0.0f,
552 0.0f, 1.0f,
553 1.0f, 0.0f,
554 0.0f, 1.0f,
555 1.0f, 1.0f,
556
557 1.0f, 0.0f, // +X side
558 0.0f, 0.0f,
559 0.0f, 1.0f,
560 0.0f, 1.0f,
561 1.0f, 1.0f,
562 1.0f, 0.0f,
563
564 0.0f, 0.0f, // +Z side
565 0.0f, 1.0f,
566 1.0f, 0.0f,
567 0.0f, 1.0f,
568 1.0f, 1.0f,
569 1.0f, 0.0f,
570};
571
572bool QSSGBufferManager::createEnvironmentMap(const QSSGLoadedTexture *inImage, QSSGRenderImageTexture *outTexture, const QString &debugObjectName)
573{
574 // The objective of this method is to take the equirectangular texture
575 // provided by inImage and create a cubeMap that contains both pre-filtered
576 // specular environment maps, as well as a irradiance map for diffuse
577 // operations.
578 // To achieve this though we first convert convert the Equirectangular texture
579 // to a cubeMap with genereated mip map levels (no filtering) to make the
580 // process of creating the prefiltered and irradiance maps eaiser. This
581 // intermediate texture as well as the original equirectangular texture are
582 // destroyed after this frame completes, and all further associations with
583 // the source lightProbe texture are instead associated with the final
584 // generated environment map.
585 // The intermediate environment cubemap is used to generate the final
586 // cubemap. This cubemap will generate 6 mip levels for each face
587 // (the remaining faces are unused). This is what the contents of each
588 // face mip level looks like:
589 // 0: Pre-filtered with roughness 0 (basically unfiltered)
590 // 1: Pre-filtered with roughness 0.25
591 // 2: Pre-filtered with roughness 0.5
592 // 3: Pre-filtered with roughness 0.75
593 // 4: Pre-filtered with rougnness 1.0
594 // 5: Irradiance map (ideally at least 16x16)
595 // It would be better if we could use a separate cubemap for irradiance, but
596 // right now there is a 1:1 association between texture sources on the front-
597 // end and backend.
598 const auto &context = m_contextInterface->rhiContext();
599 auto *rhi = context->rhi();
600 // Right now minimum face size needs to be 512x512 to be able to have 6 reasonably sized mips
601 int suggestedSize = inImage->height * 0.5f;
602 suggestedSize = qMax(512, suggestedSize);
603 const QSize environmentMapSize(suggestedSize, suggestedSize);
604 const bool isRGBE = inImage->format.format == QSSGRenderTextureFormat::Format::RGBE8;
605 const QRhiTexture::Format sourceTextureFormat = toRhiFormat(inImage->format.format);
606 // Check if we can use the source texture at all
607 if (!rhi->isTextureFormatSupported(sourceTextureFormat))
608 return false;
609
610 QRhiTexture::Format cubeTextureFormat = inImage->format.isCompressedTextureFormat()
611 ? QRhiTexture::RGBA16F // let's just assume that if compressed textures are available, then it's at least a GLES 3.0 level API
612 : sourceTextureFormat;
613#ifdef Q_OS_IOS
614 // iOS doesn't support mip map filtering on RGBA32F textures
615 if (cubeTextureFormat == QRhiTexture::RGBA32F)
616 cubeTextureFormat = QRhiTexture::RGBA16F;
617#endif
618
619 const int colorSpace = inImage->isSRGB ? 1 : 0; // 0 Linear | 1 sRGB
620
621 // Phase 1: Convert the Equirectangular texture to a Cubemap
622 QRhiTexture *envCubeMap = rhi->newTexture(cubeTextureFormat, environmentMapSize, 1,
623 QRhiTexture::RenderTarget | QRhiTexture::CubeMap | QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips);
624 if (!envCubeMap->create()) {
625 qWarning("Failed to create Environment Cube Map");
626 return false;
627 }
628 envCubeMap->deleteLater();
629
630 // Create a renderbuffer the size of a the cubeMap face
631 QRhiRenderBuffer *envMapRenderBuffer = rhi->newRenderBuffer(QRhiRenderBuffer::Color, environmentMapSize);
632 if (!envMapRenderBuffer->create()) {
633 qWarning("Failed to create Environment Map Render Buffer");
634 return false;
635 }
636 envMapRenderBuffer->deleteLater();
637
638 const QByteArray rtName = debugObjectName.toLatin1();
639
640 // Setup the 6 render targets for each cube face
641 QVarLengthArray<QRhiTextureRenderTarget *, 6> renderTargets;
642 QRhiRenderPassDescriptor *renderPassDesc = nullptr;
643 for (const auto face : QSSGRenderTextureCubeFaces) {
644 QRhiColorAttachment att(envCubeMap);
645 att.setLayer(quint8(face));
646 QRhiTextureRenderTargetDescription rtDesc;
647 rtDesc.setColorAttachments({att});
648 auto renderTarget = rhi->newTextureRenderTarget(rtDesc);
649 renderTarget->setName(rtName + QByteArrayLiteral(" env cube face: ") + QSSGBaseTypeHelpers::displayName(face));
650 renderTarget->setDescription(rtDesc);
651 if (!renderPassDesc)
652 renderPassDesc = renderTarget->newCompatibleRenderPassDescriptor();
653 renderTarget->setRenderPassDescriptor(renderPassDesc);
654 if (!renderTarget->create()) {
655 qWarning("Failed to build env map render target");
656 return false;
657 }
658 renderTarget->deleteLater();
659 renderTargets << renderTarget;
660 }
661 renderPassDesc->deleteLater();
662
663 // Setup the sampler for reading the equirectangular loaded texture
664 QSize size(inImage->width, inImage->height);
665 auto *sourceTexture = rhi->newTexture(sourceTextureFormat, size, 1);
666 if (!sourceTexture->create()) {
667 qWarning("failed to create source env map texture");
668 return false;
669 }
670 sourceTexture->deleteLater();
671
672 // Upload the equirectangular texture
673 const auto desc = inImage->textureFileData.isValid()
674 ? QRhiTextureUploadDescription(
675 { 0, 0, QRhiTextureSubresourceUploadDescription(inImage->textureFileData.getDataView().toByteArray()) })
676 : QRhiTextureUploadDescription({ 0, 0, { inImage->data, inImage->dataSizeInBytes } });
677
678 auto *rub = rhi->nextResourceUpdateBatch();
679 rub->uploadTexture(sourceTexture, desc);
680
681 const QSSGRhiSamplerDescription samplerDesc {
682 QRhiSampler::Linear,
683 QRhiSampler::Linear,
684 QRhiSampler::None,
685 QRhiSampler::ClampToEdge,
686 QRhiSampler::ClampToEdge,
687 QRhiSampler::Repeat
688 };
689 QRhiSampler *sampler = context->sampler(samplerDesc);
690
691 // Load shader and setup render pipeline
692 const auto &shaderCache = m_contextInterface->shaderCache();
693 const auto &envMapShaderStages = shaderCache->getBuiltInRhiShaders().getRhiEnvironmentmapShader();
694
695 // Vertex Buffer - Just a single cube that will be viewed from inside
696 QRhiBuffer *vertexBuffer = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube));
697 vertexBuffer->create();
698 vertexBuffer->deleteLater();
699 rub->uploadStaticBuffer(vertexBuffer, cube);
700
701 // Uniform Buffer - 2x mat4
702 int ubufElementSize = rhi->ubufAligned(128);
703 QRhiBuffer *uBuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufElementSize * 6);
704 uBuf->create();
705 uBuf->deleteLater();
706
707 int ubufEnvMapElementSize = rhi->ubufAligned(4);
708 QRhiBuffer *uBufEnvMap = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufEnvMapElementSize * 6);
709 uBufEnvMap->create();
710 uBufEnvMap->deleteLater();
711
712 // Shader Resource Bindings
713 QRhiShaderResourceBindings *envMapSrb = rhi->newShaderResourceBindings();
714 envMapSrb->setBindings({
715 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, QRhiShaderResourceBinding::VertexStage, uBuf, 128),
716 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(2, QRhiShaderResourceBinding::FragmentStage, uBufEnvMap, ubufEnvMapElementSize),
717 QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, sourceTexture, sampler)
718 });
719 envMapSrb->create();
720 envMapSrb->deleteLater();
721
722 // Pipeline
723 QRhiGraphicsPipeline *envMapPipeline = rhi->newGraphicsPipeline();
724 envMapPipeline->setCullMode(QRhiGraphicsPipeline::Front);
725 envMapPipeline->setFrontFace(QRhiGraphicsPipeline::CCW);
726 envMapPipeline->setShaderStages({
727 *envMapShaderStages->vertexStage(),
728 *envMapShaderStages->fragmentStage()
729 });
730
731 QRhiVertexInputLayout inputLayout;
732 inputLayout.setBindings({
733 { 3 * sizeof(float) }
734 });
735 inputLayout.setAttributes({
736 { 0, 0, QRhiVertexInputAttribute::Float3, 0 }
737 });
738
739 envMapPipeline->setVertexInputLayout(inputLayout);
740 envMapPipeline->setShaderResourceBindings(envMapSrb);
741 envMapPipeline->setRenderPassDescriptor(renderPassDesc);
742 if (!envMapPipeline->create()) {
743 qWarning("failed to create source env map pipeline state");
744 return false;
745 }
746 envMapPipeline->deleteLater();
747
748 // Do the actual render passes
749 auto *cb = context->commandBuffer();
750 cb->debugMarkBegin("Environment Cubemap Generation");
751 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
752 Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Environment Cubemap Generation"));
753 const QRhiCommandBuffer::VertexInput vbufBinding(vertexBuffer, 0);
754
755 // Set the Uniform Data
756 QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
757 mvp.perspective(90.0f, 1.0f, 0.1f, 10.0f);
758
759 auto lookAt = [](const QVector3D &eye, const QVector3D &center, const QVector3D &up) {
760 QMatrix4x4 viewMatrix;
761 viewMatrix.lookAt(eye, center, up);
762 return viewMatrix;
763 };
764 QVarLengthArray<QMatrix4x4, 6> views;
765 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(1.0, 0.0, 0.0), QVector3D(0.0f, -1.0f, 0.0f)));
766 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(-1.0, 0.0, 0.0), QVector3D(0.0f, -1.0f, 0.0f)));
767 if (rhi->isYUpInFramebuffer()) {
768 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 1.0, 0.0), QVector3D(0.0f, 0.0f, 1.0f)));
769 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, -1.0, 0.0), QVector3D(0.0f, 0.0f, -1.0f)));
770 } else {
771 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, -1.0, 0.0), QVector3D(0.0f, 0.0f, -1.0f)));
772 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 1.0, 0.0), QVector3D(0.0f, 0.0f, 1.0f)));
773 }
774 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 0.0, 1.0), QVector3D(0.0f, -1.0f, 0.0f)));
775 views.append(lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 0.0, -1.0), QVector3D(0.0f, -1.0f, 0.0f)));
776 for (const auto face : QSSGRenderTextureCubeFaces) {
777 rub->updateDynamicBuffer(uBuf, quint8(face) * ubufElementSize, 64, mvp.constData());
778 rub->updateDynamicBuffer(uBuf, quint8(face) * ubufElementSize + 64, 64, views[quint8(face)].constData());
779 rub->updateDynamicBuffer(uBufEnvMap, quint8(face) * ubufEnvMapElementSize, 4, &colorSpace);
780 }
781 cb->resourceUpdate(rub);
782
783 for (const auto face : QSSGRenderTextureCubeFaces) {
784 cb->beginPass(renderTargets[quint8(face)], QColor(0, 0, 0, 1), { 1.0f, 0 }, nullptr, context->commonPassFlags());
785 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
786 QSSGRHICTX_STAT(context, beginRenderPass(renderTargets[quint8(face)]));
787
788 // Execute render pass
789 cb->setGraphicsPipeline(envMapPipeline);
790 cb->setVertexInput(0, 1, &vbufBinding);
791 cb->setViewport(QRhiViewport(0, 0, environmentMapSize.width(), environmentMapSize.height()));
792 QVector<QPair<int, quint32>> dynamicOffset = {
793 { 0, quint32(ubufElementSize * quint8(face)) },
794 { 2, quint32(ubufEnvMapElementSize * quint8(face) )}
795 };
796 cb->setShaderResources(envMapSrb, 2, dynamicOffset.constData());
797 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
798 cb->draw(36);
799 QSSGRHICTX_STAT(context, draw(36, 1));
800 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderCall, 36llu | (1llu << 32), QByteArrayLiteral("environment_map"));
801
802 cb->endPass();
803 QSSGRHICTX_STAT(context, endRenderPass());
804 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("environment_map", 0, face));
805 }
806 cb->debugMarkEnd();
807 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("environment_cube_generation"));
808 Q_TRACE(QSSG_renderPass_exit);
809
810 if (!isRGBE) {
811 // Generate mipmaps for envMap
812 rub = rhi->nextResourceUpdateBatch();
813 rub->generateMips(envCubeMap);
814 cb->resourceUpdate(rub);
815 }
816
817 // Phase 2: Generate the pre-filtered environment cubemap
818 cb->debugMarkBegin("Pre-filtered Environment Cubemap Generation");
819 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
820 Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Pre-filtered Environment Cubemap Generation"));
821 QRhiTexture *preFilteredEnvCubeMap = rhi->newTexture(cubeTextureFormat, environmentMapSize, 1, QRhiTexture::RenderTarget | QRhiTexture::CubeMap| QRhiTexture::MipMapped);
822 if (!preFilteredEnvCubeMap->create())
823 qWarning("Failed to create Pre-filtered Environment Cube Map");
824 preFilteredEnvCubeMap->setName(rtName);
825 int mipmapCount = rhi->mipLevelsForSize(environmentMapSize);
826 mipmapCount = qMin(mipmapCount, 6); // don't create more than 6 mip levels
827 QMap<int, QSize> mipLevelSizes;
828 QMap<int, QVarLengthArray<QRhiTextureRenderTarget *, 6>> renderTargetsMap;
829 QRhiRenderPassDescriptor *renderPassDescriptorPhase2 = nullptr;
830
831 // Create a renderbuffer for each mip level
832 for (int mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) {
833 const QSize levelSize = QSize(environmentMapSize.width() * std::pow(0.5, mipLevel),
834 environmentMapSize.height() * std::pow(0.5, mipLevel));
835 mipLevelSizes.insert(mipLevel, levelSize);
836 // Setup Render targets (6 * mipmapCount)
837 QVarLengthArray<QRhiTextureRenderTarget *, 6> renderTargets;
838 for (const auto face : QSSGRenderTextureCubeFaces) {
839 QRhiColorAttachment att(preFilteredEnvCubeMap);
840 att.setLayer(quint8(face));
841 att.setLevel(mipLevel);
842 QRhiTextureRenderTargetDescription rtDesc;
843 rtDesc.setColorAttachments({att});
844 auto renderTarget = rhi->newTextureRenderTarget(rtDesc);
845 renderTarget->setName(rtName + QByteArrayLiteral(" env prefilter mip/face: ")
846 + QByteArray::number(mipLevel) + QByteArrayLiteral("/") + QSSGBaseTypeHelpers::displayName(face));
847 renderTarget->setDescription(rtDesc);
848 if (!renderPassDescriptorPhase2)
849 renderPassDescriptorPhase2 = renderTarget->newCompatibleRenderPassDescriptor();
850 renderTarget->setRenderPassDescriptor(renderPassDescriptorPhase2);
851 if (!renderTarget->create())
852 qWarning("Failed to build prefilter env map render target");
853 renderTarget->deleteLater();
854 renderTargets << renderTarget;
855 }
856 renderTargetsMap.insert(mipLevel, renderTargets);
857 renderPassDescriptorPhase2->deleteLater();
858 }
859
860 // Load the prefilter shader stages
861 const auto &prefilterShaderStages = shaderCache->getBuiltInRhiShaders().getRhienvironmentmapPreFilterShader(isRGBE);
862
863 // Create a new Sampler
864 const QSSGRhiSamplerDescription samplerMipMapDesc {
865 QRhiSampler::Linear,
866 QRhiSampler::Linear,
867 QRhiSampler::Linear,
868 QRhiSampler::ClampToEdge,
869 QRhiSampler::ClampToEdge,
870 QRhiSampler::Repeat
871 };
872
873 QRhiSampler *envMapCubeSampler = nullptr;
874 // Only use mipmap interpoliation if not using RGBE
875 if (!isRGBE)
876 envMapCubeSampler = context->sampler(samplerMipMapDesc);
877 else
878 envMapCubeSampler = sampler;
879
880 // Reuse Vertex Buffer from phase 1
881 // Reuse UniformBuffer from phase 1 (for vertex shader)
882
883 // UniformBuffer
884 // float roughness;
885 // float resolution;
886 // float lodBias;
887 // int sampleCount;
888 // int distribution;
889
890 int ubufPrefilterElementSize = rhi->ubufAligned(20);
891 QRhiBuffer *uBufPrefilter = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufPrefilterElementSize * mipmapCount);
892 uBufPrefilter->create();
893 uBufPrefilter->deleteLater();
894
895 // Shader Resource Bindings
896 QRhiShaderResourceBindings *preFilterSrb = rhi->newShaderResourceBindings();
897 preFilterSrb->setBindings({
898 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, QRhiShaderResourceBinding::VertexStage, uBuf, 128),
899 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(2, QRhiShaderResourceBinding::FragmentStage, uBufPrefilter, 20),
900 QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, envCubeMap, envMapCubeSampler)
901 });
902 preFilterSrb->create();
903 preFilterSrb->deleteLater();
904
905 // Pipeline
906 QRhiGraphicsPipeline *prefilterPipeline = rhi->newGraphicsPipeline();
907 prefilterPipeline->setCullMode(QRhiGraphicsPipeline::Front);
908 prefilterPipeline->setFrontFace(QRhiGraphicsPipeline::CCW);
909 prefilterPipeline->setDepthOp(QRhiGraphicsPipeline::LessOrEqual);
910 prefilterPipeline->setShaderStages({
911 *prefilterShaderStages->vertexStage(),
912 *prefilterShaderStages->fragmentStage()
913 });
914 // same as phase 1
915 prefilterPipeline->setVertexInputLayout(inputLayout);
916 prefilterPipeline->setShaderResourceBindings(preFilterSrb);
917 prefilterPipeline->setRenderPassDescriptor(renderPassDescriptorPhase2);
918 if (!prefilterPipeline->create()) {
919 qWarning("failed to create pre-filter env map pipeline state");
920 return false;
921 }
922 prefilterPipeline->deleteLater();
923
924 // Uniform Data
925 // set the roughness uniform buffer data
926 rub = rhi->nextResourceUpdateBatch();
927 const float resolution = environmentMapSize.width();
928 const float lodBias = 0.0f;
929 const int sampleCount = 1024;
930 for (int mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) {
931 Q_ASSERT(mipmapCount - 2);
932 const float roughness = float(mipLevel) / float(mipmapCount - 2);
933 const int distribution = mipLevel == (mipmapCount - 1) ? 0 : 1; // last mip level is for irradiance
934 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize, 4, &roughness);
935 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize + 4, 4, &resolution);
936 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize + 4 + 4, 4, &lodBias);
937 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize + 4 + 4 + 4, 4, &sampleCount);
938 rub->updateDynamicBuffer(uBufPrefilter, mipLevel * ubufPrefilterElementSize + 4 + 4 + 4 + 4, 4, &distribution);
939 }
940
941 cb->resourceUpdate(rub);
942
943 // Render
944 for (int mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) {
945 for (const auto face : QSSGRenderTextureCubeFaces) {
946 cb->beginPass(renderTargetsMap[mipLevel][quint8(face)], QColor(0, 0, 0, 1), { 1.0f, 0 }, nullptr, context->commonPassFlags());
947 QSSGRHICTX_STAT(context, beginRenderPass(renderTargetsMap[mipLevel][quint8(face)]));
948 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
949 cb->setGraphicsPipeline(prefilterPipeline);
950 cb->setVertexInput(0, 1, &vbufBinding);
951 cb->setViewport(QRhiViewport(0, 0, mipLevelSizes[mipLevel].width(), mipLevelSizes[mipLevel].height()));
952 QVector<QPair<int, quint32>> dynamicOffsets = {
953 { 0, quint32(ubufElementSize * quint8(face)) },
954 { 2, quint32(ubufPrefilterElementSize * mipLevel) }
955 };
956 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
957
958 cb->setShaderResources(preFilterSrb, 2, dynamicOffsets.constData());
959 cb->draw(36);
960 QSSGRHICTX_STAT(context, draw(36, 1));
961 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderCall, 36llu | (1llu << 32), QByteArrayLiteral("environment_map"));
962 cb->endPass();
963 QSSGRHICTX_STAT(context, endRenderPass());
964 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("environment_map", mipLevel, face));
965 }
966 }
967 cb->debugMarkEnd();
968 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("environment_cube_prefilter"));
969 Q_TRACE(QSSG_renderPass_exit);
970
971 outTexture->m_texture = preFilteredEnvCubeMap;
972 outTexture->m_mipmapCount = mipmapCount;
973 return true;
974}
975
976bool QSSGBufferManager::setRhiTexture(QSSGRenderImageTexture &texture,
977 const QSSGLoadedTexture *inTexture,
978 MipMode inMipMode,
979 CreateRhiTextureFlags inFlags,
980 const QString &debugObjectName,
981 bool *wasTextureCreated)
982{
983 Q_ASSERT(inMipMode != MipModeFollowRenderImage);
984 QVarLengthArray<QRhiTextureUploadEntry, 16> textureUploads;
985 int textureSampleCount = 1;
986 QRhiTexture::Flags textureFlags;
987 const bool checkTransp = inFlags.testFlag(ScanForTransparency);
988 bool hasTransp = false;
989
990 const auto &context = m_contextInterface->rhiContext();
991 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(context.get());
992 auto *rhi = context->rhi();
993 QRhiTexture::Format rhiFormat = toRhiFormat(inTexture->format.format);
994 const QTextureFileData &texFileData = inTexture->textureFileData;
995 QSize size = texFileData.isValid() ? texFileData.size() : QSize(inTexture->width, inTexture->height);
996 int mipmapCount = texFileData.isValid() ? texFileData.numLevels() : 1;
997 int depth = inFlags.testFlag(Texture3D) ? inTexture->depth : 0;
998 bool generateMipmaps = false;
999
1000 if (size.isEmpty()) {
1001 qWarning() << "Could not use 0 sized texture";
1002 return false;
1003 } else if (!rhi->isTextureFormatSupported(rhiFormat)) {
1004 qWarning() << "Unsupported texture format" << rhiFormat;
1005 return false;
1006 }
1007
1008
1009 if (wasTextureCreated)
1010 *wasTextureCreated = false;
1011
1012 if (texture.m_texture == nullptr) {
1013 if (inTexture->format.format == QSSGRenderTextureFormat::Format::RGBE8)
1014 texture.m_flags.setRgbe8(true);
1015 if (!inTexture->isSRGB)
1016 texture.m_flags.setLinear(true);
1017 if (inMipMode == MipModeBsdf && (inTexture->data || texFileData.isValid())) {
1018 // Before creating an environment map, check if the provided texture is a
1019 // pre-baked environment map
1020 if (texFileData.isValid() && texFileData.keyValueMetadata().contains("QT_IBL_BAKER_VERSION")) {
1021 Q_ASSERT(texFileData.numFaces() == 6);
1022 Q_ASSERT(texFileData.numLevels() >= 5);
1023
1024 QRhiTexture *environmentCubeMap = rhi->newTexture(rhiFormat, size, 1, QRhiTexture::CubeMap | QRhiTexture::MipMapped);
1025 environmentCubeMap->setName(debugObjectName.toLatin1());
1026 environmentCubeMap->create();
1027 texture.m_texture = environmentCubeMap;
1028 rhiCtxD->registerTexture(texture.m_texture);
1029 if (wasTextureCreated)
1030 *wasTextureCreated = true;
1031 // If we get this far then we need to create an environment map at runtime.
1032 } else if (createEnvironmentMap(inTexture, &texture, debugObjectName)) {
1033 rhiCtxD->registerTexture(texture.m_texture);
1034 if (wasTextureCreated)
1035 *wasTextureCreated = true;
1036 return true;
1037 } else {
1038 qWarning() << "Failed to create environment map";
1039 return false;
1040 }
1041 } else {
1042 if (inMipMode == MipModeEnable && mipmapCount == 1) {
1043 textureFlags |= QRhiTexture::Flag::UsedWithGenerateMips;
1044 generateMipmaps = true;
1045 mipmapCount = rhi->mipLevelsForSize(size);
1046 }
1047
1048 if (mipmapCount > 1)
1049 textureFlags |= QRhiTexture::Flag::MipMapped;
1050
1051 if (inFlags.testFlag(CubeMap))
1052 textureFlags |= QRhiTexture::CubeMap;
1053
1054 if (inFlags.testFlag(Texture3D) && depth > 0)
1055 texture.m_texture = rhi->newTexture(rhiFormat, size.width(), size.height(), depth, textureSampleCount, textureFlags);
1056 else
1057 texture.m_texture = rhi->newTexture(rhiFormat, size, textureSampleCount, textureFlags);
1058
1059 texture.m_texture->setName(debugObjectName.toLatin1());
1060 texture.m_texture->create();
1061 rhiCtxD->registerTexture(texture.m_texture);
1062 if (wasTextureCreated)
1063 *wasTextureCreated = true;
1064 }
1065 }
1066
1067 // Update resources
1068 if (inMipMode == MipModeBsdf && (inTexture->data || texFileData.isValid())) {
1069 if (texFileData.isValid() && texFileData.keyValueMetadata().contains("QT_IBL_BAKER_VERSION")) {
1070 const int faceCount = texFileData.numFaces();
1071 for (int layer = 0; layer < faceCount; ++layer) {
1072 for (int level = 0; level < mipmapCount; ++level) {
1073 QRhiTextureSubresourceUploadDescription subDesc;
1074 subDesc.setSourceSize(sizeForMipLevel(level, size));
1075 subDesc.setData(texFileData.getDataView(level, layer).toByteArray());
1076 textureUploads << QRhiTextureUploadEntry { layer, level, subDesc };
1077 }
1078 }
1079
1080 QRhiTextureUploadDescription uploadDescription;
1081 uploadDescription.setEntries(textureUploads.cbegin(), textureUploads.cend());
1082 auto *rub = rhi->nextResourceUpdateBatch();
1083 rub->uploadTexture(texture.m_texture, uploadDescription);
1084 context->commandBuffer()->resourceUpdate(rub);
1085 texture.m_mipmapCount = mipmapCount;
1086 return true;
1087 }
1088 } else if (texFileData.isValid()) {
1089 int numFaces = 1;
1090 // Just having a container with 6 faces is not enough, we only treat it
1091 // as a cubemap if it was requested to be treated as such. Otherwise
1092 // only face 0 is used.
1093 if (texFileData.numFaces() == 6 && inFlags.testFlag(CubeMap))
1094 numFaces = 6;
1095
1096 for (int level = 0; level < texFileData.numLevels(); ++level) {
1097 QRhiTextureSubresourceUploadDescription subDesc;
1098 subDesc.setSourceSize(sizeForMipLevel(level, size));
1099 for (int face = 0; face < numFaces; ++face) {
1100 subDesc.setData(texFileData.getDataView(level, face).toByteArray());
1101 textureUploads << QRhiTextureUploadEntry{ face, level, subDesc };
1102 }
1103 }
1104 if (checkTransp) {
1105 auto glFormat = texFileData.glInternalFormat() ? texFileData.glInternalFormat() : texFileData.glFormat();
1106 hasTransp = !QSGCompressedTexture::formatIsOpaque(glFormat);
1107 }
1108 } else if (inFlags.testFlag(Texture3D)) {
1109 // 3D textures are currently only setup via QQuick3DTextureData
1110 quint32 formatSize = (quint32)inTexture->format.getSizeofFormat();
1111 quint32 size2D = inTexture->width * inTexture->height * formatSize;
1112 if (inTexture->dataSizeInBytes >= (quint32)(size2D * inTexture->depth)) {
1113 for (int slice = 0; slice < inTexture->depth; ++slice) {
1114 QRhiTextureSubresourceUploadDescription sliceUpload((char *)inTexture->data + slice * size2D, size2D);
1115 textureUploads << QRhiTextureUploadEntry(slice, 0, sliceUpload);
1116 }
1117 } else {
1118 qWarning() << "Texture size set larger than the data";
1119 }
1120 } else {
1121 QRhiTextureSubresourceUploadDescription subDesc;
1122 if (!inTexture->image.isNull()) {
1123 subDesc.setImage(inTexture->image);
1124 if (checkTransp)
1125 hasTransp = QImageData::get(inTexture->image)->checkForAlphaPixels();
1126 } else if (inTexture->data) {
1127 QByteArray buf(static_cast<const char *>(inTexture->data), qMax(0, int(inTexture->dataSizeInBytes)));
1128 subDesc.setData(buf);
1129 if (checkTransp)
1130 hasTransp = inTexture->scanForTransparency();
1131 }
1132 subDesc.setSourceSize(size);
1133 if (!subDesc.data().isEmpty() || !subDesc.image().isNull())
1134 textureUploads << QRhiTextureUploadEntry{0, 0, subDesc};
1135 }
1136
1137 static const auto textureSizeWarning = [](QSize requestedSize, qsizetype maxSize) {
1138 return QStringLiteral("Requested texture width and height (%1x%2) exceeds the maximum allowed size (%3)!")
1139 .arg(requestedSize.width()).arg(requestedSize.height()).arg(maxSize);
1140 };
1141 static auto maxTextureSize = rhi->resourceLimit(QRhi::ResourceLimit::TextureSizeMax);
1142 const auto validTexSize = size.width() <= maxTextureSize && size.height() <= maxTextureSize;
1143 QSSG_ASSERT_X(validTexSize, qPrintable(textureSizeWarning(size, maxTextureSize)), return false);
1144
1145 QSSG_ASSERT(texture.m_texture != nullptr, return false);
1146
1147 if (checkTransp)
1148 texture.m_flags.setHasTransparency(hasTransp);
1149
1150 QRhiTextureUploadDescription uploadDescription;
1151 uploadDescription.setEntries(textureUploads.cbegin(), textureUploads.cend());
1152 auto *rub = rhi->nextResourceUpdateBatch(); // TODO: optimize
1153 rub->uploadTexture(texture.m_texture, uploadDescription);
1154 if (generateMipmaps)
1155 rub->generateMips(texture.m_texture);
1156 context->commandBuffer()->resourceUpdate(rub);
1157
1158 texture.m_mipmapCount = mipmapCount;
1159 return true;
1160}
1161
1162QString QSSGBufferManager::primitivePath(const QString &primitive)
1163{
1164 QByteArray theName = primitive.toUtf8();
1165 for (size_t idx = 0; idx < nPrimitives; ++idx) {
1166 if (primitives[idx].primitive == theName) {
1167 QString pathBuilder = QString::fromLatin1(primitivesDirectory);
1168 pathBuilder += QLatin1String(primitives[idx].file);
1169 return pathBuilder;
1170 }
1171 }
1172 return {};
1173}
1174
1175QMutex *QSSGBufferManager::meshUpdateMutex()
1176{
1177 return &meshBufferMutex;
1178}
1179
1180QSSGMesh::Mesh QSSGBufferManager::loadPrimitive(const QString &inRelativePath)
1181{
1182 QString path = primitivePath(inRelativePath);
1183 const quint32 id = 1;
1184 QSharedPointer<QIODevice> device(QSSGInputUtil::getStreamForFile(path));
1185 if (device) {
1186 QSSGMesh::Mesh mesh = QSSGMesh::Mesh::loadMesh(device.data(), id);
1187 if (mesh.isValid())
1188 return mesh;
1189 }
1190
1191 qCCritical(INTERNAL_ERROR, "Unable to find mesh primitive %s", qPrintable(path));
1192 return QSSGMesh::Mesh();
1193}
1194
1195QSSGRenderMesh *QSSGBufferManager::loadMesh(const QSSGRenderModel &model)
1196{
1197 // When baking lightmaps we need to make sure that the original mesh is loaded instead of the already baked mesh.
1198
1199 QSSGMeshProcessingOptions options;
1200 QSSGRenderMesh *theMesh = nullptr;
1201
1202 if (model.hasLightmap() && !currentlyLightmapBaking && validateLightmap()) {
1203 options.lightmapPath = lightmapSource;
1204 options.lightmapKey = model.lightmapKey;
1205 }
1206
1207 if (model.meshPath.isNull() && model.geometry) {
1208 theMesh = loadRenderMesh(model.geometry, options);
1209 } else {
1210 theMesh = loadRenderMesh(model.meshPath, options);
1211 }
1212
1213 return theMesh;
1214}
1215
1216QSSGMesh::Mesh QSSGBufferManager::loadLightmapMesh(const QSSGRenderModel &model)
1217{
1218 // When baking lightmaps we need to make sure that the original mesh is loaded instead of the already baked mesh.
1219 if (model.hasLightmap() && !currentlyLightmapBaking && validateLightmap() ) {
1220 auto [meshLightmap, _] = loadFromLightmapFile(lightmapSource, model.lightmapKey);
1221 if (meshLightmap.isValid())
1222 return meshLightmap;
1223 }
1224
1225 return {};
1226}
1227
1228QSSGBounds3 QSSGBufferManager::getModelBounds(const QSSGRenderModel *model)
1229{
1230 QSSGBounds3 retval;
1231
1232 if (model->hasLightmap() && !currentlyLightmapBaking && validateLightmap()) {
1233 auto [meshLightmap, _] = loadFromLightmapFile(lightmapSource, model->lightmapKey);
1234 if (meshLightmap.isValid()) {
1235 const QVector<QSSGMesh::Mesh::Subset> subsets = meshLightmap.subsets();
1236 for (const QSSGMesh::Mesh::Subset &subset : std::as_const(subsets)) {
1237 retval.include(QSSGBounds3(subset.bounds.min, subset.bounds.max));
1238 }
1239 return retval;
1240 } else {
1241 qWarning() << "Could not load lightmap" << lightmapSource << model->lightmapKey;
1242 }
1243 }
1244
1245 // Custom Geometry
1246 if (model->geometry) {
1247 retval = QSSGBounds3(model->geometry->boundsMin(), model->geometry->boundsMax());
1248 } else if (!model->meshPath.isNull()){
1249 // Check if the Mesh is already loaded
1250 QSSGRenderMesh *theMesh = nullptr;
1251 auto meshItr = meshMap.constFind(model->meshPath);
1252 if (meshItr != meshMap.cend())
1253 theMesh = meshItr.value().mesh;
1254 if (theMesh) {
1255 // The mesh was already loaded, so calculate the
1256 // bounds from subsets of the QSSGRenderMesh
1257 const auto &subSets = theMesh->subsets;
1258 for (const auto &subSet : subSets)
1259 retval.include(subSet.bounds);
1260 } else {
1261 // The model has not been loaded yet, load it without uploading the geometry
1262 // TODO: Try to do this without loading the whole mesh struct
1263 QSSGMesh::Mesh mesh = loadMeshData(model->meshPath);
1264 if (mesh.isValid()) {
1265 auto const &subsets = mesh.subsets();
1266 for (const auto &subset : subsets)
1267 retval.include(QSSGBounds3(subset.bounds.min, subset.bounds.max));
1268 }
1269 }
1270 }
1271 return retval;
1272}
1273
1274QSSGRenderMesh *QSSGBufferManager::createRenderMesh(const QSSGMesh::Mesh &mesh, const QString &debugObjectName)
1275{
1276 QSSGRenderMesh *newMesh = new QSSGRenderMesh(QSSGRenderDrawMode(mesh.drawMode()),
1277 QSSGRenderWinding(mesh.winding()));
1278 const QSSGMesh::Mesh::VertexBuffer vertexBuffer = mesh.vertexBuffer();
1279 const QSSGMesh::Mesh::IndexBuffer indexBuffer = mesh.indexBuffer();
1280 const QSSGMesh::Mesh::TargetBuffer targetBuffer = mesh.targetBuffer();
1281
1282 QSSGRenderComponentType indexBufComponentType = QSSGRenderComponentType::UnsignedInt16;
1283 QRhiCommandBuffer::IndexFormat rhiIndexFormat = QRhiCommandBuffer::IndexUInt16;
1284 if (!indexBuffer.data.isEmpty()) {
1285 indexBufComponentType = QSSGRenderComponentType(indexBuffer.componentType);
1286 const quint32 sizeofType = quint32(QSSGBaseTypeHelpers::getSizeOfType(indexBufComponentType));
1287 if (sizeofType == 2 || sizeofType == 4) {
1288 // Ensure type is unsigned; else things will fail in rendering pipeline.
1289 if (indexBufComponentType == QSSGRenderComponentType::Int16)
1290 indexBufComponentType = QSSGRenderComponentType::UnsignedInt16;
1291 if (indexBufComponentType == QSSGRenderComponentType::Int32)
1292 indexBufComponentType = QSSGRenderComponentType::UnsignedInt32;
1293 rhiIndexFormat = indexBufComponentType == QSSGRenderComponentType::UnsignedInt32
1294 ? QRhiCommandBuffer::IndexUInt32 : QRhiCommandBuffer::IndexUInt16;
1295 } else {
1296 Q_ASSERT(false);
1297 }
1298 }
1299
1300 struct {
1301 QSSGRhiBufferPtr vertexBuffer;
1302 QSSGRhiBufferPtr indexBuffer;
1303 QSSGRhiInputAssemblerState ia;
1304 QRhiTexture *targetsTexture = nullptr;
1305 } rhi;
1306
1307 QRhiResourceUpdateBatch *rub = meshBufferUpdateBatch();
1308 const auto &context = m_contextInterface->rhiContext();
1309 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(context.get());
1310 rhi.vertexBuffer = std::make_shared<QSSGRhiBuffer>(*context.get(),
1311 QRhiBuffer::Static,
1312 QRhiBuffer::VertexBuffer,
1313 vertexBuffer.stride,
1314 vertexBuffer.data.size());
1315 rhi.vertexBuffer->buffer()->setName(debugObjectName.toLatin1()); // this is what shows up in DebugView
1316 rub->uploadStaticBuffer(rhi.vertexBuffer->buffer(), vertexBuffer.data);
1317
1318 if (!indexBuffer.data.isEmpty()) {
1319 rhi.indexBuffer = std::make_shared<QSSGRhiBuffer>(*context.get(),
1320 QRhiBuffer::Static,
1321 QRhiBuffer::IndexBuffer,
1322 0,
1323 indexBuffer.data.size(),
1324 rhiIndexFormat);
1325 rub->uploadStaticBuffer(rhi.indexBuffer->buffer(), indexBuffer.data);
1326 }
1327
1328 if (!targetBuffer.data.isEmpty()) {
1329 const int arraySize = targetBuffer.entries.size() * targetBuffer.numTargets;
1330 const int numTexels = (targetBuffer.data.size() / arraySize) >> 4; // byte size to vec4
1331 const int texWidth = qCeil(qSqrt(numTexels));
1332 const QSize texSize(texWidth, texWidth);
1333 if (!rhi.targetsTexture) {
1334 rhi.targetsTexture = context->rhi()->newTextureArray(QRhiTexture::RGBA32F, arraySize, texSize);
1335 rhi.targetsTexture->create();
1336 rhiCtxD->registerTexture(rhi.targetsTexture);
1337 } else if (rhi.targetsTexture->pixelSize() != texSize
1338 || rhi.targetsTexture->arraySize() != arraySize) {
1339 rhi.targetsTexture->setPixelSize(texSize);
1340 rhi.targetsTexture->setArraySize(arraySize);
1341 rhi.targetsTexture->create();
1342 }
1343
1344 const quint32 layerSize = texWidth * texWidth * 4 * 4;
1345 for (int arrayId = 0; arrayId < arraySize; ++arrayId) {
1346 QRhiTextureSubresourceUploadDescription targetDesc(targetBuffer.data + arrayId * layerSize, layerSize);
1347 QRhiTextureUploadDescription desc(QRhiTextureUploadEntry(arrayId, 0, targetDesc));
1348 rub->uploadTexture(rhi.targetsTexture, desc);
1349 }
1350
1351 for (quint32 entryIdx = 0, entryEnd = targetBuffer.entries.size(); entryIdx < entryEnd; ++entryIdx) {
1352 const char *nameStr = targetBuffer.entries[entryIdx].name.constData();
1353 if (!strcmp(nameStr, QSSGMesh::MeshInternal::getPositionAttrName())) {
1354 rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::PositionSemantic] = entryIdx * targetBuffer.numTargets;
1355 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getNormalAttrName())) {
1356 rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::NormalSemantic] = entryIdx * targetBuffer.numTargets;
1357 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getUV0AttrName())) {
1358 rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord0Semantic] = entryIdx * targetBuffer.numTargets;
1359 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getUV1AttrName())) {
1360 rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord1Semantic] = entryIdx * targetBuffer.numTargets;
1361 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getTexTanAttrName())) {
1362 rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TangentSemantic] = entryIdx * targetBuffer.numTargets;
1363 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getTexBinormalAttrName())) {
1364 rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::BinormalSemantic] = entryIdx * targetBuffer.numTargets;
1365 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getColorAttrName())) {
1366 rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::ColorSemantic] = entryIdx * targetBuffer.numTargets;
1367 }
1368 }
1369 rhi.ia.targetCount = targetBuffer.numTargets;
1370 } else if (rhi.targetsTexture) {
1371 rhiCtxD->releaseTexture(rhi.targetsTexture);
1372 rhi.targetsTexture = nullptr;
1373 rhi.ia.targetOffsets = { UINT8_MAX, UINT8_MAX, UINT8_MAX, UINT8_MAX,
1374 UINT8_MAX, UINT8_MAX, UINT8_MAX };
1375 rhi.ia.targetCount = 0;
1376 }
1377
1378 QVector<QSSGRenderVertexBufferEntry> entryBuffer;
1379 entryBuffer.resize(vertexBuffer.entries.size());
1380 for (quint32 entryIdx = 0, entryEnd = vertexBuffer.entries.size(); entryIdx < entryEnd; ++entryIdx)
1381 entryBuffer[entryIdx] = vertexBuffer.entries[entryIdx].toRenderVertexBufferEntry();
1382
1383 QVarLengthArray<QRhiVertexInputAttribute, 4> inputAttrs;
1384 for (quint32 entryIdx = 0, entryEnd = entryBuffer.size(); entryIdx < entryEnd; ++entryIdx) {
1385 const QSSGRenderVertexBufferEntry &vbe(entryBuffer[entryIdx]);
1386 const int binding = 0;
1387 const int location = 0; // for now, will be resolved later, hence the separate inputLayoutInputNames list
1388 const QRhiVertexInputAttribute::Format format = QSSGRhiHelpers::toVertexInputFormat(vbe.m_componentType, vbe.m_numComponents);
1389 const int offset = int(vbe.m_firstItemOffset);
1390
1391 bool ok = true;
1392 const char *nameStr = vbe.m_name.constData();
1393 if (!strcmp(nameStr, QSSGMesh::MeshInternal::getPositionAttrName())) {
1394 rhi.ia.inputs << QSSGRhiInputAssemblerState::PositionSemantic;
1395 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getNormalAttrName())) {
1396 rhi.ia.inputs << QSSGRhiInputAssemblerState::NormalSemantic;
1397 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getUV0AttrName())) {
1398 rhi.ia.inputs << QSSGRhiInputAssemblerState::TexCoord0Semantic;
1399 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getUV1AttrName())) {
1400 rhi.ia.inputs << QSSGRhiInputAssemblerState::TexCoord1Semantic;
1401 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getLightmapUVAttrName())) {
1402 rhi.ia.inputs << QSSGRhiInputAssemblerState::TexCoordLightmapSemantic;
1403 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getTexTanAttrName())) {
1404 rhi.ia.inputs << QSSGRhiInputAssemblerState::TangentSemantic;
1405 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getTexBinormalAttrName())) {
1406 rhi.ia.inputs << QSSGRhiInputAssemblerState::BinormalSemantic;
1407 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getColorAttrName())) {
1408 rhi.ia.inputs << QSSGRhiInputAssemblerState::ColorSemantic;
1409 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getJointAttrName())) {
1410 rhi.ia.inputs << QSSGRhiInputAssemblerState::JointSemantic;
1411 } else if (!strcmp(nameStr, QSSGMesh::MeshInternal::getWeightAttrName())) {
1412 rhi.ia.inputs << QSSGRhiInputAssemblerState::WeightSemantic;
1413 } else {
1414 qWarning("Unknown vertex input %s in mesh", nameStr);
1415 ok = false;
1416 }
1417 if (ok) {
1418 QRhiVertexInputAttribute inputAttr(binding, location, format, offset);
1419 inputAttrs.append(inputAttr);
1420 }
1421 }
1422 rhi.ia.inputLayout.setAttributes(inputAttrs.cbegin(), inputAttrs.cend());
1423 rhi.ia.inputLayout.setBindings({ vertexBuffer.stride });
1424 rhi.ia.topology = QSSGRhiHelpers::toTopology(QSSGRenderDrawMode(mesh.drawMode()));
1425
1426 if (rhi.ia.topology == QRhiGraphicsPipeline::TriangleFan && !context->rhi()->isFeatureSupported(QRhi::TriangleFanTopology))
1427 qWarning("Mesh topology is TriangleFan but this is not supported with the active graphics API. Rendering will be incorrect.");
1428
1429 QVector<QSSGMesh::Mesh::Subset> meshSubsets = mesh.subsets();
1430 for (quint32 subsetIdx = 0, subsetEnd = meshSubsets.size(); subsetIdx < subsetEnd; ++subsetIdx) {
1431 QSSGRenderSubset subset;
1432 const QSSGMesh::Mesh::Subset &source(meshSubsets[subsetIdx]);
1433 subset.bounds = QSSGBounds3(source.bounds.min, source.bounds.max);
1434 subset.count = source.count;
1435 subset.offset = source.offset;
1436 for (auto &lod : source.lods)
1437 subset.lods.append(QSSGRenderSubset::Lod({lod.count, lod.offset, lod.distance}));
1438
1439
1440 if (rhi.vertexBuffer) {
1441 subset.rhi.vertexBuffer = rhi.vertexBuffer;
1442 subset.rhi.ia = rhi.ia;
1443 }
1444 if (rhi.indexBuffer)
1445 subset.rhi.indexBuffer = rhi.indexBuffer;
1446 if (rhi.targetsTexture)
1447 subset.rhi.targetsTexture = rhi.targetsTexture;
1448
1449 newMesh->subsets.push_back(subset);
1450 }
1451
1452 if (!meshSubsets.isEmpty())
1453 newMesh->lightmapSizeHint = meshSubsets.first().lightmapSizeHint;
1454
1455 return newMesh;
1456}
1457
1458void QSSGBufferManager::releaseGeometry(QSSGRenderGeometry *geometry)
1459{
1460 QMutexLocker meshMutexLocker(&meshBufferMutex);
1461 const auto meshItr = customMeshMap.constFind(geometry);
1462 if (meshItr != customMeshMap.cend()) {
1463 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
1464 qDebug() << "- releaseGeometry: " << geometry << currentLayer;
1465 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DCustomMeshLoad);
1466 Q_TRACE_SCOPE(QSSG_customMeshUnload);
1467 decreaseMemoryStat(meshItr.value().mesh);
1468 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(m_contextInterface->rhiContext().get());
1469 rhiCtxD->releaseMesh(meshItr.value().mesh);
1470 customMeshMap.erase(meshItr);
1471 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DCustomMeshLoad,
1472 stats.meshDataSize, geometry->profilingId);
1473 }
1474}
1475
1476void QSSGBufferManager::releaseTextureData(const QSSGRenderTextureData *data)
1477{
1478 QVarLengthArray<CustomImageCacheKey, 4> keys;
1479 for (auto it = customTextureMap.cbegin(), end = customTextureMap.cend(); it != end; ++it) {
1480 if (it.key().data == data)
1481 keys.append(it.key());
1482 }
1483 for (const CustomImageCacheKey &key : keys)
1484 releaseTextureData(key);
1485}
1486
1487void QSSGBufferManager::releaseTextureData(const CustomImageCacheKey &key)
1488{
1489 const auto textureDataItr = customTextureMap.constFind(key);
1490 if (textureDataItr != customTextureMap.cend()) {
1491 auto rhiTexture = textureDataItr.value().renderImageTexture.m_texture;
1492 if (rhiTexture) {
1493 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
1494 qDebug() << "- releaseTextureData: " << rhiTexture << currentLayer;
1495 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DTextureLoad);
1496 Q_TRACE_SCOPE(QSSG_textureUnload);
1497 decreaseMemoryStat(rhiTexture);
1498 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(m_contextInterface->rhiContext().get());
1499 rhiCtxD->releaseTexture(rhiTexture);
1500 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DTextureLoad,
1501 stats.imageDataSize, 0);
1502
1503 }
1504 customTextureMap.erase(textureDataItr);
1505 }
1506}
1507
1508void QSSGBufferManager::releaseExtensionResult(const QSSGRenderExtension &rext)
1509{
1510 renderExtensionTexture.remove(&rext);
1511}
1512
1513void QSSGBufferManager::releaseMesh(const QSSGRenderPath &inSourcePath)
1514{
1515 QMutexLocker meshMutexLocker(&meshBufferMutex);
1516 const auto meshItr = meshMap.constFind(inSourcePath);
1517 if (meshItr != meshMap.cend()) {
1518 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
1519 qDebug() << "- releaseMesh: " << inSourcePath.path() << currentLayer;
1520 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DMeshLoad);
1521 Q_TRACE_SCOPE(QSSG_meshUnload);
1522 decreaseMemoryStat(meshItr.value().mesh);
1523 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(m_contextInterface->rhiContext().get());
1524 rhiCtxD->releaseMesh(meshItr.value().mesh);
1525 meshMap.erase(meshItr);
1526 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DMeshLoad,
1527 stats.meshDataSize, inSourcePath.path().toUtf8());
1528 }
1529}
1530
1531void QSSGBufferManager::releaseImage(const ImageCacheKey &key)
1532{
1533 const auto imageItr = imageMap.constFind(key);
1534 if (imageItr != imageMap.cend()) {
1535 auto rhiTexture = imageItr.value().renderImageTexture.m_texture;
1536 if (rhiTexture) {
1537 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
1538 qDebug() << "- releaseTexture: " << key.path.path() << currentLayer;
1539 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DTextureLoad);
1540 Q_TRACE_SCOPE(QSSG_textureUnload);
1541 decreaseMemoryStat(rhiTexture);
1542 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(m_contextInterface->rhiContext().get());
1543 rhiCtxD->releaseTexture(rhiTexture);
1544 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DTextureLoad,
1545 stats.imageDataSize, key.path.path().toUtf8());
1546 }
1547 imageMap.erase(imageItr);
1548 }
1549}
1550
1551bool QSSGBufferManager::validateLightmap()
1552{
1553 if (lightmapSourceDirty) {
1554 lightmapSourceDirty = false;
1555 QSharedPointer<QSSGLightmapLoader> loader = QSSGLightmapLoader::open(lightmapSource);
1556 lightmapFileValid = loader != nullptr;
1557 if (!lightmapFileValid)
1558 qCWarning(WARNING, "Lightmaps are disabled.");
1559 }
1560 return lightmapFileValid;
1561}
1562
1563void QSSGBufferManager::cleanupUnreferencedBuffers(quint32 frameId, QSSGRenderLayer *currentLayer)
1564{
1565 Q_UNUSED(currentLayer);
1566
1567 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(m_contextInterface->rhiContext().get());
1568
1569 // Don't cleanup if
1570 if (frameId == frameCleanupIndex)
1571 return;
1572
1573 auto isUnused = [] (const QHash<QSSGRenderLayer*, uint32_t> &usages) -> bool {
1574 for (const auto &value : std::as_const(usages))
1575 if (value != 0)
1576 return false;
1577 return true;
1578 };
1579
1580 {
1581 QMutexLocker meshMutexLocker(&meshBufferMutex);
1582 // Meshes (by path)
1583 auto meshIterator = meshMap.cbegin();
1584 while (meshIterator != meshMap.cend()) {
1585 if (isUnused(meshIterator.value().usageCounts)) {
1586 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
1587 qDebug() << "- releaseGeometry: " << meshIterator.key().path() << currentLayer;
1588 decreaseMemoryStat(meshIterator.value().mesh);
1589 rhiCtxD->releaseMesh(meshIterator.value().mesh);
1590 meshIterator = meshMap.erase(meshIterator);
1591 } else {
1592 ++meshIterator;
1593 }
1594 }
1595
1596 // Meshes (custom)
1597 auto customMeshIterator = customMeshMap.cbegin();
1598 while (customMeshIterator != customMeshMap.cend()) {
1599 if (isUnused(customMeshIterator.value().usageCounts)) {
1600 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
1601 qDebug() << "- releaseGeometry: " << customMeshIterator.key() << currentLayer;
1602 decreaseMemoryStat(customMeshIterator.value().mesh);
1603 rhiCtxD->releaseMesh(customMeshIterator.value().mesh);
1604 customMeshIterator = customMeshMap.erase(customMeshIterator);
1605 } else {
1606 ++customMeshIterator;
1607 }
1608 }
1609 }
1610
1611 // SG Textures
1612 auto sgIterator = qsgImageMap.cbegin();
1613 while (sgIterator != qsgImageMap.cend()) {
1614 if (isUnused(sgIterator.value().usageCounts)) {
1615 // Texture is no longer uses, so stop tracking
1616 // We do not need to delete/release the texture
1617 // because we don't own it.
1618 sgIterator = qsgImageMap.erase(sgIterator);
1619 } else {
1620 ++sgIterator;
1621 }
1622 }
1623
1624 // Images
1625 auto imageKeyIterator = imageMap.cbegin();
1626 while (imageKeyIterator != imageMap.cend()) {
1627 if (isUnused(imageKeyIterator.value().usageCounts)) {
1628 auto rhiTexture = imageKeyIterator.value().renderImageTexture.m_texture;
1629 if (rhiTexture) {
1630 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
1631 qDebug() << "- releaseTexture: " << imageKeyIterator.key().path.path() << currentLayer;
1632 decreaseMemoryStat(rhiTexture);
1633 rhiCtxD->releaseTexture(rhiTexture);
1634 }
1635 imageKeyIterator = imageMap.erase(imageKeyIterator);
1636 } else {
1637 ++imageKeyIterator;
1638 }
1639 }
1640
1641 // Custom Texture Data
1642 auto textureDataIterator = customTextureMap.cbegin();
1643 while (textureDataIterator != customTextureMap.cend()) {
1644 if (isUnused(textureDataIterator.value().usageCounts)) {
1645 auto rhiTexture = textureDataIterator.value().renderImageTexture.m_texture;
1646 if (rhiTexture) {
1647 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
1648 qDebug() << "- releaseTextureData: " << rhiTexture << currentLayer;
1649 decreaseMemoryStat(rhiTexture);
1650 rhiCtxD->releaseTexture(rhiTexture);
1651 }
1652 textureDataIterator = customTextureMap.erase(textureDataIterator);
1653 } else {
1654 ++textureDataIterator;
1655 }
1656 }
1657
1658 // Textures from render extensions
1659 auto renderExtensionTextureKeyIterator = renderExtensionTexture.cbegin();
1660 while (renderExtensionTextureKeyIterator != renderExtensionTexture.cend()) {
1661 // We do not own the textures, so we just keep track of usage, but
1662 // if the texture is no longer valid it means that the extension
1663 // has unregistered it, so we can remove it from the map.
1664 auto rhiTexture = renderExtensionTextureKeyIterator.value().renderImageTexture.m_texture;
1665 if (!rhiTexture)
1666 renderExtensionTextureKeyIterator = renderExtensionTexture.erase(renderExtensionTextureKeyIterator);
1667 else
1668 ++renderExtensionTextureKeyIterator;
1669 }
1670
1671 // Resource Tracking Debug Code
1672 frameCleanupIndex = frameId;
1673 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Usage)) {
1674 qDebug() << "QSSGBufferManager::cleanupUnreferencedBuffers()" << this << "frame:" << frameCleanupIndex << currentLayer;
1675 qDebug() << "Textures(by path): " << imageMap.count();
1676 qDebug() << "Textures(custom): " << customTextureMap.count();
1677 qDebug() << "Textures(Extension)" << renderExtensionTexture.count();
1678 qDebug() << "Textures(qsg): " << qsgImageMap.count();
1679 qDebug() << "Geometry(by path): " << meshMap.count();
1680 qDebug() << "Geometry(custom): " << customMeshMap.count();
1681 }
1682}
1683
1684void QSSGBufferManager::resetUsageCounters(quint32 frameId, QSSGRenderLayer *layer)
1685{
1686 currentLayer = layer;
1687 if (frameResetIndex == frameId)
1688 return;
1689
1690 // SG Textures
1691 for (auto &imageData : qsgImageMap)
1692 imageData.usageCounts[layer] = 0;
1693
1694 // Images
1695 for (auto &imageData : imageMap)
1696 imageData.usageCounts[layer] = 0;
1697
1698 // TextureDatas
1699 for (auto &imageData : customTextureMap)
1700 imageData.usageCounts[layer] = 0;
1701 // Meshes
1702 for (auto &meshData : meshMap)
1703 meshData.usageCounts[layer] = 0;
1704
1705 // Meshes (custom)
1706 for (auto &meshData : customMeshMap)
1707 meshData.usageCounts[layer] = 0;
1708
1709 // Textures from render extensions
1710 // NOTE: We retain the usage count as 1 as long as there is a texture
1711 // registered by a extension.
1712 for (auto &retData : renderExtensionTexture) {
1713 const bool hasTexture = (retData.renderImageTexture.m_texture != nullptr);
1714 retData.usageCounts[layer] = uint32_t(hasTexture) * 1;
1715 }
1716
1717 frameResetIndex = frameId;
1718}
1719
1720void QSSGBufferManager::registerMeshData(const QString &assetId, const QVector<QSSGMesh::Mesh> &meshData)
1721{
1722 auto it = g_assetMeshMap->find(assetId);
1723 if (it != g_assetMeshMap->end())
1724 ++it->ref;
1725 else
1726 g_assetMeshMap->insert(assetId, { meshData, 1 });
1727}
1728
1729void QSSGBufferManager::unregisterMeshData(const QString &assetId)
1730{
1731 auto it = g_assetMeshMap->find(assetId);
1732 if (it != g_assetMeshMap->end() && (--it->ref == 0))
1733 g_assetMeshMap->erase(AssetMeshMap::const_iterator(it));
1734}
1735
1736QSSGRenderMesh *QSSGBufferManager::loadRenderMesh(const QSSGRenderPath &inMeshPath, QSSGMeshProcessingOptions options)
1737{
1738 if (inMeshPath.isNull())
1739 return nullptr;
1740
1741 // check if it is already loaded
1742 auto meshItr = meshMap.find(inMeshPath);
1743 if (meshItr != meshMap.cend()) {
1744 if (options.isCompatible(meshItr.value().options)) {
1745 meshItr.value().usageCounts[currentLayer]++;
1746 return meshItr.value().mesh;
1747 } else {
1748 // Re-Insert the mesh with a new name and a "zero" usage count, this will cause the
1749 // mesh to be released before the next frame starts.
1750 auto *mesh = meshItr->mesh;
1751 meshMap.erase(meshItr);
1752 meshMap.insert(QSSGRenderPath(inMeshPath.path() + u"@reaped"), { mesh, {{currentLayer, 0}}, 0, {} });
1753 }
1754 }
1755
1756 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DMeshLoad);
1757 Q_TRACE_SCOPE(QSSG_meshLoadPath, inMeshPath.path());
1758
1759 auto [mesh, debugObjectName] = loadFromLightmapFile(options.lightmapPath, options.lightmapKey);
1760
1761 if (!mesh.isValid()) {
1762 mesh = loadMeshData(inMeshPath);
1763 debugObjectName = QFileInfo(inMeshPath.path()).fileName();
1764 }
1765
1766 if (!mesh.isValid()) {
1767 qCWarning(WARNING, "Failed to load mesh: %s", qPrintable(inMeshPath.path()));
1768 Q_QUICK3D_PROFILE_END_WITH_PAYLOAD(QQuick3DProfiler::Quick3DMeshLoad,
1769 stats.meshDataSize);
1770 return nullptr;
1771 }
1772 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
1773 qDebug() << "+ uploadGeometry: " << inMeshPath.path() << currentLayer;
1774
1775 auto ret = createRenderMesh(mesh, debugObjectName);
1776 meshMap.insert(inMeshPath, { ret, {{currentLayer, 1}}, 0, options });
1777 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(m_contextInterface->rhiContext().get());
1778 rhiCtxD->registerMesh(ret);
1779 increaseMemoryStat(ret);
1780 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DMeshLoad,
1781 stats.meshDataSize, inMeshPath.path().toUtf8());
1782 return ret;
1783}
1784
1785QSSGRenderMesh *QSSGBufferManager::loadRenderMesh(QSSGRenderGeometry *geometry, QSSGMeshProcessingOptions options)
1786{
1787 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(m_contextInterface->rhiContext().get());
1788
1789 auto meshIterator = customMeshMap.find(geometry);
1790 if (meshIterator == customMeshMap.end()) {
1791 meshIterator = customMeshMap.insert(geometry, MeshData());
1792 } else if (geometry->generationId() != meshIterator->generationId || !options.isCompatible(meshIterator->options)) {
1793 // Release old data
1794 releaseGeometry(geometry);
1795 meshIterator = customMeshMap.insert(geometry, MeshData());
1796 } else {
1797 // An up-to-date mesh was found
1798 meshIterator.value().usageCounts[currentLayer]++;
1799 return meshIterator.value().mesh;
1800 }
1801
1802 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DCustomMeshLoad);
1803 Q_TRACE_SCOPE(QSSG_customMeshLoad);
1804
1805 auto [mesh, debugObjectName] = loadFromLightmapFile(options.lightmapPath, options.lightmapKey);
1806
1807 // We have either a valid lightmap mesh or a geometry with data
1808 if (!geometry->meshData().m_vertexBuffer.isEmpty() || mesh.isValid()) {
1809 QString error;
1810
1811 if (!mesh.isValid()) {
1812 mesh = QSSGMesh::Mesh::fromRuntimeData(geometry->meshData(), &error);
1813 debugObjectName = geometry->debugObjectName;
1814 }
1815
1816 if (mesh.isValid()) {
1817 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
1818 qDebug() << "+ uploadGeometry: " << geometry << currentLayer;
1819 meshIterator->mesh = createRenderMesh(mesh, debugObjectName);
1820 meshIterator->usageCounts[currentLayer] = 1;
1821 meshIterator->generationId = geometry->generationId();
1822 meshIterator->options = options;
1823 rhiCtxD->registerMesh(meshIterator->mesh);
1824 increaseMemoryStat(meshIterator->mesh);
1825 } else {
1826 qWarning("Mesh building failed: %s", qPrintable(error));
1827 }
1828 }
1829 // else an empty mesh is not an error, leave the QSSGRenderMesh null, it will not be rendered then
1830
1831 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DCustomMeshLoad,
1832 stats.meshDataSize, geometry->profilingId);
1833 return meshIterator->mesh;
1834}
1835
1836std::unique_ptr<QSSGMeshBVH> QSSGBufferManager::loadMeshBVH(const QSSGRenderPath &inSourcePath)
1837{
1838 const QSSGMesh::Mesh mesh = loadMeshData(inSourcePath);
1839 if (!mesh.isValid()) {
1840 qCWarning(WARNING, "Failed to load mesh: %s", qPrintable(inSourcePath.path()));
1841 return nullptr;
1842 }
1843 QSSGMeshBVHBuilder meshBVHBuilder(mesh);
1844 return meshBVHBuilder.buildTree();
1845}
1846
1847std::unique_ptr<QSSGMeshBVH> QSSGBufferManager::loadMeshBVH(QSSGRenderGeometry *geometry)
1848{
1849 if (!geometry)
1850 return nullptr;
1851
1852 // We only support generating a BVH with Triangle primitives
1853 if (geometry->primitiveType() != QSSGMesh::Mesh::DrawMode::Triangles)
1854 return nullptr;
1855
1856 // Build BVH
1857 bool hasIndexBuffer = false;
1858 QSSGRenderComponentType indexBufferFormat = QSSGRenderComponentType::UnsignedInt32;
1859 bool hasUV = false;
1860 int uvOffset = -1;
1861 int posOffset = -1;
1862
1863 for (int i = 0; i < geometry->attributeCount(); ++i) {
1864 auto attribute = geometry->attribute(i);
1865 if (attribute.semantic == QSSGMesh::RuntimeMeshData::Attribute::PositionSemantic) {
1866 posOffset = attribute.offset;
1867 } else if (attribute.semantic == QSSGMesh::RuntimeMeshData::Attribute::TexCoord0Semantic) {
1868 hasUV = true;
1869 uvOffset = attribute.offset;
1870 } else if (!hasUV && attribute.semantic == QSSGMesh::RuntimeMeshData::Attribute::TexCoord1Semantic) {
1871 hasUV = true;
1872 uvOffset = attribute.offset;
1873 } else if (attribute.semantic == QSSGMesh::RuntimeMeshData::Attribute::IndexSemantic) {
1874 hasIndexBuffer = true;
1875 indexBufferFormat = attribute.componentType;
1876 if (indexBufferFormat != QSSGRenderComponentType::Int16
1877 && indexBufferFormat != QSSGRenderComponentType::Int32
1878 && indexBufferFormat != QSSGRenderComponentType::UnsignedInt16
1879 && indexBufferFormat != QSSGRenderComponentType::UnsignedInt32) {
1880 qWarning() << "Unsupported index buffer format for geometry";
1881 return nullptr;
1882 }
1883 }
1884 }
1885
1886 QSSGMeshBVHBuilder meshBVHBuilder(geometry->vertexBuffer(),
1887 geometry->stride(),
1888 posOffset,
1889 hasUV,
1890 uvOffset,
1891 hasIndexBuffer,
1892 geometry->indexBuffer(),
1893 indexBufferFormat);
1894 return meshBVHBuilder.buildTree();
1895}
1896
1897std::unique_ptr<QSSGMeshBVH> QSSGBufferManager::loadMeshBVH(const QSSGMesh::Mesh &mesh)
1898{
1899 QSSGMeshBVHBuilder meshBVHBuilder(mesh);
1900 return meshBVHBuilder.buildTree();
1901}
1902
1903QSSGMesh::Mesh QSSGBufferManager::loadMeshData(const QSSGRenderPath &inMeshPath)
1904{
1905 QSSGMesh::Mesh result;
1906
1907 // check to see if this is a primitive mesh
1908 if (inMeshPath.path().startsWith(QChar::fromLatin1('#')))
1909 result = loadPrimitive(inMeshPath.path());
1910
1911 // check if this is an imported mesh. Expected path format: !name@path_to_asset
1912 if (!result.isValid() && inMeshPath.path().startsWith(u'!')) {
1913 const auto &[idx, assetId] = splitRuntimeMeshPath(inMeshPath);
1914 if (idx >= 0) {
1915 const auto ait = g_assetMeshMap->constFind(assetId);
1916 if (ait != g_assetMeshMap->constEnd()) {
1917 const auto &meshes = ait->meshes;
1918 if (idx < meshes.size())
1919 result = ait->meshes.at(idx);
1920 }
1921 } else {
1922 qWarning("Unexpected mesh path!");
1923 }
1924 }
1925
1926 // Attempt a load from the filesystem otherwise.
1927 if (!result.isValid()) {
1928 QString pathBuilder = inMeshPath.path();
1929 int poundIndex = pathBuilder.lastIndexOf(QChar::fromLatin1('#'));
1930 quint32 id = 0;
1931 if (poundIndex != -1) {
1932 id = QStringView(pathBuilder).mid(poundIndex + 1).toUInt();
1933 pathBuilder = pathBuilder.left(poundIndex);
1934 }
1935 if (!pathBuilder.isEmpty()) {
1936 QSharedPointer<QIODevice> device(QSSGInputUtil::getStreamForFile(pathBuilder));
1937 if (device) {
1938 QSSGMesh::Mesh mesh = QSSGMesh::Mesh::loadMesh(device.data(), id);
1939 if (mesh.isValid())
1940 result = mesh;
1941 }
1942 }
1943 }
1944
1945 return result;
1946}
1947
1948QSSGMesh::Mesh QSSGBufferManager::loadMeshData(const QSSGRenderGeometry *geometry)
1949{
1950 QString error;
1951 QSSGMesh::Mesh mesh = QSSGMesh::Mesh::fromRuntimeData(geometry->meshData(), &error);
1952 if (!mesh.isValid())
1953 qWarning("loadMeshDataForCustomMeshUncached failed: %s", qPrintable(error));
1954
1955 return mesh;
1956}
1957
1958void QSSGBufferManager::registerExtensionResult(const QSSGRenderExtension &extensions,
1959 QRhiTexture *texture)
1960{
1961 if (texture) {
1962 const bool isMipMapped = texture->flags().testFlag(QRhiTexture::Flag::MipMapped);
1963 const auto mipLevels = isMipMapped ? QRhi::mipLevelsForSize(texture->pixelSize()) : 0;
1964 QSSGRenderImageTextureFlags flags;
1965 const bool isSRGB = texture->flags().testFlag(QRhiTexture::Flag::sRGB);
1966 flags.setLinear(!isSRGB);
1967 const bool isRGBA8 = (texture->format() == QRhiTexture::Format::RGBA8);
1968 flags.setRgbe8(isRGBA8);
1969 renderExtensionTexture.insert(&extensions, ImageData { QSSGRenderImageTexture{ texture, mipLevels, flags }, {}, 0 /* version */ });
1970 } else {
1971 renderExtensionTexture.insert(&extensions, {});
1972 }
1973}
1974
1975void QSSGBufferManager::clear()
1976{
1977 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(m_contextInterface->rhiContext().get());
1978
1979 if (meshBufferUpdates) {
1980 meshBufferUpdates->release();
1981 meshBufferUpdates = nullptr;
1982 }
1983
1984 {
1985 QMutexLocker meshMutexLocker(&meshBufferMutex);
1986 // Meshes (by path)
1987 auto meshMapCopy = meshMap;
1988 meshMapCopy.detach();
1989 for (auto iter = meshMapCopy.begin(), end = meshMapCopy.end(); iter != end; ++iter) {
1990 QSSGRenderMesh *theMesh = iter.value().mesh;
1991 if (theMesh) {
1992 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
1993 qDebug() << "- releaseGeometry: " << iter.key().path() << currentLayer;
1994 decreaseMemoryStat(theMesh);
1995 rhiCtxD->releaseMesh(theMesh);
1996 }
1997 }
1998 meshMap.clear();
1999
2000 // Meshes (custom)
2001 auto customMeshMapCopy = customMeshMap;
2002 customMeshMapCopy.detach();
2003 for (auto iter = customMeshMapCopy.begin(), end = customMeshMapCopy.end(); iter != end; ++iter) {
2004 QSSGRenderMesh *theMesh = iter.value().mesh;
2005 if (theMesh) {
2006 if (QSSGBufferManagerStat::enabled(QSSGBufferManagerStat::Level::Debug))
2007 qDebug() << "- releaseGeometry: " << iter.key() << currentLayer;
2008 decreaseMemoryStat(theMesh);
2009 rhiCtxD->releaseMesh(theMesh);
2010 }
2011 }
2012 customMeshMap.clear();
2013 }
2014
2015 // Textures (by path)
2016 for (auto it = imageMap.constBegin(), end = imageMap.constEnd(); it != end; ++it)
2017 releaseImage(it.key());
2018
2019 imageMap.clear();
2020
2021 // Textures (custom)
2022 for (auto it = customTextureMap.cbegin(), end = customTextureMap.cend(); it != end; ++it)
2023 releaseTextureData(it.key());
2024
2025 customTextureMap.clear();
2026
2027 // Textures (QSG)
2028 // these don't have any owned objects to release so just clearing is fine.
2029 qsgImageMap.clear();
2030
2031 // To allow trying to read the lightmap file again
2032 lightmapSourceDirty = true;
2033}
2034
2035QRhiResourceUpdateBatch *QSSGBufferManager::meshBufferUpdateBatch()
2036{
2037 if (!meshBufferUpdates)
2038 meshBufferUpdates = m_contextInterface->rhiContext()->rhi()->nextResourceUpdateBatch();
2039 return meshBufferUpdates;
2040}
2041
2042void QSSGBufferManager::commitBufferResourceUpdates()
2043{
2044 if (meshBufferUpdates) {
2045 m_contextInterface->rhiContext()->commandBuffer()->resourceUpdate(meshBufferUpdates);
2046 meshBufferUpdates = nullptr;
2047 }
2048}
2049
2050void QSSGBufferManager::processResourceLoader(const QSSGRenderResourceLoader *loader)
2051{
2052 for (auto &mesh : std::as_const(loader->meshes))
2053 loadRenderMesh(mesh, {});
2054
2055 for (auto customMesh : std::as_const(loader->geometries))
2056 loadRenderMesh(static_cast<QSSGRenderGeometry*>(customMesh), {});
2057
2058 for (auto texture : std::as_const(loader->textures)) {
2059 const auto image = static_cast<QSSGRenderImage *>(texture);
2060 loadRenderImage(image);
2061 }
2062
2063 // Make sure the uploads occur
2064 commitBufferResourceUpdates();
2065}
2066
2067static inline quint64 textureMemorySize(QRhiTexture *texture)
2068{
2069 quint64 s = 0;
2070 if (!texture)
2071 return s;
2072
2073 auto format = texture->format();
2074 if (format == QRhiTexture::UnknownFormat)
2075 return 0;
2076
2077 s = texture->pixelSize().width() * texture->pixelSize().height();
2078 /*
2079 UnknownFormat,
2080 RGBA8,
2081 BGRA8,
2082 R8,
2083 RG8,
2084 R16,
2085 RG16,
2086 RED_OR_ALPHA8,
2087 RGBA16F,
2088 RGBA32F,
2089 R16F,
2090 R32F,
2091 RGB10A2,
2092 R8SI,
2093 R32SI,
2094 RG32SI,
2095 RGBA32SI,
2096 R8UI,
2097 R32UI,
2098 RG32UI,
2099 RGBA32UI,
2100 D16,
2101 D24,
2102 D24S8,
2103 D32F,
2104 D32FS8*/
2105 static const quint64 pixelSizes[] = {0, 4, 4, 1, 2, 2, 4, 1, 2, 4, 2, 4, 4, 1, 4, 8, 16, 1, 4, 8, 16, 2, 4, 4, 4, 8};
2106 /*
2107 BC1,
2108 BC2,
2109 BC3,
2110 BC4,
2111 BC5,
2112 BC6H,
2113 BC7,
2114 ETC2_RGB8,
2115 ETC2_RGB8A1,
2116 ETC2_RGBA8,*/
2117 static const quint64 blockSizes[] = {8, 16, 16, 8, 16, 16, 16, 8, 8, 16};
2118 Q_STATIC_ASSERT_X(QRhiTexture::BC1 == 26 && QRhiTexture::ETC2_RGBA8 == 35,
2119 "QRhiTexture format constant value missmatch.");
2120 if (format < QRhiTexture::BC1)
2121 s *= pixelSizes[format];
2122 else if (format >= QRhiTexture::BC1 && format <= QRhiTexture::ETC2_RGBA8)
2123 s /= blockSizes[format - QRhiTexture::BC1];
2124 else
2125 s /= 16;
2126
2127 if (texture->flags() & QRhiTexture::MipMapped)
2128 s += s / 4;
2129 if (texture->flags() & QRhiTexture::CubeMap)
2130 s *= 6;
2131 return s;
2132}
2133
2134static inline quint64 bufferMemorySize(const QSSGRhiBufferPtr &buffer)
2135{
2136 quint64 s = 0;
2137 if (!buffer)
2138 return s;
2139 s = buffer->buffer()->size();
2140 return s;
2141}
2142
2143void QSSGBufferManager::increaseMemoryStat(QRhiTexture *texture)
2144{
2145 stats.imageDataSize += textureMemorySize(texture);
2146 QSSGRhiContextStats::get(*m_contextInterface->rhiContext()).imageDataSizeChanges(stats.imageDataSize);
2147}
2148
2149void QSSGBufferManager::decreaseMemoryStat(QRhiTexture *texture)
2150{
2151 stats.imageDataSize = qMax(0u, stats.imageDataSize - textureMemorySize(texture));
2152 QSSGRhiContextStats::get(*m_contextInterface->rhiContext()).imageDataSizeChanges(stats.imageDataSize);
2153}
2154
2155void QSSGBufferManager::increaseMemoryStat(QSSGRenderMesh *mesh)
2156{
2157 stats.meshDataSize += bufferMemorySize(mesh->subsets.at(0).rhi.vertexBuffer)
2158 + bufferMemorySize(mesh->subsets.at(0).rhi.indexBuffer);
2159 QSSGRhiContextStats::get(*m_contextInterface->rhiContext()).meshDataSizeChanges(stats.meshDataSize);
2160}
2161
2162void QSSGBufferManager::decreaseMemoryStat(QSSGRenderMesh *mesh)
2163{
2164 quint64 s = 0;
2165 if (mesh)
2166 s = bufferMemorySize(mesh->subsets.at(0).rhi.vertexBuffer)
2167 + bufferMemorySize(mesh->subsets.at(0).rhi.indexBuffer);
2168 stats.meshDataSize = qMax(0u, stats.meshDataSize - s);
2169 QSSGRhiContextStats::get(*m_contextInterface->rhiContext()).meshDataSizeChanges(stats.meshDataSize);
2170}
2171
2172void QSSGBufferManager::setLightmapSource(const QString &source)
2173{
2174 if (lightmapSource != source) {
2175 lightmapSource = source;
2176 lightmapSourceDirty = true;
2177 }
2178}
2179
2180void QSSGBufferManager::setCurrentlyLightmapBaking(bool value)
2181{
2182 currentlyLightmapBaking = value;
2183}
2184
2185size_t qHash(const QSSGBufferManager::CustomImageCacheKey &k, size_t seed) noexcept
2186{
2187 // NOTE: The data pointer should never be null, as null data pointers shouldn't be inserted into
2188 // the cached (make sure to check that before inserting!!!)
2189 using MipMap_t = std::underlying_type_t<QSSGBufferManager::MipMode>;
2190 return qHash(*k.data, seed) ^ MipMap_t(k.mipMode);
2191}
2192
2193QT_END_NAMESPACE
Q_TRACE_POINT(qtcore, QCoreApplication_postEvent_exit)
Q_TRACE_POINT(qtcore, QFactoryLoader_update, const QString &fileName)
constexpr size_t qHash(const QSize &s, size_t seed=0) noexcept
Definition qsize.h:191
QSSGLightmapIODataTag
static const PrimitiveEntry primitives[nPrimitives]
static constexpr QSize sizeForMipLevel(int mipLevel, const QSize &baseLevelSize)
static const int nPrimitives
static quint64 textureMemorySize(QRhiTexture *texture)
static MeshIdxNamePair splitRuntimeMeshPath(const QSSGRenderPath &rpath)
static const float cube[]
static const char * primitivesDirectory
static quint64 bufferMemorySize(const QSSGRhiBufferPtr &buffer)
static QPair< QSSGMesh::Mesh, QString > loadFromLightmapFile(const QString &lightmapPath, const QString &lightmapKey)
QVector< QSSGMesh::Mesh > meshes
static constexpr bool enabled(Level)