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