nx_sdfrender_mt.nx source
↩ module page · 63 lines · 2812 B
1// nx_sdfrender_mt.nx -- R1c of the sovereign software-GPU ladder: the SDF renderer fanned out across CPU
2// cores on nx_thread_pool (the 2026-07-07 threading-toolchain close applied to a real organ). ADDITIVE lane:
3// nx_sdfrender itself stays single-threaded + wasm-safe; only THIS organ pulls in threads (native surfaces --
4// showcase, gates, asset bakers). DETERMINISM LAW: a row task writes ONLY its own fb rows and reads the part
5// arena read-only, so the threaded frame is BYTE-IDENTICAL to sdf_render under ANY scheduling. Gate-proven
6// (nx_sdfrender_mt_gate T1/T2/T5), not asserted. license_tier: ORIGINAL
7import "nx_sdfrender.nx"
8import "nx_thread_pool.nx"
9
10// one per-row job record (8 i64 = 64B). Per-ROW tasks, not fixed bands: the figure is center-weighted so row
11// cost is uneven; the shared MPMC queue load-balances rows across workers for free.
12struct SdfMtJob {
13 jb_base: i64,
14 jb_yaw: i64,
15 jb_camz: i64,
16 jb_sr: i64,
17 jb_sg: i64,
18 jb_sb: i64,
19 jb_y0: i64,
20 jb_y1: i64,
21}
22const SDFMT_JOB_BYTES: i64 = 64
23
24func _sdfmt_task(ctx: i64) -> i64 {
25 let j: *SdfMtJob = ctx as *SdfMtJob
26 sdf_render_rows(j.jb_base, j.jb_yaw, j.jb_camz, j.jb_sr, j.jb_sg, j.jb_sb, j.jb_y0, j.jb_y1)
27 return 0
28}
29
30// render one frame on an EXISTING pool (reusable across frames). NOTE the pool task arena is 16384 slots per
31// pool LIFETIME (slots are not recycled yet); per-row jobs burn H (384) per frame -> ~42 frames per pool.
32// Showcase-scale fits comfortably; recreate the pool for longer runs.
33func sdfmt_render_pool(pool: *NxThreadPool, base: i64, yaw: i64, camz_units: i64, skin_r: i64, skin_g: i64, skin_b: i64) -> i64 {
34 let hpx: i64 = hh()
35 let jb: i64 = sys_mmap(hpx * SDFMT_JOB_BYTES) as i64
36 let start: i64 = nx_pool_n_completed(pool)
37 var y: i64 = 0
38 while y < hpx {
39 let cx: i64 = jb + y * SDFMT_JOB_BYTES
40 let j: *SdfMtJob = cx as *SdfMtJob
41 j.jb_base = base
42 j.jb_yaw = yaw
43 j.jb_camz = camz_units
44 j.jb_sr = skin_r
45 j.jb_sg = skin_g
46 j.jb_sb = skin_b
47 j.jb_y0 = y
48 j.jb_y1 = y + 1
49 if nx_pool_submit(pool, _sdfmt_task, cx as i64) != 0 { return 0 - 2 }
50 y = y + 1
51 }
52 if nx_pool_wait(pool, start + hpx) != 0 { return 0 - 1 }
53 return 0
54}
55
56// one-shot convenience: spin a pool (nworkers <= 0 -> one per hardware worker), render, shut down. Thread
57// spawn cost is microseconds against a multi-second frame; hold a pool via sdfmt_render_pool to reuse.
58func sdfmt_render(base: i64, yaw: i64, camz_units: i64, skin_r: i64, skin_g: i64, skin_b: i64, nworkers: i64) -> i64 {
59 let pool: *NxThreadPool = nx_pool_new(nworkers, 512)
60 let rc: i64 = sdfmt_render_pool(pool, base, yaw, camz_units, skin_r, skin_g, skin_b)
61 nx_pool_shutdown(pool)
62 return rc
63}