code wiki / _hdl_build / nx_raster3d.nx

nx_raster3d.nx source

↩ module page · 204 lines · 13187 B

1// nx_raster3d.nx -- GATE: the SOVEREIGN INTEGER-DETERMINISTIC 3D PIPELINE (closes the GRAPHICS-API "3D pipeline" 2// gap -- the LAST software OS gap). Extends nx_raster (2D edge-function fill) to a real 3D renderer, 100% integer/ 3// fixed-point => BIT-EXACT on ANY CPU (x86/RISC-V/no-FPU), NO GPU/driver. Stages: 4// (1) TRANSFORM -- fixed-point (Q8) rotate-around-Y + translate (model->view). 5// (2) PROJECT -- perspective projection: screen = center + focal*coord / view_z (the divide-by-z that makes 3D). 6// (3) Z-BUFFER -- per-pixel depth test (barycentric-interpolated view-z); nearer occludes farther = hidden-surface 7// removal, the defining 3D feature. 8// (4) SHADE -- integer flat Lambert: intensity = dot(faceNormal, light) normalized by integer isqrt. 9// T1 projection KAT (a view point projects to the exact screen pixel). 10// T2 Z-BUFFER occlusion is ORDER-INDEPENDENT (near occludes far whether drawn near-first or far-first). 11// T3 full pipeline renders a tetrahedron (transform->project->z-buffer); a real image, front faces occlude back. 12// T4 (EXCEED) determinism -- render twice -> BIT-IDENTICAL framebuffer + z-buffer checksum. 13// T5 flat-shading KAT (normal-toward-light=full, perpendicular=dark) + bounds (no OOB writes). 14// expect_exit: 0 Sovereign: nx_syscalls. NEVER-BRICK: software render, writes 0 GPU firmware. 15import "nx_syscalls.nx" 16const K_MAGIC_5381: i64 = 5381 17 18func g_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 19func g_pn(v: i64) -> i64 { let b: *u8=sys_mmap(28); var x: i64=v; if x<0{b[0]=45;sys_write(1,b,1);x=0-x} if x==0{b[0]=48;sys_write(1,b,1);return 0} var d: i64=0; var y: i64=x; while y>0{d=d+1;y=y/10} var i: i64=d-1; y=x; while i>=0{b[i]=(48+(y%10)) as u8;y=y/10;i=i-1} sys_write(1,b,d); return 0 } 20func ck(name: *u8, c: i64) -> i64 { if c==1 { g_puts(" PASS " as *u8) } else { g_puts(" FAIL " as *u8) } g_puts(name); g_puts("\n" as *u8); return c } 21 22const W: i64 = 64 23const H: i64 = 64 24const SCALE: i64 = 256 // Q8 fixed-point 25const FOCAL: i64 = 350 26const CAMZ: i64 = 420 // push the model in front of the camera 27const CX: i64 = 32 28const CY: i64 = 32 29const ZINF: i64 = 1000000000 30// rotate-around-Y by 30 deg: sin=128 cos=222 (Q8) 31const SIN30: i64 = 128 32const COS30: i64 = 222 33 34func isqrt(n: i64) -> i64 { if n<=0 { return 0 } var x: i64=n; var y: i64=(x+1)/2; while y<x { x=y; y=(x+n/x)/2 } return x } 35func fb_clear(fb: *i64, zb: *i64) -> i64 { var i: i64=0; while i<W*H { fb[i]=0; zb[i]=ZINF; i=i+1 } return 0 } 36func fb_get(fb: *i64, x: i64, y: i64) -> i64 { return fb[y*W+x] } 37func zb_get(zb: *i64, x: i64, y: i64) -> i64 { return zb[y*W+x] } 38func fb_filled(fb: *i64) -> i64 { var c: i64=0; var i: i64=0; while i<W*H { if fb[i]!=0 { c=c+1 } i=i+1 } return c } 39func cksum(fb: *i64) -> i64 { var h: i64=K_MAGIC_5381; var i: i64=0; while i<W*H { h=((h<<5)+h)+fb[i]; i=i+1 } return h } 40 41// TRANSFORM: rotate world vertex (wx,wy,wz) around Y (Q8) + translate to view space; write view coords -> vout[0..2]. 42func transform(wx: i64, wy: i64, wz: i64, vout: *i64) -> i64 { 43 let rx: i64 = (wx*COS30 + wz*SIN30) / SCALE 44 let rz: i64 = (0-wx)*SIN30/SCALE + wz*COS30/SCALE 45 vout[0]=rx; vout[1]=wy; vout[2]=rz + CAMZ 46 return 0 47} 48// PROJECT: perspective divide-by-z; write screen (sx,sy) + depth(view z) -> sout[0..2]. 49func project(vx: i64, vy: i64, vz: i64, sout: *i64) -> i64 { 50 var z: i64=vz; if z<1 { z=1 } 51 sout[0] = CX + (vx*FOCAL)/z 52 sout[1] = CY - (vy*FOCAL)/z 53 sout[2] = z // z-buffer depth (nearer = smaller) 54 return 0 55} 56func edge(ax: i64, ay: i64, bx: i64, by: i64, px: i64, py: i64) -> i64 { return (bx-ax)*(py-ay) - (by-ay)*(px-ax) } 57 58// rasterize a triangle given SCREEN coords + per-vertex depth, with Z-BUFFER test. color already shaded. 59func draw_tri3d(fb: *i64, zb: *i64, x0: i64, y0: i64, z0: i64, x1: i64, y1: i64, z1: i64, x2: i64, y2: i64, z2: i64, color: i64) -> i64 { 60 let area: i64 = edge(x0,y0,x1,y1,x2,y2) 61 if area==0 { return 0 } 62 // bounding box (clamped) 63 var minx: i64=x0; if x1<minx {minx=x1} if x2<minx {minx=x2} if minx<0 {minx=0} 64 var maxx: i64=x0; if x1>maxx {maxx=x1} if x2>maxx {maxx=x2} if maxx>W-1 {maxx=W-1} 65 var miny: i64=y0; if y1<miny {miny=y1} if y2<miny {miny=y2} if miny<0 {miny=0} 66 var maxy: i64=y0; if y1>maxy {maxy=y1} if y2>maxy {maxy=y2} if maxy>H-1 {maxy=H-1} 67 var py: i64=miny 68 while py<=maxy { 69 var px: i64=minx 70 while px<=maxx { 71 let w0: i64=edge(x1,y1,x2,y2,px,py) // opposite v0 72 let w1: i64=edge(x2,y2,x0,y0,px,py) // opposite v1 73 let w2: i64=edge(x0,y0,x1,y1,px,py) // opposite v2 74 var inside: i64=0 75 if area>0 { if w0>=0 { if w1>=0 { if w2>=0 { inside=1 } } } } 76 else { if w0<=0 { if w1<=0 { if w2<=0 { inside=1 } } } } 77 if inside==1 { 78 let depth: i64 = (w0*z0 + w1*z1 + w2*z2) / area // barycentric-interpolated view-z 79 let idx: i64 = py*W+px 80 if depth < zb[idx] { zb[idx]=depth; fb[idx]=color } 81 } 82 px=px+1 83 } 84 py=py+1 85 } 86 return 0 87} 88 89// FLAT SHADE: integer Lambert. faceNormal = cross(b-a, c-a) in VIEW space; intensity = dot(N,L) / (|N||L|) * 255. 90func shade(cr: i64, ax: i64, ay: i64, az: i64, bx: i64, by: i64, bz: i64, cx: i64, cy: i64, cz: i64, lx: i64, ly: i64, lz: i64) -> i64 { 91 let ux: i64=bx-ax; let uy: i64=by-ay; let uz: i64=bz-az 92 let vx: i64=cx-ax; let vy: i64=cy-ay; let vz: i64=cz-az 93 let nx: i64=uy*vz - uz*vy; let ny: i64=uz*vx - ux*vz; let nz: i64=ux*vy - uy*vx 94 var dot: i64 = nx*lx + ny*ly + nz*lz 95 if dot<0 { dot = 0-dot } // double-sided: light either face 96 let lenN: i64 = isqrt(nx*nx+ny*ny+nz*nz); let lenL: i64 = isqrt(lx*lx+ly*ly+lz*lz) 97 if lenN==0 { return cr } if lenL==0 { return cr } 98 var inten: i64 = (dot*255) / (lenN*lenL) 99 if inten>255 { inten=255 } 100 let r: i64=(cr>>16)&255; let g: i64=(cr>>8)&255; let b: i64=cr&255 101 let r2: i64=(r*inten)/255; let g2: i64=(g*inten)/255; let b2: i64=(b*inten)/255 102 return (r2<<16)|(g2<<8)|b2 103} 104 105// render a tetrahedron through the FULL pipeline into fb/zb. returns filled pixel count. 106func render_tetra(fb: *i64, zb: *i64) -> i64 { 107 fb_clear(fb, zb) 108 // world vertices 109 let wx: *i64=sys_mmap(4*8) as *i64; let wy: *i64=sys_mmap(4*8) as *i64; let wz: *i64=sys_mmap(4*8) as *i64 110 wx[0]=0; wy[0]=90; wz[0]=0 111 wx[1]=0-80; wy[1]=0-50; wz[1]=60 112 wx[2]=80; wy[2]=0-50; wz[2]=60 113 wx[3]=0; wy[3]=0-50; wz[3]=0-90 114 // transform + project each vertex 115 let vx: *i64=sys_mmap(4*8) as *i64; let vy: *i64=sys_mmap(4*8) as *i64; let vz: *i64=sys_mmap(4*8) as *i64 116 let sx: *i64=sys_mmap(4*8) as *i64; let sy: *i64=sys_mmap(4*8) as *i64; let sz: *i64=sys_mmap(4*8) as *i64 117 let vo: *i64=sys_mmap(3*8) as *i64; let so: *i64=sys_mmap(3*8) as *i64 118 var i: i64=0 119 while i<4 { 120 transform(wx[i],wy[i],wz[i],vo); vx[i]=vo[0]; vy[i]=vo[1]; vz[i]=vo[2] 121 project(vx[i],vy[i],vz[i],so); sx[i]=so[0]; sy[i]=so[1]; sz[i]=so[2] 122 i=i+1 123 } 124 // faces (vertex triples) + base colors 125 let fa: *i64=sys_mmap(4*8) as *i64; let fb2: *i64=sys_mmap(4*8) as *i64; let fc: *i64=sys_mmap(4*8) as *i64; let fcol: *i64=sys_mmap(4*8) as *i64 126 fa[0]=0; fb2[0]=2; fc[0]=1; fcol[0]=0xFF0000 127 fa[1]=0; fb2[1]=1; fc[1]=3; fcol[1]=0x00FF00 128 fa[2]=0; fb2[2]=3; fc[2]=2; fcol[2]=0x0000FF 129 fa[3]=1; fb2[3]=2; fc[3]=3; fcol[3]=0xFFFF00 130 var f: i64=0 131 while f<4 { 132 let a: i64=fa[f]; let b: i64=fb2[f]; let c: i64=fc[f] 133 let col: i64 = shade(fcol[f], vx[a],vy[a],vz[a], vx[b],vy[b],vz[b], vx[c],vy[c],vz[c], 40,80,0-256) 134 draw_tri3d(fb, zb, sx[a],sy[a],sz[a], sx[b],sy[b],sz[b], sx[c],sy[c],sz[c], col) 135 f=f+1 136 } 137 return fb_filled(fb) 138} 139 140func main() -> i64 { 141 g_puts("nx_raster3d (SOVEREIGN integer-deterministic 3D pipeline: transform + perspective projection + Z-BUFFER + flat shading)\n" as *u8) 142 var pass: i64=0; var total: i64=0 143 let fb: *i64 = sys_mmap(W*H*8) as *i64; let zb: *i64 = sys_mmap(W*H*8) as *i64 144 let so: *i64 = sys_mmap(3*8) as *i64 145 146 // T1: projection KAT -- (0,0,z)->center; (x,0,z)->center + focal*x/z 147 project(0,0,CAMZ,so); let cxp: i64=so[0]; let cyp: i64=so[1] 148 project(70,0,CAMZ,so); let offx: i64=so[0] 149 let expect: i64 = CX + (70*FOCAL)/CAMZ 150 var t1: i64=0; if cxp==CX { if cyp==CY { if offx==expect { t1=1 } } } 151 g_puts(" T1 project (0,0,z)->("); g_pn(cxp); g_puts(","); g_pn(cyp); g_puts(") (70,0,z)->sx="); g_pn(offx); g_puts(" (expect "); g_pn(expect); g_puts(")\n" as *u8) 152 pass=pass+ck("T1: perspective projection -- point-on-axis maps to screen center; off-axis divides by depth (KAT)" as *u8, t1); total=total+1 153 154 // T2: Z-BUFFER occlusion order-independence. FAR triangle depth 500, NEAR triangle depth 300, overlapping region. 155 // (screen coords direct; constant per-vertex depth so the interpolated depth is that constant.) 156 let ov: i64 = 30*W+30 // an overlap pixel index (30,30) 157 // draw FAR then NEAR 158 fb_clear(fb, zb) 159 draw_tri3d(fb, zb, 10,10,500, 55,15,500, 20,55,500, 0x111111) // far (dark) 160 draw_tri3d(fb, zb, 12,12,300, 58,18,300, 22,58,300, 0xFFFFFF) // near (bright) overlaps 161 let cFarNear: i64 = fb[ov]; let zFarNear: i64 = zb[ov] 162 // draw NEAR then FAR (reverse order) -- z-test must REJECT the far triangle at the overlap 163 fb_clear(fb, zb) 164 draw_tri3d(fb, zb, 12,12,300, 58,18,300, 22,58,300, 0xFFFFFF) // near first 165 draw_tri3d(fb, zb, 10,10,500, 55,15,500, 20,55,500, 0x111111) // far second (must not overwrite near) 166 let cNearFar: i64 = fb[ov]; let zNearFar: i64 = zb[ov] 167 var t2: i64=0; if cFarNear==0xFFFFFF { if cNearFar==0xFFFFFF { if zFarNear==300 { if zNearFar==300 { t2=1 } } } } 168 g_puts(" T2 overlap(30,30): far-then-near color="); g_pn(cFarNear); g_puts(" near-then-far color="); g_pn(cNearFar); g_puts(" depth="); g_pn(zNearFar); g_puts(" (both must be the NEAR 16777215 @depth 300)\n" as *u8) 169 pass=pass+ck("T2: Z-BUFFER occlusion is ORDER-INDEPENDENT -- the nearer surface wins regardless of draw order (HSR)" as *u8, t2); total=total+1 170 171 // T3: full pipeline renders the tetrahedron 172 let filled: i64 = render_tetra(fb, zb) 173 g_puts(" T3 tetrahedron rendered: filled "); g_pn(filled); g_puts(" / "); g_pn(W*H); g_puts(" pixels (transform->project->z-buffer->shade)\n" as *u8) 174 var t3: i64=0; if filled>200 { t3=1 } 175 pass=pass+ck("T3: the FULL 3D pipeline renders a tetrahedron mesh (transform + projection + z-buffer + shading)" as *u8, t3); total=total+1 176 177 // T4 (EXCEED): determinism -- render twice, bit-identical fb + zbuf 178 let fb2: *i64 = sys_mmap(W*H*8) as *i64; let zb2: *i64 = sys_mmap(W*H*8) as *i64 179 render_tetra(fb2, zb2) 180 let c1: i64 = cksum(fb); let c2: i64 = cksum(fb2); let zc1: i64 = cksum(zb); let zc2: i64 = cksum(zb2) 181 var t4: i64=0; if c1==c2 { if zc1==zc2 { t4=1 } } 182 g_puts(" T4 determinism: fb-checksum run1="); g_pn(c1); g_puts(" run2="); g_pn(c2); g_puts(" zbuf run1="); g_pn(zc1); g_puts(" run2="); g_pn(zc2); g_puts("\n" as *u8) 183 pass=pass+ck("T4 (EXCEED): DETERMINISTIC -- the 3D frame + z-buffer are BIT-IDENTICAL across runs (GPU/DirectX cannot guarantee)" as *u8, t4); total=total+1 184 185 // T5: flat-shading KAT (normal toward light = full 255; perpendicular = 0) + bounds 186 // face in XY plane (normal +Z), light along +Z -> full; light along +X (perpendicular) -> dark. 187 let full: i64 = shade(0xFFFFFF, 0,0,0, 10,0,0, 0,10,0, 0,0,256) // N=+Z, L=+Z -> intensity 255 -> white 188 let dark: i64 = shade(0xFFFFFF, 0,0,0, 10,0,0, 0,10,0, 256,0,0) // N=+Z, L=+X -> dot 0 -> black 189 // bounds: an off-screen-ish tiny triangle must not write far pixels 190 fb_clear(fb, zb); draw_tri3d(fb, zb, 2,2,300, 5,2,300, 2,5,300, 0x00FF00) 191 var oob: i64=0; if fb_get(fb,50,50)!=0 { oob=1 } 192 var t5: i64=0; if full==0xFFFFFF { if dark==0 { if oob==0 { t5=1 } } } 193 g_puts(" T5 shading KAT: normal-toward-light="); g_pn(full); g_puts(" (255->16777215) perpendicular="); g_pn(dark); g_puts(" (0); OOB="); g_pn(oob); g_puts("\n" as *u8) 194 pass=pass+ck("T5: flat-shading KAT (toward-light=full, perpendicular=dark) + bounds (no OOB writes)" as *u8, t5); total=total+1 195 196 var okall: i64=0; if pass==total { okall=1 } 197 g_puts("---- nx_raster3d: passed "); g_pn(pass); g_puts(" / "); g_pn(total); g_puts(" ----\n" as *u8) 198 if okall==1 { 199 let logf: i64=sys_openat_append("knowledge/status/raster3d.log" as *u8, 420) 200 if logf>=0 { let z: i64=sys_write(logf,"NXRASTER3D GREEN: integer 3D pipeline -- transform + perspective projection + Z-BUFFER hidden-surface removal + flat Lambert shading; bit-exact deterministic on any CPU, no GPU\n" as *u8,168); sys_close(logf) } 201 g_puts("verdict=GREEN (sovereign integer 3D pipeline: transform + perspective projection + z-buffer + shading, bit-exact on any hardware; the GRAPHICS 3D-pipeline gap closed)\n" as *u8); sys_exit(0); return 0 202 } 203 g_puts("verdict=RED\n" as *u8); sys_exit(1); return 1 204}