# Conflicts:
#	crates/ui-egui/locales/de-formats.json
#	crates/ui-egui/locales/de.json
#	crates/ui-egui/locales/es-formats.json
#	crates/ui-egui/locales/es.json
#	crates/ui-egui/locales/fr-formats.json
#	crates/ui-egui/locales/fr.json
#	crates/ui-egui/locales/ja-formats.json
#	crates/ui-egui/locales/ja.json
#	crates/ui-egui/locales/pt-br-formats.json
#	crates/ui-egui/locales/pt-br.json
#	crates/ui-egui/locales/ru-formats.json
#	crates/ui-egui/locales/ru.json
#	crates/ui-egui/locales/uk-formats.json
#	crates/ui-egui/locales/uk.json
#	crates/ui-egui/locales/zh-hans-formats.json
#	crates/ui-egui/locales/zh-hans.json
#	crates/ui-egui/locales/zh-hant-formats.json
#	crates/ui-egui/locales/zh-hant.json
#	docs/parity-checklist.md
#	docs/whats-new.md
This commit is contained in:
Brandon Thomas committed 2026-10-14 22:32:39 -04:00
commit c8c97883cd
23 files changed
+203 -24

No files matched your search

+129 -1
View File
@@ -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<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> {
@@ -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 <config>/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() {
+3 -1
View File
@@ -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}"
}
+2 -1
View File
@@ -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"
}
+3 -1
View File
@@ -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}"
}
+2 -1
View File
@@ -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"
}
+3 -1
View File
@@ -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}"
}
+2 -1
View File
@@ -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é"
}
+3 -1
View File
@@ -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}"
}
+2 -1
View File
@@ -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が終了しました"
}
+3 -1
View File
@@ -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}"
}
+2 -1
View File
@@ -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"
}
+3 -1
View File
@@ -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}"
}
+2 -1
View File
@@ -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 остановился"
}
+3 -1
View File
@@ -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}"
}
+2 -1
View File
@@ -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 зупинився"
}
+3 -1
View File
@@ -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}"
}
+2 -1
View File
@@ -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 已停止"
}
+3 -1
View File
@@ -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}"
}
+2 -1
View File
@@ -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 已停止"
}
+11
View File
@@ -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 }
}
+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` →
+2 -1
View File
@@ -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 |
+8 -4
View File
@@ -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