Add opt-in Windows VRR flicker workaround

Add a restart-required setting that opts Windows DX12 into DirectComposition to help prevent G-SYNC/FreeSync flicker. Keep other renderers unchanged, and note that hardware verification is pending.
This commit is contained in:
Kaan
2026-10-20 23:45:57 +03:00
parent 5fe74557ec
commit 520d0917c0
20 changed files with 82 additions and 25 deletions
+5 -1
View File
@@ -916,8 +916,11 @@ fn main() -> eframe::Result {
lightcraft_engine::gpu::set_enabled(gpu_on);
// the window's renderer: probed (Windows) and with a fallback chain, so a driver that can't
// build egui's pipeline never kills the start-up
let compose_window =
prefs.as_ref().map_or_else(|| lightcraft_ui_egui::state::AppSettings::default().compose_window, |ui| ui.settings.compose_window)
&& std::env::var_os("WGPU_BACKEND").is_none();
let win_files = std::sync::Arc::new(window_files(in_memory));
let window_gpu::Chosen { renderer, wgpu_options, which: window_renderer, notice: window_notice } = window_gpu::choose(&win_files);
let window_gpu::Chosen { renderer, wgpu_options, which: window_renderer, notice: window_notice } = window_gpu::choose(&win_files, compose_window);
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
@@ -930,6 +933,7 @@ fn main() -> eframe::Result {
viewport: as_remembered(egui::ViewportBuilder::default().with_title("LightCraft"), opens_as)
.with_min_inner_size(lightcraft_ui_egui::window_place::MIN_SIZE)
.with_drag_and_drop(true)
.with_transparent(window_gpu::composes_window(compose_window, window_renderer))
.with_fullsize_content_view(true)
.with_titlebar_shown(false)
.with_title_shown(false)
+37 -6
View File
@@ -85,7 +85,7 @@ impl Files {
}
/// The renderer for this start: see the module docs.
pub fn choose(files: &Files) -> Chosen {
pub fn choose(files: &Files, compose_window: bool) -> Chosen {
let forced =
backend::forced_window_renderer(std::env::var("LIGHTCRAFT_GPU_BACKEND").ok().as_deref(), std::env::var("WGPU_BACKEND").ok().as_deref());
let failed = files.take_failed();
@@ -96,7 +96,7 @@ pub fn choose(files: &Files) -> Chosen {
let mut notice = failed.map(|f| format!("The window could not start with its {} renderer last time.", f.name()));
let mut problems = Vec::new();
let chosen = if cfg!(windows) {
pick_probed(&order, &mut problems)
pick_probed(&order, &mut problems, compose_window)
} else if forced == Some(WindowRenderer::Glow) || (forced.is_none() && failed.is_some_and(|f| f != WindowRenderer::Glow)) {
// elsewhere eframe's own wgpu set-up stays the default; glow after a failed start
Some((WindowRenderer::Glow, None))
@@ -147,12 +147,12 @@ pub fn base_options() -> WgpuConfiguration {
}
/// The first renderer of `order` that probes cleanly (glow is taken without a probe).
fn pick_probed(order: &[WindowRenderer], problems: &mut Vec<String>) -> Option<(WindowRenderer, Option<WgpuSetupExisting>)> {
fn pick_probed(order: &[WindowRenderer], problems: &mut Vec<String>, compose_window: bool) -> Option<(WindowRenderer, Option<WgpuSetupExisting>)> {
for &r in order {
match r {
WindowRenderer::Glow => return Some((r, None)),
WindowRenderer::Wgpu(b) => {
let probed = std::panic::catch_unwind(|| probe(b)).unwrap_or_else(|_| Err("panicked".into()));
let probed = std::panic::catch_unwind(|| probe(b, compose_window)).unwrap_or_else(|_| Err("panicked".into()));
match probed {
Ok(e) => return Some((r, Some(e))),
Err(e) => {
@@ -167,11 +167,11 @@ fn pick_probed(order: &[WindowRenderer], problems: &mut Vec<String>) -> Option<(
}
/// Create a device on `backends` and build egui's pipeline on it; any wgpu error is returned.
fn probe(backends: Backends) -> Result<WgpuSetupExisting, String> {
fn probe(backends: Backends, compose_window: bool) -> Result<WgpuSetupExisting, String> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends,
flags: backend::instance_flags_from_env(wgpu::InstanceFlags::from_build_config().with_env()),
backend_options: backend::backend_options(),
backend_options: presentation_options(backends, compose_window),
memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
display: None,
});
@@ -314,3 +314,34 @@ mod tests {
let _ = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl("not wgsl".into()) });
}
}
/// Only DX12 windows use the compositor; fallback renderers retain their normal presentation.
/// Adapted from kmnkv1's PhotoCraft PR: https://github.com/storytold/photocraft/pull/7035.
pub fn composes_window(enabled: bool, renderer: WindowRenderer) -> bool {
cfg!(target_os = "windows") && enabled && renderer == WindowRenderer::Wgpu(Backends::DX12)
}
fn presentation_options(backends: Backends, enabled: bool) -> wgpu::BackendOptions {
let mut options = backend::backend_options();
if composes_window(enabled, WindowRenderer::Wgpu(backends)) {
options.dx12.presentation_system = wgpu::Dx12SwapchainKind::DxgiFromVisual;
}
options
}
#[test]
fn composition_only_changes_windows_dx12_presentation() {
let legacy: lightcraft_ui_egui::state::AppSettings = serde_json::from_str("{}").unwrap();
assert!(!legacy.compose_window, "existing settings keep the workaround off until opted in");
assert!(!lightcraft_ui_egui::state::AppSettings::default().compose_window);
let enabled: lightcraft_ui_egui::state::AppSettings = serde_json::from_str(r#"{"composeWindow":true}"#).unwrap();
let saved = serde_json::to_value(&enabled).unwrap();
assert_eq!(saved["composeWindow"], true);
for backend in [Backends::DX12, Backends::VULKAN, Backends::GL] {
let wanted = cfg!(target_os = "windows") && backend == Backends::DX12;
assert_eq!(composes_window(true, WindowRenderer::Wgpu(backend)), wanted);
assert_eq!(presentation_options(backend, true).dx12.presentation_system == wgpu::Dx12SwapchainKind::DxgiFromVisual, wanted);
assert!(!composes_window(false, WindowRenderer::Wgpu(backend)));
}
assert!(!composes_window(true, WindowRenderer::Glow));
}
+1 -1
View File
@@ -242,7 +242,7 @@ impl Store for MemStore {
}
}
#[cfg(test)]
#[cfg(all(test, unix))]
mod tests_staging {
use super::*;
+2 -1
View File
@@ -2063,5 +2063,6 @@
"Open Settings": "Otevřít nastavení",
"Select none": "Zrušit celý výběr",
"Select all": "Vybrat vše",
"Visualize HDR": "Zobrazit rozsah HDR"
"Visualize HDR": "Zobrazit rozsah HDR",
"Prevent G-SYNC/FreeSync flicker (applies at next launch)": "Zabránit blikání G-SYNC/FreeSync (platí po dalším spuštění)"
}
+2 -1
View File
@@ -2341,5 +2341,6 @@
"Checking the GPU…": "GPU wird geprüft …",
"CPU": "CPU",
"A face model uses the GPU only when a check shows it gives the same results faster.": "Ein Gesichtsmodell nutzt die GPU nur, wenn eine Prüfung zeigt, dass sie dieselben Ergebnisse schneller liefert.",
"Checking the GPU for faces": "GPU für Gesichter wird geprüft"
"Checking the GPU for faces": "GPU für Gesichter wird geprüft",
"Prevent G-SYNC/FreeSync flicker (applies at next launch)": "G-SYNC-/FreeSync-Flimmern verhindern (gilt ab dem nächsten Start)"
}
+2 -1
View File
@@ -2340,5 +2340,6 @@
"Checking the GPU…": "Comprobando la GPU…",
"CPU": "CPU",
"A face model uses the GPU only when a check shows it gives the same results faster.": "Un modelo de caras usa la GPU solo cuando una comprobación muestra que da los mismos resultados más rápido.",
"Checking the GPU for faces": "Comprobando la GPU para las caras"
"Checking the GPU for faces": "Comprobando la GPU para las caras",
"Prevent G-SYNC/FreeSync flicker (applies at next launch)": "Evitar el parpadeo de G-SYNC/FreeSync (se aplica al reiniciar)"
}
+2 -1
View File
@@ -2338,5 +2338,6 @@
"Checking the GPU…": "Vérification du GPU…",
"CPU": "Processeur",
"A face model uses the GPU only when a check shows it gives the same results faster.": "Un modèle de visages n’utilise le GPU que si une vérification montre qu’il donne les mêmes résultats plus vite.",
"Checking the GPU for faces": "Vérification du GPU pour les visages"
"Checking the GPU for faces": "Vérification du GPU pour les visages",
"Prevent G-SYNC/FreeSync flicker (applies at next launch)": "Empêcher le scintillement G-SYNC/FreeSync (au prochain démarrage)"
}
+2 -1
View File
@@ -1990,5 +1990,6 @@
"Test Face Model": "Testa il modello dei volti",
"This build cannot run recognition models: they can be added and chosen, not used.": "Questa versione non può eseguire modelli di riconoscimento: si possono aggiungere e scegliere, non usare.",
"This build has nowhere to keep face models, so the model list is not available. (The desktop app has.)": "Questa versione non ha dove tenere i modelli dei volti, quindi l'elenco dei modelli non è disponibile. (L'app desktop ce l'ha.)",
"Unnamed Faces": "Volti senza nome"
"Unnamed Faces": "Volti senza nome",
"Prevent G-SYNC/FreeSync flicker (applies at next launch)": "Evita lo sfarfallio G-SYNC/FreeSync (dal prossimo avvio)"
}
+2 -1
View File
@@ -2340,5 +2340,6 @@
"Checking the GPU…": "GPU を確認中…",
"CPU": "CPU",
"A face model uses the GPU only when a check shows it gives the same results faster.": "顔モデルが GPU を使うのは、同じ結果をより速く出せると確認できた場合だけです。",
"Checking the GPU for faces": "顔用の GPU を確認中"
"Checking the GPU for faces": "顔用の GPU を確認中",
"Prevent G-SYNC/FreeSync flicker (applies at next launch)": "G-SYNC/FreeSyncのちらつきを防ぐ(次回起動時に適用)"
}
+2 -1
View File
@@ -2718,5 +2718,6 @@
"≥ at least": "≥ არანაკლებ",
"⌘-drag to straighten, ⇧G Guided Upright, a grid while transforming.": "⌘-გადათრევა გასასწორებლად, ⇧G მართვადი Upright, ბადე გარდაქმნისას.",
"日本語": "日本語",
"繁體中文(台灣)": "繁體中文(台灣)"
"繁體中文(台灣)": "繁體中文(台灣)",
"Prevent G-SYNC/FreeSync flicker (applies at next launch)": "G-SYNC/FreeSync-ის ციმციმის თავიდან აცილება (ამოქმედდება შემდეგი გაშვებისას)"
}
+2 -1
View File
@@ -2157,5 +2157,6 @@
"This is a Lightroom catalog, not a LightCraft library. Import it into a LightCraft library instead?": "To jest katalog Lightrooma, a nie biblioteka LightCraft. Zaimportować go zamiast tego do biblioteki LightCraft?",
"Unnamed Faces": "Nienazwane twarze",
"Vertical inset (% of short edge)": "Margines pionowy (% krótszego boku)",
"not added to Apple Photos (cancelled)": "nie dodano do aplikacji Zdjęcia (anulowano)"
"not added to Apple Photos (cancelled)": "nie dodano do aplikacji Zdjęcia (anulowano)",
"Prevent G-SYNC/FreeSync flicker (applies at next launch)": "Zapobiegaj migotaniu G-SYNC/FreeSync (po ponownym uruchomieniu)"
}
+2 -1
View File
@@ -2186,5 +2186,6 @@
"Color Overlay on B&W": "Sobreposição de cor em P&B",
"Image on Black": "Imagem sobre preto",
"Image on White": "Imagem sobre branco",
"White on Black": "Branco sobre preto"
"White on Black": "Branco sobre preto",
"Prevent G-SYNC/FreeSync flicker (applies at next launch)": "Evitar cintilação de G-SYNC/FreeSync (no próximo início)"
}
+2 -1
View File
@@ -2333,5 +2333,6 @@
"Checking the GPU…": "Проверка ГП…",
"CPU": "ЦП",
"A face model uses the GPU only when a check shows it gives the same results faster.": "Модель лиц использует ГП, только если проверка показала, что он даёт те же результаты быстрее.",
"Checking the GPU for faces": "Проверка ГП для лиц"
"Checking the GPU for faces": "Проверка ГП для лиц",
"Prevent G-SYNC/FreeSync flicker (applies at next launch)": "Предотвращать мерцание G-SYNC/FreeSync (после перезапуска)"
}
+2 -1
View File
@@ -2338,5 +2338,6 @@
"Checking the GPU…": "Перевірка ГП…",
"CPU": "ЦП",
"A face model uses the GPU only when a check shows it gives the same results faster.": "Модель облич використовує ГП, лише якщо перевірка показала, що він дає ті самі результати швидше.",
"Checking the GPU for faces": "Перевірка ГП для облич"
"Checking the GPU for faces": "Перевірка ГП для облич",
"Prevent G-SYNC/FreeSync flicker (applies at next launch)": "Запобігати мерехтінню G-SYNC/FreeSync (після перезапуску)"
}
+2 -1
View File
@@ -2179,5 +2179,6 @@
"Checking the GPU…": "正在检查 GPU…",
"CPU": "CPU",
"A face model uses the GPU only when a check shows it gives the same results faster.": "只有检查表明 GPU 能更快给出相同结果时,人脸模型才会使用 GPU。",
"Checking the GPU for faces": "正在检查用于人脸的 GPU"
"Checking the GPU for faces": "正在检查用于人脸的 GPU",
"Prevent G-SYNC/FreeSync flicker (applies at next launch)": "防止 G-SYNC/FreeSync 闪烁(下次启动时生效)"
}
+2 -1
View File
@@ -2141,5 +2141,6 @@
"Checking the GPU…": "正在檢查 GPU…",
"CPU": "CPU",
"A face model uses the GPU only when a check shows it gives the same results faster.": "只有在檢查顯示 GPU 能更快得出相同結果時,臉孔模型才會使用 GPU。",
"Checking the GPU for faces": "正在檢查用於臉孔的 GPU"
"Checking the GPU for faces": "正在檢查用於臉孔的 GPU",
"Prevent G-SYNC/FreeSync flicker (applies at next launch)": "防止 G-SYNC/FreeSync 閃爍(下次啟動時生效)"
}
+2
View File
@@ -416,6 +416,8 @@ fn import_tab(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Tokens) {
fn performance_tab(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Tokens) {
use lightcraft_engine::gpu;
heading(ui, t, crate::i18n::tr("Rendering"));
#[cfg(target_os = "windows")]
check(ui, "settings.composeWindow", &mut app.ui.settings.compose_window, "Prevent G-SYNC/FreeSync flicker (applies at next launch)");
check(ui, "settings.gpu", &mut app.ui.settings.gpu, "Use the GPU for rendering");
let status = if !gpu::available() {
match gpu::unavailable_reason() {
+3
View File
@@ -348,6 +348,8 @@ pub struct AppSettings {
pub ask_before_move: bool,
/// GPU rendering allowed (`app.gpu`).
pub gpu: bool,
/// Use Windows DirectComposition to prevent VRR flicker; applies at next launch.
pub compose_window: bool,
/// Largest long edge (pixels) the user lets the loupe render at; 0 = Automatic (the size it is
/// drawn at, up to the photo's own pixels and [`LOUPE_EDGE_CEILING`]). Not the old
/// `previewEdge` key: its 2560 px default was the soft-image bug (issue #323).
@@ -402,6 +404,7 @@ impl Default for AppSettings {
confirm_delete: true,
ask_before_move: true,
gpu: true,
compose_window: false,
preview_limit: 0,
external_editor: String::new(),
external_file: EditFile::default(),
+4 -2
View File
@@ -1,6 +1,6 @@
# Gaps: where LightCraft falls short of Lightroom
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-15 · **Change:** minor (gap 15: Print Page Options — page numbers, page info, crop marks)
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-20 · **Change:** minor (gap 15: Print Page Options — page numbers, page info, crop marks)
Every known shortfall, one entry each, ranked by user impact (how many Lightroom users hit it, and
how hard it blocks them), then by effort. **Agents pick work from the top** (the first entry nobody is
@@ -41,7 +41,7 @@ Hours are Opus 5.5 agent wall-clock hours (see [target-app-parity.md](target-app
| 24 | **Brush live preview and masking feel** (#517, #518); Feather / Edge mask refinement (15.5) | issues | masking feels slower than Lightroom | 8–12 | [ui-parity](ui-parity.md) |
| 25 | **File writes**: AVIF import; Render to DNG as a catalog command (lossy DNG and the Render to DNG export format landed) (PSD export landed with Edit in PhotoCraft, #953) | file-format-parity.md | interchange edge cases | 19–34 | [file-format-parity](file-format-parity.md) |
| 26 | **Performance at scale**: 85k-photo library opens in 1.7–4.7 s; 100k grid test pending; colour NR at half resolution; at 100k photos stack and album lookups are indexed (an "In a Stack" or "Album" smart album counts in ~14 ms, was 0.6–2.7 s) | ROADMAP log, gpu-pipeline.md | large-catalog users | 15–30 | [target-app-parity](target-app-parity.md#by-dimension) |
| 27 | **Windows / Linux runtime coverage**: Windows installer UI unverified on Windows (#79); GPU path proven on Apple and on one AMD GPU on Linux (Radeon 8060S, Vulkan); NVIDIA, Intel and DX12 unverified; the Linux riscv64 tarball (new in 0.6.0, #844) is cross-compiled and never run beyond a QEMU smoke test | ROADMAP, issues, `release.yml` | non-Mac users find bugs first | 13–27 | [hardware-parity](hardware-parity.md) |
| 27 | **Windows / Linux runtime coverage**: Windows installer UI unverified on Windows (#79); GPU path proven on Apple and on one AMD GPU on Linux (Radeon 8060S, Vulkan); NVIDIA, Intel and DX12 unverified; Windows DirectComposition VRR workaround implemented, monitor verification pending; the Linux riscv64 tarball (new in 0.6.0, #844) is cross-compiled and never run beyond a QEMU smoke test | ROADMAP, issues, `release.yml` | non-Mac users find bugs first | 13–27 | [hardware-parity](hardware-parity.md) |
| 29 | **HDR merge leftovers** (LR-MERGE-HDR): AI Denoise doesn't run on merged (linear) DNGs; JPEG brackets are treated as linear (no camera response curve); every frame is held as 32-bit float, ~0.86 GB per 67 MP frame (half-float frames would halve it) | tested on Sony A7R VI brackets 2026-10-14 ([merge.md](merge.md)); Lightroom's Denoise supports HDR DNGs | HDR shooters who denoise their merges, merge JPEGs, or merge 7+ frames of high-resolution raws on 16 GB machines | 10–20 (+ denoise linear-RGB contract, gap 11) | [merge](merge.md) |
| 28 | **Small Classic shortcuts and keys**: rating `[` `]`, flag cycling, filter-bar keys, F1 help, panel keys (Tab, F5, F8; F6 and F7 toggle the filmstrip and the left panel) | [ui-parity](ui-parity.md#shortcuts) | Classic muscle memory | 4–8 | [ui-parity](ui-parity.md) |
@@ -126,3 +126,5 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w
| 2026-10-10 | minor | Gap 8: Sony ILCE-7RM6 in-file distortion added next to the ILCE-7RM4A's, its lateral CA parsed but off by default (docs/sony-ilce7rm6-lens-corrections.md); the gap stays open (no lens database), hours unchanged |
| 2026-10-14 | minor | Gap 10 shrinks: 97 Canon CR2, Pentax and Ricoh models verified on one CC0 file each (334 models, ~50% on a verified body) |
| 2026-10-14 | minor | Gap 10: 515 of 1,446 models verified (36%), ~84% of photographers on a verified body (integration batch 36) |
| 2026-10-20 | minor | Opt-in Windows DirectComposition preference added; VRR hardware verification pending; parity counts unchanged |
+4 -2
View File
@@ -1,6 +1,6 @@
# Lightroom parity checklist
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-15 · **Change:** minor (LRC-PRINT-PAGE: Print Page Options) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-20 · **Change:** minor (LRC-PRINT-PAGE: Print Page Options) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
The measured, row-by-row checklist: one row per Lightroom feature, menu item and shortcut, what we
have and what is missing. It answers *does the feature exist?* (feature breadth). Whether it is good
@@ -434,7 +434,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts).
| LR-PREF-ACCOUNT | Account | OOS | 🚫 | | |
| LR-PREF-INTERFACE | Interface options | P1 | ✅ | `cmd:app.settings`, `cmd:view.appearance`, `cmd:view.theme` | Appearance Mode (Sync with System / Dark / Light; the top bar button cycles Auto, Light, Dark) with separate dark (Charcoal, Midnight) and light (Silver, Paper) themes previewed in cards; hover stars persist favorite themes, the appearance button context menu opens Interface settings and lists light favorites before dark favorites; filmstrip names/badges, grid badges (auto/always/never), square-grid names, navigator, info overlay |
| LR-PREF-DISPLAY | Monitor (display) colour management | P1 | 🟡 | `cmd:app.displayProfile`, `crates/codecs/src/display.rs`, `crates/engine/src/display.rs`, `crates/ui-egui/src/panels/settings.rs`, `apps/lightcraft/src/screen_profile.rs`, `apps/lightcraft/src/colord.rs`, `docs/display-profiles.md` | Settings ▸ Display: a monitor ICC profile (matrix/TRC exactly, LUT-based through the CMS) chosen by hand, or on macOS the profile of the screen showing the window (followed across screens, #787), on Linux the profile the desktop assigns in colord (read off the UI thread every 3 s; used with one display, or several sharing one profile); the loupe, Before, Compare, hover previews and the second window render into the display's primaries (its full gamut, CPU and GPU), thumbnails and stand-ins are converted from sRGB; histogram, preview caches and exports stay sRGB; soft proofing's display gamut warning uses the display's gamut. Missing: Windows' WCS profile (needs FFI, so `unsafe`), X11 `_ICC_PROFILE`, per-monitor profiles on Linux (eframe doesn't say which monitor the window is on) |
| LR-PREF-PERFORMANCE | GPU / performance | P1 | ✅ | `cmd:app.gpu`, `cmd:app.memoryBudget`, `cmd:app.settings` | GPU on/off (a crash while the GPU device or its kernels are built, issue #250, turns it off for the next launch), preview size (Automatic = the whole-frame loupe render at canvas size, up to 2560 px; or a 1600–5120 px size for it; zoomed views add a window at up to 100 %, issue #323), memory budget (a quarter of it is the idle per-view stage caches' share; the open photo's views are counted and reported, and cut only past four times that, the before side's and the zoom window's first — a Before/After at 1:1 on a 2800 px canvas holds about 1 GB of GPU stages, plus the one 288 MB copy of the original, which `ui.inspect` reports as `sharedSourceBytes`), thumbnail cache size in Settings |
| LR-PREF-PERFORMANCE | GPU / performance | P1 | ✅ | `cmd:app.gpu`, `cmd:app.memoryBudget`, `cmd:app.settings` | GPU on/off (a crash while the GPU device or its kernels are built, issue #250, turns it off for the next launch), preview size (Automatic = the whole-frame loupe render at canvas size, up to 2560 px; or a 1600–5120 px size for it; zoomed views add a window at up to 100 %, issue #323), memory budget (a quarter of it is the idle per-view stage caches' share; the open photo's views are counted and reported, and cut only past four times that, the before side's and the zoom window's first — a Before/After at 1:1 on a 2800 px canvas holds about 1 GB of GPU stages, plus the one 288 MB copy of the original, which `ui.inspect` reports as `sharedSourceBytes`), thumbnail cache size in Settings; Opt-in Windows DX12 DirectComposition preference to prevent VRR flicker (restart required; hardware verification pending) |
| LR-PREF-PEOPLE | Face recognition | P2 | 🟡 | `cmd:faces.enable`, `cmd:faces.models.list`, `cmd:faces.models.inspect`, `cmd:faces.models.install`, `cmd:faces.models.download`, `cmd:faces.models.downloads`, `cmd:faces.models.downloadCancel`, `cmd:faces.models.remove`, `cmd:faces.models.select`, `cmd:dialog.faceModel`, `crates/faces/src/lib.rs`, `crates/ui-egui/src/panels/faces.rs` | Settings ▸ Faces: an off-by-default toggle and the face model list. Models are opt-in and never bundled, the 232 KB YuNet detector included. Press Download (terms first; then a pinned address, size and SHA-256 checked, fetched over pure-Rust https with `lightcraft-fetch`), or add any `.onnx` by picking or dropping it: it is recognised by hash or described from its shape, shows its licence terms and training-data note, runs a self-test, and installs only after an "I accept" box; an installed model is chosen and recognition switched on at once (the newest wins, each model keeps its own embedding cache). Non-commercial models are add-by-file only, never offered for download. Suggestions of names run on the shared pure-Rust CPU interpreter (`crates/faces/src/net.rs`, `crates/faces/src/runtime.rs`; bounded im2col/GEMM, checked ONNX shapes/operators; no external inference runtime; 40 public crops agree within 2.1e-6, one-thread SFace 34–36 ms / AuraFace 233–252 ms, see docs/faces.md) in a background worker and are never applied automatically. Thresholds are starting points calibrated on busts, not yet on live faces; the Recognise faces switch is off until a recognition model is chosen and installed (`faces.enable {enabled: true}` refuses otherwise; removing the last recogniser switches it off), issue #678 |
| LR-PREF-WATERMARK | Watermark settings | P1 | ✅ | `cmd:export.savePreset`, `crates/ui-egui/src/panels/dialogs.rs` | set in the Export dialog; kept with Export with Previous and in saved export presets |
| LR-PREF-SHORTCUTS | Shortcut customisation | — | ✅ | `cmd:app.setShortcut`, `cmd:app.resetShortcuts`, `crates/ui-egui/src/panels/keymap.rs` | an extra (the reference app has no keymap editor): Help ▸ Keyboard Shortcuts (⌘/) lists every command; click a shortcut and press the new keys (Esc cancels), × removes, ↺ restores, Reset All; a key another command had moves over; saved in `ui.json`; native menu accelerators follow. Rating/label digits and secondary keys are listed read-only and yield to a key given to a command |
@@ -891,3 +891,5 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove
| 2026-10-13 | minor | LRC-LIB-CATALOG-IMPORT review: offline Local promotion and result dismissal across library switches covered; redundant operation/settings copies removed. Status and summary counts unchanged. |
| 2026-10-14 | patch | LRC-LIB-CATALOG-IMPORT: reconciled with upstream phased progress, cancellation kinds, WAL-only import, translations and current progress tracking. Status and summary counts unchanged. |
| 2026-10-13 | minor | KEYC-COMPARE: `=` / `-` (also `+`) increase / decrease the grid thumbnail size (`view.thumbSizeUp` / `view.thumbSizeDown`, View menu), grids only; status unchanged (🟡) |
| 2026-10-20 | minor | Opt-in Windows DirectComposition preference added; VRR hardware verification pending; parity counts unchanged |