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}