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

source code of qtquick3d/src/runtimerender/resourcemanager/qssgrenderbuffermanager.cpp