mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-22 07:04:10 -04:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f61806048 | ||
|
|
b8c5503985 | ||
|
|
d06c3168cc | ||
|
|
2a69d49f67 | ||
|
|
5c874b79dc | ||
|
|
ba91c9fe05 | ||
|
|
3c888969fe | ||
|
|
dc5aedd175 | ||
|
|
763e348442 | ||
|
|
028c40d694 | ||
|
|
6af6481df4 |
No files matched your search
@@ -443,6 +443,14 @@ pub fn read_display_server(path: Option<&Path>) -> photocraft_engine::prefs::Lin
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Preferences › Performance › Low-Latency Drawing (`performance.lowLatencyDrawing`, off unless
|
||||
/// turned on), 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)
|
||||
}
|
||||
|
||||
/// Read the policy leniently at startup, including old settings without renderingMode.
|
||||
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);
|
||||
@@ -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_off() {
|
||||
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 (off)");
|
||||
assert!(!read("not json"));
|
||||
assert!(!read_low_latency(None), "no settings file: the default (off)");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_server_preference_is_read_before_the_window_opens() {
|
||||
use photocraft_engine::prefs::LinuxDisplayServer;
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
//!
|
||||
//! Usage: `photocraft [--control <port>] [--control-token <64-hex> |
|
||||
//! --control-token-file <path>] [--automation-read-root <dir>]
|
||||
//! [--automation-write-root <dir>] [--safe-gpu] [--in-window-menus] [files…]`
|
||||
//! [--automation-write-root <dir>] [--safe-gpu] [--in-window-menus] [--present-mode <mode>]
|
||||
//! [--frame-latency <n>] [--stroke-trace <stderr|file.csv>] [files…]`
|
||||
//!
|
||||
//! `--present-mode` (`fifo`, `autovsync`, `novsync`) and `--frame-latency` (1–3) override how
|
||||
//! frames reach the display for this launch (`PHOTOCRAFT_PRESENT_MODE` / `…_FRAME_LATENCY`;
|
||||
//! `presentation`); `--stroke-trace` times each brush stroke's frames (`PHOTOCRAFT_STROKE_TRACE`;
|
||||
//! `photocraft_ui_egui::stroke_trace`).
|
||||
//!
|
||||
//! `--safe-gpu` starts with the CPU renderer (no GPU canvas; a software adapter for the window
|
||||
//! where the platform has one) for this launch, e.g. after a graphics driver crash. A start that
|
||||
@@ -44,6 +50,7 @@ mod linux_libs;
|
||||
mod logging;
|
||||
mod monitor_profile;
|
||||
mod photoshop_settings;
|
||||
mod presentation;
|
||||
mod screen_color;
|
||||
mod services;
|
||||
// Windows gets pen pressure from winit (WM_POINTER); the web runner has its own listener.
|
||||
@@ -191,6 +198,7 @@ fn main() -> eframe::Result {
|
||||
// reported in a notice once the app is up; a control token is a usage error like a bad port.
|
||||
let mut unreadable_paths: Vec<String> = Vec::new();
|
||||
let mut safe_gpu = false;
|
||||
let (mut present_mode_arg, mut frame_latency_arg, mut stroke_trace_arg): (Option<String>, Option<String>, Option<String>) = (None, None, None);
|
||||
let mut in_window_menus = std::env::var_os("PHOTOCRAFT_IN_WINDOW_MENUS").is_some_and(|v| !v.is_empty() && v != "0");
|
||||
let mut args = std::env::args_os().skip(1);
|
||||
while let Some(a) = args.next() {
|
||||
@@ -211,6 +219,18 @@ fn main() -> eframe::Result {
|
||||
Some("--automation-read-root") => automation_read_root = args.next().map(std::path::PathBuf::from),
|
||||
Some("--automation-write-root") => automation_write_root = args.next().map(std::path::PathBuf::from),
|
||||
Some("--safe-gpu") => safe_gpu = true,
|
||||
Some(flag @ ("--present-mode" | "--frame-latency" | "--stroke-trace")) => {
|
||||
let flag = flag.to_string();
|
||||
match args.next().map(std::ffi::OsString::into_string) {
|
||||
Some(Ok(v)) => match flag.as_str() {
|
||||
"--present-mode" => present_mode_arg = Some(v),
|
||||
"--frame-latency" => frame_latency_arg = Some(v),
|
||||
_ => stroke_trace_arg = Some(v),
|
||||
},
|
||||
// A bad value is reported and ignored; it never fails the launch.
|
||||
_ => eprintln!("photocraft: {flag}: missing or unreadable value; ignored"),
|
||||
}
|
||||
}
|
||||
Some("--in-window-menus") => in_window_menus = true,
|
||||
Some("--version") => {
|
||||
println!("photocraft {}", photocraft_engine::build_info::long_version());
|
||||
@@ -364,6 +384,21 @@ 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());
|
||||
// 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_args_or_env(present_mode_arg.as_deref(), frame_latency_arg.as_deref());
|
||||
options.wgpu_options.surface = present.apply(options.wgpu_options.surface);
|
||||
let present_desc = presentation::describe(&options.wgpu_options.surface, &present);
|
||||
// Without vsync, frames are capped at 240 fps (#4472) so repaint loops don't spin flat out.
|
||||
let uncapped = !matches!(
|
||||
options.wgpu_options.surface.present_mode,
|
||||
eframe::wgpu::PresentMode::Fifo | eframe::wgpu::PresentMode::FifoRelaxed | eframe::wgpu::PresentMode::AutoVsync
|
||||
);
|
||||
log::info!("presentation: {present_desc}");
|
||||
let sentinel_ms = t_sentinel.elapsed().as_secs_f64() * 1000.0;
|
||||
log::info!("GPU startup: {:?} ({sentinel_ms:.2} ms)", plan);
|
||||
let retry_cpu = !safe_gpu && plan.backend != photocraft_engine::prefs::GpuBackend::Cpu;
|
||||
@@ -392,6 +427,7 @@ fn main() -> eframe::Result {
|
||||
}
|
||||
}
|
||||
let mut app = PhotocraftApp::new(Session::new(), services);
|
||||
app.min_frame_ms = uncapped.then_some(1000.0 / 240.0);
|
||||
app.integrated_titlebar = cfg!(target_os = "macos");
|
||||
app.custom_titlebar = custom_titlebar;
|
||||
// Only the title bar's free gap drags the window, never the menus (mac_window.rs).
|
||||
@@ -423,6 +459,10 @@ fn main() -> eframe::Result {
|
||||
// composites on the CPU).
|
||||
let info = &mut app.perf.gpu_info;
|
||||
info.preference = pref.name().to_string();
|
||||
info.present = present_desc.clone();
|
||||
if let Some(v) = &stroke_trace_arg {
|
||||
app.stroke_trace.set(v);
|
||||
}
|
||||
info.selected = if plan.env.is_some() { "env".into() } else { plan.backend.name().to_string() };
|
||||
info.fallback = match (plan.reason.clone(), gpu_note.lock().unwrap_or_else(std::sync::PoisonError::into_inner).clone()) {
|
||||
(Some(reason), Some(note)) => Some(format!("{reason}. {note}")),
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
//! Launch overrides for how frames reach the display, so testers can compare input-to-pixel
|
||||
//! latency without a rebuild (#1343): `PHOTOCRAFT_PRESENT_MODE` (`fifo`, `autovsync`, or `novsync`,
|
||||
//! also spelled `immediate`, `mailbox` or `autonovsync`: no vsync, wgpu's Immediate → Mailbox → Fifo
|
||||
//! by what the driver offers) and `PHOTOCRAFT_FRAME_LATENCY` (`1` to `3`, frames the swap chain may
|
||||
//! queue). Unset, the platform default applies. A value that isn't one of these is ignored with a
|
||||
//! warning in the log, never a failed start; a mode the driver may not offer is never requested.
|
||||
//! The result is logged at start and shown in the stroke trace.
|
||||
|
||||
use eframe::egui_wgpu::SurfaceConfig;
|
||||
use eframe::wgpu::PresentMode;
|
||||
|
||||
/// The present mode and frame latency this launch asked for (`None`: the default).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Overrides {
|
||||
pub mode: Option<PresentMode>,
|
||||
pub latency: Option<u32>,
|
||||
}
|
||||
|
||||
/// Parse the two variables' values. Returns the overrides and a warning per rejected value.
|
||||
pub fn parse(mode: Option<&str>, latency: Option<&str>) -> (Overrides, Vec<String>) {
|
||||
let mut warnings = Vec::new();
|
||||
let mode = mode.map(str::trim).filter(|v| !v.is_empty()).and_then(|v| {
|
||||
let m = match v.to_ascii_lowercase().as_str() {
|
||||
// Only modes every surface takes: wgpu crashes on an explicit Immediate, Mailbox or
|
||||
// FifoRelaxed the driver doesn't offer (X11/NVIDIA has no Mailbox, #1343). Without
|
||||
// vsync, wgpu picks Immediate, else Mailbox, else Fifo.
|
||||
"fifo" | "vsync" => PresentMode::Fifo,
|
||||
"autovsync" => PresentMode::AutoVsync,
|
||||
"novsync" | "immediate" | "mailbox" | "autonovsync" => PresentMode::AutoNoVsync,
|
||||
_ => {
|
||||
warnings.push(format!("PHOTOCRAFT_PRESENT_MODE={v:?} isn't fifo, autovsync or novsync (immediate, mailbox); ignored"));
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(m)
|
||||
});
|
||||
let latency = latency.map(str::trim).filter(|v| !v.is_empty()).and_then(|v| match v.parse::<u32>() {
|
||||
Ok(n @ 1..=3) => Some(n),
|
||||
_ => {
|
||||
warnings.push(format!("PHOTOCRAFT_FRAME_LATENCY={v:?} isn't 1, 2 or 3; ignored"));
|
||||
None
|
||||
}
|
||||
});
|
||||
(Overrides { mode, latency }, warnings)
|
||||
}
|
||||
|
||||
impl Overrides {
|
||||
/// `--present-mode` / `--frame-latency` when given, else `PHOTOCRAFT_PRESENT_MODE` /
|
||||
/// `PHOTOCRAFT_FRAME_LATENCY`; rejected values are logged.
|
||||
pub fn from_args_or_env(mode: Option<&str>, latency: Option<&str>) -> Self {
|
||||
let env_mode = std::env::var("PHOTOCRAFT_PRESENT_MODE").ok();
|
||||
let env_latency = std::env::var("PHOTOCRAFT_FRAME_LATENCY").ok();
|
||||
let (o, warnings) = parse(mode.or(env_mode.as_deref()), latency.or(env_latency.as_deref()));
|
||||
for w in warnings {
|
||||
log::warn!("{w}");
|
||||
}
|
||||
o
|
||||
}
|
||||
|
||||
/// `surface` with the overrides applied.
|
||||
pub fn apply(&self, surface: SurfaceConfig) -> SurfaceConfig {
|
||||
SurfaceConfig {
|
||||
present_mode: self.mode.unwrap_or(surface.present_mode),
|
||||
desired_maximum_frame_latency: self.latency.or(surface.desired_maximum_frame_latency),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How `surface` presents, for the stroke trace and System Info: "fifo, latency 2", with
|
||||
/// "(override)" after a value an environment variable set. wgpu's GL backend ignores the latency.
|
||||
pub fn describe(surface: &SurfaceConfig, overrides: &Overrides) -> String {
|
||||
let mode = match surface.present_mode {
|
||||
PresentMode::AutoVsync => "autovsync",
|
||||
PresentMode::AutoNoVsync => "autonovsync",
|
||||
PresentMode::Fifo => "fifo",
|
||||
PresentMode::FifoRelaxed => "fiforelaxed",
|
||||
PresentMode::Immediate => "immediate",
|
||||
PresentMode::Mailbox => "mailbox",
|
||||
};
|
||||
let latency = surface.desired_maximum_frame_latency.map_or("default".to_string(), |n| n.to_string());
|
||||
let tag = |set: bool| if set { " (override)" } else { "" };
|
||||
format!("{mode}{}, latency {latency}{}", tag(overrides.mode.is_some()), tag(overrides.latency.is_some()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn values_parse_and_bad_ones_are_ignored_with_a_warning() {
|
||||
assert_eq!(parse(None, None), (Overrides::default(), vec![]));
|
||||
assert_eq!(parse(Some(""), Some(" ")), (Overrides::default(), vec![]));
|
||||
let (o, w) = parse(Some(" Mailbox "), Some("1"));
|
||||
assert_eq!((o.mode, o.latency, w.len()), (Some(PresentMode::AutoNoVsync), Some(1), 0));
|
||||
assert_eq!(parse(Some("vsync"), None).0.mode, Some(PresentMode::Fifo));
|
||||
// Never a mode a driver may lack (wgpu crashes on it): no vsync falls back by support.
|
||||
for v in ["IMMEDIATE", "mailbox", "novsync", "autonovsync"] {
|
||||
assert_eq!(parse(Some(v), None).0.mode, Some(PresentMode::AutoNoVsync), "{v}");
|
||||
}
|
||||
for (mode, latency) in
|
||||
[(Some("fast"), None), (None, Some("0")), (None, Some("4")), (None, Some("-1")), (None, Some("one")), (None, Some("99999999999"))]
|
||||
{
|
||||
let (o, w) = parse(mode, latency);
|
||||
assert_eq!((o, w.len()), (Overrides::default(), 1), "{mode:?} {latency:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overrides_replace_only_what_they_set_and_are_described() {
|
||||
let default = SurfaceConfig::HIGH_THROUGHPUT;
|
||||
assert_eq!(Overrides::default().apply(default), default);
|
||||
let latency_only = Overrides { mode: None, latency: Some(1) };
|
||||
let s = latency_only.apply(default);
|
||||
assert_eq!((s.present_mode, s.desired_maximum_frame_latency), (PresentMode::AutoVsync, Some(1)));
|
||||
assert_eq!(describe(&s, &latency_only), "autovsync, latency 1 (override)");
|
||||
let novsync = Overrides { mode: Some(PresentMode::AutoNoVsync), latency: None };
|
||||
assert_eq!(describe(&novsync.apply(default), &novsync), "autonovsync (override), latency 2");
|
||||
let none = SurfaceConfig { present_mode: PresentMode::Fifo, desired_maximum_frame_latency: None };
|
||||
assert_eq!(describe(&none, &Overrides::default()), "fifo, latency default");
|
||||
}
|
||||
}
|
||||
@@ -529,6 +529,13 @@ 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). wgpu
|
||||
/// picks Immediate where the driver has it (most Vulkan and DX12 drivers on Windows and X11),
|
||||
/// so the canvas usually tears while it changes; Mailbox (no tearing) only where Immediate is
|
||||
/// missing. Frames are capped at 240 fps. Off (the default): vsync, smooth but late. Desktop
|
||||
/// only.
|
||||
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 +567,7 @@ impl Default for Performance {
|
||||
rendering_mode: None,
|
||||
gpu_backend: GpuBackend::Auto,
|
||||
low_resolution_previews: true,
|
||||
low_latency_drawing: false,
|
||||
linux_display_server: LinuxDisplayServer::Auto,
|
||||
effect_cache_mb: 768,
|
||||
legacy_compositing: false,
|
||||
|
||||
@@ -131,8 +131,9 @@ pub enum Outcome {
|
||||
/// field's value is validated before the first one is applied, so a typo, an unknown field, a
|
||||
/// bad value or a bad nested key can't reply with success while nothing — or only half of it —
|
||||
/// changed (#412).
|
||||
pub const UI_SET_FIELDS: [&str; 37] = [
|
||||
pub const UI_SET_FIELDS: [&str; 38] = [
|
||||
"tool",
|
||||
"strokeTrace",
|
||||
"panels",
|
||||
"dock",
|
||||
"dockTabs",
|
||||
@@ -492,6 +493,19 @@ fn dispatch(app: &mut PhotocraftApp, ctx: &egui::Context, req: &ControlRequest)
|
||||
None => None,
|
||||
};
|
||||
let gradient_classic = bool_field(p, "gradientClassic")?;
|
||||
// The stroke latency trace (`stroke_trace`), at runtime: on (stderr) or off. A CSV
|
||||
// file is only for the user's own launch (`--stroke-trace <path>`): a path over the
|
||||
// control channel would write outside the automation write root.
|
||||
let stroke_trace = match p.get("strokeTrace") {
|
||||
None | Some(Value::Null) => None,
|
||||
Some(Value::Bool(b)) => Some(if *b { "stderr" } else { "off" }),
|
||||
Some(Value::String(v)) => match v.trim() {
|
||||
"stderr" | "on" | "true" => Some("stderr"),
|
||||
"off" | "false" => Some("off"),
|
||||
_ => return Err("strokeTrace must be true, false, \"stderr\" or \"off\"; a CSV file is set at launch (--stroke-trace <path>)".into()),
|
||||
},
|
||||
Some(_) => return Err("strokeTrace must be true, false, \"stderr\" or \"off\"".into()),
|
||||
};
|
||||
// The Eyedropper's options bar (#1649): Sample Size, Sample, Show Sampling Ring.
|
||||
let eyedropper_size = match p.get("eyedropperSampleSize") {
|
||||
Some(v) => Some(
|
||||
@@ -663,6 +677,9 @@ fn dispatch(app: &mut PhotocraftApp, ctx: &egui::Context, req: &ControlRequest)
|
||||
if let Some(mode) = gradient_blend {
|
||||
app.ui.tool_options.gradient_blend_mode = mode;
|
||||
}
|
||||
if let Some(v) = &stroke_trace {
|
||||
app.stroke_trace.set(v);
|
||||
}
|
||||
if let Some(classic) = gradient_classic {
|
||||
app.ui.tool_options.gradient_classic = classic;
|
||||
}
|
||||
@@ -1385,6 +1402,26 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_set_turns_the_stroke_trace_on_and_off_at_runtime() {
|
||||
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
|
||||
let ctx = egui::Context::default();
|
||||
assert!(!app.stroke_trace.enabled());
|
||||
assert_eq!(call(&mut app, &ctx, "ui.set", json!({"strokeTrace": true}))["ok"], true);
|
||||
assert!(app.stroke_trace.enabled());
|
||||
assert_eq!(call(&mut app, &ctx, "ui.set", json!({"strokeTrace": "off"}))["ok"], true);
|
||||
assert!(!app.stroke_trace.enabled());
|
||||
let bad = call(&mut app, &ctx, "ui.set", json!({"strokeTrace": 3, "tool": "Pencil"}));
|
||||
assert_eq!(bad["ok"], false);
|
||||
// A file path isn't accepted over the channel (it would write outside the automation
|
||||
// write root); nothing is created.
|
||||
let path = std::env::temp_dir().join(format!("photocraft-trace-{}.csv", std::process::id()));
|
||||
let bad = call(&mut app, &ctx, "ui.set", json!({"strokeTrace": path.to_str().unwrap(), "tool": "Pencil"}));
|
||||
assert_eq!(bad["ok"], false);
|
||||
assert!(!path.exists());
|
||||
assert!(!app.stroke_trace.enabled() && app.ui.tool != crate::state::Tool::Pencil, "a rejected call applies nothing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn camera_raw_control_updates_and_commits_or_cancels_an_open_raw() {
|
||||
use photocraft_raw::testgen::{DngSpec, mosaic, scene};
|
||||
|
||||
@@ -945,6 +945,9 @@ pub struct GpuInfo {
|
||||
pub canvas: String,
|
||||
/// Why the GPU canvas was dropped during this session (device lost or a GPU error).
|
||||
pub lost: Option<String>,
|
||||
/// Present mode and frame latency ("fifo, latency 2"; launch overrides marked, see the
|
||||
/// app's `presentation`). Empty in hosts that don't set it.
|
||||
pub present: String,
|
||||
}
|
||||
|
||||
impl GpuInfo {
|
||||
|
||||
@@ -3804,3 +3804,4 @@ navigator Zoom Zvětšení
|
||||
Take the shortcut from the other command and select it Odebrat zkratku druhému příkazu a vybrat ho
|
||||
Undo Changes Vrátit změny
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} je vyhrazena systémem a nelze ji přiřadit.
|
||||
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.
|
@@ -3198,3 +3198,4 @@ curves Smooth Glätten
|
||||
Take the shortcut from the other command and select it Den Kurzbefehl dem anderen Befehl entziehen und diesen auswählen
|
||||
Undo Changes Änderungen rückgängig machen
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} ist vom System reserviert und kann nicht zugewiesen werden.
|
||||
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.
|
@@ -3896,3 +3896,4 @@ curves Smooth Εξομάλυνση
|
||||
Take the shortcut from the other command and select it Αφαίρεση της συντόμευσης από την άλλη εντολή και επιλογή της
|
||||
Undo Changes Αναίρεση αλλαγών
|
||||
{shortcut} is reserved by the system and can't be assigned. Η συντόμευση {shortcut} δεσμεύεται από το σύστημα και δεν μπορεί να εκχωρηθεί.
|
||||
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.
|
@@ -4158,3 +4158,4 @@ navigator Zoom Zoom
|
||||
Take the shortcut from the other command and select it Quitar el método abreviado al otro comando y seleccionarlo
|
||||
Undo Changes Deshacer cambios
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} está reservado por el sistema y no se puede asignar.
|
||||
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.
|
@@ -3201,3 +3201,4 @@ curves Smooth Lisser
|
||||
Take the shortcut from the other command and select it Retirer le raccourci de l’autre commande et la sélectionner
|
||||
Undo Changes Annuler les modifications
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} est réservé par le système et ne peut pas être attribué.
|
||||
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.
|
@@ -4115,3 +4115,4 @@ cameraRaw Yellow Sárga
|
||||
Take the shortcut from the other command and select it A billentyűparancs elvétele a másik parancstól, és annak kijelölése
|
||||
Undo Changes Változások visszavonása
|
||||
{shortcut} is reserved by the system and can't be assigned. A(z) {shortcut} a rendszer számára fenntartott, nem rendelhető hozzá.
|
||||
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.
|
@@ -3188,3 +3188,4 @@ curves Smooth Haluskan
|
||||
Take the shortcut from the other command and select it Ambil pintasan dari perintah lain dan pilih perintah itu
|
||||
Undo Changes Batalkan Perubahan
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} dicadangkan oleh sistem dan tidak dapat ditetapkan.
|
||||
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.
|
@@ -4317,3 +4317,4 @@ shape Appearance Aspetto
|
||||
Take the shortcut from the other command and select it Togli la scorciatoia all'altro comando e selezionalo
|
||||
Undo Changes Annulla modifiche
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} è riservata dal sistema e non può essere assegnata.
|
||||
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.
|
@@ -3872,3 +3872,4 @@ curves Smooth スムーズ
|
||||
Take the shortcut from the other command and select it もう一方のコマンドからショートカットを外して選択
|
||||
Undo Changes 変更を取り消し
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} はシステムで予約されているため割り当てられません。
|
||||
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.
|
@@ -3374,3 +3374,4 @@ curves Smooth 매끄럽게
|
||||
Take the shortcut from the other command and select it 다른 명령에서 단축키를 빼고 그 명령 선택
|
||||
Undo Changes 변경 사항 취소
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut}은(는) 시스템에서 예약되어 할당할 수 없습니다.
|
||||
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.
|
@@ -3956,3 +3956,4 @@ curves Smooth Vloeiend maken
|
||||
Take the shortcut from the other command and select it De sneltoets van de andere opdracht afnemen en die selecteren
|
||||
Undo Changes Wijzigingen ongedaan maken
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} is door het systeem gereserveerd en kan niet worden toegewezen.
|
||||
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.
|
@@ -3794,3 +3794,4 @@ curves Smooth Wygładź
|
||||
Take the shortcut from the other command and select it Odbierz skrót drugiemu poleceniu i zaznacz je
|
||||
Undo Changes Cofnij zmiany
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} jest zarezerwowany przez system i nie można go przypisać.
|
||||
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.
|
@@ -3986,3 +3986,4 @@ layerLabel No Color Sem cor
|
||||
Take the shortcut from the other command and select it Tirar o atalho do outro comando e selecioná-lo
|
||||
Undo Changes Desfazer alterações
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} é reservado pelo sistema e não pode ser atribuído.
|
||||
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.
|
@@ -3166,3 +3166,4 @@ curves Smooth Сгладить
|
||||
Take the shortcut from the other command and select it Снять комбинацию с другой команды и выбрать её
|
||||
Undo Changes Отменить изменения
|
||||
{shortcut} is reserved by the system and can't be assigned. Комбинация {shortcut} зарезервирована системой и не может быть назначена.
|
||||
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.
|
@@ -4269,3 +4269,4 @@ shape Appearance Izgled
|
||||
Take the shortcut from the other command and select it Uzmi prečicu drugoj komandi i izaberi je
|
||||
Undo Changes Poništi izmene
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} je rezervisana od strane sistema i ne može se dodeliti.
|
||||
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.
|
@@ -3926,3 +3926,4 @@ cameraData Lens Lens
|
||||
Take the shortcut from the other command and select it Kısayolu diğer komuttan al ve o komutu seç
|
||||
Undo Changes Değişiklikleri Geri Al
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} sistem tarafından ayrılmıştır ve atanamaz.
|
||||
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.
|
@@ -3895,3 +3895,4 @@ curves Smooth Згладити
|
||||
Take the shortcut from the other command and select it Забрати скорочення в іншої команди й вибрати її
|
||||
Undo Changes Скасувати зміни
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} зарезервовано системою, його не можна призначити.
|
||||
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.
|
@@ -3682,3 +3682,4 @@ curves Smooth Làm mượt
|
||||
Take the shortcut from the other command and select it Lấy phím tắt khỏi lệnh kia và chọn lệnh đó
|
||||
Undo Changes Hoàn tác thay đổi
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} được hệ thống dành riêng và không thể gá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.
|
@@ -2812,3 +2812,4 @@ curves Smooth 平滑
|
||||
Take the shortcut from the other command and select it 从另一个命令移除该快捷键并选中它
|
||||
Undo Changes 撤消更改
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} 由系统保留,无法指定。
|
||||
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.
|
@@ -2797,3 +2797,4 @@ curves Smooth 平滑
|
||||
Take the shortcut from the other command and select it 從另一個指令移除該快速鍵並選取它
|
||||
Undo Changes 還原變更
|
||||
{shortcut} is reserved by the system and can't be assigned. {shortcut} 由系統保留,無法指定。
|
||||
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.
|
@@ -800,6 +800,12 @@ pub struct PhotocraftApp {
|
||||
/// `jobs_ui`). The desktop app turns it on; off (the default), everything runs inline as
|
||||
/// before, which tests and scripts rely on.
|
||||
pub background_jobs: bool,
|
||||
/// Shortest time between frames (ms), or `None` for no cap. A present mode that doesn't wait
|
||||
/// for the display (Low-Latency Drawing) would otherwise redraw as fast as it can while
|
||||
/// anything keeps requesting repaints (a drag hovering, autoscroll, Liquify held still).
|
||||
/// After #4472 (astigmata).
|
||||
pub min_frame_ms: Option<f64>,
|
||||
last_frame_start_ms: f64,
|
||||
/// Background job bookkeeping: opening tabs, control replies waiting on a job.
|
||||
pub jobs: jobs_ui::JobsUi,
|
||||
#[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
|
||||
@@ -935,6 +941,8 @@ impl PhotocraftApp {
|
||||
allow_close: false,
|
||||
stylus: Default::default(),
|
||||
background_jobs: false,
|
||||
min_frame_ms: None,
|
||||
last_frame_start_ms: 0.0,
|
||||
jobs: Default::default(),
|
||||
#[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
|
||||
live_tokens: theme::live::LiveTokens::from_env(),
|
||||
@@ -1759,6 +1767,20 @@ impl PhotocraftApp {
|
||||
}
|
||||
}
|
||||
|
||||
impl PhotocraftApp {
|
||||
/// Wait out the rest of [`Self::min_frame_ms`] since the last frame started.
|
||||
fn cap_frame_rate(&mut self) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(min) = self.min_frame_ms.filter(|m| m.is_finite() && *m > 0.0) {
|
||||
let wait = min.min(100.0) - (gpu_canvas::now_ms() - self.last_frame_start_ms);
|
||||
if wait > 0.0 {
|
||||
std::thread::sleep(std::time::Duration::from_secs_f64(wait / 1000.0));
|
||||
}
|
||||
}
|
||||
self.last_frame_start_ms = gpu_canvas::now_ms();
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for PhotocraftApp {
|
||||
fn on_exit(&mut self) {
|
||||
if let Err(err) = tool_memory::save_on_exit(self) {
|
||||
@@ -1767,6 +1789,7 @@ impl eframe::App for PhotocraftApp {
|
||||
}
|
||||
|
||||
fn logic(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
self.cap_frame_rate();
|
||||
i18n::set_current(i18n::Lang::from_pref(&self.session.prefs().interface.language));
|
||||
// Caps Lock state read from the OS once per frame (see `Services::caps_lock`): the canvas
|
||||
// cursor block needs it before the frame renders. `None` (Wayland/web) keeps `false`.
|
||||
@@ -2004,7 +2027,10 @@ impl eframe::App for PhotocraftApp {
|
||||
if self.stroke_trace.enabled() {
|
||||
self.stroke_trace.canvas(self.perf.frame_refresh_ms);
|
||||
let info = &self.perf.gpu_info;
|
||||
self.stroke_trace.end_frame(ui_ms, self.live_stroke.is_some(), || format!("{} {}", info.backend, info.adapter).trim().to_string());
|
||||
self.stroke_trace.end_frame(ui_ms, self.live_stroke.is_some(), || {
|
||||
let gpu = format!("{} {}", info.backend, info.adapter).trim().to_string();
|
||||
if info.present.is_empty() { gpu } else { format!("{gpu}, {}", info.present) }
|
||||
});
|
||||
}
|
||||
// Synthetic input is injected one press/release step per frame: keep frames coming until
|
||||
// the queue is empty, then release control replies waiting on it.
|
||||
|
||||
@@ -1120,6 +1120,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 {
|
||||
@@ -1556,6 +1561,8 @@ fn field_visible(section: &str, obj: &Map<String, Value>, k: &str, query: &str)
|
||||
// View › Rulers (⌘R) sets it, as in Photoshop.
|
||||
|| path == "unitsAndRulers.showRulers"
|
||||
|| (section == "performance" && matches!(k, "useGpu" | "gpuBackend" | "renderingMode"))
|
||||
// The browser presents frames itself: Low-Latency Drawing does nothing there.
|
||||
|| (cfg!(target_arch = "wasm32") && path == "performance.lowLatencyDrawing")
|
||||
|| (section == "export" && !export_field_visible(obj, k))
|
||||
|| (section == "tools" && !tools_field_visible(obj, k));
|
||||
(query.is_empty() || preference_matches(section, k, query))
|
||||
|
||||
@@ -113,13 +113,32 @@ impl StrokeTrace {
|
||||
/// Reads `PHOTOCRAFT_STROKE_TRACE` (off on the web, where there is no clock).
|
||||
pub fn from_env() -> Self {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(v) = std::env::var_os("PHOTOCRAFT_STROKE_TRACE").filter(|v| !v.is_empty()) {
|
||||
let output = if v == "1" || v == "stderr" { Output::Stderr } else { Output::Csv(v.into()) };
|
||||
return Self { output, ..Default::default() };
|
||||
if let Some(v) = std::env::var_os("PHOTOCRAFT_STROKE_TRACE") {
|
||||
return Self::from_value(&v.to_string_lossy());
|
||||
}
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// A trace set by value: `""`, `"0"` or `"off"` is off, `"1"` or `"stderr"` prints
|
||||
/// summaries, anything else is a CSV path (`--stroke-trace`, `PHOTOCRAFT_STROKE_TRACE`; the
|
||||
/// control channel's `ui.set {"strokeTrace"}` passes only on or off, never a path).
|
||||
pub fn from_value(v: &str) -> Self {
|
||||
let v = v.trim();
|
||||
let output = match v {
|
||||
_ if cfg!(target_arch = "wasm32") => Output::Off,
|
||||
"" | "0" | "off" | "false" => Output::Off,
|
||||
"1" | "stderr" | "true" => Output::Stderr,
|
||||
path => Output::Csv(path.into()),
|
||||
};
|
||||
Self { output, ..Default::default() }
|
||||
}
|
||||
|
||||
/// Switch the output, keeping the last summary.
|
||||
pub fn set(&mut self, v: &str) {
|
||||
let last = self.last.take();
|
||||
*self = Self { last, ..Self::from_value(v) };
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.output != Output::Off
|
||||
}
|
||||
@@ -265,6 +284,19 @@ mod tests {
|
||||
assert!(off.last.is_none() && off.rows.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_trace_is_set_by_value() {
|
||||
assert_eq!(StrokeTrace::from_value("off").output, Output::Off);
|
||||
assert_eq!(StrokeTrace::from_value(" 0 ").output, Output::Off);
|
||||
assert_eq!(StrokeTrace::from_value("stderr").output, Output::Stderr);
|
||||
assert_eq!(StrokeTrace::from_value("true").output, Output::Stderr);
|
||||
assert_eq!(StrokeTrace::from_value("t.csv").output, Output::Csv("t.csv".into()));
|
||||
let mut t = on();
|
||||
t.last = Some(Summary::default());
|
||||
t.set("off");
|
||||
assert!(!t.enabled() && t.last.is_some(), "turning it off keeps the last summary");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn csv_rows_and_odd_numbers() {
|
||||
let rows = [Row { t: 1.0, interval: 16.5, moves: 2, stroke: 0.25, canvas: 1.0, ui: 3.0, cpu_prev: None }];
|
||||
|
||||
@@ -53,6 +53,7 @@ The transport is `apps/photocraft/src/control_server.rs`, and the handlers are i
|
||||
- `ui.set` also accepts `gradientBlendMode` (a layer blend mode name, such as `Difference`) and `gradientClassic` (boolean) for the Gradient tool options bar.
|
||||
- `ui.set` also accepts `camTransformOnDrop` (boolean), the Content-Aware Move tool's Transform On Drop checkbox: a drop then puts a scale/rotate box on the patch (`ui.inspect` › `camTransform`: its document/revision, drag `offset` and `frame`), driven with `ui.pointer` (handles as Free Transform's; outside the box rotates), committed with `ui.key Enter` (one `paint.contentAwareMove` with `scaleX`, `scaleY`, `rotate`) or dropped with `Escape`.
|
||||
- `ui.set` also accepts `tileGrid` (boolean), a developer overlay of the active layer's storage tiles: the tile grid over the document, every stored tile filled and labelled `tx,ty`, tiles with identical pixels in a shared colour and unique ones in blue. It has no menu item and View › Extras doesn't hide it. `ui.inspect` reports it as `view.tile_grid`.
|
||||
- `ui.set` also accepts `strokeTrace` (`true`, `false`, `"stderr"` or `"off"`): turn the stroke latency trace on or off at runtime; each stroke's summary is in `ui.inspect` `perf.strokeTrace`. A CSV file of every frame is set only at launch (`PHOTOCRAFT_STROKE_TRACE=<file.csv>` / `--stroke-trace <file.csv>`): the control channel doesn't write files outside the automation write root
|
||||
- `ui.set` also accepts `layerFilter` (#5048), the Layers panel filter row: `{by?, kinds?, name?, mode?}`, any subset. `by` is `kind`, `name` or `mode` (the filter-type dropdown); `kinds` lists the Kind toggles that are on (`pixel`, `adjustment`, `type`, `shape`, `smart`); `name` matches part of a layer name, ignoring case; `mode` is a blend mode name (`Multiply`, `Pass Through`). Kind and Name filter only once a kind is on or text is typed; Mode always filters, by Normal at first, as in Photoshop. Fields left out keep their value; a bad one is an error and changes nothing.
|
||||
- Each painting tool keeps its own brush, as in Photoshop: `tools.setBrush` and `ui.set {brushSize}` change the active tool's brush, and switching tools swaps in that tool's own (size, hardness, mode, opacity, flow, dynamics, smoothing). A tool used for the first time starts from the brush in hand. The colours, the erase flag and the stroke seed stay shared. So `ui.set {tool: "eraser", brushSize: 12}` sets the Eraser's size, and `ui.set {tool: "brush"}` afterwards brings the Brush's own size back.
|
||||
- `ui.set` also accepts the Eyedropper options bar (#1649): `eyedropperSampleSize` (`"point"` or 1, 3, 5, 11, 31, 51, 101: the side of the averaged square, shared by the Eyedropper, a painting tool's Alt-click eyedropper, the Curves/Color Picker eyedroppers and the Info panel), `eyedropperSample` (`current`, `currentAndBelow`, `all`, `allNoAdjustments`, `currentAndBelowNoAdjustments`) and `eyedropperRing` (boolean, Show Sampling Ring). `ui.inspect` reports them under `toolOptions` (`eyedropper_size`, `eyedropper_sample`, `eyedropper_ring`). The engine command `document.sampleColor {x, y, size, sampleLayer}` returns the colour such a sample picks.
|
||||
|
||||
+4
-2
@@ -119,7 +119,9 @@ By default PhotoCraft's own crates log at `info` and everything else at `warn`.
|
||||
| `PHOTOCRAFT_CPU_COMPOSE=1` | Keep the wgpu canvas but composite on the CPU (compare GPU vs CPU renders, e.g. with the snapshot example) |
|
||||
| `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_STROKE_TRACE=1` (or `=<file.csv>`; also `--stroke-trace <stderr|file.csv>`, or `ui.set {"strokeTrace": true|false}` at runtime, without a file) | 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` (or `--present-mode`) | Present mode for this launch: `fifo`, `autovsync`, or `novsync` (also `immediate`, `mailbox`: no vsync, the first of Immediate, Mailbox, Fifo the driver offers). Default: vsync, or no vsync when Preferences › Performance › Low-Latency Drawing is on (it ships off). For latency comparisons; shown in the stroke trace |
|
||||
| `PHOTOCRAFT_FRAME_LATENCY=1` (or `--frame-latency`) | 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 |
|
||||
|
||||
### Live design tokens
|
||||
@@ -148,7 +150,7 @@ stroke trace: 62 frames on vulkan NVIDIA GeForce RTX 5090 (p50/p95 ms): interval
|
||||
| `cpu` | eframe's CPU time per frame: UI, tessellation and GPU submission, without the vsync wait |
|
||||
| `wait` | `interval − cpu`: waiting on vsync or the swap chain, or idle |
|
||||
|
||||
What the app can't time is the rest of input-to-photon: the OS input queue before a frame starts (up to one `interval`), frames queued for the display (the surface keeps up to two; the GL backend ignores that setting), and the compositor. So the first line names the backend. The last summary is also in the control channel's `perf.strokeTrace` (`ui.inspect`). The code is `crates/ui-egui/src/stroke_trace.rs`.
|
||||
What the app can't time is the rest of input-to-photon: the OS input queue before a frame starts (up to one `interval`), frames queued for the display (the surface keeps up to two; the GL backend ignores that setting), and the compositor. So the first line names the backend and how frames are presented. To compare presentation settings without rebuilding, launch with `PHOTOCRAFT_PRESENT_MODE` and `PHOTOCRAFT_FRAME_LATENCY` (see [Environment variables](#environment-variables)): with `novsync` the app no longer waits on vsync (`wait` near 0): each refresh shows the newest frame, and where the driver only offers Immediate, the screen may tear. The last summary is also in the control channel's `perf.strokeTrace` (`ui.inspect`). The code is `crates/ui-egui/src/stroke_trace.rs`.
|
||||
|
||||
## Driving the app programmatically
|
||||
|
||||
|
||||
+2
-2
@@ -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 | 14 | 6 | io corpus: oracle ≥ 148/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 249/309, round trip ≥ 308/309; photoshop oracles: oracle ≥ 188/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 166 (target 0) |
|
||||
| [Workspace and UI](#workspace-and-ui) | 13 | 6 | 4 | settings that do nothing: 26 of 167 (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 29 of 29 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 166** (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 167** (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`.
|
||||
|
||||
|
||||
Reference in new issue
Block a user