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}