Loading...
How it works
This demo renders a ray-traced scene using Web Workers that share memory through SharedArrayBuffer, powered by cljs-thread and the EVE AtomDomain.
-
Scene + framebuffer are stored in a shared EVE
atombacked by SharedArrayBuffer. Workersderefthe atom to read scene data and write pixels directly to the shared framebuffer — zero serialization. -
Work distribution uses
pmapto parallelize tile rendering across all available workers. Each tile is rendered independently. - Live preview — the screen thread runs a 60fps draw loop, continuously blitting the shared framebuffer to canvas while workers render.
The Core Parallelism
The entire render loop is just a parallel map over tile indices:
;; Render all tiles in parallel across workers
(pmap (fn [tile-idx]
(render-tile! app-state tile-idx
tile-w tile-h img-w img-h spp))
tile-indices)
Each worker reads scene data from the shared atom and writes directly to the shared framebuffer:
;; Worker — render one tile directly to shared framebuffer
(defn render-tile! [app-state tile-idx tile-w tile-h img-w img-h spp]
(let [state @app-state
{:keys [spheres materials camera-opts]} (unpack-scene state)
camera (cam/make-camera camera-opts)
framebuffer (arr/get-typed-view (:framebuffer state))]
(render-tile-to-framebuffer! tile-idx tile-w tile-h img-w img-h spp
framebuffer camera spheres materials)
tile-idx))
The screen thread continuously draws the framebuffer while rendering proceeds:
;; Screen thread draw loop (runs at 60fps)
(in :screen
(letfn [(draw-loop []
(when (:rendering? @app-state)
(let [buf (arr/get-typed-view (:framebuffer @app-state))
img-data (.createImageData ctx img-w img-h)]
(.set (.-data img-data) buf)
(.putImageData ctx img-data 0 0))
(js/requestAnimationFrame draw-loop)))]
(js/requestAnimationFrame draw-loop)))