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

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