code wiki / _hdl_build / nx_raster3d_tex.nx
nx_raster3d_tex.nx source
↩ module page · 128 lines · 9459 B
1// nx_raster3d_tex.nx -- GATE: PERSPECTIVE-CORRECT TEXTURE MAPPING, the SOTA upgrade to the 3D pipeline (closes the
2// GRAPHICS-API "Polish: textures" gap). Extends the integer-deterministic rasterizer (nx_raster3d) with textured
3// triangles that interpolate u/z, v/z, 1/z linearly in screen space and per-pixel divide to recover perspective-
4// correct (u,v) -- the Quake-era SOTA technique. Affine mapping (interpolating u,v directly) WARPS under perspective;
5// this gate PROVES the difference with an exact KAT: on an edge from a near vertex (u=0) to one 3x farther (u=8), the
6// screen midpoint must sample the PERSPECTIVE value u=2 (the near vertex dominates), not the affine midpoint u=4.
7// T1 perspective-correct KAT: midpoint samples u=2 (NOT affine 4).
8// T2 a textured checkerboard quad renders (filled + both texture colors present).
9// T3 (EXCEED) determinism -- render twice -> BIT-IDENTICAL framebuffer.
10// T4 equal-depth sanity: with all z equal, perspective-correct REDUCES to affine (u=4) -- correct in both regimes.
11// expect_exit: 0 Sovereign: nx_syscalls. NEVER-BRICK: software render, writes 0 GPU firmware.
12import "nx_syscalls.nx"
13const INVZ_MAGIC_5381: i64 = 5381
14
15func 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 }
16func 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 }
17func 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 }
18
19const W: i64 = 64
20const H: i64 = 64
21const ZINF: i64 = 1000000000
22const INVZ_Q: i64 = 1048576 // 2^20: perspective inverse-depth fixed-point scale
23
24func 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 }
25func 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 }
26func cksum(fb: *i64) -> i64 { var h: i64=INVZ_MAGIC_5381; var i: i64=0; while i<W*H { h=((h<<5)+h)+fb[i]; i=i+1 } return h }
27func edge(ax: i64, ay: i64, bx: i64, by: i64, px: i64, py: i64) -> i64 { return (bx-ax)*(py-ay) - (by-ay)*(px-ax) }
28
29// PERSPECTIVE-CORRECT textured triangle. per vertex: screen (x,y), view-depth z, texel (u,tv). Interpolate 1/z, u/z,
30// v/z linearly in screen space (barycentric), per-pixel divide to recover (u,v), sample tex[v*texw+u]. z-buffer uses
31// screen-linear depth (as the base pipeline). SOTA technique; affine (interpolating u,v directly) warps.
32func draw_tri_tex(fb: *i64, zb: *i64, x0: i64, y0: i64, z0: i64, u0: i64, v0: i64, x1: i64, y1: i64, z1: i64, u1: i64, v1: i64, x2: i64, y2: i64, z2: i64, u2: i64, v2: i64, tex: *i64, texw: i64, texh: i64) -> i64 {
33 let area0: i64 = edge(x0,y0,x1,y1,x2,y2)
34 if area0==0 { return 0 }
35 let iz0: i64=INVZ_Q/z0; let iz1: i64=INVZ_Q/z1; let iz2: i64=INVZ_Q/z2 // 1/z in fixed point
36 let uz0: i64=u0*iz0; let uz1: i64=u1*iz1; let uz2: i64=u2*iz2 // u/z, v/z
37 let sz0: i64=v0*iz0; let sz1: i64=v1*iz1; let sz2: i64=v2*iz2
38 var minx: i64=x0; if x1<minx {minx=x1} if x2<minx {minx=x2} if minx<0 {minx=0}
39 var maxx: i64=x0; if x1>maxx {maxx=x1} if x2>maxx {maxx=x2} if maxx>W-1 {maxx=W-1}
40 var miny: i64=y0; if y1<miny {miny=y1} if y2<miny {miny=y2} if miny<0 {miny=0}
41 var maxy: i64=y0; if y1>maxy {maxy=y1} if y2>maxy {maxy=y2} if maxy>H-1 {maxy=H-1}
42 var py: i64=miny
43 while py<=maxy {
44 var px: i64=minx
45 while px<=maxx {
46 let w0: i64=edge(x1,y1,x2,y2,px,py); let w1: i64=edge(x2,y2,x0,y0,px,py); let w2: i64=edge(x0,y0,x1,y1,px,py)
47 var inside: i64=0
48 if area0>0 { if w0>=0 { if w1>=0 { if w2>=0 { inside=1 } } } }
49 else { if w0<=0 { if w1<=0 { if w2<=0 { inside=1 } } } }
50 if inside==1 {
51 var aw0: i64=w0; var aw1: i64=w1; var aw2: i64=w2; var aar: i64=area0
52 if area0<0 { aw0=0-w0; aw1=0-w1; aw2=0-w2; aar=0-area0 } // normalize to non-negative weights
53 let den: i64 = aw0*iz0 + aw1*iz1 + aw2*iz2
54 if den!=0 {
55 var u: i64 = (aw0*uz0 + aw1*uz1 + aw2*uz2)/den // perspective-correct: (Sum wi*ui/zi)/(Sum wi/zi)
56 var v: i64 = (aw0*sz0 + aw1*sz1 + aw2*sz2)/den
57 if u<0 {u=0} if u>texw-1 {u=texw-1}
58 if v<0 {v=0} if v>texh-1 {v=texh-1}
59 let depth: i64 = (aw0*z0 + aw1*z1 + aw2*z2)/aar
60 let idx: i64 = py*W+px
61 if depth < zb[idx] { zb[idx]=depth; fb[idx]=tex[v*texw+u] }
62 }
63 }
64 px=px+1
65 }
66 py=py+1
67 }
68 return 0
69}
70
71func main() -> i64 {
72 g_puts("nx_raster3d_tex (SOVEREIGN perspective-correct texture mapping: the SOTA upgrade to the integer 3D pipeline)\n" as *u8)
73 var pass: i64=0; var total: i64=0
74 let fb: *i64=sys_mmap(W*H*8) as *i64; let zb: *i64=sys_mmap(W*H*8) as *i64
75 let TW: i64=16; let TH: i64=16
76 let tex: *i64=sys_mmap(TW*TH*8) as *i64
77
78 // T1: PERSPECTIVE-CORRECT KAT. texture encodes u (tex[v*TW+u]=u). Triangle: v0 near (z=100,u=0), v1 far 3x
79 // (z=300,u=8), v2 (z=200,u=4). At the screen midpoint of edge v0-v1 (30,30) the perspective u = 8*zNear/(zNear+
80 // zFar) = 8*100/400 = 2; affine would be (0+8)/2 = 4. The near vertex dominates -> u=2.
81 var ti: i64=0; while ti<TW*TH { tex[ti]=ti%TW; ti=ti+1 }
82 fb_clear(fb, zb)
83 draw_tri_tex(fb, zb, 10,30,100,0,0, 50,30,300,8,0, 30,60,200,4,8, tex, TW, TH)
84 let sampU: i64 = fb[30*W+30]
85 var t1: i64=0; if sampU==2 { t1=1 }
86 g_puts(" T1 midpoint of near->far edge samples u="); g_pn(sampU); g_puts(" (PERSPECTIVE-correct=2; affine-hack would give 4)\n" as *u8)
87 pass=pass+ck("T1: PERSPECTIVE-CORRECT texture mapping -- near vertex dominates the midpoint (u=2, not affine 4)" as *u8, t1); total=total+1
88
89 // T4 (do first, shares the u-texture): EQUAL-DEPTH sanity -- with all z equal there is no perspective distortion,
90 // so perspective-correct must REDUCE to affine (u=4 at the same midpoint). Correct in BOTH regimes.
91 fb_clear(fb, zb)
92 draw_tri_tex(fb, zb, 10,30,200,0,0, 50,30,200,8,0, 30,60,200,4,8, tex, TW, TH)
93 let sampUflat: i64 = fb[30*W+30]
94 var t4: i64=0; if sampUflat==4 { t4=1 }
95 g_puts(" T4 equal-depth midpoint samples u="); g_pn(sampUflat); g_puts(" (must be affine 4 -- perspective-correct reduces to affine when z is constant)\n" as *u8)
96
97 // T2: textured CHECKERBOARD quad (2 triangles), verify it renders the pattern (filled + both colors present).
98 var tj: i64=0; while tj<TW*TH { let cu: i64=tj%TW; let cv: i64=tj/TW; if (((cu>>1)^(cv>>1))&1)==0 { tex[tj]=0xFFFFFF } else { tex[tj]=0x00AAFF } tj=tj+1 }
99 fb_clear(fb, zb)
100 draw_tri_tex(fb, zb, 8,8,220, 0,0, 55,10,320, 15,0, 8,55,220, 0,15, tex, TW, TH)
101 draw_tri_tex(fb, zb, 55,10,320, 15,0, 55,55,320, 15,15, 8,55,220, 0,15, tex, TW, TH)
102 let filled: i64 = fb_filled(fb)
103 var nWhite: i64=0; var nBlue: i64=0; var pi: i64=0; while pi<W*H { if fb[pi]==0xFFFFFF {nWhite=nWhite+1} if fb[pi]==0x00AAFF {nBlue=nBlue+1} pi=pi+1 }
104 var t2: i64=0; if filled>200 { if nWhite>0 { if nBlue>0 { t2=1 } } }
105 g_puts(" T2 textured quad: filled="); g_pn(filled); g_puts(" white="); g_pn(nWhite); g_puts(" blue="); g_pn(nBlue); g_puts(" (checkerboard pattern sampled)\n" as *u8)
106 pass=pass+ck("T2: a textured checkerboard quad renders through the pipeline (both texels sampled, real texturing)" as *u8, t2); total=total+1
107
108 // T3 (EXCEED): determinism -- re-render, bit-identical framebuffer.
109 let fb2: *i64=sys_mmap(W*H*8) as *i64; let zb2: *i64=sys_mmap(W*H*8) as *i64
110 fb_clear(fb2, zb2)
111 draw_tri_tex(fb2, zb2, 8,8,220, 0,0, 55,10,320, 15,0, 8,55,220, 0,15, tex, TW, TH)
112 draw_tri_tex(fb2, zb2, 55,10,320, 15,0, 55,55,320, 15,15, 8,55,220, 0,15, tex, TW, TH)
113 let c1: i64=cksum(fb); let c2: i64=cksum(fb2)
114 var t3: i64=0; if c1==c2 { t3=1 }
115 g_puts(" T3 determinism: run1 cksum="); g_pn(c1); g_puts(" run2 cksum="); g_pn(c2); g_puts("\n" as *u8)
116 pass=pass+ck("T3 (EXCEED): textured render is BIT-IDENTICAL across runs (integer-deterministic on any CPU, no GPU)" as *u8, t3); total=total+1
117
118 pass=pass+ck("T4: perspective-correct texturing REDUCES to affine when depth is constant (correct in both regimes)" as *u8, t4); total=total+1
119
120 var okall: i64=0; if pass==total { okall=1 }
121 g_puts("---- nx_raster3d_tex: passed "); g_pn(pass); g_puts(" / "); g_pn(total); g_puts(" ----\n" as *u8)
122 if okall==1 {
123 let logf: i64=sys_openat_append("knowledge/status/raster3d_tex.log" as *u8, 420)
124 if logf>=0 { let z: i64=sys_write(logf,"NXRASTER3DTEX GREEN: perspective-correct texture mapping (u/z,v/z,1/z interpolation + per-pixel divide) -- KAT proves it beats affine (near vertex dominates), checker quad renders, bit-deterministic; the GRAPHICS textures gap closed\n" as *u8,220); sys_close(logf) }
125 g_puts("verdict=GREEN (sovereign perspective-correct texture mapping: KAT-proven vs affine, checkerboard renders, bit-deterministic; the GRAPHICS-API textures gap closed -- textured integer 3D)\n" as *u8); sys_exit(0); return 0
126 }
127 g_puts("verdict=RED\n" as *u8); sys_exit(1); return 1
128}