Revision control

++ // Copyright (C) 2025 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include "qssglightmapbaker_p.h"
++
++ #ifdef QT_QUICK3D_HAS_LIGHTMAPPER
++ #include <QGuiApplication>
++ #include <QString>
++ #include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
++ #include <QThreadPool>
++ #include "qssgrendercontextcore.h"
++ #if QT_CONFIG(opengl)
++ #include <QOffscreenSurface>
++ #endif // QT_CONFIG(opengl)
++
++ QT_BEGIN_NAMESPACE
++
++ struct QSSGLightmapBakerPrivate
++ {
++ QSSGLightmapBaker::Context ctx;
++ std::unique_ptr<QSSGLightmapper> lightmapper = nullptr;
++ QSSGLightmapBaker::Status currentStatus = QSSGLightmapBaker::Status::Preparing;
++ // Use a local threadpool to be able to set highest thread priority on the bake thread
++ QThreadPool localThreadPool;
++ QOffscreenSurface *fallbackSurface = nullptr;
++ bool waitingForInvoke = false;
++ };
++
++ QSSGLightmapBaker::QSSGLightmapBaker(const QSSGLightmapBaker::Context &ctx)
++ : d(new QSSGLightmapBakerPrivate)
++ {
++ d->ctx = ctx;
++ d->currentStatus = QSSGLightmapBaker::Status::Preparing;
++ d->localThreadPool.setMaxThreadCount(1);
++ }
++
++ QSSGLightmapBaker::~QSSGLightmapBaker()
++ {
++ delete d;
++ }
++
++ QSSGLightmapBaker::Status QSSGLightmapBaker::process()
++ {
++ if (d->waitingForInvoke)
++ return d->currentStatus;
++
++ auto &env = d->ctx.env;
++ auto &settings = d->ctx.settings;
++ auto &callbacks = d->ctx.callbacks;
++
++ if (d->currentStatus == Status::Preparing) {
++ // We need to prepare for lightmap baking by doing another frame so that
++ // we can reload all meshes to use the original one and NOT the baked one.
++ // When disableLightmaps is set on the layer, the mesh loader will always load the
++ // original mesh and not the lightmap mesh.
++ callbacks.setCurrentlyBaking(true);
++ callbacks.triggerNewFrame(true);
++
++ #if QT_CONFIG(opengl)
++ d->waitingForInvoke = true;
++ QMetaObject::invokeMethod(qApp, [this]() {
++ d->fallbackSurface = QRhiGles2InitParams::newFallbackSurface();
++ d->currentStatus = Status::Running;
++ d->waitingForInvoke = false;
++ },
++ Qt::QueuedConnection);
++ #else
++ d->currentStatus = Status::Running;
++ #endif
++ } else if (d->currentStatus == Status::Running) {
++ d->lightmapper = std::make_unique<QSSGLightmapper>();
++ d->lightmapper->setRhiBackend(env.rhiCtx->rhi()->backend());
++ d->lightmapper->setOptions(env.lmOptions);
++ d->lightmapper->setOutputCallback(callbacks.lightmapBakingOutput);
++ d->lightmapper->setDenoiseOnly(settings.denoiseRequested);
++
++ // bakedLightingModels contains all models with
++ // usedInBakedLighting: true. These, together with lights that
++ // have a bakeMode set to either Indirect or All, form the
++ // lightmapped scene. A lightmap is stored persistently only
++ // for models that have their lightmapKey set.
++ const auto &bakedLightingModels = callbacks.modelsToBake();
++ for (int i = 0, ie = bakedLightingModels.size(); i != ie; ++i)
++ d->lightmapper->add(bakedLightingModels[i]);
++
++ if (!d->lightmapper->setupLights(*env.renderer)) {
++ d->currentStatus = Status::Finished;
++ callbacks.setCurrentlyBaking(false);
++ callbacks.triggerNewFrame(true);
++ if (settings.quitWhenFinished) {
++ qDebug("Lightmap baking/denoising done, exiting application");
++ QMetaObject::invokeMethod(qApp, "quit");
++ }
++ return Status::Finished;
++ }
++
++ QRhiCommandBuffer *cb = env.rhiCtx->commandBuffer();
++ cb->debugMarkBegin("Quick3D lightmap baking/denoising");
++
++ d->currentStatus = Status::Baking;
++
++ const auto bakeTask = [this, callbacks, settings] {
++ #if QT_CONFIG(thread)
++ QThread::currentThread()->setPriority(QThread::HighestPriority);
++ #endif // QT_CONFIG(thread)
++ d->lightmapper->run(d->fallbackSurface);
++
++ callbacks.setCurrentlyBaking(false);
++ callbacks.triggerNewFrame(true);
++
++ #if QT_CONFIG(opengl)
++ d->waitingForInvoke = true;
++ QMetaObject::invokeMethod(qApp, [this]() {
++ delete d->fallbackSurface;
++ d->fallbackSurface = nullptr;
++ d->currentStatus = Status::Finished;
++ d->waitingForInvoke = false;
++ }, Qt::QueuedConnection);
++ #else
++ d->currentStatus = Status::Finished;
++ #endif
++ if (settings.quitWhenFinished) {
++ qDebug("Lightmap baking/denoising done, exiting application");
++ QMetaObject::invokeMethod(qApp, "quit");
++ }
++ };
++
++ #if QT_CONFIG(thread)
++ d->localThreadPool.start(bakeTask);
++ #else
++ bakeTask();
++ #endif // QT_CONFIG(thread)
++
++ // Wait until lightmapper is finished initializing
++ d->lightmapper->waitForInit();
++ cb->debugMarkEnd();
++ }
++
++ return d->currentStatus;
++ }
++
++ QT_END_NAMESPACE
++
++ #else // QT_QUICK3D_HAS_LIGHTMAPPER
++
++ QT_BEGIN_NAMESPACE
++
++ QSSGLightmapBaker::QSSGLightmapBaker(const QSSGLightmapBaker::Context &ctx)
++ {
++ qWarning("QSSGLightmapBaker: This build has no lightmap baking support.");
++ #if defined(qApp)
++ if (ctx.settings.quitWhenFinished)
++ QMetaObject::invokeMethod(qApp, "quit");
++ #else
++ Q_UNUSED(ctx);
++ #endif
++ }
++
++ QSSGLightmapBaker::~QSSGLightmapBaker() = default;
++
++ QSSGLightmapBaker::Status QSSGLightmapBaker::process()
++ {
++ return QSSGLightmapBaker::Status::Finished;
++ }
++
++ QT_END_NAMESPACE
++
++ #endif // QT_QUICK3D_HAS_LIGHTMAPPER