Preferences › Performance › Low-Latency Drawing, on by default: no vsync wait while drawing (#1343)

Testers' traces put the ~50 ms stroke lag in the swap chain's vsync wait. With vsync off it
disappears (X11/NVIDIA; Windows/NVIDIA via the driver), and a shorter queue doesn't help. The
surface now presents with AutoNoVsync by default: wgpu picks Immediate, else Mailbox, else
Fifo by what the driver offers, so no unsupported mode is ever requested. Turning the
preference off brings vsync back (smooth, but late). It applies at the next launch, like the
other graphics settings. Where only Immediate exists the canvas may tear while it changes;
frames render as fast as input arrives during a stroke and stop when idle.
This commit is contained in:
Tanish Manku committed 2026-10-14 13:25:45 -07:00
1 parent 34ba17cfd4
commit 9a9eaed068
26 files changed
+65 -4

No files matched your search

+25
View File
@@ -444,6 +444,14 @@ pub fn read_display_server(path: Option<&Path>) -> photocraft_engine::prefs::Lin
}
/// Read the policy leniently at startup, including old settings without renderingMode.
/// Preferences › Performance › Low-Latency Drawing (`performance.lowLatencyDrawing`, on unless
/// set off), read before the window opens: it picks the surface's present mode.
pub fn read_low_latency(path: Option<&Path>) -> bool {
let v: Value = path.and_then(|p| std::fs::read_to_string(p).ok()).and_then(|t| serde_json::from_str(&t).ok()).unwrap_or(Value::Null);
let default = photocraft_engine::prefs::Preferences::default().performance.low_latency_drawing;
v.get("performance").and_then(|p| p.get("lowLatencyDrawing")).and_then(Value::as_bool).unwrap_or(default)
}
pub fn read_rendering_prefs(path: Option<&Path>) -> (GpuBackend, RenderingMode) {
let v: Value = path.and_then(|p| std::fs::read_to_string(p).ok()).and_then(|t| serde_json::from_str(&t).ok()).unwrap_or(Value::Null);
let perf = v.get("performance");
@@ -463,6 +471,23 @@ mod tests {
Marker { backend: backend.into(), adapter: "Intel(R) UHD Graphics".into(), adapter_backend: adapter_backend.into(), ..Default::default() }
}
#[test]
fn low_latency_drawing_is_read_before_the_window_opens_and_defaults_on() {
let dir = std::env::temp_dir().join(format!("photocraft-low-latency-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("preferences.json");
let read = |json: &str| {
std::fs::write(&file, json).unwrap();
read_low_latency(Some(&file))
};
assert!(read(r#"{"performance":{"lowLatencyDrawing":true}}"#));
assert!(!read(r#"{"performance":{"lowLatencyDrawing":false}}"#));
assert!(read(r#"{"performance":{"lowLatencyDrawing":"nope"}}"#), "garbage keeps the default");
assert!(read("not json"));
assert!(read_low_latency(None), "no settings file: the default");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn display_server_preference_is_read_before_the_window_opens() {
use photocraft_engine::prefs::LinuxDisplayServer;
+6 -1
View File
@@ -363,7 +363,12 @@ fn main() -> eframe::Result {
let gpu_note: std::sync::Arc<std::sync::Mutex<Option<String>>> = Default::default();
// The adapter's real texture limits (egui asks for 8192 px), so big documents stay on the GPU.
gpu_startup::configure(&mut options.wgpu_options.wgpu_setup, &plan, os, sentinel.clone(), gpu_note.clone());
// Testers' present mode and frame latency overrides (#1343), after the platform default.
// Preferences › Performance › Low-Latency Drawing (#1343): without vsync a stroke shows about
// 50 ms sooner; wgpu picks Immediate, else Mailbox, else Fifo by what the driver offers.
if gpu_startup::read_low_latency(services::prefs_file().as_deref()) {
options.wgpu_options.surface.present_mode = eframe::wgpu::PresentMode::AutoNoVsync;
}
// Testers' present mode and frame latency overrides (#1343), after the preference.
let present = presentation::Overrides::from_env();
options.wgpu_options.surface = present.apply(options.wgpu_options.surface);
let present_desc = presentation::describe(&options.wgpu_options.surface, &present);
+6
View File
@@ -529,6 +529,11 @@ pub struct Performance {
/// sliders while they drag) render on a reduced copy: fast, but blocky when zoomed in. Off:
/// they render at full resolution.
pub low_resolution_previews: bool,
/// Low-latency drawing (applies at next launch): frames reach the screen without waiting for
/// vsync, so a stroke follows the pen about 50 ms sooner (the swap chain's wait, #1343). Where
/// the driver can't replace queued frames (Mailbox), the canvas may tear while it changes.
/// Off: vsync, smooth but late.
pub low_latency_drawing: bool,
/// Linux display server (applies at next launch; see [`LinuxDisplayServer`]).
pub linux_display_server: LinuxDisplayServer,
/// Memory budget of the layer-effect cache, in MB.
@@ -560,6 +565,7 @@ impl Default for Performance {
rendering_mode: None,
gpu_backend: GpuBackend::Auto,
low_resolution_previews: true,
low_latency_drawing: true,
linux_display_server: LinuxDisplayServer::Auto,
effect_cache_mb: 768,
legacy_compositing: false,
+1
View File
@@ -3783,3 +3783,4 @@ navigator Zoom Zvětšení
# View › Guides › right-click a guide
Edit Selected Guides Upravit vybraná vodítka
Clear Selected Guides Vymazat vybraná vodítka
Low-Latency Drawing (vsync off, applies at next launch) Kreslení s nízkou latencí (bez vertikální synchronizace, platí po dalším spuštění)
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3177,3 +3177,4 @@ curves Smooth Glätten
# View › Guides › right-click a guide
Edit Selected Guides Ausgewählte Hilfslinien bearbeiten
Clear Selected Guides Ausgewählte Hilfslinien löschen
Low-Latency Drawing (vsync off, applies at next launch) Zeichnen mit geringer Latenz (ohne VSync, gilt ab dem nächsten Start)
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3875,3 +3875,4 @@ curves Smooth Εξομάλυνση
# View › Guides › right-click a guide
Edit Selected Guides Επεξεργασία επιλεγμένων οδηγών
Clear Selected Guides Απαλοιφή επιλεγμένων οδηγών
Low-Latency Drawing (vsync off, applies at next launch) Σχεδίαση χαμηλής καθυστέρησης (χωρίς vsync, ισχύει στην επόμενη εκκίνηση)
Can't render this file because it contains an unexpected character in line 1030 and column 18.
+1
View File
@@ -4137,3 +4137,4 @@ navigator Zoom Zoom
# View › Guides › right-click a guide
Edit Selected Guides Editar guías seleccionadas
Clear Selected Guides Borrar guías seleccionadas
Low-Latency Drawing (vsync off, applies at next launch) Dibujo de baja latencia (sin sincronización vertical, se aplica al volver a iniciar)
Can't render this file because it contains an unexpected character in line 4 and column 47.
+1
View File
@@ -3179,3 +3179,4 @@ curves Smooth Lisser
# View › Guides › right-click a guide
Edit Selected Guides Modifier les repères sélectionnés
Clear Selected Guides Effacer les repères sélectionnés
Low-Latency Drawing (vsync off, applies at next launch) Dessin à faible latence (sans synchronisation verticale, au prochain lancement)
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -4094,3 +4094,4 @@ cameraRaw Yellow Sárga
# View › Guides › right-click a guide
Edit Selected Guides Kijelölt segédvonalak szerkesztése
Clear Selected Guides Kijelölt segédvonalak törlése
Low-Latency Drawing (vsync off, applies at next launch) Kis késleltetésű rajzolás (vsync nélkül, a következő indításkor lép életbe)
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3166,3 +3166,4 @@ curves Smooth Haluskan
# View › Guides › right-click a guide
Edit Selected Guides Edit Panduan Terpilih
Clear Selected Guides Hapus Panduan Terpilih
Low-Latency Drawing (vsync off, applies at next launch) Menggambar latensi rendah (tanpa vsync, berlaku saat peluncuran berikutnya)
Can't render this file because it contains an unexpected character in line 4 and column 47.
+1
View File
@@ -4296,3 +4296,4 @@ shape Appearance Aspetto
# View › Guides › right-click a guide
Edit Selected Guides Modifica guide selezionate
Clear Selected Guides Cancella guide selezionate
Low-Latency Drawing (vsync off, applies at next launch) Disegno a bassa latenza (senza vsync, si applica al prossimo avvio)
Can't render this file because it contains an unexpected character in line 2654 and column 53.
+1
View File
@@ -3851,3 +3851,4 @@ curves Smooth スムーズ
# View › Guides › right-click a guide
Edit Selected Guides 選択したガイドを編集
Clear Selected Guides 選択したガイドを消去
Low-Latency Drawing (vsync off, applies at next launch) 低遅延描画(垂直同期オフ、次回起動時に適用)
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3353,3 +3353,4 @@ curves Smooth 매끄럽게
# View › Guides › right-click a guide
Edit Selected Guides 선택한 안내선 편집
Clear Selected Guides 선택한 안내선 지우기
Low-Latency Drawing (vsync off, applies at next launch) 저지연 그리기(수직 동기화 끔, 다음 실행 시 적용)
Can't render this file because it contains an unexpected character in line 3166 and column 53.
+1
View File
@@ -3935,3 +3935,4 @@ curves Smooth Vloeiend maken
# View › Guides › right-click a guide
Edit Selected Guides Geselecteerde hulplijnen bewerken
Clear Selected Guides Geselecteerde hulplijnen wissen
Low-Latency Drawing (vsync off, applies at next launch) Tekenen met lage latentie (zonder vsync, na de volgende start)
Can't render this file because it contains an unexpected character in line 8 and column 34.
+1
View File
@@ -3773,3 +3773,4 @@ curves Smooth Wygładź
# View › Guides › right-click a guide
Edit Selected Guides Edytuj zaznaczone prowadnice
Clear Selected Guides Usuń zaznaczone prowadnice
Low-Latency Drawing (vsync off, applies at next launch) Rysowanie z małym opóźnieniem (bez synchronizacji pionowej, od następnego uruchomienia)
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3965,3 +3965,4 @@ layerLabel No Color Sem cor
# View › Guides › right-click a guide
Edit Selected Guides Editar guias selecionadas
Clear Selected Guides Limpar guias selecionadas
Low-Latency Drawing (vsync off, applies at next launch) Desenho de baixa latência (sem sincronização vertical, aplicado na próxima inicialização)
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3144,3 +3144,4 @@ curves Smooth Сгладить
# View › Guides › right-click a guide
Edit Selected Guides Изменить выбранные направляющие
Clear Selected Guides Удалить выбранные направляющие
Low-Latency Drawing (vsync off, applies at next launch) Рисование с малой задержкой (без вертикальной синхронизации, после перезапуска)
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -4248,3 +4248,4 @@ shape Appearance Izgled
# View › Guides › right-click a guide
Edit Selected Guides Uredi izabrane vodilje
Clear Selected Guides Ukloni izabrane vodilje
Low-Latency Drawing (vsync off, applies at next launch) Crtanje sa malim kašnjenjem (bez vsync-a, primenjuje se pri sledećem pokretanju)
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3905,3 +3905,4 @@ cameraData Lens Lens
# View › Guides › right-click a guide
Edit Selected Guides Seçili Kılavuzları Düzenle
Clear Selected Guides Seçili Kılavuzları Temizle
Low-Latency Drawing (vsync off, applies at next launch) Düşük gecikmeli çizim (dikey eşitleme kapalı, sonraki açılışta geçerli)
Can't render this file because it contains an unexpected character in line 8 and column 34.
+1
View File
@@ -3874,3 +3874,4 @@ curves Smooth Згладити
# View › Guides › right-click a guide
Edit Selected Guides Редагувати вибрані напрямні
Clear Selected Guides Очистити вибрані напрямні
Low-Latency Drawing (vsync off, applies at next launch) Малювання з малою затримкою (без вертикальної синхронізації, після перезапуску)
Can't render this file because it contains an unexpected character in line 1388 and column 18.
+1
View File
@@ -3661,3 +3661,4 @@ curves Smooth Làm mượt
# View › Guides › right-click a guide
Edit Selected Guides Sửa đường căn đã chọn
Clear Selected Guides Xóa đường căn đã chọn
Low-Latency Drawing (vsync off, applies at next launch) Vẽ độ trễ thấp (tắt vsync, áp dụng ở lần khởi động sau)
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -2790,3 +2790,4 @@ curves Smooth 平滑
# View › Guides › right-click a guide
Edit Selected Guides 编辑所选参考线
Clear Selected Guides 清除所选参考线
Low-Latency Drawing (vsync off, applies at next launch) 低延迟绘画(关闭垂直同步,下次启动时生效)
Can't render this file because it contains an unexpected character in line 2600 and column 53.
+1
View File
@@ -2775,3 +2775,4 @@ curves Smooth 平滑
# View › Guides › right-click a guide
Edit Selected Guides 編輯所選參考線
Clear Selected Guides 清除所選參考線
Low-Latency Drawing (vsync off, applies at next launch) 低延遲繪圖(關閉垂直同步,下次啟動時生效)
Can't render this file because it contains an unexpected character in line 6 and column 47.
+5
View File
@@ -1077,6 +1077,11 @@ fn humanize(key: &str) -> String {
if key == "nudgeBundlePauseMs" {
return "Nudge Bundle Pause (ms)".into();
}
// Performance: presentation without vsync (#1343); like the other graphics settings, it
// takes effect when the window next opens.
if key == "lowLatencyDrawing" {
return "Low-Latency Drawing (vsync off, applies at next launch)".into();
}
let mut s = String::new();
for (i, ch) in key.chars().enumerate() {
if i == 0 {
+1 -1
View File
@@ -120,7 +120,7 @@ By default PhotoCraft's own crates log at `info` and everything else at `warn`.
| `PHOTOCRAFT_LOCALE` | Override Auto's native UI language for this launch; unsupported tags use English. See [system language detection](localization.md#first-launch-and-system-language). |
| `PHOTOCRAFT_FX_TRACE=1` | Print the CPU time spent on GPU effect shapes and distance fields per rebuild |
| `PHOTOCRAFT_STROKE_TRACE=1` (or `=<file.csv>`) | Time every frame of each brush stroke; print a summary per stroke, and with a path also append each frame to that CSV (see [Stroke latency trace](#stroke-latency-trace)) |
| `PHOTOCRAFT_PRESENT_MODE=novsync` | Present mode for this launch: `fifo`, `autovsync`, or `novsync` (also `immediate`, `mailbox`: no vsync, the first of Immediate, Mailbox, Fifo the driver offers). Default: the platform's. For latency comparisons; shown in the stroke trace |
| `PHOTOCRAFT_PRESENT_MODE=novsync` | Present mode for this launch: `fifo`, `autovsync`, or `novsync` (also `immediate`, `mailbox`: no vsync, the first of Immediate, Mailbox, Fifo the driver offers). Default: no vsync, or vsync when Preferences › Performance › Low-Latency Drawing is off. For latency comparisons; shown in the stroke trace |
| `PHOTOCRAFT_FRAME_LATENCY=1` | Frames the swap chain may queue, 1 to 3 (wgpu's GL backend ignores it). For latency comparisons; shown in the stroke trace |
| `PHOTOCRAFT_THEME_FILE=tokens.json` | **Debug builds only:** live design-token overrides, re-read on change |
+2 -2
View File
@@ -9,7 +9,7 @@ Generated by `cargo xtask scorecard` from `perf/budgets.toml`, `perf/baseline.js
| [Performance](#performance) | 3 | 22 | 22 | 56 scenarios: 3 within budget, 22 over budget or failing, 9 tracked only, 17 not run, 5 not measurable yet (baseline 2026-10-05) |
| [File compatibility](#file-compatibility) | 7 | 13 | 7 | io corpus: oracle ≥ 148/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 249/309, round trip ≥ 308/309; photoshop oracles: oracle ≥ 185/256, round trip ≥ 258/256 |
| [Tools](#tools) | 33 | 11 | 0 | |
| [Workspace and UI](#workspace-and-ui) | 13 | 6 | 4 | settings that do nothing: 26 of 164 (target 0) |
| [Workspace and UI](#workspace-and-ui) | 13 | 6 | 4 | settings that do nothing: 26 of 165 (target 0) |
| [Type](#type) | 4 | 3 | 2 | |
| [Automation](#automation) | 5 | 2 | 4 | 631 of 631 menu items live (100.0%) |
| [Reliability](#reliability) | 5 | 2 | 0 | never-crash lints on 28 of 28 crates; panic_hunt present |
@@ -177,7 +177,7 @@ Corpus floors (`crates/io/tests/corpus.rs`): the enforced minimums; the corpus t
## Workspace and UI
**Settings that do nothing: 26 of 164** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't.
**Settings that do nothing: 26 of 165** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't.
Unread: `general.color_picker`, `general.animated_zoom`, `general.zoom_resizes_windows`, `workspace.auto_show_hidden_panels`, `workspace.enable_floating_document_window_docking`, `file_handling.save_in_background`, `performance.cache_levels`, `performance.legacy_compositing`, `scratch_disks.disks`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `plug_ins.generator_enabled`, `type_.text_engine`, `type_.recent_fonts`, `enhanced_controls.scrubby_slider_acceleration`, `enhanced_controls.touch_gestures`, `raw_defaults.color_space`, `raw_defaults.bit_depth`, `raw_defaults.resolution`, `raw_defaults.sharpen_for`, `raw_defaults.open_as_smart_object`, `raw_defaults.apply_auto_tone`, `integrations.allow_agent_control`, `integrations.control_port`.