Commit Graph
5 Commits
Author SHA1 Message Date
Dylan M. Taylor 2b0b11848c Refactor paint for strict Clippy checks 2026-10-27 23:47:06 +00:00
UnemarsandClaude Fable 5.1 4b06b0cc5a Brush: small tips keep their speed next to the parallel large ones
The first version slowed small brushes by about a third (reviewer's measurement: a 20 px soft zigzag 856 → 1736 ms over 6000 dabs), for three reasons, all gone:

- `CoverageMap::accumulate` looked the dab's tiles up by walking the whole tile map (`iter_mut().filter_map`) on every dab, so a long stroke's thousands of tiles taxed every small dab. The touched tiles now leave the map (`remove`), accumulate, and go back: one to four lookups for a small tip.
- The composite allocated a fresh 64 KB buffer per tile per frame (6872 `brk` calls per 3000 dabs against main's 542): the heap grew and trimmed every frame. The buffers live in a pool on the renderer (`RegionPool`, emptied on clone) and are reused from composite to composite.
- The row / tile bodies passed as closures to the helpers cost a 20 px tip's `rasterize` half as much again (7.5 → 11.6 µs). They are `#[inline(always)]` steps on `Copy` structs (`RoundTip::row`, `DabValues::tile`), called from plain loops below the threshold and from rayon above it; `rasterize` is back to main's time.

`examples/stroke_bench.rs` times a long zigzag stroke of one tip, push + composite per point (best of four, loaded machine; main → now): 20 px 667 → 706 ms, 50 px 1805 → 1734 ms, 150 px 11 017 → 5163 ms.

Comments: the per-tile copy of `src` in the composite is the intended reading (a tile without per-dab colour only occurs in strokes without per-dab colour, where `src` is never rewritten), and `set_parallel_min_pixels` changes a process-wide static harmlessly. `parallel()` and the helpers' `pixels` are `cfg`'d so the wasm build has no unused warnings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-18 15:53:07 +02:00
UnemarsandClaude Fable 5.1 7deeaeeba1 Brush: large tips rasterize, accumulate and composite in parallel
A 2000 px soft tip is a 12 MP dab, and every dab ran on one thread: the tip's
coverage (92 ms), its accumulation into the stroke's tiles (44 ms) and the
composite of the dirty tiles into the layer (177 ms), about 300 ms a dab, so a
stroke across a 24 MP photo lagged by a quarter second per frame. Photoshop
keeps such strokes fluid by spreading them over the cores.

Each stage is independent per row (the round tip's coverage) or per tile (the
accumulation, the composite), so they now run on the thread pool from 256²
pixels of work up, to the same pixels: the per-pixel maths is untouched, the
dither and Dissolve hash positions, and a test renders a 700 px soft stroke,
a Dissolve stroke and a per-dab-colour stroke (with a selection, 8-bit) both
ways and compares every pixel. Below the threshold, and on the web, nothing
changes. The composite's writes to the layer stay in order; its tiles are
composited in batches so the pending regions stay small.

`examples/big_dab_bench.rs` prints the stages of one dab. On a loaded 16-thread
machine the 12 MP dab went from about 300 ms to 100–140 ms; the hard 2000 px
tip from 75 to 35 ms.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-18 01:16:29 +02:00
Brandon ThomasandClaude Opus 5.5 f7eb6ea9e7 Format: adopt the craft-family rustfmt.toml and format the workspace
CI's `cargo fmt --all -- --check` has failed on every run because the
repo had no rustfmt config and was never formatted. Use the same settings
as EffectCraft/FilmCraft (max_width 160, use_small_heuristics Max), which
also keeps the churn small. Formatting only; no code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:31:17 -04:00
Brandon Thomas 536a7346fb more algorithms, progress 2026-10-01 08:25:06 -04:00