| 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 | |
| 15 | QT_BEGIN_NAMESPACE |
| 16 | |
| 17 | struct 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 | |
| 28 | QSSGLightmapBaker::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 | |
| 36 | QSSGLightmapBaker::~QSSGLightmapBaker() |
| 37 | { |
| 38 | delete d; |
| 39 | } |
| 40 | |
| 41 | QSSGLightmapBaker::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 | |
| 133 | QT_END_NAMESPACE |
| 134 | |
| 135 | #else // QT_QUICK3D_HAS_LIGHTMAPPER |
| 136 | |
| 137 | QT_BEGIN_NAMESPACE |
| 138 | |
| 139 | QSSGLightmapBaker::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 | |
| 150 | QSSGLightmapBaker::~QSSGLightmapBaker() = default; |
| 151 | |
| 152 | QSSGLightmapBaker::Status QSSGLightmapBaker::process() |
| 153 | { |
| 154 | return QSSGLightmapBaker::Status::Finished; |
| 155 | } |
| 156 | |
| 157 | QT_END_NAMESPACE |
| 158 | |
| 159 | #endif // QT_QUICK3D_HAS_LIGHTMAPPER |
| 160 | |