From 2dd02f6f97f4d91e680254979b2fbb309ed9a043 Mon Sep 17 00:00:00 2001 From: Jason Luker <176962611+ZJHeepfixer@users.noreply.github.com> Date: Fri, 23 Oct 2026 22:08:35 -0700 Subject: [PATCH] PERF-WINDOW: the window queues one frame, not two (eframe's low-latency surface) window_gpu::base_options() built the window's wgpu options from egui_wgpu::WgpuConfiguration::default(), which is egui-wgpu's HIGH_THROUGHPUT surface (desired_maximum_frame_latency 2; three drawables on Metal), not eframe's default. eframe's NativeOptions::default() uses LOW_LATENCY (latency 1). The window had eframe's default until #136 started building the options by hand to choose the backends. Start from eframe::NativeOptions::default().wgpu_options and change only the backends, the DX12 compiler and the instance flags, as before. The probed Windows configurations are built on the same options. Measured on an M2 Max at 120 Hz (Metal System Trace, heavy develop drag, presents joined to WindowServer's reads of the same IOSurface): the window cycles 2 drawables instead of 3, and a frame reaches the compositor about 8.3 ms (one frame) sooner, 9.5-10.6 ms instead of 17.9-18.9 ms after it is presented, at the same ~119 frames a second. Test: window_gpu::tests::window_surface_keeps_one_frame_in_flight (fails on the old options: latency Some(2) instead of Some(1)). Co-Authored-By: Claude Opus 5.5 --- apps/lightcraft/src/window_gpu.rs | 24 +++++++++++++++++++++++- docs/gpu-pipeline.md | 9 +++++++++ docs/whats-new.md | 10 ++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/apps/lightcraft/src/window_gpu.rs b/apps/lightcraft/src/window_gpu.rs index fefa65a51..69418028a 100644 --- a/apps/lightcraft/src/window_gpu.rs +++ b/apps/lightcraft/src/window_gpu.rs @@ -142,8 +142,13 @@ fn user_notice(failed: Option, forced: Option, p } /// eframe's defaults with the app's backends and DX12 shader compiler (issues #136, #471). +/// +/// Taken from `NativeOptions::default()`, whose surface is egui-wgpu's `LOW_LATENCY` (one frame in +/// flight; two drawables on Metal). `WgpuConfiguration::default()` is `HIGH_THROUGHPUT` instead (two +/// frames queued, three drawables): every drag, slider and crop handle reached the screen a frame +/// later than eframe's default. Live window resizing still gets three drawables from egui-wgpu. pub fn base_options() -> WgpuConfiguration { - let mut c = WgpuConfiguration::default(); + let mut c = eframe::NativeOptions::default().wgpu_options; if let WgpuSetup::CreateNew(n) = &mut c.wgpu_setup { n.instance_descriptor.backends = backend::window_backends(); n.instance_descriptor.backend_options = backend::backend_options(); @@ -290,6 +295,23 @@ mod tests { assert!(died.contains("last time") && !died.contains("Validation Error"), "{died}"); } + /// The window keeps eframe's low-latency surface: one frame queued (two Metal drawables), not + /// the high-throughput `WgpuConfiguration::default()`, which held input a frame longer. The + /// probed (Windows) and forced-backend configurations are built on the same options. + #[test] + fn window_surface_keeps_one_frame_in_flight() { + let surface = base_options().surface; + assert_eq!(surface, eframe::NativeOptions::default().wgpu_options.surface); + assert_eq!(surface, eframe::egui_wgpu::SurfaceConfig::LOW_LATENCY); + assert_eq!(surface.desired_maximum_frame_latency, Some(1)); + assert_eq!(surface.present_mode, wgpu::PresentMode::AutoVsync); + assert_ne!(surface, WgpuConfiguration::default().surface, "egui-wgpu's own default is the high-throughput one"); + // what `main` hands eframe (on Windows `choose` probes the adapters first; not here) + if !cfg!(windows) { + assert_eq!(choose(&Files::new(None)).wgpu_options.surface, surface); + } + } + /// The crash report: a pipeline that fails validation must come back as an error, never as /// wgpu's default panic. Needs an adapter; skipped where there is none (CI without a GPU). #[test] diff --git a/docs/gpu-pipeline.md b/docs/gpu-pipeline.md index f88f81c39..0370e6407 100644 --- a/docs/gpu-pipeline.md +++ b/docs/gpu-pipeline.md @@ -113,6 +113,15 @@ renderer last. `LIGHTCRAFT_GPU_BACKEND=dx12 | vulkan | gl | glow` (or `WGPU_BACK first because a broken Vulkan driver can crash the process uncatchably (#136). Compute keeps its own device and falls back to the CPU as before. +**The window's surface: one frame in flight.** The window's wgpu options start from eframe's defaults +(`NativeOptions::default().wgpu_options`, `window_gpu::base_options`), whose surface is egui-wgpu's +`SurfaceConfig::LOW_LATENCY`: vsync, at most one frame queued ahead of the display (two drawables on +Metal). egui-wgpu's bare `WgpuConfiguration::default()` is `HIGH_THROUGHPUT` (two frames queued, three +drawables), and the window used it from #136 until this change, so a drag reached the screen one frame +(8.3 ms at 120 Hz, 16.7 ms at 60 Hz) later. The window's own GPU work is small (egui's meshes and +texture uploads; renders run on the compute device), which is what `LOW_LATENCY` is meant for. +During a live window resize egui-wgpu still uses three drawables on macOS. + **Troubleshooting a crash at startup (Windows).** Start LightCraft from a `.cmd` file or a terminal with `set LIGHTCRAFT_GPU_BACKEND=dx12` (the default since #136), or `=off` to keep the GPU out of rendering; `set VK_LOADER_DRIVERS_DISABLE=*igvk64*` (Vulkan loader) hides a specific Vulkan driver diff --git a/docs/whats-new.md b/docs/whats-new.md index c6c259200..3c5a0b187 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -1,5 +1,15 @@ # What's new in LightCraft +## 2026-10-25 + +### Drags reach the screen a frame sooner +- The window queues one frame ahead of the display again instead of two, as eframe does by default (since #136 it + used egui-wgpu's high-throughput setting). Sliders, crop handles, brushes and every other drag show up one display + frame sooner: 8.3 ms at 120 Hz, 16.7 ms at 60 Hz. On a MacBook Pro at 120 Hz, a heavy develop drag went from + about 18 ms to about 10 ms between a frame being handed to macOS and macOS compositing it, with the same 120 frames + a second. On a Mac the window also holds one drawable less: its width × height × 4 bytes, 15–22 MB for a + 1200–1500-point-wide window on a Retina display. + ## 2026-10-24 ### Relative-date filters and smart albums follow the clock