LR-BEHAV-GPU: Turn GPU rendering off after the GPU is lost while running (#1337)

Report it as LightCraft stopping instead of the window failing to open, and start on the CPU next time.
This commit is contained in:
Michael Anderson committed 2026-10-14 07:32:44 -05:00
1 parent a4b53b28cc
commit b0b86aca84
23 files changed
+204 -25

No files matched your search

+129 -1
View File
@@ -162,6 +162,46 @@ fn gpu_crash_notice(what: &str) -> String {
)
}
/// Written when the window stopped drawing after the GPU device was lost (issue #1337): the next
/// launch renders on the CPU and says why.
fn gpu_lost_marker_path() -> Option<std::path::PathBuf> {
gpu_marker_path().map(|p| p.with_file_name("gpu-lost.marker"))
}
/// Why the GPU was lost, when the GPU pipeline stopped after a device failure in this process (a
/// driver reset, a hang, the Windows watchdog): `None` for other reasons it is off, or when it
/// never failed.
fn gpu_lost_reason() -> Option<String> {
lightcraft_engine::gpu::unavailable_reason().and_then(|r| r.strip_prefix("stopped after a GPU failure: ").map(str::to_string))
}
/// Record that LightCraft stopped after the GPU was lost, for the next launch.
fn record_gpu_lost(marker: Option<&std::path::Path>, reason: &str) {
let Some(m) = marker else { return };
if let Some(d) = m.parent() {
let _ = std::fs::create_dir_all(d);
}
if let Err(e) = std::fs::write(m, format!("{reason}\n")) {
log::warn!("could not write {}: {e}", m.display());
}
}
/// A GPU loss the last launch stopped on: its notice, once (the marker is removed, except by a
/// `--memory` session, which leaves the disk as it found it).
fn gpu_lost_check_at(marker: Option<&std::path::Path>, in_memory: bool) -> Option<String> {
let m = marker?;
let reason = std::fs::read_to_string(m).ok()?;
if !in_memory {
let _ = std::fs::remove_file(m);
}
Some(format!(
"LightCraft stopped last time after the graphics driver reset the GPU ({}) while photos rendered on it, so GPU rendering \
is now off and photos render on the CPU. To try the GPU again, turn on Settings ▸ Performance ▸ Use the GPU for rendering; \
an up-to-date graphics driver may help.",
reason.trim()
))
}
/// Where the system keeps monitor profiles, for the Choose Profile dialog to start in: the user's
/// own profiles first (Linux: colord / DisplayCAL; macOS: ColorSync), then the system's.
fn display_profile_dir() -> Option<std::path::PathBuf> {
@@ -808,7 +848,7 @@ fn main() -> eframe::Result {
let library_dir = library_dir.or_else(lightcraft_engine::library::default_dir);
// GPU compute: off if the preference says so, or if the last launch died creating the device
// (issue #136) — before anything can create it
let gpu_crash = gpu_crash_check(in_memory);
let gpu_crash = gpu_crash_check(in_memory).or_else(|| gpu_lost_check_at(gpu_lost_marker_path().as_deref(), in_memory));
let gpu_on = prefs.as_ref().is_none_or(|u| u.settings.gpu) && gpu_crash.is_none();
lightcraft_engine::gpu::set_enabled(gpu_on);
// the window's renderer: probed (Windows) and with a fallback chain, so a driver that can't
@@ -817,6 +857,9 @@ fn main() -> eframe::Result {
let window_gpu::Chosen { renderer, wgpu_options, which: window_renderer, notice: window_notice } = window_gpu::choose(&win_files);
win_files.arm(window_renderer);
let win_files_app = win_files.clone();
// the window came up: a failure after this is LightCraft stopping, not a start that failed
let came_up = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let came_up_app = came_up.clone();
let options = eframe::NativeOptions {
renderer,
viewport: egui::ViewportBuilder::default()
@@ -854,6 +897,7 @@ fn main() -> eframe::Result {
Box::new(move |cc| {
// the window's renderer is up (eframe calls this after creating it)
win_files_app.started(window_renderer);
came_up_app.store(true, std::sync::atomic::Ordering::Relaxed);
let (mut session, problem) = open_session(in_memory, library_dir, seed_demo && files.is_empty());
// AI masks: the SAM 3 checkpoint (facebook/sam3) in <config>/models/sam3, or LIGHTCRAFT_SAM3_DIR
// (never required: without it, AI masks offer to download it; see docs/ai-masks.md)
@@ -915,6 +959,31 @@ fn main() -> eframe::Result {
}),
)
}));
if came_up.load(std::sync::atomic::Ordering::Relaxed) {
// the window was up: a panic that escaped it now is LightCraft stopping, typically the
// window's renderer after the graphics driver reset the GPU (issue #1337: egui-wgpu panics
// on the lost device). Not a failed start: no other renderer is tried, and the message
// says what happened.
let stopped = match started {
Ok(r) => r,
Err(_) => Err(eframe::Error::AppCreation("its window stopped drawing (the renderer panicked)".into())),
};
if let Err(e) = &stopped {
let lost = gpu_lost_reason();
if let Some(reason) = &lost
&& !in_memory
{
record_gpu_lost(gpu_lost_marker_path().as_deref(), reason);
}
// (not "app creation error: …": the app had been created)
let what = match e {
eframe::Error::AppCreation(m) => m.to_string(),
e => e.to_string(),
};
stopped_while_running(&stopped_text(&what, lost.as_deref()), log_file.as_deref());
}
return stopped;
}
// a panic that still escaped the renderer's start (the probe and error handler should
// prevent it): an error like any other failed start
let started = started.unwrap_or_else(|_| Err(eframe::Error::AppCreation("the window's renderer panicked while starting".into())));
@@ -929,6 +998,34 @@ fn main() -> eframe::Result {
started
}
/// What to tell the user when LightCraft stopped while running (`error`), and why when the GPU
/// was lost (`lost`): GPU rendering is off from the next start.
fn stopped_text(error: &str, lost: Option<&str>) -> String {
match lost {
Some(reason) => format!(
"{error}. The graphics driver reset the GPU ({reason}). LightCraft renders on the CPU from its next start; \
Settings ▸ Performance ▸ Use the GPU for rendering turns the GPU back on"
),
None => error.to_string(),
}
}
/// LightCraft stopped while its window was up: say so in the log and in a native message box
/// naming the log file (as [`startup_failed`] does for a start that failed).
fn stopped_while_running(text: &str, log_file: Option<&std::path::Path>) {
log::error!("LightCraft stopped: {text}");
if std::env::var_os("LIGHTCRAFT_NO_PREFS").is_some() {
return; // tests and scripts: no dialog to click away
}
let (title, text) = lightcraft_ui_egui::i18n::stopped_message(text, log_file.map(|p| p.display().to_string()).as_deref());
let _ = rfd::MessageDialog::new()
.set_level(rfd::MessageLevel::Error)
.set_title(title)
.set_description(text)
.set_buttons(rfd::MessageButtons::Ok)
.show();
}
/// The window could not start (e.g. no usable graphics device): say so in the log and in a native
/// message box naming the log file, instead of exiting without a trace — the release builds have
/// no console on Windows, and an app started from Finder shows no standard error (issue #260).
@@ -1131,6 +1228,37 @@ mod tests {
let _ = std::fs::remove_dir_all(&d);
}
/// Issue #1337: the window stopped drawing after the graphics driver reset the GPU during an
/// export, and the dialog said the window "could not open". The stop is recorded; the next
/// launch says why once and renders on the CPU (a `--memory` session leaves the record).
#[test]
fn a_gpu_lost_while_running_turns_the_gpu_off_next_time() {
let d = dir("gpu-lost");
let m = d.join("gpu-lost.marker");
assert_eq!(gpu_lost_check_at(Some(&m), false), None, "nothing recorded");
assert_eq!(gpu_lost_check_at(None, false), None, "no settings folder");
record_gpu_lost(Some(&m), "device lost (Unknown): Device is lost");
let notice = gpu_lost_check_at(Some(&m), true).unwrap();
assert!(
notice.contains("(device lost (Unknown): Device is lost)")
&& notice.contains("GPU rendering is now off")
&& notice.contains("Use the GPU for rendering"),
"{notice}"
);
assert!(m.exists(), "a --memory session leaves it");
assert!(gpu_lost_check_at(Some(&m), false).is_some());
assert!(!m.exists());
assert_eq!(gpu_lost_check_at(Some(&m), false), None, "said once");
// the message box: LightCraft stopped (not "could not open its window"), and why
let text = stopped_text("its window stopped drawing (the renderer panicked)", Some("device lost (Unknown): Device is lost"));
assert!(text.contains("reset the GPU (device lost (Unknown)") && text.contains("renders on the CPU from its next start"), "{text}");
assert_eq!(stopped_text("its window stopped drawing", None), "its window stopped drawing");
let (title, body) = lightcraft_ui_egui::i18n::stopped_message(&text, Some("/logs/lightcraft.log"));
assert_eq!(title, "LightCraft stopped");
assert!(body.starts_with("LightCraft stopped unexpectedly: its window stopped drawing") && body.ends_with("/logs/lightcraft.log"), "{body}");
let _ = std::fs::remove_dir_all(&d);
}
/// Issue #136: a GPU init marker left by a crashed launch is reported once and removed.
#[test]
fn gpu_crash_marker_is_reported_once() {
+3 -1
View File
@@ -247,5 +247,7 @@
"{n} photos opened for editing; each is stacked with its original": "{n} Fotos zur Bearbeitung geöffnet; jedes ist mit seinem Original gestapelt",
"{name} opened in the other editor": "{name} im anderen Editor geöffnet",
"{n} originals opened in the other editor": "{n} Originale im anderen Editor geöffnet",
"· {in_source} identical copies": "· {in_source} identische Kopien"
"· {in_source} identical copies": "· {in_source} identische Kopien",
"LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft wurde unerwartet beendet: {e}\n\nEinzelheiten stehen in der Protokolldatei: {path}",
"LightCraft stopped unexpectedly: {e}": "LightCraft wurde unerwartet beendet: {e}"
}
+2 -1
View File
@@ -2118,5 +2118,6 @@
"Adding to Apple Photos": "Wird zu Apple Fotos hinzugefügt",
"No graphics processor found: LightCraft renders on the CPU, which is much slower. Details in Help → System Info.": "Kein Grafikprozessor gefunden: LightCraft rendert auf der CPU, was viel langsamer ist. Details unter Hilfe → Systeminformationen.",
"On NixOS, start it from `nix develop`.": "Unter NixOS starten Sie es aus `nix develop`.",
"Files with the same content as another file in this import: only the first is imported.": "Dateien mit demselben Inhalt wie eine andere Datei dieses Imports: Nur die erste wird importiert."
"Files with the same content as another file in this import: only the first is imported.": "Dateien mit demselben Inhalt wie eine andere Datei dieses Imports: Nur die erste wird importiert.",
"LightCraft stopped": "LightCraft wurde beendet"
}
+3 -1
View File
@@ -247,5 +247,7 @@
"{n} photos opened for editing; each is stacked with its original": "{n} fotos abiertas para editar; cada una está apilada con su original",
"{name} opened in the other editor": "{name} abierto en el otro editor",
"{n} originals opened in the other editor": "{n} originales abiertos en el otro editor",
"· {in_source} identical copies": "· {in_source} copias idénticas"
"· {in_source} identical copies": "· {in_source} copias idénticas",
"LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft se detuvo inesperadamente: {e}\n\nLos detalles están en el archivo de registro: {path}",
"LightCraft stopped unexpectedly: {e}": "LightCraft se detuvo inesperadamente: {e}"
}
+2 -1
View File
@@ -1995,5 +1995,6 @@
"Adding to Apple Photos": "Añadiendo a Fotos de Apple",
"No graphics processor found: LightCraft renders on the CPU, which is much slower. Details in Help → System Info.": "No se encontró ningún procesador gráfico: LightCraft renderiza en la CPU, lo que es mucho más lento. Detalles en Ayuda → Información del sistema.",
"On NixOS, start it from `nix develop`.": "En NixOS, inícielo desde `nix develop`.",
"Files with the same content as another file in this import: only the first is imported.": "Archivos con el mismo contenido que otro archivo de esta importación: solo se importa el primero."
"Files with the same content as another file in this import: only the first is imported.": "Archivos con el mismo contenido que otro archivo de esta importación: solo se importa el primero.",
"LightCraft stopped": "LightCraft se detuvo"
}
+3 -1
View File
@@ -247,5 +247,7 @@
"{n} photos opened for editing; each is stacked with its original": "{n} photos ouvertes pour modification ; chacune est empilée avec son original",
"{name} opened in the other editor": "{name} ouvert dans l’autre éditeur",
"{n} originals opened in the other editor": "{n} originaux ouverts dans l’autre éditeur",
"· {in_source} identical copies": "· {in_source} copies identiques"
"· {in_source} identical copies": "· {in_source} copies identiques",
"LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft s’est arrêté de façon inattendue : {e}\n\nConsultez le fichier journal pour plus de détails : {path}",
"LightCraft stopped unexpectedly: {e}": "LightCraft s’est arrêté de façon inattendue : {e}"
}
+2 -1
View File
@@ -1957,5 +1957,6 @@
"Adding to Apple Photos": "Ajout à Apple Photos",
"No graphics processor found: LightCraft renders on the CPU, which is much slower. Details in Help → System Info.": "Aucun processeur graphique trouvé : LightCraft effectue le rendu sur le processeur, ce qui est beaucoup plus lent. Détails dans Aide → Informations système.",
"On NixOS, start it from `nix develop`.": "Sous NixOS, lancez-le depuis `nix develop`.",
"Files with the same content as another file in this import: only the first is imported.": "Fichiers au contenu identique à un autre fichier de cette importation : seul le premier est importé."
"Files with the same content as another file in this import: only the first is imported.": "Fichiers au contenu identique à un autre fichier de cette importation : seul le premier est importé.",
"LightCraft stopped": "LightCraft s’est arrêté"
}
+3 -1
View File
@@ -247,5 +247,7 @@
"{n} photos opened for editing; each is stacked with its original": "{n}枚の写真を編集用に開きました。それぞれ元の写真とスタックされています",
"{name} opened in the other editor": "{name}を別のエディタで開きました",
"{n} originals opened in the other editor": "{n}個の元ファイルを別のエディタで開きました",
"· {in_source} identical copies": "・同一のコピー {in_source} 件"
"· {in_source} identical copies": "・同一のコピー {in_source} 件",
"LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraftが予期せず終了しました: {e}\n\n詳しくはログファイルをご覧ください: {path}",
"LightCraft stopped unexpectedly: {e}": "LightCraftが予期せず終了しました: {e}"
}
+2 -1
View File
@@ -1961,5 +1961,6 @@
"Adding to Apple Photos": "Appleの写真に追加中",
"No graphics processor found: LightCraft renders on the CPU, which is much slower. Details in Help → System Info.": "グラフィックプロセッサが見つかりません。LightCraftはCPUで描画するため、かなり遅くなります。詳細は「ヘルプ → システム情報」を参照してください。",
"On NixOS, start it from `nix develop`.": "NixOSでは `nix develop` から起動してください。",
"Files with the same content as another file in this import: only the first is imported.": "この読み込み内の別のファイルと内容が同じファイルです。最初のファイルだけが読み込まれます。"
"Files with the same content as another file in this import: only the first is imported.": "この読み込み内の別のファイルと内容が同じファイルです。最初のファイルだけが読み込まれます。",
"LightCraft stopped": "LightCraftが終了しました"
}
+3 -1
View File
@@ -247,5 +247,7 @@
"{n} photos opened for editing; each is stacked with its original": "{n} fotos abertas para edição; cada uma está empilhada com a original",
"{name} opened in the other editor": "{name} aberto no outro editor",
"{n} originals opened in the other editor": "{n} originais abertos no outro editor",
"· {in_source} identical copies": "· {in_source} cópias idênticas"
"· {in_source} identical copies": "· {in_source} cópias idênticas",
"LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "O LightCraft parou inesperadamente: {e}\n\nO arquivo de log tem os detalhes: {path}",
"LightCraft stopped unexpectedly: {e}": "O LightCraft parou inesperadamente: {e}"
}
+2 -1
View File
@@ -1998,5 +1998,6 @@
"Recognition models": "Modelos de reconhecimento",
"No graphics processor found: LightCraft renders on the CPU, which is much slower. Details in Help → System Info.": "Nenhum processador gráfico encontrado: o LightCraft renderiza na CPU, o que é muito mais lento. Detalhes em Ajuda → Informações do sistema.",
"On NixOS, start it from `nix develop`.": "No NixOS, inicie-o a partir de `nix develop`.",
"Files with the same content as another file in this import: only the first is imported.": "Arquivos com o mesmo conteúdo de outro arquivo desta importação: apenas o primeiro é importado."
"Files with the same content as another file in this import: only the first is imported.": "Arquivos com o mesmo conteúdo de outro arquivo desta importação: apenas o primeiro é importado.",
"LightCraft stopped": "O LightCraft parou"
}
+3 -1
View File
@@ -247,5 +247,7 @@
"{n} photos opened for editing; each is stacked with its original": "Открыто для редактирования фото: {n}; каждое объединено в стопку с оригиналом",
"{name} opened in the other editor": "{name} открыт в другом редакторе",
"{n} originals opened in the other editor": "Открыто оригиналов в другом редакторе: {n}",
"· {in_source} identical copies": "· идентичных копий: {in_source}"
"· {in_source} identical copies": "· идентичных копий: {in_source}",
"LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft неожиданно остановился: {e}\n\nПодробности в файле журнала: {path}",
"LightCraft stopped unexpectedly: {e}": "LightCraft неожиданно остановился: {e}"
}
+2 -1
View File
@@ -2151,5 +2151,6 @@
"Adding to Apple Photos": "Добавление в Apple Фото",
"No graphics processor found: LightCraft renders on the CPU, which is much slower. Details in Help → System Info.": "Графический процессор не найден: LightCraft выполняет рендеринг на CPU, а это намного медленнее. Подробности: Справка → Сведения о системе.",
"On NixOS, start it from `nix develop`.": "В NixOS запускайте его из `nix develop`.",
"Files with the same content as another file in this import: only the first is imported.": "Файлы с тем же содержимым, что и другой файл этого импорта: импортируется только первый."
"Files with the same content as another file in this import: only the first is imported.": "Файлы с тем же содержимым, что и другой файл этого импорта: импортируется только первый.",
"LightCraft stopped": "LightCraft остановился"
}
+3 -1
View File
@@ -247,5 +247,7 @@
"{n} photos opened for editing; each is stacked with its original": "Відкрито для редагування фото: {n}; кожне об’єднано в стос з оригіналом",
"{name} opened in the other editor": "{name} відкрито в іншому редакторі",
"{n} originals opened in the other editor": "Відкрито оригіналів в іншому редакторі: {n}",
"· {in_source} identical copies": "· ідентичних копій: {in_source}"
"· {in_source} identical copies": "· ідентичних копій: {in_source}",
"LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft несподівано зупинився: {e}\n\nПодробиці у файлі журналу: {path}",
"LightCraft stopped unexpectedly: {e}": "LightCraft несподівано зупинився: {e}"
}
+2 -1
View File
@@ -2151,5 +2151,6 @@
"Adding to Apple Photos": "Додавання до Apple Фото",
"No graphics processor found: LightCraft renders on the CPU, which is much slower. Details in Help → System Info.": "Графічний процесор не знайдено: LightCraft виконує рендеринг на ЦП, а це набагато повільніше. Докладніше: Довідка → Інформація про систему.",
"On NixOS, start it from `nix develop`.": "У NixOS запускайте його з `nix develop`.",
"Files with the same content as another file in this import: only the first is imported.": "Файли з тим самим вмістом, що й інший файл цього імпорту: імпортується лише перший."
"Files with the same content as another file in this import: only the first is imported.": "Файли з тим самим вмістом, що й інший файл цього імпорту: імпортується лише перший.",
"LightCraft stopped": "LightCraft зупинився"
}
+3 -1
View File
@@ -247,5 +247,7 @@
"{n} photos opened for editing; each is stacked with its original": "已打开 {n} 张照片进行编辑;每张都与其原片堆叠在一起",
"{name} opened in the other editor": "已在其他编辑器中打开 {name}",
"{n} originals opened in the other editor": "已在其他编辑器中打开 {n} 个原始文件",
"· {in_source} identical copies": "· {in_source} 个相同副本"
"· {in_source} identical copies": "· {in_source} 个相同副本",
"LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft 意外停止:{e}\n\n详细信息见日志文件:{path}",
"LightCraft stopped unexpectedly: {e}": "LightCraft 意外停止:{e}"
}
+2 -1
View File
@@ -1997,5 +1997,6 @@
"Adding to Apple Photos": "正在添加到 Apple 照片",
"No graphics processor found: LightCraft renders on the CPU, which is much slower. Details in Help → System Info.": "未找到图形处理器:LightCraft 改用 CPU 渲染,速度会慢很多。详情见“帮助 → 系统信息”。",
"On NixOS, start it from `nix develop`.": "在 NixOS 上,请从 `nix develop` 启动。",
"Files with the same content as another file in this import: only the first is imported.": "与本次导入中另一个文件内容相同的文件:只导入第一个。"
"Files with the same content as another file in this import: only the first is imported.": "与本次导入中另一个文件内容相同的文件:只导入第一个。",
"LightCraft stopped": "LightCraft 已停止"
}
+3 -1
View File
@@ -247,5 +247,7 @@
"{n} photos opened for editing; each is stacked with its original": "已開啟 {n} 張相片進行編輯;每張都與其原始相片堆疊在一起",
"{name} opened in the other editor": "已在其他編輯器中開啟 {name}",
"{n} originals opened in the other editor": "已在其他編輯器中開啟 {n} 個原始檔案",
"· {in_source} identical copies": "· {in_source} 個相同副本"
"· {in_source} identical copies": "· {in_source} 個相同副本",
"LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft 意外停止:{e}\n\n詳細資訊請見記錄檔:{path}",
"LightCraft stopped unexpectedly: {e}": "LightCraft 意外停止:{e}"
}
+2 -1
View File
@@ -1959,5 +1959,6 @@
"Adding to Apple Photos": "正在加入 Apple 照片",
"No graphics processor found: LightCraft renders on the CPU, which is much slower. Details in Help → System Info.": "找不到圖形處理器:LightCraft 改用 CPU 算繪,速度會慢很多。詳情請見「說明 → 系統資訊」。",
"On NixOS, start it from `nix develop`.": "在 NixOS 上,請從 `nix develop` 啟動。",
"Files with the same content as another file in this import: only the first is imported.": "與本次匯入中另一個檔案內容相同的檔案:只匯入第一個。"
"Files with the same content as another file in this import: only the first is imported.": "與本次匯入中另一個檔案內容相同的檔案:只匯入第一個。",
"LightCraft stopped": "LightCraft 已停止"
}
+11
View File
@@ -212,6 +212,17 @@ pub fn startup_failed_message(error: &str, log_file: Option<&str>) -> (String, S
(tr("LightCraft could not start").to_string(), text)
}
/// The title and text of the message box shown when LightCraft stopped while its window was up
/// (issue #1337: the window's renderer after the graphics driver reset the GPU), in the UI
/// language: what happened and, when there is one, the log file to look in.
pub fn stopped_message(error: &str, log_file: Option<&str>) -> (String, String) {
let text = match log_file {
Some(path) => tr_format!("LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}", e = error, path = path),
None => tr_format!("LightCraft stopped unexpectedly: {e}", e = error),
};
(tr("LightCraft stopped").to_string(), text)
}
pub fn builtin_label(source: &str, builtin: bool) -> &str {
if builtin { tr(source) } else { source }
}
+8
View File
@@ -105,6 +105,14 @@ rendering; `set VK_LOADER_DRIVERS_DISABLE=*igvk64*` (Vulkan loader) hides a spec
from every program started with it. Help ▸ System Info and `app.gpu` show the adapter and backend in
use (e.g. `Intel(R) UHD Graphics 630 (Dx12)`).
**LightCraft stops during an export or a render; the log says "device lost" (Windows, issue #1337).**
When the graphics driver resets the GPU (the Windows watchdog, a driver hang: DXGI `0x887A0005`, "GPU
device instance has been suspended"), renders fall back to the CPU, but the window's renderer (egui-wgpu)
panics on the lost device and LightCraft stops. It says so ("LightCraft stopped unexpectedly", not "could
not open its window") and records it in `gpu-lost.marker` in the settings folder: the next start turns
*Use the GPU for rendering* off and says why once. Turn it back on in Settings ▸ Performance after a
driver update.
**Window never appears, "not responding" (Windows, NVIDIA; issue #374).** With *Shader Cache Size*
set to **Disabled** in NVIDIA Control Panel (Manage 3D settings → Global Settings), NVIDIA's driver
walks the whole system drive while the window's GPU device is created (`D3D12CreateDevice` →
+3 -2
View File
@@ -1,6 +1,6 @@
# Lightroom parity checklist
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (integration batch 35: KEYC-SELECTONLYACTIVE, Select Only Active Photo ⌘⇧D; LR-BEHAV-AUTOSAVE, damaged snapshots repaired on open; LR-EDIT-OPTICS-PROFILE, Nikon Z 6II / Z 8 / Z f in-file distortion; LRC-LIB-CATALOG-IMPORT, WAL-only catalogs open; LR-EXP-DIM, enlargements stop at 512 MP; LR-IMP-CAMERA-COVERAGE, Canon EOS RP CR3 verified; model folder retry) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (LR-BEHAV-GPU: a GPU lost while running turns GPU rendering off for the next start (#1337); integration batch 35: KEYC-SELECTONLYACTIVE, Select Only Active Photo ⌘⇧D; LR-BEHAV-AUTOSAVE, damaged snapshots repaired on open; LR-EDIT-OPTICS-PROFILE, Nikon Z 6II / Z 8 / Z f in-file distortion; LRC-LIB-CATALOG-IMPORT, WAL-only catalogs open; LR-EXP-DIM, enlargements stop at 512 MP; LR-IMP-CAMERA-COVERAGE, Canon EOS RP CR3 verified; model folder retry) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
The measured, row-by-row checklist: one row per Lightroom feature, menu item and shortcut, what we
have and what is missing. It answers *does the feature exist?* (feature breadth). Whether it is good
@@ -459,7 +459,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts).
| LR-BEHAV-BG-TASKS | Background tasks | P0 | ✅ | `cmd:activity.list`, `cmd:activity.cancel`, `crates/engine/src/activity.rs`, `crates/ui-egui/src/panels/activity.rs`, `docs/background-tasks.md`, `crates/preview/src/lib.rs` (`JobPool`) | renders off the UI thread; exports run several photos of a batch side by side (`export_parallelism`, memory-gated; files, names and order as one at a time, #496); one activity stack in the top-left corner under the top bar, as in Lightroom Classic (issue #345): imports, folder scans, exports (and the contact sheet PDF), Synchronize Folder (its scan and its run), Build / Smart Previews, Lightroom catalog inspect / import, Photo Merge, model downloads (SAM 3, face, AI Denoise), the face scan, AI Denoise and Find Missing Photos each show a row (name, progress bar or a sweep while the amount isn't known, count and current item) once 0.5 s old, three rows then "+N more"; ✕ cancels the jobs that can stop ("Stopping…" until they do); quitting asks while a cancellable task runs; agents and `ui.inspect` (`activity`) see the same list. No pause (Lightroom can pause some tasks) |
| LR-BEHAV-OFFLINE | Offline editing | P1 | ✅ | | local-first: everything works offline |
| LR-BEHAV-WEB-SAFETY | Library safety in the browser build | P1 | 🟡 | `cmd:file.backupLibrary`, `cmd:file.restoreLibrary`, `apps/lightcraft-web/src/safety.rs`, `apps/lightcraft-web/src/backup.rs`, `apps/lightcraft-web/src/files.rs` | web only (experimental): Back Up Library (zip of the catalog and every stored original) and a non-destructive Restore; failed saves show as unsaved and are retried; a photo that can't be stored isn't added; one tab per library (Web Locks); notices for storage that isn't persistent and for a library that can't open; `?reset` asks first; a panic shows a message. Missing: `?safe` start, deleting originals of removed photos, zip64 (backups > 4 GB) |
| LR-BEHAV-GPU | GPU acceleration | P0 | ✅ | `crates/gpu/src/render.rs`, `cmd:app.gpu`, `docs/gpu-pipeline.md`, `crates/ui-egui/src/panels/notices.rs` | CPU fallback per render on device limits, out of memory, device errors / loss, incomplete or black results (`perf.gpuReason`, `perf.gpuFallback`; each render reports its own reason — `lightcraft_gpu::try_render`, `gpuFallback` per exported file — issue #675); short submissions for slow iGPUs; backend choice (DX12 only on Windows, `LIGHTCRAFT_GPU_BACKEND` / `WGPU_BACKEND`), device created after the window opens, crash sentinel (issue #136); quitting waits (up to 2 s) for renders in flight and starts no new GPU work, so none is inside the driver when the process exits (`crates/gpu/src/exit.rs`, issue #620; seen crashing only on Linux + NVIDIA, other drivers unverified); with GPU rendering on and no adapter found at all (e.g. no Vulkan loader on NixOS outside `nix develop`), the desktop app says once per session that it renders on the CPU and points to Help ▸ System Info (`gpu_notice`; not when the GPU is turned off or only a software adapter exists); no tiling beyond the buffer limit (CPU) |
| LR-BEHAV-GPU | GPU acceleration | P0 | ✅ | `crates/gpu/src/render.rs`, `cmd:app.gpu`, `docs/gpu-pipeline.md`, `crates/ui-egui/src/panels/notices.rs` | CPU fallback per render on device limits, out of memory, device errors / loss, incomplete or black results (`perf.gpuReason`, `perf.gpuFallback`; each render reports its own reason — `lightcraft_gpu::try_render`, `gpuFallback` per exported file — issue #675); short submissions for slow iGPUs; backend choice (DX12 only on Windows, `LIGHTCRAFT_GPU_BACKEND` / `WGPU_BACKEND`), device created after the window opens, crash sentinel (issue #136); a GPU lost while LightCraft runs (driver reset) stops it with that message and turns GPU rendering off for the next start, which says why (issue #1337; the window's renderer can't survive the lost device); quitting waits (up to 2 s) for renders in flight and starts no new GPU work, so none is inside the driver when the process exits (`crates/gpu/src/exit.rs`, issue #620; seen crashing only on Linux + NVIDIA, other drivers unverified); with GPU rendering on and no adapter found at all (e.g. no Vulkan loader on NixOS outside `nix develop`), the desktop app says once per session that it renders on the CPU and points to Help ▸ System Info (`gpu_notice`; not when the GPU is turned off or only a software adapter exists); no tiling beyond the buffer limit (CPU) |
| LR-BEHAV-RENDER-FIDELITY | Rendering matches Lightroom | P1 | 🟡 | `crates/pipeline/src/lib.rs`, `crates/pipeline/src/tone.rs` | every slider exists and works, but the character of the result (tone curve shape, highlight recovery, texture / clarity / dehaze, noise reduction, sharpening, default look) is tuned by eye; there is no systematic side-by-side comparison against Lightroom on the same CC0 raws. Needs a fidelity suite: Lightroom reference renders kept only in the local `plan/` (never committed), compared per slider and preset with a perceptual metric. Tuning that changes how existing settings render ships as a new process version (LRC-DEV-PROCESS, `docs/process-versions.md`) |
| LR-BEHAV-DRAGDROP | Drag and drop | P1 | ✅ | `crates/ui-egui/src/lib.rs`, `crates/ui-egui/src/panels/grid.rs` (`drag_feedback`), `crates/ui-egui/src/panels/left.rs` (`drop_target`, `album_drag_over`, `top_level_drop_target`, `drag_auto_scroll`) | files → app (import); grid photos → an album row (adds the selection, with a count badge while dragging); sidebar album / folder rows → a folder (moves it; its name follows the pointer); a drag of albums or grid photos near the top or bottom edge of the sidebar scrolls it (faster nearer the edge, only while the pointer is over the sidebar) |
| LR-BEHAV-TOAST | Toast notifications | P1 | ✅ | `crates/ui-egui/src/panels/mod.rs` | |
@@ -815,6 +815,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove
| 2026-10-13 | patch | LR-BEHAV-AUTOSAVE: a damaged snapshot that still parses is repaired on open (ids, stacks, folder loops, id counters) instead of panicking or hanging; status unchanged |
| 2026-10-13 | minor | LR-EDIT-OPTICS-PROFILE: Nikon Z 6II / Z 8 / Z f NEFs use the camera's in-file distortion correction (NikonNEFInfo); status unchanged (🟡) |
| 2026-10-11 | minor | Model setup folder action, opener error and retry (LR-MASK-OBJECTS). |
| 2026-10-13 | patch | LR-BEHAV-GPU: a GPU lost while LightCraft runs (driver reset) is reported as a stop, not a failed start, and the next start renders on the CPU and says why (issue #1337); status unchanged |
| 2026-10-11 | minor | KEY-SLIDER-TYPE: honor immediate Return/Escape and keep typed keys in the value editor; add regression references |
| 2026-10-11 | minor | LR-PROF-CAMERACOLOR: raws with a black-and-white camera JPEG take its tone curve (issue #577) |
| 2026-10-11 | minor | LR-VIEW-ZOOM and LR-VIEW-COMPARE: Compare and Reference are sharp at 1:1 (zoom windows per pane, two originals kept decoded while they are open); statuses unchanged |
+8 -4
View File
@@ -2,6 +2,10 @@
## 2026-10-14
### Reliability
- A photo whose Defringe amount was stored far past the sliders' 20 (in a damaged catalog or sidecar) no longer
crashes LightCraft each time it renders: the amount acts as 20.
### Hard-edged Heal and Clone spots
- A spot with Feather at 0 no longer draws black pixels exactly on its radius. This also fixes spots at 0% Opacity
changing pixels along their edge. The hard edge now leaves pixels on the radius unchanged.
@@ -18,10 +22,6 @@
models in a `LightCraftData` folder beside it instead of the per-user settings folder (issue #1200), as
PhotoCraft does. If that folder can't be written, the usual folder is used and the log says why.
### Reliability
- A photo whose Defringe amount was stored far past the sliders' 20 (in a damaged catalog or sidecar) no longer
crashes LightCraft each time it renders: the amount acts as 20.
### Reliability
- Importing a preset zip that unpacks to far more than any preset (a "zip bomb") is refused with a message naming
the file, instead of asking for gigabytes of memory, which could end LightCraft.
@@ -108,6 +108,10 @@
- An export enlarged far past the photo's size (20000 typed for 2000 with Don't Enlarge off) no longer ends
LightCraft: an enlargement stops at 512 megapixels, as in Lightroom, and one that needs more memory than the
computer has is refused with the size it would take, before it starts.
- When the graphics driver resets the GPU while LightCraft is exporting or rendering (issue #1337, seen on Windows
with NVIDIA), LightCraft still stops, because its window can't keep drawing on the lost device, but it now says
"LightCraft stopped unexpectedly" and why, instead of "could not open its window", and the next start renders
on the CPU and explains how to turn the GPU back on.
### Slider drags on large raws
- Dragging a slider no longer re-renders the edited photo's filmstrip thumbnail on every frame; it follows once you