Speed up the frame, and drop loop/recur from the domain
The frame went 5.86ms to 3.17ms -- a 170fps ceiling to 315 -- and `loop`/`recur` is gone from src/ entirely. Two real wins, both from measuring rather than guessing: - `->rgba` was 3.16ms of that frame and was scene-independent: a `nth` into a vector of vectors is four protocol dispatches per pixel, 64,000 pixels a frame. The palette is now flattened once and cached by identity of the source vector -- palettes are values, so identity is exactly the right test and there is no invalidation to get wrong. At zoom 1 on a little-endian machine the inner loop is one 32-bit write per pixel through a Uint32Array view of the same buffer: 0.11ms, 28x. Every other case walks bytes off the same flat palette. raster-test pins both against a naive per-pixel reference at three zooms, because a fast path that is subtly wrong about colour would look like a palette bug rather than like an optimisation. - The per-frame z sort was re-deriving a constant. Draw order is a function of the z paths, which change when the scene changes and never because the playhead moved, so `draw-rank` computes it once and a frame sorts small integers. Every op drops its `:i` and `:z-path` fields as a result. The loop pass, and an honest note on it: it came out NET POSITIVE on lines, which is the wrong direction for a cleanup. geom is -3 (transduce for the accumulators, `(-> (iterate refine ref) (nth iters))` for Procrustes, which is what the algorithm says rather than a counter that happens to stop), channel -3, fill-poly!'s copy loop 7 lines to 1. Against that, eval-into went from one four-deep pyramid with seven positional parameters to `place` / `emit` / a fold over a ctx map -- less nesting, more lines, and a different change from "fix the loops" that should not have been bundled with it. Two idioms were reverted for being worse here than what they replaced, both the same mistake -- reaching for a form that allocates inside a hot loop: - `partition 2` over an `array-seq` per scanline is some five thousand throwaway objects a frame and took draw from 0.88ms to 1.48ms. Now a pairwise `dotimes` over the array. - `z-lex` via `(map compare a b)` allocated three lazy seqs per call, ~700 calls a frame. Made moot by `draw-rank`. And one DRY move reverted for coupling things that only coincide: a `geom-path` table had `node/valid-paths` and `scene/emit` deriving from one source, which ties what a kind may CARRY to what the renderer READS off it. Those are the same today and are not the same question, and the table put a spec change in charge of what gets drawn, across a namespace boundary. `emit`'s three branches are three different marks and stay three branches. Kept, because it is one operation with two callers rather than two concerns that rhyme: `lineage`, which `depth` and `z-path` were both walking separately. Its cycle check is now a length bound -- a chain that does not repeat cannot be longer than the node count -- instead of a `seen` set. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PDfHGdV39zu6rvgbBTfDaT
This commit is contained in:
parent
18d6495592
commit
11192d61c6
7 changed files with 396 additions and 288 deletions
|
|
@ -189,3 +189,32 @@
|
|||
(is (= #{0 1 2 4} (set (array-seq (:buf ras)))))
|
||||
(is (thrown-with-msg? ExceptionInfo #"not rasterisable"
|
||||
(r/draw-ops! ras [{:kind :bitmap}])))))
|
||||
|
||||
(deftest rgba-matches-a-naive-reference-at-every-zoom
|
||||
;; The fast path writes one 32-bit word per pixel through a Uint32Array view,
|
||||
;; which is a very different thing from the four byte writes it replaced. A
|
||||
;; mistake in it — a channel order, an endianness assumption, an off-by-one on
|
||||
;; the row — would present as a palette bug rather than as an optimisation, so
|
||||
;; it is pinned against the obvious implementation rather than trusted.
|
||||
(let [naive (fn [{:keys [w h buf]} palette zoom]
|
||||
(let [W (* w zoom) H (* h zoom)
|
||||
d (js/Uint8ClampedArray. (* W H 4))]
|
||||
(dotimes [y H]
|
||||
(dotimes [x W]
|
||||
(let [c (or (nth palette (aget buf (+ (* (quot y zoom) w) (quot x zoom))) nil)
|
||||
[255 0 255])
|
||||
o (* (+ (* y W) x) 4)]
|
||||
(aset d o (nth c 0)) (aset d (+ o 1) (nth c 1))
|
||||
(aset d (+ o 2) (nth c 2)) (aset d (+ o 3) 255))))
|
||||
(vec (array-seq d))))
|
||||
ras (r/make 23 17)] ; deliberately not a round size
|
||||
(dotimes [i (* 23 17)]
|
||||
(aset (:buf ras) i (if (zero? (mod i 13)) 200 (mod i (count pal/rgb)))))
|
||||
(doseq [zoom [1 2 3]]
|
||||
(is (= (naive ras pal/rgb zoom)
|
||||
(vec (array-seq (:data (r/->rgba ras pal/rgb zoom)))))
|
||||
(str "zoom " zoom)))
|
||||
(testing "and writing into a caller's buffer gives the same bytes"
|
||||
(let [dest (js/Uint8ClampedArray. (* 23 17 4))]
|
||||
(is (= (naive ras pal/rgb 1)
|
||||
(vec (array-seq (:data (r/->rgba ras pal/rgb 1 dest))))))))))
|
||||
|
|
|
|||
|
|
@ -291,7 +291,8 @@
|
|||
;; ---- the hand-written scene, end to end ----
|
||||
|
||||
(deftest the-hand-written-scene-is-valid
|
||||
(is (= "" (scene/problems-str demo/scene)))
|
||||
(let [ps (scene/problems demo/scene)]
|
||||
(is (empty? ps) (pr-str ps)))
|
||||
(is (pos? (:frames demo/scene))))
|
||||
|
||||
(deftest the-hand-written-scene-renders-and-moves
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue