diff --git a/apps/lightcraft/src/main.rs b/apps/lightcraft/src/main.rs index def7d2b90..f113618ba 100644 --- a/apps/lightcraft/src/main.rs +++ b/apps/lightcraft/src/main.rs @@ -163,6 +163,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 { + 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 { + 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 { + 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 { @@ -829,7 +869,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 @@ -838,6 +878,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() @@ -875,6 +918,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 /models/sam3, or LIGHTCRAFT_SAM3_DIR // (never required: without it, AI masks offer to download it; see docs/ai-masks.md) @@ -936,6 +980,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()))); @@ -950,6 +1019,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). @@ -1152,6 +1249,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() { diff --git a/crates/ui-egui/locales/de-formats.json b/crates/ui-egui/locales/de-formats.json index 03d8bd405..be8bd041d 100644 --- a/crates/ui-egui/locales/de-formats.json +++ b/crates/ui-egui/locales/de-formats.json @@ -283,5 +283,7 @@ "{s} s of AV1 video": "{s} s AV1-Video", "Slideshow exported: {what}": "Diashow exportiert: {what}", " · {n} skipped (could not be rendered)": " · {n} übersprungen (nicht darstellbar)", - "{n} raws can now be developed from their raw data": "{n} RAW-Dateien können jetzt aus ihren Rohdaten entwickelt werden" + "{n} raws can now be developed from their raw data": "{n} RAW-Dateien können jetzt aus ihren Rohdaten entwickelt werden", + "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}" } diff --git a/crates/ui-egui/locales/de.json b/crates/ui-egui/locales/de.json index 120131bd7..64fae80ad 100644 --- a/crates/ui-egui/locales/de.json +++ b/crates/ui-egui/locales/de.json @@ -2282,5 +2282,6 @@ "Same folder as original photo": "Im selben Ordner wie das Originalfoto", "Tidying the thumbnail cache": "Miniaturcache wird aufgeräumt", "Reset all edits (Cmd+Shift+R)": "Alle Bearbeitungen zurücksetzen (⌘⇧R)", - "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "Intelligente Alben aktualisieren sich selbst: Ändern Sie ihre Regeln oder verwenden Sie Foto → Foto löschen" + "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "Intelligente Alben aktualisieren sich selbst: Ändern Sie ihre Regeln oder verwenden Sie Foto → Foto löschen", + "LightCraft stopped": "LightCraft wurde beendet" } diff --git a/crates/ui-egui/locales/es-formats.json b/crates/ui-egui/locales/es-formats.json index ab35484fd..c79604f83 100644 --- a/crates/ui-egui/locales/es-formats.json +++ b/crates/ui-egui/locales/es-formats.json @@ -283,5 +283,7 @@ "{s} s of AV1 video": "{s} s de vídeo AV1", "Slideshow exported: {what}": "Presentación exportada: {what}", " · {n} skipped (could not be rendered)": " · {n} omitidas (no se pudieron representar)", - "{n} raws can now be developed from their raw data": "{n} RAW ya se pueden revelar a partir de sus datos RAW" + "{n} raws can now be developed from their raw data": "{n} RAW ya se pueden revelar a partir de sus datos RAW", + "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}" } diff --git a/crates/ui-egui/locales/es.json b/crates/ui-egui/locales/es.json index 5b94df5e2..6af9bd359 100644 --- a/crates/ui-egui/locales/es.json +++ b/crates/ui-egui/locales/es.json @@ -2281,5 +2281,6 @@ "Same folder as original photo": "Misma carpeta que la foto original", "Tidying the thumbnail cache": "Ordenando la caché de miniaturas", "Reset all edits (Cmd+Shift+R)": "Restablecer todas las ediciones (Cmd+Mayús+R)", - "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "Los álbumes inteligentes se actualizan solos: cambie sus reglas o use Foto → Eliminar foto" + "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "Los álbumes inteligentes se actualizan solos: cambie sus reglas o use Foto → Eliminar foto", + "LightCraft stopped": "LightCraft se detuvo" } diff --git a/crates/ui-egui/locales/fr-formats.json b/crates/ui-egui/locales/fr-formats.json index d6d816fea..91a56cd1a 100644 --- a/crates/ui-egui/locales/fr-formats.json +++ b/crates/ui-egui/locales/fr-formats.json @@ -283,5 +283,7 @@ "{s} s of AV1 video": "{s} s de vidéo AV1", "Slideshow exported: {what}": "Diaporama exporté : {what}", " · {n} skipped (could not be rendered)": " · {n} ignorées (rendu impossible)", - "{n} raws can now be developed from their raw data": "{n} fichiers RAW peuvent désormais être développés à partir de leurs données RAW" + "{n} raws can now be developed from their raw data": "{n} fichiers RAW peuvent désormais être développés à partir de leurs données RAW", + "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}" } diff --git a/crates/ui-egui/locales/fr.json b/crates/ui-egui/locales/fr.json index 50b4dfb18..b5e3adcea 100644 --- a/crates/ui-egui/locales/fr.json +++ b/crates/ui-egui/locales/fr.json @@ -2279,5 +2279,6 @@ "Same folder as original photo": "Même dossier que la photo d’origine", "Tidying the thumbnail cache": "Rangement du cache des miniatures", "Reset all edits (Cmd+Shift+R)": "Réinitialiser toutes les retouches (Cmd+Maj+R)", - "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "Les albums intelligents se mettent à jour d'eux-mêmes : modifiez leurs règles ou utilisez Photo → Supprimer la photo" + "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "Les albums intelligents se mettent à jour d'eux-mêmes : modifiez leurs règles ou utilisez Photo → Supprimer la photo", + "LightCraft stopped": "LightCraft s’est arrêté" } diff --git a/crates/ui-egui/locales/ja-formats.json b/crates/ui-egui/locales/ja-formats.json index 777f7bbdc..0ab1d2958 100644 --- a/crates/ui-egui/locales/ja-formats.json +++ b/crates/ui-egui/locales/ja-formats.json @@ -283,5 +283,7 @@ "{s} s of AV1 video": "AV1 ビデオ {s} 秒", "Slideshow exported: {what}": "スライドショーを書き出しました:{what}", " · {n} skipped (could not be rendered)": " · {n} 枚をスキップ(レンダリングできません)", - "{n} raws can now be developed from their raw data": "{n}枚のRAWをRAWデータから現像できるようになりました" + "{n} raws can now be developed from their raw data": "{n}枚のRAWをRAWデータから現像できるようになりました", + "LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraftが予期せず終了しました: {e}\n\n詳しくはログファイルをご覧ください: {path}", + "LightCraft stopped unexpectedly: {e}": "LightCraftが予期せず終了しました: {e}" } diff --git a/crates/ui-egui/locales/ja.json b/crates/ui-egui/locales/ja.json index ae5c50c3e..c876b1f46 100644 --- a/crates/ui-egui/locales/ja.json +++ b/crates/ui-egui/locales/ja.json @@ -2281,5 +2281,6 @@ "Same folder as original photo": "元の写真と同じフォルダー", "Tidying the thumbnail cache": "サムネールキャッシュを整理中", "Reset all edits (Cmd+Shift+R)": "すべての編集をリセット(⌘⇧R)", - "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "スマートアルバムは自動で更新されます。ルールを変更するか、写真 → 写真を削除を使ってください" + "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "スマートアルバムは自動で更新されます。ルールを変更するか、写真 → 写真を削除を使ってください", + "LightCraft stopped": "LightCraftが終了しました" } diff --git a/crates/ui-egui/locales/pt-br-formats.json b/crates/ui-egui/locales/pt-br-formats.json index f2ddeaa51..5391dba20 100644 --- a/crates/ui-egui/locales/pt-br-formats.json +++ b/crates/ui-egui/locales/pt-br-formats.json @@ -283,5 +283,7 @@ "{s} s of AV1 video": "{s} s de vídeo AV1", "Slideshow exported: {what}": "Apresentação exportada: {what}", " · {n} skipped (could not be rendered)": " · {n} ignoradas (não foi possível renderizar)", - "{n} raws can now be developed from their raw data": "{n} RAW agora podem ser revelados a partir dos dados RAW" + "{n} raws can now be developed from their raw data": "{n} RAW agora podem ser revelados a partir dos dados RAW", + "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}" } diff --git a/crates/ui-egui/locales/pt-br.json b/crates/ui-egui/locales/pt-br.json index d466f1fff..3544ef9a9 100644 --- a/crates/ui-egui/locales/pt-br.json +++ b/crates/ui-egui/locales/pt-br.json @@ -2121,5 +2121,6 @@ "Same folder as original photo": "Mesma pasta da foto original", "Tidying the thumbnail cache": "Organizando o cache de miniaturas", "Reset all edits (Cmd+Shift+R)": "Redefinir todas as edições (Cmd+Shift+R)", - "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "Álbuns inteligentes se atualizam sozinhos: altere as regras ou use Foto → Excluir foto" + "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "Álbuns inteligentes se atualizam sozinhos: altere as regras ou use Foto → Excluir foto", + "LightCraft stopped": "O LightCraft parou" } diff --git a/crates/ui-egui/locales/ru-formats.json b/crates/ui-egui/locales/ru-formats.json index 6ec3b62a1..5f6dd0c16 100644 --- a/crates/ui-egui/locales/ru-formats.json +++ b/crates/ui-egui/locales/ru-formats.json @@ -283,5 +283,7 @@ "{s} s of AV1 video": "{s} с видео AV1", "Slideshow exported: {what}": "Слайд-шоу экспортировано: {what}", " · {n} skipped (could not be rendered)": " · пропущено: {n} (не удалось отрисовать)", - "{n} raws can now be developed from their raw data": "RAW-файлов, которые теперь можно проявлять из RAW-данных: {n}" + "{n} raws can now be developed from their raw data": "RAW-файлов, которые теперь можно проявлять из RAW-данных: {n}", + "LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft неожиданно остановился: {e}\n\nПодробности в файле журнала: {path}", + "LightCraft stopped unexpectedly: {e}": "LightCraft неожиданно остановился: {e}" } diff --git a/crates/ui-egui/locales/ru.json b/crates/ui-egui/locales/ru.json index 8b7d219ae..26db9f45f 100644 --- a/crates/ui-egui/locales/ru.json +++ b/crates/ui-egui/locales/ru.json @@ -2274,5 +2274,6 @@ "Same folder as original photo": "Та же папка, что и у оригинала", "Tidying the thumbnail cache": "Упорядочивание кэша миниатюр", "Reset all edits (Cmd+Shift+R)": "Сбросить все правки (Cmd+Shift+R)", - "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "Смарт-альбомы обновляются сами: измените их правила или выберите Фото → Удалить фото" + "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "Смарт-альбомы обновляются сами: измените их правила или выберите Фото → Удалить фото", + "LightCraft stopped": "LightCraft остановился" } diff --git a/crates/ui-egui/locales/uk-formats.json b/crates/ui-egui/locales/uk-formats.json index 50344f160..54cf6505f 100644 --- a/crates/ui-egui/locales/uk-formats.json +++ b/crates/ui-egui/locales/uk-formats.json @@ -283,5 +283,7 @@ "{s} s of AV1 video": "{s} с відео AV1", "Slideshow exported: {what}": "Слайд-шоу експортовано: {what}", " · {n} skipped (could not be rendered)": " · пропущено: {n} (не вдалося відтворити)", - "{n} raws can now be developed from their raw data": "RAW-файлів, які тепер можна проявляти з RAW-даних: {n}" + "{n} raws can now be developed from their raw data": "RAW-файлів, які тепер можна проявляти з RAW-даних: {n}", + "LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft несподівано зупинився: {e}\n\nПодробиці у файлі журналу: {path}", + "LightCraft stopped unexpectedly: {e}": "LightCraft несподівано зупинився: {e}" } diff --git a/crates/ui-egui/locales/uk.json b/crates/ui-egui/locales/uk.json index f7d5db2ab..62b5f6402 100644 --- a/crates/ui-egui/locales/uk.json +++ b/crates/ui-egui/locales/uk.json @@ -2279,5 +2279,6 @@ "Same folder as original photo": "Та сама папка, що й оригінал", "Tidying the thumbnail cache": "Упорядкування кешу мініатюр", "Reset all edits (Cmd+Shift+R)": "Скинути всі зміни (Cmd+Shift+R)", - "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "Розумні альбоми оновлюються самі: змініть їхні правила або виберіть Фото → Видалити фото" + "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "Розумні альбоми оновлюються самі: змініть їхні правила або виберіть Фото → Видалити фото", + "LightCraft stopped": "LightCraft зупинився" } diff --git a/crates/ui-egui/locales/zh-hans-formats.json b/crates/ui-egui/locales/zh-hans-formats.json index bd8e77b78..5deaf511e 100644 --- a/crates/ui-egui/locales/zh-hans-formats.json +++ b/crates/ui-egui/locales/zh-hans-formats.json @@ -283,5 +283,7 @@ "{s} s of AV1 video": "{s} 秒 AV1 视频", "Slideshow exported: {what}": "幻灯片放映已导出:{what}", " · {n} skipped (could not be rendered)": " · 已跳过 {n} 张(无法渲染)", - "{n} raws can now be developed from their raw data": "{n} 个 RAW 现在可以从 RAW 数据冲印" + "{n} raws can now be developed from their raw data": "{n} 个 RAW 现在可以从 RAW 数据冲印", + "LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft 意外停止:{e}\n\n详细信息见日志文件:{path}", + "LightCraft stopped unexpectedly: {e}": "LightCraft 意外停止:{e}" } diff --git a/crates/ui-egui/locales/zh-hans.json b/crates/ui-egui/locales/zh-hans.json index 542f8226a..a8df6403a 100644 --- a/crates/ui-egui/locales/zh-hans.json +++ b/crates/ui-egui/locales/zh-hans.json @@ -2120,5 +2120,6 @@ "Same folder as original photo": "与原始照片相同的文件夹", "Tidying the thumbnail cache": "正在整理缩略图缓存", "Reset all edits (Cmd+Shift+R)": "重置所有编辑 (Cmd+Shift+R)", - "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "智能相册会自动更新:请更改其规则,或使用“照片 → 删除照片”" + "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "智能相册会自动更新:请更改其规则,或使用“照片 → 删除照片”", + "LightCraft stopped": "LightCraft 已停止" } diff --git a/crates/ui-egui/locales/zh-hant-formats.json b/crates/ui-egui/locales/zh-hant-formats.json index 11837fb64..620bcde7c 100644 --- a/crates/ui-egui/locales/zh-hant-formats.json +++ b/crates/ui-egui/locales/zh-hant-formats.json @@ -283,5 +283,7 @@ "{s} s of AV1 video": "{s} 秒 AV1 視訊", "Slideshow exported: {what}": "幻燈片播放已匯出:{what}", " · {n} skipped (could not be rendered)": " · 已略過 {n} 張(無法算繪)", - "{n} raws can now be developed from their raw data": "{n} 個 RAW 現在可以從 RAW 資料沖印" + "{n} raws can now be developed from their raw data": "{n} 個 RAW 現在可以從 RAW 資料沖印", + "LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft 意外停止:{e}\n\n詳細資訊請見記錄檔:{path}", + "LightCraft stopped unexpectedly: {e}": "LightCraft 意外停止:{e}" } diff --git a/crates/ui-egui/locales/zh-hant.json b/crates/ui-egui/locales/zh-hant.json index 34b6bf34e..b09761c3b 100644 --- a/crates/ui-egui/locales/zh-hant.json +++ b/crates/ui-egui/locales/zh-hant.json @@ -2082,5 +2082,6 @@ "Same folder as original photo": "與原始照片相同的資料夾", "Tidying the thumbnail cache": "正在整理縮圖快取", "Reset all edits (Cmd+Shift+R)": "重設所有編輯(Cmd+Shift+R)", - "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "智慧型相簿會自動更新:請變更其規則,或使用「照片 → 刪除照片」" + "Smart albums update automatically: change their rules, or use Photo ▸ Delete Photo": "智慧型相簿會自動更新:請變更其規則,或使用「照片 → 刪除照片」", + "LightCraft stopped": "LightCraft 已停止" } diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index e54ea8026..a2cf4d1c0 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -214,6 +214,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 } } diff --git a/docs/gpu-pipeline.md b/docs/gpu-pipeline.md index 1fc80c95b..b74af85a2 100644 --- a/docs/gpu-pipeline.md +++ b/docs/gpu-pipeline.md @@ -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` → diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 42b2990e5..ef23dbe37 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -465,7 +465,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` | | @@ -843,6 +843,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove | 2026-10-11 | minor | LR-EXP-LOCATION: Export to "Same folder as original photo" (`destinationMode`), issue #956; status unchanged (✅) | | 2026-10-12 | minor | LR-IMP-CAMERA-COVERAGE: 117 Nikon and Fujifilm Adobe models verified on one CC0 file each; COOLPIX 5700 / 8400 / 8800 found decoding wrongly; status unchanged (🟡) | | 2026-10-13 | patch | LR-EXP-QUALITY: JPEG exports use Huffman tables made for each image (same pixels, 12–14 % smaller files, issue #1373); status unchanged | +| 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 | diff --git a/docs/whats-new.md b/docs/whats-new.md index 6f81c757e..62d1a3c9d 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -71,6 +71,10 @@ carries Huffman tables made for its own picture instead of the standard example tables. Every viewer opens them as before. +### 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. @@ -87,10 +91,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. @@ -187,6 +187,10 @@ and a smaller export look like the full-size result scaled down; 1:1 views and full-size exports are unchanged. Like in Lightroom, judge noise reduction at 1:1: at small sizes the noise it removes is mostly averaged away. Cached thumbnails are redrawn. +- 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