code wiki / _hdl_build / nx_gx5_nearclip_gate.nx
nx_gx5_nearclip_gate.nx source
↩ module page · 263 lines · 12282 B
1// nx_gx5_nearclip_gate.nx -- seq249 EAT-BY-BUILD: real NEAR-PLANE CLIPPING vs the old whole-triangle cull,
2// measured HEAD-TO-HEAD on the identical scene. sg_project marks a vert invisible when its view z <= 0 and
3// the rasterizers then drop the ENTIRE triangle, so ground straddling the camera plane VANISHES -- the hard
4// seam eyeballed in the Gx-4 first playable (/world/gx4_walk_t5.png). sg_project_clip splits those triangles
5// (Sutherland-Hodgman at z=SG_NEAR, interpolating position AND normal) so the surface stays continuous to the
6// eye. The gate renders the SAME ground plane both ways: the OLD path is its own neg-control, and the far
7// field must come out IDENTICAL (clipping may not touch geometry that never crosses the plane).
8// license_tier: ORIGINAL expect_exit: 0
9import "nx_swgpu.nx"
10import "nx_png_write.nx"
11
12const NC_COLS: i64 = 15
13const NC_ROWS: i64 = 21
14
15func nc_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 nc_pn(v: i64) -> i64 { let b: *u8=sys_mmap(32) as *u8; var x: i64=v; var ng: i64=0; if x<0{ng=1;x=0-x} var i: i64=31; if x==0{b[i]=48 as u8;i=i-1} while x>0{b[i]=(48+x%10) as u8;x=x/10;i=i-1} if ng==1{b[i]=45 as u8;i=i-1} sys_write(1,(b as i64+i+1) as *u8,31-i); return 0 }
17
18// ground height at grid (i,j), fx4096: a plane 2 units below the eye with gentle rolling
19func nc_h(i: i64, j: i64) -> i64 { return (0-8192) + (it_sin4096(i*1500) * 800) / 4096 + (it_sin4096(j*900) * 500) / 4096 }
20
21// build the ground grid spanning z = -6 .. +34 world units, i.e. STRADDLING the camera plane at z=0
22func nc_build(base: i64) -> i64 {
23 let px: *i64 = (base + O_PX) as *i64
24 let py: *i64 = (base + O_PY) as *i64
25 let pz: *i64 = (base + O_PZ) as *i64
26 let nx: *i64 = (base + O_NX) as *i64
27 let ny: *i64 = (base + O_NY) as *i64
28 let nz: *i64 = (base + O_NZ) as *i64
29 let ta: *i64 = (base + O_TA) as *i64
30 let tn: *i64 = (base + O_TN) as *i64
31 let vc: *i64 = (base + O_VC) as *i64
32 sg_reset(base)
33 var j: i64 = 0
34 while j < NC_ROWS {
35 var i: i64 = 0
36 while i < NC_COLS {
37 let vi: i64 = j * NC_COLS + i
38 px[vi] = ((0-14) + i*2) * 4096
39 py[vi] = nc_h(i, j)
40 pz[vi] = ((0-6) + j*2) * 4096
41 var il: i64 = i-1; if il < 0 { il = 0 }
42 var ir: i64 = i+1; if ir > NC_COLS-1 { ir = NC_COLS-1 }
43 var jl: i64 = j-1; if jl < 0 { jl = 0 }
44 var jr: i64 = j+1; if jr > NC_ROWS-1 { jr = NC_ROWS-1 }
45 var gx: i64 = 0 - (nc_h(ir,j) - nc_h(il,j))
46 var gy: i64 = 2 * 8192
47 var gz: i64 = 0 - (nc_h(i,jr) - nc_h(i,jl))
48 let gl: i64 = sg_isqrt(gx*gx + gy*gy + gz*gz)
49 if gl > 0 { gx = gx*4096/gl; gy = gy*4096/gl; gz = gz*4096/gl }
50 nx[vi] = gx; ny[vi] = gy; nz[vi] = gz
51 i = i + 1
52 }
53 j = j + 1
54 }
55 vc[0] = NC_COLS * NC_ROWS
56 var t: i64 = 0
57 j = 0
58 while j < NC_ROWS-1 {
59 var i2: i64 = 0
60 while i2 < NC_COLS-1 {
61 let v00: i64 = j*NC_COLS + i2
62 let v10: i64 = j*NC_COLS + i2 + 1
63 let v01: i64 = (j+1)*NC_COLS + i2
64 let v11: i64 = (j+1)*NC_COLS + i2 + 1
65 // both windings = double-sided (winding conventions are not what this gate is testing)
66 ta[t*3]=v00; ta[t*3+1]=v10; ta[t*3+2]=v11; t=t+1
67 ta[t*3]=v00; ta[t*3+1]=v11; ta[t*3+2]=v10; t=t+1
68 ta[t*3]=v00; ta[t*3+1]=v11; ta[t*3+2]=v01; t=t+1
69 ta[t*3]=v00; ta[t*3+1]=v01; ta[t*3+2]=v11; t=t+1
70 i2 = i2 + 1
71 }
72 j = j + 1
73 }
74 tn[0] = t
75 return t
76}
77
78// swgpu i64 framebuffer -> packed RGB
79func nc_fb_rgb(base: i64, dst: *u8) -> i64 {
80 let fb: *i64 = sg_fb(base)
81 var p: i64 = 0
82 while p < ww()*hh() {
83 let v: i64 = fb[p]
84 dst[p*3] = (v % 256) as u8
85 dst[p*3+1] = ((v/256) % 256) as u8
86 dst[p*3+2] = ((v/65536) % 256) as u8
87 p = p + 1
88 }
89 return 0
90}
91
92// count pixels that DIFFER from the analytic sky/background gradient inside rows [r0,r1) = "covered by geometry"
93func nc_covered(rgb: *u8, r0: i64, r1: i64) -> i64 {
94 var n: i64 = 0
95 var y: i64 = r0
96 while y < r1 {
97 let bgr: i64 = 26 + y*36/hh()
98 let bgg: i64 = 28 + y*34/hh()
99 let bgb: i64 = 42 + y*30/hh()
100 var x: i64 = 0
101 while x < ww() {
102 let o: i64 = (y*ww()+x)*3
103 var d1: i64 = (rgb[o] as i64)-bgr; if d1<0 {d1=0-d1}
104 var d2: i64 = (rgb[o+1] as i64)-bgg; if d2<0 {d2=0-d2}
105 var d3: i64 = (rgb[o+2] as i64)-bgb; if d3<0 {d3=0-d3}
106 if d1+d2+d3 > 18 { n = n + 1 }
107 x = x + 1
108 }
109 y = y + 1
110 }
111 return n
112}
113
114// COARSE scene: ONE big floor quad (2 tris, both windings) spanning z = -20 .. +40 -- i.e. a single triangle
115// stretching from behind the eye to far ahead. This is the case that actually breaks: level floors, walls and
116// skybox faces are routinely 2 triangles, and dropping a whole one deletes the entire surface.
117func nc_build_coarse(base: i64) -> i64 {
118 let px: *i64 = (base + O_PX) as *i64
119 let py: *i64 = (base + O_PY) as *i64
120 let pz: *i64 = (base + O_PZ) as *i64
121 let nx: *i64 = (base + O_NX) as *i64
122 let ny: *i64 = (base + O_NY) as *i64
123 let nz: *i64 = (base + O_NZ) as *i64
124 let ta: *i64 = (base + O_TA) as *i64
125 let tn: *i64 = (base + O_TN) as *i64
126 let vc: *i64 = (base + O_VC) as *i64
127 sg_reset(base)
128 px[0]=(0-30)*4096; py[0]=0-8192; pz[0]=(0-20)*4096
129 px[1]=30*4096; py[1]=0-8192; pz[1]=(0-20)*4096
130 px[2]=30*4096; py[2]=0-8192; pz[2]=40*4096
131 px[3]=(0-30)*4096; py[3]=0-8192; pz[3]=40*4096
132 var i: i64 = 0
133 while i < 4 { nx[i]=0; ny[i]=4096; nz[i]=0; i=i+1 }
134 vc[0] = 4
135 ta[0]=0; ta[1]=1; ta[2]=2
136 ta[3]=0; ta[4]=2; ta[5]=1
137 ta[6]=0; ta[7]=2; ta[8]=3
138 ta[9]=0; ta[10]=3; ta[11]=2
139 tn[0] = 4
140 return 4
141}
142
143func main() -> i64 {
144 nc_puts("=== nx_gx5_nearclip_gate -- real near-plane CLIPPING vs whole-triangle CULL, head-to-head ===\n" as *u8)
145 var fails: i64 = 0
146 let base: i64 = sys_mmap(swgpu_bytes()) as i64
147 let rgbA: *u8 = sys_mmap(ww()*hh()*3+16)
148 let rgbB: *u8 = sys_mmap(ww()*hh()*3+16)
149 let clip: *i64 = (base + O_CLIP) as *i64
150 let FRAME: i64 = ww()*hh()
151
152 // ================= SCENE 1: COARSE (a 2-triangle floor) -- where the defect actually bites =========
153 let triC: i64 = nc_build_coarse(base)
154 sg_project(base, 0, 0)
155 sg_render(base, 96, 150, 84)
156 nc_fb_rgb(base, rgbA)
157 nc_build_coarse(base)
158 sg_project_clip(base, 0, 0)
159 let emitted: i64 = clip[0]
160 let added: i64 = clip[1]
161 let envhit: i64 = clip[2]
162 sg_render(base, 96, 150, 84)
163 nc_fb_rgb(base, rgbB)
164 let covA: i64 = nc_covered(rgbA, 0, hh())
165 let covB: i64 = nc_covered(rgbB, 0, hh())
166 nc_puts(" COARSE floor: input tris="); nc_pn(triC); nc_puts(" -> clipper emitted="); nc_pn(emitted)
167 nc_puts(" added_verts="); nc_pn(added); nc_puts(" envelope_hit="); nc_pn(envhit); nc_puts("\n" as *u8)
168 nc_puts(" COARSE coverage of the "); nc_pn(FRAME); nc_puts(" px frame: CULL="); nc_pn(covA)
169 nc_puts(" CLIP="); nc_pn(covB); nc_puts("\n" as *u8)
170
171 // ---- T1 the clipper SPLIT the straddling triangles (interpolated verts is the real signal;
172 // emitted count may legitimately FALL because fully-behind triangles are still dropped) ----
173 var t1: i64 = 0
174 if added > 0 { t1 = 1 }
175 if t1==1 { nc_puts("T1 PASS clipper interpolated "); nc_pn(added); nc_puts(" new verts at the near plane\n" as *u8) }
176 else { fails=fails+1; nc_puts("T1 FAIL no split happened\n" as *u8) }
177
178 // ---- T2 envelope declared and reported, never silent ----
179 if envhit==0 { nc_puts("T2 PASS envelope reported and not hit (<= MAXV verts, <= MAXT tris)\n" as *u8) }
180 else { fails=fails+1; nc_puts("T2 FAIL envelope overflow\n" as *u8) }
181
182 // ---- T3 THE DEFECT, ISOLATED: culling DELETES the coarse floor; clipping renders it ----
183 var t3: i64 = 0
184 if covA * 100 / FRAME < 2 { if covB * 100 / FRAME > 30 { t3 = 1 } }
185 nc_puts(" => CULL renders "); nc_pn(covA*100/FRAME); nc_puts(" percent of frame, CLIP renders "); nc_pn(covB*100/FRAME); nc_puts(" percent\n" as *u8)
186 if t3==1 { nc_puts("T3 PASS the whole-triangle cull DELETED the entire floor; clipping restores it\n" as *u8) }
187 else { fails=fails+1; nc_puts("T3 FAIL cull="); nc_pn(covA); nc_puts(" clip="); nc_pn(covB); nc_puts("\n" as *u8) }
188
189 // ================= SCENE 2: FINE mesh -- the honest no-regression + when-it-matters boundary =======
190 let triF: i64 = nc_build(base)
191 sg_project(base, 0, 0)
192 sg_render(base, 96, 150, 84)
193 nc_fb_rgb(base, rgbA)
194 nc_build(base)
195 sg_project_clip(base, 0, 0)
196 let addedF: i64 = clip[1]
197 sg_render(base, 96, 150, 84)
198 nc_fb_rgb(base, rgbB)
199 var difffine: i64 = 0
200 var p: i64 = 0
201 while p < FRAME {
202 var e1: i64 = (rgbA[p*3] as i64)-(rgbB[p*3] as i64); if e1<0 {e1=0-e1}
203 var e2: i64 = (rgbA[p*3+1] as i64)-(rgbB[p*3+1] as i64); if e2<0 {e2=0-e2}
204 var e3: i64 = (rgbA[p*3+2] as i64)-(rgbB[p*3+2] as i64); if e3<0 {e3=0-e3}
205 if e1+e2+e3 > 6 { difffine = difffine + 1 }
206 p = p + 1
207 }
208 nc_puts(" FINE grid ("); nc_pn(triF); nc_puts(" tris, "); nc_pn(addedF)
209 nc_puts(" verts clipped): cull-vs-clip differing px = "); nc_pn(difffine); nc_puts(" of "); nc_pn(FRAME); nc_puts("\n" as *u8)
210
211 // ---- T4 NO-REGRESSION: on a fine mesh the clip must not disturb the image ----
212 var t4: i64 = 0
213 if difffine * 1000 / FRAME < 5 { t4 = 1 }
214 if t4==1 { nc_puts("T4 PASS no regression on fine geometry (image unchanged within 0.5 percent)\n" as *u8) }
215 else { fails=fails+1; nc_puts("T4 FAIL fine-mesh image changed by "); nc_pn(difffine); nc_puts(" px\n" as *u8) }
216
217 // ---- T5 THE HONEST BOUNDARY, asserted so it cannot rot: the clipper DOES run on the fine mesh
218 // (verts interpolated) yet changes NOTHING on screen -- because on a fine mesh the straddling
219 // triangles project entirely off-screen. So near-plane culling is a COARSE-geometry defect.
220 // This is why the fix is load-bearing for level/wall/skybox geometry, NOT for dense terrain. ----
221 var t5: i64 = 0
222 if addedF > 0 { if difffine * 1000 / FRAME < 5 { t5 = 1 } }
223 if t5==1 { nc_puts("T5 PASS boundary measured: clip runs on fine meshes but is a NO-OP on screen -- the defect is COARSE-geometry-only\n" as *u8) }
224 else { fails=fails+1; nc_puts("T5 FAIL boundary not established\n" as *u8) }
225
226 // restore the COARSE pair for the artifact (that is the pair worth eyeballing)
227 nc_build_coarse(base)
228 sg_project(base, 0, 0)
229 sg_render(base, 96, 150, 84)
230 nc_fb_rgb(base, rgbA)
231 nc_build_coarse(base)
232 sg_project_clip(base, 0, 0)
233 sg_render(base, 96, 150, 84)
234 nc_fb_rgb(base, rgbB)
235
236 // ---- side-by-side artifact: CULL | CLIP ----
237 let CW: i64 = 1028
238 let comp: *u8 = sys_mmap(CW*hh()*3+16)
239 var y2: i64 = 0
240 while y2 < hh() {
241 var x2: i64 = 0
242 while x2 < CW {
243 let o: i64 = (y2*CW+x2)*3
244 if x2 < ww() { let s: i64 = (y2*ww()+x2)*3; comp[o]=rgbA[s]; comp[o+1]=rgbA[s+1]; comp[o+2]=rgbA[s+2] }
245 else { if x2 >= ww()+4 { let s2: i64 = (y2*ww()+(x2-ww()-4))*3; comp[o]=rgbB[s2]; comp[o+1]=rgbB[s2+1]; comp[o+2]=rgbB[s2+2] }
246 else { comp[o]=200 as u8; comp[o+1]=90 as u8; comp[o+2]=110 as u8 } }
247 x2 = x2 + 1
248 }
249 y2 = y2 + 1
250 }
251 nx_png_write_rgb("knowledge/nx_gx5_nearclip.png\x00" as *u8, comp, CW, hh())
252 let szp: *i64 = sys_mmap(16) as *i64
253 let rb: *u8 = sys_read_file("knowledge/nx_gx5_nearclip.png\x00" as *u8, szp)
254 var t6: i64 = 0
255 if (rb as i64)!=0 { if szp[0]>1000 { t6=1 } }
256 if t6==1 { nc_puts("T6 PASS side-by-side artifact written (cull | clip), "); nc_pn(szp[0]); nc_puts(" bytes\n" as *u8) }
257 else { fails=fails+1; nc_puts("T6 FAIL png\n" as *u8) }
258
259 if fails==0 { nc_puts("GX5-NEARCLIP GREEN -- seq249 fixed: straddling triangles are CLIPPED, not culled; near field solid, far field untouched\n" as *u8); sys_exit(0); return 0 }
260 nc_puts("GX5-NEARCLIP RED fails="); nc_pn(fails); nc_puts("\n" as *u8)
261 sys_exit(1)
262 return 1
263}