aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonas Karlsson <[email protected]>2025-07-04 13:06:26 +0200
committerJonas Karlsson <[email protected]>2025-07-04 14:23:02 +0200
commit102b7bcb274e60e841f7211c43b304637158ecfc (patch)
tree4527d2b02ca705b3604cac60e3ee3c28d0705a63
parentce50a40a962eebe06ab1381c3dd886ed65c7015e (diff)
Lightmapper: modify bake progress windowHEADdev
- Rearrange the output window components. Add a label for showing the baking stage and remove the elapsed time label. Add a Frame around the log text. - Add some proper types checks of the payload (fixes a bug where 0 numbers would mean false and not update the value). Pick-to: 6.10 Change-Id: Ibbecfeb9dd37a3987e5b46204c37203871b82af1 Reviewed-by: Kristoffer Skau <[email protected]>
-rw-r--r--src/quick3d/LightmapperOutputWindow.qml81
-rw-r--r--src/runtimerender/rendererimpl/qssglightmapper.cpp84
2 files changed, 115 insertions, 50 deletions
diff --git a/src/quick3d/LightmapperOutputWindow.qml b/src/quick3d/LightmapperOutputWindow.qml
index 75c9f134..15cfad66 100644
--- a/src/quick3d/LightmapperOutputWindow.qml
+++ b/src/quick3d/LightmapperOutputWindow.qml
@@ -10,25 +10,28 @@ Pane {
anchors.fill: parent
property double totalProgress: 0
- property double totalTimeRemaining: 0
- property double totalTimeElapsed: 0
+ property int totalTimeRemaining: -1
+ property string stage : "Preparing..."
function clearText() {
textArea.clear();
}
function update(payload) {
- if (payload["message"]) {
- textArea.insert(textArea.length, payload["message"] + "\n")
+ if ("message" in payload && typeof payload.message === "string" && payload.message) {
+ textArea.insert(textArea.length, payload.message + "\n");
}
- if (payload["totalProgress"]) {
- root.totalProgress = payload["totalProgress"];
+
+ if ("totalProgress" in payload && typeof payload.totalProgress === "number") {
+ root.totalProgress = payload.totalProgress;
}
- if (payload["totalTimeRemaining"]) {
- root.totalTimeRemaining = payload["totalTimeRemaining"];
+
+ if ("totalTimeRemaining" in payload && typeof payload.totalTimeRemaining === "number") {
+ root.totalTimeRemaining = payload.totalTimeRemaining;
}
- if (payload["totalTimeElapsed"]) {
- root.totalTimeElapsed = payload["totalTimeElapsed"]
+
+ if ("stage" in payload && typeof payload.stage === "string") {
+ root.stage = payload.stage;
}
}
@@ -54,47 +57,51 @@ Pane {
ColumnLayout {
anchors.fill: parent
- ScrollView {
- Layout.fillWidth: true
- Layout.fillHeight: true
- TextArea {
- id: textArea
- readOnly: true
- placeholderText: qsTr("Qt Lightmapper")
- font.pixelSize: 12
- wrapMode: Text.WordWrap
+ RowLayout {
+ Label {
+ padding: 0
+ text: root.stage
+ }
+ Item {
+ Layout.fillWidth: true
+ }
+ Label {
+ padding: 0
+ text: (root.totalProgress * 100).toFixed(0) + "%"
}
}
ProgressBar {
Layout.fillWidth: true
- Layout.preferredHeight: 25
value: root.totalProgress
}
RowLayout {
- //Layout.fillWidth: true
- Item {
- Layout.preferredWidth: 10
- }
Label {
- text: (root.totalProgress * 100).toFixed(0) + "% complete"
+ padding: 0
+ text: totalTimeRemaining > 0 ? "Remaining: " + root.formatDuration(root.totalTimeRemaining) : ""
}
Item {
Layout.fillWidth: true
}
- Label {
- text: "Remaining: " + root.formatDuration(root.totalTimeRemaining)
- }
- Item {
- Layout.fillWidth: true
- }
- Label {
- Layout.alignment: Qt.AlignRight
- text: "Elapsed: " + root.formatDuration(root.totalTimeElapsed)
- }
- Item {
- Layout.preferredWidth: 10
+ }
+
+ Frame {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ ScrollView {
+ width: parent.width
+ height: parent.height
+ id: scroll
+ TextArea {
+ id: textArea
+ width: parent.width
+ height: parent.height
+ readOnly: true
+ placeholderText: qsTr("Qt Lightmapper")
+ font.pixelSize: 12
+ wrapMode: Text.WordWrap
+ }
}
}
diff --git a/src/runtimerender/rendererimpl/qssglightmapper.cpp b/src/runtimerender/rendererimpl/qssglightmapper.cpp
index d47a9645..ab828461 100644
--- a/src/runtimerender/rendererimpl/qssglightmapper.cpp
+++ b/src/runtimerender/rendererimpl/qssglightmapper.cpp
@@ -259,6 +259,7 @@ struct QSSGLightmapperPrivate
std::optional<QString> msg,
bool outputToConsole = true,
bool outputConsoleTimeRemanining = false);
+ void updateStage(const QString &newStage);
bool commitGeometry();
bool prepareLightmaps();
QVector<QVector3D> computeDirectLight(int lmIdx);
@@ -281,6 +282,8 @@ struct QSSGLightmapperPrivate
QVector3D sampleDirectLight(QVector3D worldPos, QVector3D normal, bool allLight) const;
QByteArray dilate(const QSize &pixelSize, const QByteArray &image);
+
+ QString stage = QStringLiteral("Initializing");
};
// Used to output progress ETA during baking.
@@ -2545,10 +2548,23 @@ void QSSGLightmapperPrivate::sendOutputInfo(QSSGLightmapper::BakingStatus type,
if (outputCallback) {
QVariantMap payload;
payload[QStringLiteral("status")] = (int)type;
+ payload[QStringLiteral("stage")] = stage;
payload[QStringLiteral("message")] = msg.value_or(QString());
payload[QStringLiteral("totalTimeRemaining")] = estimatedTimeRemaining;
payload[QStringLiteral("totalProgress")] = totalProgress;
- payload[QStringLiteral("totalTimeElapsed")] = totalTimer.elapsed();
+ outputCallback(payload, &bakingControl);
+ }
+}
+
+void QSSGLightmapperPrivate::updateStage(const QString &newStage)
+{
+ if (newStage == stage)
+ return;
+
+ stage = newStage;
+ if (outputCallback) {
+ QVariantMap payload;
+ payload[QStringLiteral("stage")] = stage;
outputCallback(payload, &bakingControl);
}
}
@@ -2557,9 +2573,11 @@ bool QSSGLightmapper::bake()
{
d->totalTimer.start();
+ d->updateStage(QStringLiteral("Preparing"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral("Bake starting..."));
if (!isValidSavePath(d->options.source)) {
+ d->updateStage(QStringLiteral("Failed"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral("Source path %1 is not a writable location").
arg(d->options.source));
return false;
@@ -2570,6 +2588,7 @@ bool QSSGLightmapper::bake()
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral("Total models registered: %1").arg(d->bakedLightingModels.size()));
if (d->bakedLightingModels.isEmpty()) {
+ d->updateStage(QStringLiteral("Failed"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral("No Models to bake"));
return false;
}
@@ -2577,22 +2596,28 @@ bool QSSGLightmapper::bake()
// ------------- Commit geometry -------------
if (!d->commitGeometry()) {
+ d->updateStage(QStringLiteral("Failed"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral("Baking failed"));
return false;
}
- if (d->userCancelled())
+ if (d->userCancelled()) {
+ d->updateStage(QStringLiteral("Cancelled"));
return false;
+ }
// ------------- Prepare lightmaps -------------
if (!d->prepareLightmaps()) {
+ d->updateStage(QStringLiteral("Failed"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral("Baking failed"));
return false;
}
- if (d->userCancelled())
+ if (d->userCancelled()) {
+ d->updateStage(QStringLiteral("Cancelled"));
return false;
+ }
// indirect lighting is slow, so parallelize per groups of samples,
// e.g. if sample count is 256 and workgroup size is 32, then do up to
@@ -2600,6 +2625,7 @@ bool QSSGLightmapper::bake()
// are really done concurrently that's up to the thread pool to manage)
const int wgSizePerGroup = qMax(1, d->options.indirectLightWorkgroupSize);
const int wgCount = (d->options.indirectLightSamples / wgSizePerGroup) + (d->options.indirectLightSamples % wgSizePerGroup ? 1: 0);
+
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral("Sample count: %1, Workgroup size: %2, Max bounces: %3, Multiplier: %4").
arg(d->options.indirectLightSamples).
arg(wgSizePerGroup).
@@ -2625,16 +2651,20 @@ bool QSSGLightmapper::bake()
// ------------- Store metadata -------------
+ d->updateStage(QStringLiteral("Storing Metadata"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral("Storing metadata..."));
auto writer = QSSGLightmapWriter::open(workFile);
for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
- if (d->userCancelled())
+ if (d->userCancelled()) {
+ d->updateStage(QStringLiteral("Cancelled"));
return false;
+ }
QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
if (!lm.model->hasLightmap())
continue;
if (!d->storeMetadata(lmIdx, writer)) {
+ d->updateStage(QStringLiteral("Failed"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
QStringLiteral("[%1/%2] Failed to store metadata for '%3'")
.arg(lmIdx + 1)
@@ -2648,13 +2678,16 @@ bool QSSGLightmapper::bake()
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral("Storing mask images..."));
for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
- if (d->userCancelled())
+ if (d->userCancelled()) {
+ d->updateStage(QStringLiteral("Cancelled"));
return false;
+ }
QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
if (!lm.model->hasLightmap())
continue;
if (!d->storeMaskImage(lmIdx, writer)) {
+ d->updateStage(QStringLiteral("Failed"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
QStringLiteral("[%1/%2] Failed to store mask for '%3'")
.arg(lmIdx + 1)
@@ -2666,15 +2699,20 @@ bool QSSGLightmapper::bake()
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
QStringLiteral("Took %1").arg(formatDuration(timer.restart())));
- if (d->userCancelled())
+ if (d->userCancelled()) {
+ d->updateStage(QStringLiteral("Cancelled"));
return false;
+ }
// ------------- Direct compute / store -------------
+ d->updateStage(QStringLiteral("Computing Direct Light"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral("Computing direct light..."));
for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
- if (d->userCancelled())
+ if (d->userCancelled()) {
+ d->updateStage(QStringLiteral("Cancelled"));
return false;
+ }
QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
if (!lm.model->hasLightmap())
continue;
@@ -2707,8 +2745,10 @@ bool QSSGLightmapper::bake()
}
}
- if (d->userCancelled())
+ if (d->userCancelled()) {
+ d->updateStage(QStringLiteral("Cancelled"));
return false;
+ }
// ------------- Indirect compute / store -------------
@@ -2735,12 +2775,15 @@ bool QSSGLightmapper::bake()
timeoutsSinceOutput = (timeoutsSinceOutput + 1) % consoleOutputInterval;
});
timerThread.start();
+ d->updateStage(QStringLiteral("Computing Indirect Light"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
QStringLiteral("Computing indirect light..."));
indirectTimer.start();
for (int lmIdx = 0; lmIdx < bakedLightingModelCount; ++lmIdx) {
- if (d->userCancelled())
+ if (d->userCancelled()) {
+ d->updateStage(QStringLiteral("Cancelled"));
return false;
+ }
QSSGBakedLightingModel &lm = d->bakedLightingModels[lmIdx];
if (!lm.model->hasLightmap())
continue;
@@ -2748,6 +2791,7 @@ bool QSSGLightmapper::bake()
timer.restart();
const QVector<QVector3D> indirectLight = d->computeIndirectLight(lmIdx, wgCount, wgSizePerGroup);
if (indirectLight.empty()) {
+ d->updateStage(QStringLiteral("Failed"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
QStringLiteral("[%1/%2] Failed to compute '%3'")
.arg(lmIdx + 1)
@@ -2763,10 +2807,13 @@ bool QSSGLightmapper::bake()
.arg(lm.model->lightmapKey)
.arg(formatDuration(timer.elapsed())));
- if (d->userCancelled())
+ if (d->userCancelled()) {
+ d->updateStage(QStringLiteral("Cancelled"));
return false;
+ }
if (!d->storeIndirectLightData(lmIdx, indirectLight, writer)) {
+ d->updateStage(QStringLiteral("Failed"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed,
QStringLiteral("[%1/%2] Failed to store data for '%3'")
.arg(lmIdx + 1)
@@ -2780,12 +2827,15 @@ bool QSSGLightmapper::bake()
// ------------- Store meshes -------------
if (!d->storeMeshes(writer)) {
+ d->updateStage(QStringLiteral("Failed"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral("Failed to store meshes"));
return false;
}
- if (d->userCancelled())
+ if (d->userCancelled()) {
+ d->updateStage(QStringLiteral("Cancelled"));
return false;
+ }
// ------------- Copy file from tmp -------------
@@ -2805,25 +2855,33 @@ bool QSSGLightmapper::bake()
return false;
}
- if (d->userCancelled())
+ if (d->userCancelled()) {
+ d->updateStage(QStringLiteral("Cancelled"));
return false;
+ }
// ------------- Denoising -------------
+ d->updateStage(QStringLiteral("Denoising"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral("Denoising..."));
timer.restart();
if (!d->denoiseLightmaps()) {
+ d->updateStage(QStringLiteral("Failed"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Failed, QStringLiteral("Denoising failed"));
return false;
}
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info, QStringLiteral("Took %1").arg(formatDuration(timer.elapsed())));
- if (d->userCancelled())
+ if (d->userCancelled()) {
+ d->updateStage(QStringLiteral("Cancelled"));
return false;
+ }
// -------------------------------------
d->totalProgress = 1.0;
+ d->estimatedTimeRemaining = -1;
+ d->updateStage(QStringLiteral("Done"));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Info,
QStringLiteral("Baking took %1").arg(formatDuration(d->totalTimer.elapsed())));
d->sendOutputInfo(QSSGLightmapper::BakingStatus::Complete, std::nullopt);