code wiki / (root) / nx_framebuffer.nx

nx_framebuffer.nx source

↩ module page · 213 lines · 8453 B

1// nx_framebuffer.nx -- u32 pixel buffer + z-buffer foundation. 2// 3// Per gap roadmap Phase 1: substrate needs a framebuffer for the 4// software rasterizer to write into. This primitive provides: 5// - u32 packed-RGBA framebuffer (w x h i64 cells) 6// - Q14 depth buffer (parallel array) 7// - Clear / set-pixel / set-depth primitives 8// - Tile-clear for chunked updates 9// 10// PIXEL FORMAT: 0xRRGGBBAA packed in low 32 bits of i64. 11// DEPTH FORMAT: Q14 metres signed. Initial depth = MAX (far plane). 12// 13// Caller allocates the buffer; primitive just provides accessors. 14// To compose with the canvas blit, the JS shim reads the buffer's 15// low 32 bits and writes RGBA8. 16// 17// PUBLIC APIs: 18// nx_fb_alloc(w, h) -> *i64 framebuffer (w * h cells) 19// nx_fb_depth_alloc(w, h) -> *i64 depth buffer 20// nx_fb_clear(fb, w, h, color) fill with solid color 21// nx_fb_clear_depth(zb, w, h) reset depth to far plane 22// nx_fb_set(fb, w, h, x, y, color) 23// nx_fb_get(fb, w, h, x, y) -> i64 24// nx_fb_depth_test_set(fb, zb, w, h, x, y, z, color) -> 0/1 25// Returns 1 if pixel written (z < existing); 0 otherwise. 26// nx_fb_pack_rgba(r, g, b, a) -> i64 packed color helper 27// 28// genealogy_id: software_rasterizer_canon + frame_buffer_canon 29// lineage_id: nx_framebuffer_q14_v1 30 31// nx_safety_envelope: 32// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 33// sil_target: SIL1 34// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 35// verdict: NOT_YET_EVALUATED 36 37import "nx_syscalls.nx" 38import "nx_tier.nx" 39const NX_MAGIC_65536: i64 = 65536 40const NX_MAGIC_16777216: i64 = 16777216 41 42const NX_FB_Q: nx_int = 16384 43 44// ===== Far-plane depth ============================================== 45// 1e6 metres in Q14 = ~1.6e10; fits comfortably in i64. 46const NX_FB_FAR_DEPTH_Q14: nx_int = 16384000000 47 48// ===== Allocate framebuffer ======================================== 49// Returns pointer to w * h i64 cells. Zero-initialised. 50func nx_fb_alloc(w: nx_int, h: nx_int) -> *i64 { 51 if w <= 0 { return 0 as *i64 } 52 if h <= 0 { return 0 as *i64 } 53 let bytes: nx_int = w * h * NX_SIZEOF_NX_INT 54 return (sys_mmap(bytes)) as *i64 55} 56 57func nx_fb_depth_alloc(w: nx_int, h: nx_int) -> *i64 { 58 if w <= 0 { return 0 as *i64 } 59 if h <= 0 { return 0 as *i64 } 60 let bytes: nx_int = w * h * NX_SIZEOF_NX_INT 61 return (sys_mmap(bytes)) as *i64 62} 63 64// ===== Clear ======================================================== 65func nx_fb_clear(fb: *i64, w: nx_int, h: nx_int, color: nx_int) { 66 let n: nx_int = w * h 67 var i: nx_int = 0 68 while i < n { fb[i] = color; i = i + 1 } 69} 70 71func nx_fb_clear_depth(zb: *i64, w: nx_int, h: nx_int) { 72 let n: nx_int = w * h 73 var i: nx_int = 0 74 while i < n { zb[i] = NX_FB_FAR_DEPTH_Q14; i = i + 1 } 75} 76 77// Sky-gradient overpaint -- the per-cell variant -- lives in 78// nishi-engine/nx_voxel_runtime.nx as `_runtime_overpaint_sky`. 79// It composes nx_fb_pack_rgba + NX_FB_FAR_DEPTH_Q14 with depth-test 80// gating so the gradient shows through only on rays that miss every 81// voxel face. No framebuffer-side helper needed. 82 83 84// ===== Bounds-safe pixel set ======================================= 85func nx_fb_set( 86 fb: *i64, w: nx_int, h: nx_int, x: nx_int, y: nx_int, color: nx_int 87) { 88 if x < 0 { return } 89 if x >= w { return } 90 if y < 0 { return } 91 if y >= h { return } 92 fb[y * w + x] = color 93} 94 95func nx_fb_get( 96 fb: *i64, w: nx_int, h: nx_int, x: nx_int, y: nx_int 97) -> nx_int { 98 if x < 0 { return 0 } 99 if x >= w { return 0 } 100 if y < 0 { return 0 } 101 if y >= h { return 0 } 102 return fb[y * w + x] 103} 104 105// ===== Depth-tested set ============================================ 106// Writes pixel + depth iff z_q14 < zb[idx]. Returns 1 if written. 107func nx_fb_depth_test_set( 108 fb: *i64, zb: *i64, 109 w: nx_int, h: nx_int, 110 x: nx_int, y: nx_int, 111 z_q14: nx_int, color: nx_int 112) -> nx_int { 113 if x < 0 { return 0 } 114 if x >= w { return 0 } 115 if y < 0 { return 0 } 116 if y >= h { return 0 } 117 let idx: nx_int = y * w + x 118 if z_q14 < zb[idx] { 119 fb[idx] = color 120 zb[idx] = z_q14 121 return 1 122 } 123 return 0 124} 125 126// ===== Color helpers =============================================== 127// Pack (R, G, B, A) in [0, 255] into a single i64. RGBA order; the 128// JS shim reads as little-endian RGBA8 from the same 32 bits. 129func nx_fb_pack_rgba(r: nx_int, g: nx_int, b: nx_int, a: nx_int) -> nx_int { 130 var rr: nx_int = r 131 if rr < 0 { rr = 0 } 132 if rr > 255 { rr = 255 } 133 var gg: nx_int = g 134 if gg < 0 { gg = 0 } 135 if gg > 255 { gg = 255 } 136 var bb: nx_int = b 137 if bb < 0 { bb = 0 } 138 if bb > 255 { bb = 255 } 139 var aa: nx_int = a 140 if aa < 0 { aa = 0 } 141 if aa > 255 { aa = 255 } 142 return rr + gg * 256 + bb * NX_MAGIC_65536 + aa * NX_MAGIC_16777216 143} 144 145// Unpack (mostly for tests). 146func nx_fb_unpack_r(color: nx_int) -> nx_int { return color % 256 } 147func nx_fb_unpack_g(color: nx_int) -> nx_int { return (color / 256) % 256 } 148func nx_fb_unpack_b(color: nx_int) -> nx_int { return (color / NX_MAGIC_65536) % 256 } 149func nx_fb_unpack_a(color: nx_int) -> nx_int { return (color / NX_MAGIC_16777216) % 256 } 150 151// ===== Self-test ==================================================== 152func main() -> i64 { 153 let q: nx_int = NX_FB_Q 154 155 // T1: Allocate small framebuffer + depth buffer. 156 let w: nx_int = 8 157 let h: nx_int = 8 158 let fb: *i64 = nx_fb_alloc(w, h) 159 let zb: *i64 = nx_fb_depth_alloc(w, h) 160 161 // T2: Clear + read back. 162 let red: nx_int = nx_fb_pack_rgba(255, 0, 0, 255) 163 nx_fb_clear(fb, w, h, red) 164 if nx_fb_get(fb, w, h, 0, 0) != red { return __syscall(93, 1, 0, 0, 0, 0, 0) } 165 if nx_fb_get(fb, w, h, 7, 7) != red { return __syscall(93, 2, 0, 0, 0, 0, 0) } 166 // Out-of-bounds reads return 0. 167 if nx_fb_get(fb, w, h, 0 - 1, 0) != 0 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 168 if nx_fb_get(fb, w, h, 100, 0) != 0 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 169 170 // T3: Set + get a single pixel. 171 let blue: nx_int = nx_fb_pack_rgba(0, 0, 255, 255) 172 nx_fb_set(fb, w, h, 3, 4, blue) 173 if nx_fb_get(fb, w, h, 3, 4) != blue { return __syscall(93, 10, 0, 0, 0, 0, 0) } 174 // Neighbour pixels still red. 175 if nx_fb_get(fb, w, h, 2, 4) != red { return __syscall(93, 11, 0, 0, 0, 0, 0) } 176 177 // T4: Pack / unpack RGBA roundtrip. 178 let color: nx_int = nx_fb_pack_rgba(128, 64, 32, 200) 179 if nx_fb_unpack_r(color) != 128 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 180 if nx_fb_unpack_g(color) != 64 { return __syscall(93, 21, 0, 0, 0, 0, 0) } 181 if nx_fb_unpack_b(color) != 32 { return __syscall(93, 22, 0, 0, 0, 0, 0) } 182 if nx_fb_unpack_a(color) != 200 { return __syscall(93, 23, 0, 0, 0, 0, 0) } 183 // Out-of-range clamp. 184 let clamped: nx_int = nx_fb_pack_rgba(0 - 50, 300, 1000, 50) 185 if nx_fb_unpack_r(clamped) != 0 { return __syscall(93, 24, 0, 0, 0, 0, 0) } 186 if nx_fb_unpack_g(clamped) != 255 { return __syscall(93, 25, 0, 0, 0, 0, 0) } 187 if nx_fb_unpack_b(clamped) != 255 { return __syscall(93, 26, 0, 0, 0, 0, 0) } 188 189 // T5: Depth-test write. 190 nx_fb_clear_depth(zb, w, h) 191 // Initial: NEAR (z=5) writes; subsequent FAR (z=10) blocks. 192 let green: nx_int = nx_fb_pack_rgba(0, 255, 0, 255) 193 let wrote1: nx_int = nx_fb_depth_test_set(fb, zb, w, h, 1, 1, 5 * q, green) 194 if wrote1 != 1 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 195 // Same pixel, FARTHER z -> rejected. 196 let wrote2: nx_int = nx_fb_depth_test_set(fb, zb, w, h, 1, 1, 10 * q, red) 197 if wrote2 != 0 { return __syscall(93, 31, 0, 0, 0, 0, 0) } 198 if nx_fb_get(fb, w, h, 1, 1) != green { return __syscall(93, 32, 0, 0, 0, 0, 0) } 199 // Same pixel, NEARER z -> accepted. 200 let wrote3: nx_int = nx_fb_depth_test_set(fb, zb, w, h, 1, 1, 2 * q, blue) 201 if wrote3 != 1 { return __syscall(93, 33, 0, 0, 0, 0, 0) } 202 if nx_fb_get(fb, w, h, 1, 1) != blue { return __syscall(93, 34, 0, 0, 0, 0, 0) } 203 204 // T6: Depth-clear resets all cells to far plane. 205 nx_fb_clear_depth(zb, w, h) 206 if zb[0] != NX_FB_FAR_DEPTH_Q14 { return __syscall(93, 40, 0, 0, 0, 0, 0) } 207 if zb[w * h - 1] != NX_FB_FAR_DEPTH_Q14 { return __syscall(93, 41, 0, 0, 0, 0, 0) } 208 // After clear, any z writes successfully. 209 let wrote4: nx_int = nx_fb_depth_test_set(fb, zb, w, h, 5, 5, 100 * q, green) 210 if wrote4 != 1 { return __syscall(93, 42, 0, 0, 0, 0, 0) } 211 212 return 0 213}