mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-28 02:02:04 +01:00
* Distance fields: the Euclidean passes run on threads, results unchanged (#2119) The chiselled bevel fields (#2006) and the precise glow fields are built from an exact Euclidean distance transform, a few sweeps over the whole map that all ran on one thread. They now run on scoped OS threads, in groups of rows or columns handed out from a queue: edt_nearest (the column pass, then the row pass), sharp_edges (rows), refine_edge_normals (one edge pixel each) and the final per-pixel maps of the bevel and plain Euclidean fields. Not rayon, as for the blurs (rows_par): maps are built from inside rayon tiles, where a waiting worker steals other tiles (#276). Maps under 65536 pixels stay on the calling thread, wasm always does. Every unit is computed from its own inputs, in the same arithmetic as before, so the fields are bit for bit the old ones whatever the thread count: 216 fingerprints (bevel in and out, plain in and out, bevel_maps on bars at 0 to 150 degrees, thin bars, discs, rounded rectangles, soft, patchy and half-opaque masks, 40 stars, tiny and 1 pixel maps) are equal at 1, 7, 16 and the default threads, and equal to the code before. edt_nearest returns the nearest pixel as u32 (it was usize), which takes its working memory from 20 to 16 bytes per pixel; a map past 2^32 pixels or a slice that does not match its size gets "far" distances instead of a panic. CPU compositor, 12 MP, bevel size change (ms, bevel_bench), main -> this: ellipse 474 -> 217 (size 8), 990 -> 318 (250); text 747 -> 214 (8), 1110 -> 267 (250). A distance_field call on a 256x256 map goes from 2.2 to 1.8 ms, on 512x512 from 8.0 to 2.8, on 2048x2048 from 99 to 15. Tests: par_units hands every unit to the closure once with its own slices (short last unit, no second slice, units of different sizes, 1 to 64 threads); the nearest-pixel transform equals itself on 1, 2, 3 and 8 threads on awkward sizes and returns a seed at the distance it reports; degenerate sizes; sharp edges and the four Euclidean fields are bit-equal on 1, 2, 5 and 16 threads on discs, rounded rectangles, bars at several angles, patchy opacity and an edge on the border. The nested-rayon deadlock test (#276) now has a chiselled bevel layer in its document. * GPU effect maps: the Euclidean fields are one window, not bands (#2119) A bevel size change rebuilds the distance fields over the whole effect region (the region grows with the size). The GPU path cut that into bands of max(128, halo) rows, each computed from a window grown by the halo, which for the bevel fields is reach + 10: at size 250, 262-row bands from windows of about 786 rows. Every pixel went through the transform and the edge refinement about three times, a 12 MP region gave about 12 tasks for 32 threads, and the two bevel fields ran one after the other. The Euclidean fields (chiselled bevels, precise glows) now come from one window, and distance_field spreads its own passes over the threads. A rebuild is the whole region, which is also what the CPU compositor computes, so the GPU canvas gets exactly the CPU's field: bands resolved ties between equidistant edge pixels per window, and on a rebuild they differed from the whole region by up to 0.07 px (ellipse) and 1.3 px (600 discs) at size 250, on a few dozen pixels in 12 MP. A partial update (a brush dab) still computes the damage grown by the halo, from one window. The chamfer fields (shadows, strokes, spread) are a sequential sweep and keep the bands, and so does wasm, where there are no threads to spread over and bands keep the memory small. RTX 4080 SUPER, release, 12 MP, ms per size change on the GPU canvas (bevel_bench from #2118, p50, median of 3 runs, before #2006 / main / this), ellipse: 90 / 103 / 98 (8), 162 / 188 / 119 (50), 286 / 446 / 219 (250); text: 83 / 139 / 113, 140 / 215 / 130, 222 / 489 / 165. Alone in the process (--no-cpu) ellipse at 250 is 407 -> 157 and text 468 -> 169. 36 MP ellipse at 250: 779 -> 382. Peak RSS: 12 MP at 250 1604 -> 862 MiB, 36 MP at 250 2987 -> 1726 MiB; at size 8 on 36 MP it is 163 MiB more (1380 -> 1543). Precise glow fields on 12 MP: 46-84 ms -> 26 ms at reach 52-252 (32 ms at 12). The window test now checks that the whole region is bit for bit the CPU's field, and that a too small halo is noticed on a part of the region. --------- Co-authored-by: Brandon Thomas <echelon@gmail.com>