1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qssglightmapbaker_p.h"
5
6#ifdef QT_QUICK3D_HAS_LIGHTMAPPER
7#include <QString>
8#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
9#include <QThreadPool>
10#include "qssgrendercontextcore.h"
11#if QT_CONFIG(opengl)
12#include <QOffscreenSurface>
13#endif // QT_CONFIG(opengl)
14
15QT_BEGIN_NAMESPACE
16
17struct QSSGLightmapBakerPrivate
18{
19 QSSGLightmapBaker::Context ctx;
20 std::unique_ptr<QSSGLightmapper> lightmapper = nullptr;
21 QSSGLightmapBaker::Status currentStatus = QSSGLightmapBaker::Status::Preparing;
22 // Use a local threadpool to be able to set highest thread priority on the bake thread
23 QThreadPool localThreadPool;
24 QOffscreenSurface *fallbackSurface = nullptr;
25 bool waitingForInvoke = false;
26};
27
28QSSGLightmapBaker::QSSGLightmapBaker(const QSSGLightmapBaker::Context &ctx)
29 : d(new QSSGLightmapBakerPrivate)
30{
31 d->ctx = ctx;
32 d->currentStatus = QSSGLightmapBaker::Status::Preparing;
33 d->localThreadPool.setMaxThreadCount(1);
34}
35
36QSSGLightmapBaker::~QSSGLightmapBaker()
37{
38 delete d;
39}
40
41QSSGLightmapBaker::Status QSSGLightmapBaker::process()
42{
43 if (d->waitingForInvoke)
44 return d->currentStatus;
45
46 auto &env = d->ctx.env;
47 auto &settings = d->ctx.settings;
48 auto &callbacks = d->ctx.callbacks;
49
50 if (d->currentStatus == Status::Preparing) {
51 // We need to prepare for lightmap baking by doing another frame so that
52 // we can reload all meshes to use the original one and NOT the baked one.
53 // When disableLightmaps is set on the layer, the mesh loader will always load the
54 // original mesh and not the lightmap mesh.
55 callbacks.setCurrentlyBaking(true);
56 callbacks.triggerNewFrame(true);
57
58#if QT_CONFIG(opengl)
59 d->waitingForInvoke = true;
60 QMetaObject::invokeMethod(qApp, function: [this]() {
61 d->fallbackSurface = QRhiGles2InitParams::newFallbackSurface();
62 d->currentStatus = Status::Running;
63 d->waitingForInvoke = false;
64 },
65 type: Qt::QueuedConnection);
66#else
67 d->currentStatus = Status::Running;
68#endif
69 } else if (d->currentStatus == Status::Running) {
70 d->lightmapper = std::make_unique<QSSGLightmapper>();
71 d->lightmapper->setRhiBackend(env.rhiCtx->rhi()->backend());
72 d->lightmapper->setOptions(env.lmOptions);
73 d->lightmapper->setOutputCallback(callbacks.lightmapBakingOutput);
74 d->lightmapper->setDenoiseOnly(settings.denoiseRequested);
75
76 // bakedLightingModels contains all models with
77 // usedInBakedLighting: true. These, together with lights that
78 // have a bakeMode set to either Indirect or All, form the
79 // lightmapped scene. A lightmap is stored persistently only
80 // for models that have their lightmapKey set.
81 const auto &bakedLightingModels = callbacks.modelsToBake();
82 for (int i = 0, ie = bakedLightingModels.size(); i != ie; ++i)
83 d->lightmapper->add(model: bakedLightingModels[i]);
84
85 if (!d->lightmapper->setupLights(*env.renderer)) {
86 d->currentStatus = Status::Finished;
87 callbacks.setCurrentlyBaking(false);
88 callbacks.triggerNewFrame(true);
89 if (settings.quitWhenFinished) {
90 qDebug(msg: "Lightmap baking/denoising done, exiting application");
91 QMetaObject::invokeMethod(qApp, member: "quit");
92 }
93 return Status::Finished;
94 }
95
96 QRhiCommandBuffer *cb = env.rhiCtx->commandBuffer();
97 cb->debugMarkBegin(name: "Quick3D lightmap baking/denoising");
98
99 d->currentStatus = Status::Baking;
100 d->localThreadPool.start(functionToRun: [this, callbacks, settings] {
101 QThread::currentThread()->setPriority(QThread::HighestPriority);
102 d->lightmapper->run(fallbackSurface: d->fallbackSurface);
103
104 callbacks.setCurrentlyBaking(false);
105 callbacks.triggerNewFrame(true);
106
107#if QT_CONFIG(opengl)
108 d->waitingForInvoke = true;
109 QMetaObject::invokeMethod(qApp, function: [this]() {
110 delete d->fallbackSurface;
111 d->fallbackSurface = nullptr;
112 d->currentStatus = Status::Finished;
113 d->waitingForInvoke = false;
114 },
115 type: Qt::QueuedConnection);
116#else
117 d->currentStatus = Status::Finished;
118#endif
119 if (settings.quitWhenFinished) {
120 qDebug(msg: "Lightmap baking/denoising done, exiting application");
121 QMetaObject::invokeMethod(qApp, member: "quit");
122 }
123 });
124
125 // Wait until lightmapper is finished initializing
126 d->lightmapper->waitForInit();
127 cb->debugMarkEnd();
128 }
129
130 return d->currentStatus;
131}
132
133QT_END_NAMESPACE
134
135#else // QT_QUICK3D_HAS_LIGHTMAPPER
136
137QT_BEGIN_NAMESPACE
138
139QSSGLightmapBaker::QSSGLightmapBaker(const QSSGLightmapBaker::Context &ctx)
140{
141 qWarning("QSSGLightmapBaker: This build has no lightmap baking support.");
142#if defined(qApp)
143 if (ctx.settings.quitWhenFinished)
144 QMetaObject::invokeMethod(qApp, "quit");
145#else
146 Q_UNUSED(ctx);
147#endif
148}
149
150QSSGLightmapBaker::~QSSGLightmapBaker() = default;
151
152QSSGLightmapBaker::Status QSSGLightmapBaker::process()
153{
154 return QSSGLightmapBaker::Status::Finished;
155}
156
157QT_END_NAMESPACE
158
159#endif // QT_QUICK3D_HAS_LIGHTMAPPER
160

source code of qtquick3d/src/runtimerender/rendererimpl/qssglightmapbaker.cpp