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