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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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