mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-30 05:16:56 -06:00
M7: performance: retained canvas renders, virtual layers panel, shared text layouts, cargo xtask bench (#35)
Large documents stay interactive: the canvas re-renders only what a pan uncovers or an edit touches, the layers panel lays out only the rows in view, and per-frame work no longer walks the whole page. - Benchmark: `cargo xtask bench [SIZES]` builds synthetic pages (`figcraft_engine::synthetic`: screens with an auto layout header, a card group, button instances and shapes/text) at 1k/10k/50k layers and prints engine/render timings (`figcraft-cli perf --layers 1000,10000`) and headless whole-UI frame timings (an ignored ui-egui test). - Retained canvas rendering (`figcraft_render::Retained`, used by the render worker): views snap to whole device pixels, so a pan shifts the previous image in place and renders only the uncovered strips; an edit renders only where changed layers were and are (`Document::changed_ids` diffs the two versions skipping shared structure; ancestors that paint from their children's pixels - booleans, effects, masks - count whole; background blur reach and noise are allowed for). Zoom, page, options, font or page-level changes, or large changes, render the whole view as before. `ui.inspect` reports `canvas.renderWork`. - The texture image is converted on the render thread, not the UI thread. - Layers panel: only visible rows are laid out (stable ids while scrolling); rows are cached per document version, page, expanded set and filter. - Frame labels and layout grids of off-screen top-level frames are skipped; the Prototype tab's connection sources are cached per document version. - Text: the layout cache keys by a cheap fingerprint confirmed by `TextData` equality (no Debug formatting per lookup, 32k entries) and hands out shared layouts; a layout generation marks renders stale when fonts arrive. Overflowing text is culled by its glyphs, so it no longer vanishes while its box is off screen. Before -> after (release, Windows, 1440x900 view, ms; 10k / 50k layers): - move one layer, re-render @100%: 7.65 / 6.49 -> 0.18 / 0.81 - move one layer, re-render zoomed out: 11.95 / 24.10 -> 0.16 / 0.77 - pan 40 px @100%: 7.53 / 6.82 -> 0.69 / 0.81 - pan 40 px zoomed out: 12.07 / 24.30 -> 1.62 / 4.17 - full render @100% / zoomed to fit: 7.78 / 44.22 -> 6.56 / 38.83 (10k @100%, 50k fit) - UI frame, every screen expanded in layers: 6.16 / 39.07 -> 0.24 / 0.57 - UI frame, scrolling that panel: 6.11 / 40.18 -> 0.43 / 0.40 - UI frame, layers filtered by name: 1.03 / 6.92 -> 0.17 / 0.36 - UI frame, Prototype tab: 0.89 / 6.45 -> 0.18 / 0.80 - UI frame, idle: 0.31 / 1.43 -> 0.11 / 0.39 Unchanged and within budget: single-layer edits through the engine ~0.01 ms, normalize after a one-node edit < 0.01 ms, hit test 26 / 93 us. Left: save/load at 50k (~0.7 s each), zoomed-out renders at 50k (~40 ms). Tests: retained renders match full renders after pans, moves, recolours, hide/remove/add, undo, masks, overflowing text, edits under a background blur and an inner-shadow/noise layer, a drop- shadowed group, and a blur panned in from the view's edge; zoom/page/options/page background render full; junk views don't panic. Layer-row and prototype-source caches follow edits, undo, page switches, expansion and the filter. `changed_ids`, the text cache, and the perf CLI. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+20
-1
@@ -7,6 +7,7 @@
|
||||
//! - `version` print the version, or `version set X.Y.Z`
|
||||
//! - `icon` regenerate the app icon PNGs/ICO from code (original artwork)
|
||||
//! - `wasm` check the core crates and web app for wasm32
|
||||
//! - `bench` release-build timings at 1k/10k/50k layers (`bench 1000,10000` for other sizes)
|
||||
|
||||
mod icon;
|
||||
mod layers;
|
||||
@@ -37,8 +38,9 @@ fn main() -> ExitCode {
|
||||
"version" => version(args.get(1).map(String::as_str), args.get(2).map(String::as_str)),
|
||||
"icon" => icon::generate(&root()),
|
||||
"wasm" => wasm(),
|
||||
"bench" => bench(args.get(1).map_or("1000,10000,50000", String::as_str)),
|
||||
_ => {
|
||||
eprintln!("tasks: ci, layers, assets, parity, version [set X.Y.Z], icon, wasm");
|
||||
eprintln!("tasks: ci, layers, assets, parity, version [set X.Y.Z], icon, wasm, bench [SIZES]");
|
||||
task == "help"
|
||||
}
|
||||
};
|
||||
@@ -58,6 +60,23 @@ fn wasm() -> bool {
|
||||
run(cargo().args(["check", "--target", "wasm32-unknown-unknown", "-p", "figcraft-engine", "-p", "figcraft-ui-egui", "-p", "figcraft-web"]))
|
||||
}
|
||||
|
||||
/// Engine/render timings (`figcraft-cli perf`) and UI frame timings (an ignored test driving the
|
||||
/// whole UI headless), release builds, for each comma-separated document size in layers.
|
||||
fn bench(sizes: &str) -> bool {
|
||||
run(cargo().args(["run", "--release", "-p", "figcraft-cli", "--", "perf", "--layers", sizes]))
|
||||
&& run(cargo().env("FIGCRAFT_BENCH_LAYERS", sizes).args([
|
||||
"test",
|
||||
"--release",
|
||||
"-p",
|
||||
"figcraft-ui-egui",
|
||||
"--lib",
|
||||
"perf_ui_frames",
|
||||
"--",
|
||||
"--ignored",
|
||||
"--nocapture",
|
||||
]))
|
||||
}
|
||||
|
||||
/// Every asset file must be listed in ATTRIBUTION.md.
|
||||
fn assets() -> bool {
|
||||
let root = root();
|
||||
|
||||
Reference in New Issue
Block a user