mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-19 02:10:25 -05:00
Merge PR #1548
# 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:
commit
c8c97883cd
23 files changed
+203
-24
No files matched your search
+129
-1
@@ -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() {
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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é"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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が終了しました"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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 остановился"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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 зупинився"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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 已停止"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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 已停止"
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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` →
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user