code wiki / _hdl_build / nx_gx6_tonemap_gate.nx

nx_gx6_tonemap_gate.nx source

↩ module page · 197 lines · 9471 B

1// nx_gx6_tonemap_gate.nx -- seq260 EAT-BY-BUILD: a real fixed-point TONEMAP replaces the hard 255 clamp. 2// MEASURED defect: 50.4 percent of a real game frame (Gx-4 walk tick5, 99090/196608 px) clamped to pure 3// 255,255,255 -- every bright surface went FLAT, all material detail in highlights destroyed. A blown-out 4// surface scores WORSE than a dim one with any detail-scoring critic, so this capped human_fidelity directly. 5// sg_tonemap = extended Reinhard out = x(1+x/W^2)/(1+x) with an explicit white point. The point is not that 6// pixels get darker -- it is that DISTINCT RADIANCES STAY DISTINCT. This gate proves exactly that, at the 7// function level (against a simulated old clamp) and on a real bright render. license_tier: ORIGINAL expect_exit: 0 8import "nx_swgpu.nx" 9import "nx_png_write.nx" 10 11func tm_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 12func tm_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 } 13 14// the OLD behaviour, simulated so the comparison is in-gate and not a remembered number 15func tm_oldclamp(v: i64) -> i64 { if v > 255 { return 255 } if v < 0 { return 0 } return v } 16 17func tm_fb_rgb(base: i64, dst: *u8) -> i64 { 18 let fb: *i64 = sg_fb(base) 19 var p: i64 = 0 20 while p < ww()*hh() { 21 let v: i64 = fb[p] 22 dst[p*3] = (v % 256) as u8 23 dst[p*3+1] = ((v/256) % 256) as u8 24 dst[p*3+2] = ((v/65536) % 256) as u8 25 p = p + 1 26 } 27 return 0 28} 29 30// count pixels blown to pure white, and how many DISTINCT red levels the lit body actually shows 31func tm_stats(rgb: *u8, hist: *i64, out: *i64) -> i64 { 32 var i: i64 = 0 33 while i < 256 { hist[i] = 0; i = i + 1 } 34 var blown: i64 = 0 35 var body: i64 = 0 36 var y: i64 = 0 37 while y < hh() { 38 let bgr: i64 = 26 + y*36/hh() 39 let bgg: i64 = 28 + y*34/hh() 40 let bgb: i64 = 42 + y*30/hh() 41 var x: i64 = 0 42 while x < ww() { 43 let o: i64 = (y*ww()+x)*3 44 let r: i64 = rgb[o] as i64 45 let g: i64 = rgb[o+1] as i64 46 let b: i64 = rgb[o+2] as i64 47 var d1: i64 = r-bgr; if d1<0 {d1=0-d1} 48 var d2: i64 = g-bgg; if d2<0 {d2=0-d2} 49 var d3: i64 = b-bgb; if d3<0 {d3=0-d3} 50 if d1+d2+d3 > 18 { 51 body = body + 1 52 hist[r] = hist[r] + 1 53 if r==255 { if g==255 { if b==255 { blown = blown + 1 } } } 54 } 55 x = x + 1 56 } 57 y = y + 1 58 } 59 var distinct: i64 = 0 60 i = 0 61 while i < 256 { if hist[i] > 0 { distinct = distinct + 1 } i = i + 1 } 62 out[0] = blown 63 out[1] = body 64 out[2] = distinct 65 return 0 66} 67 68func main() -> i64 { 69 tm_puts("=== nx_gx6_tonemap_gate -- real tonemap vs the hard 255 clamp (seq260) ===\n" as *u8) 70 var fails: i64 = 0 71 72 // ---- T1 MONOTONIC: brighter in must never be darker out, or shading order inverts ---- 73 var mono: i64 = 1 74 var prev: i64 = 0-1 75 var v: i64 = 0 76 while v <= 600 { 77 let o: i64 = sg_tonemap(v) 78 if o < prev { mono = 0 } 79 prev = o 80 v = v + 1 81 } 82 if mono==1 { tm_puts("T1 PASS tonemap is monotonic over 0..600 (no detail inversion)\n" as *u8) } 83 else { fails=fails+1; tm_puts("T1 FAIL not monotonic\n" as *u8) } 84 85 // ---- T2 endpoints + the clamp is UNREACHABLE by construction below the white point ---- 86 let at0: i64 = sg_tonemap(0) 87 let atW: i64 = sg_tonemap(SG_TM_WHITE10 * 255 / 10 * 10 / SG_TM_EXPOSURE10) 88 let atMax: i64 = sg_tonemap(600) 89 // the FULL transfer curve, printed so the midtone cost of tonemapping is visible, never asserted away 90 tm_puts(" TRANSFER CURVE in->out: 64->"); tm_pn(sg_tonemap(64)) 91 tm_puts(" 128->"); tm_pn(sg_tonemap(128)) 92 tm_puts(" 192->"); tm_pn(sg_tonemap(192)) 93 tm_puts(" 255->"); tm_pn(sg_tonemap(255)) 94 tm_puts(" 360(peak)->"); tm_pn(sg_tonemap(360)); tm_puts("\n" as *u8) 95 tm_puts(" tonemap(0)="); tm_pn(at0); tm_puts(" tonemap(whitepoint)="); tm_pn(atW); tm_puts(" tonemap(600)="); tm_pn(atMax); tm_puts("\n" as *u8) 96 var t2: i64 = 0 97 if at0==0 { if atW>=250 { if atW<=255 { if atMax<=255 { t2=1 } } } } 98 if t2==1 { tm_puts("T2 PASS black stays black, the white point lands on 255, nothing can exceed it\n" as *u8) } 99 else { fails=fails+1; tm_puts("T2 FAIL endpoints\n" as *u8) } 100 101 // ---- T3 THE FIX, AT FUNCTION LEVEL: over the range that used to clip, count DISTINCT outputs. 102 // Old clamp collapsed every radiance >=255 onto ONE value. That is the detail loss. ---- 103 let seenOld: *i64 = sys_mmap(8*256) as *i64 104 let seenNew: *i64 = sys_mmap(8*256) as *i64 105 var z: i64 = 0 106 while z < 256 { seenOld[z]=0; seenNew[z]=0; z=z+1 } 107 v = 255 108 while v <= 400 { 109 let ov: i64 = tm_oldclamp(v) 110 let nv: i64 = sg_tonemap(v) 111 if ov >= 0 { if ov < 256 { seenOld[ov] = 1 } } 112 if nv >= 0 { if nv < 256 { seenNew[nv] = 1 } } 113 v = v + 1 114 } 115 var dOld: i64 = 0 116 var dNew: i64 = 0 117 z = 0 118 while z < 256 { if seenOld[z]==1 { dOld=dOld+1 } if seenNew[z]==1 { dNew=dNew+1 } z=z+1 } 119 tm_puts(" radiance 255..400 -> distinct output levels: OLD clamp="); tm_pn(dOld) 120 tm_puts(" NEW tonemap="); tm_pn(dNew); tm_puts("\n" as *u8) 121 var t3: i64 = 0 122 if dOld==1 { if dNew>=20 { t3=1 } } 123 if t3==1 { tm_puts("T3 PASS highlight DETAIL RECOVERED: the old clamp had 1 level, the tonemap has "); tm_pn(dNew); tm_puts("\n" as *u8) } 124 else { fails=fails+1; tm_puts("T3 FAIL dOld="); tm_pn(dOld); tm_puts(" dNew="); tm_pn(dNew); tm_puts("\n" as *u8) } 125 126 // ---- render the WORST CASE: a very high-albedo (snow) figure, the material that blew out ---- 127 let base: i64 = sys_mmap(swgpu_bytes()) as i64 128 let rgbS: *u8 = sys_mmap(ww()*hh()*3+16) 129 let rgbK: *u8 = sys_mmap(ww()*hh()*3+16) 130 sg_humanoid(base, 20) 131 sg_project(base, 500, 0-5) 132 sg_render(base, 240, 242, 248) 133 tm_fb_rgb(base, rgbS) 134 sg_humanoid(base, 20) 135 sg_project(base, 500, 0-5) 136 sg_render(base, 240, 182, 158) 137 tm_fb_rgb(base, rgbK) 138 139 let hist: *i64 = sys_mmap(8*256) as *i64 140 let st: *i64 = sys_mmap(8*8) as *i64 141 tm_stats(rgbS, hist, st) 142 let blownS: i64 = st[0] 143 let bodyS: i64 = st[1] 144 let distS: i64 = st[2] 145 tm_puts(" SNOW-albedo figure: body px="); tm_pn(bodyS); tm_puts(" blown-to-pure-white="); tm_pn(blownS) 146 tm_puts(" distinct levels="); tm_pn(distS); tm_puts("\n" as *u8) 147 148 // ---- T4 the worst-case material no longer floods to white ---- 149 var t4: i64 = 0 150 if bodyS > 10000 { if blownS * 100 / bodyS < 5 { t4 = 1 } } 151 if t4==1 { tm_puts("T4 PASS the brightest material no longer floods: blown is "); tm_pn(blownS*100/bodyS); tm_puts(" percent of the body\n" as *u8) } 152 else { fails=fails+1; tm_puts("T4 FAIL blown="); tm_pn(blownS); tm_puts(" of "); tm_pn(bodyS); tm_puts("\n" as *u8) } 153 154 // ---- T5 and it is GRADATION, not flatness: many distinct levels survive on that bright surface ---- 155 var t5: i64 = 0 156 if distS >= 40 { t5 = 1 } 157 if t5==1 { tm_puts("T5 PASS bright surface carries real gradation ("); tm_pn(distS); tm_puts(" distinct levels, not a flat blob)\n" as *u8) } 158 else { fails=fails+1; tm_puts("T5 FAIL only "); tm_pn(distS); tm_puts(" levels\n" as *u8) } 159 160 // ---- T6 NORMAL materials still render richly (the curve must not crush ordinary skin) ---- 161 tm_stats(rgbK, hist, st) 162 let distK: i64 = st[2] 163 let bodyK: i64 = st[1] 164 tm_puts(" SKIN-albedo figure: body px="); tm_pn(bodyK); tm_puts(" distinct levels="); tm_pn(distK); tm_puts("\n" as *u8) 165 var t6: i64 = 0 166 if distK >= 40 { t6 = 1 } 167 if t6==1 { tm_puts("T6 PASS ordinary material keeps its gradation too ("); tm_pn(distK); tm_puts(" levels)\n" as *u8) } 168 else { fails=fails+1; tm_puts("T6 FAIL skin levels="); tm_pn(distK); tm_puts("\n" as *u8) } 169 170 // ---- side-by-side artifact: SNOW | SKIN, both tonemapped ---- 171 let CW: i64 = 1028 172 let comp: *u8 = sys_mmap(CW*hh()*3+16) 173 var y2: i64 = 0 174 while y2 < hh() { 175 var x2: i64 = 0 176 while x2 < CW { 177 let o: i64 = (y2*CW+x2)*3 178 if x2 < ww() { let s: i64 = (y2*ww()+x2)*3; comp[o]=rgbS[s]; comp[o+1]=rgbS[s+1]; comp[o+2]=rgbS[s+2] } 179 else { if x2 >= ww()+4 { let s2: i64 = (y2*ww()+(x2-ww()-4))*3; comp[o]=rgbK[s2]; comp[o+1]=rgbK[s2+1]; comp[o+2]=rgbK[s2+2] } 180 else { comp[o]=200 as u8; comp[o+1]=90 as u8; comp[o+2]=110 as u8 } } 181 x2 = x2 + 1 182 } 183 y2 = y2 + 1 184 } 185 nx_png_write_rgb("knowledge/nx_gx6_tonemap.png\x00" as *u8, comp, CW, hh()) 186 let szp: *i64 = sys_mmap(16) as *i64 187 let rb: *u8 = sys_read_file("knowledge/nx_gx6_tonemap.png\x00" as *u8, szp) 188 var t7: i64 = 0 189 if (rb as i64)!=0 { if szp[0]>1000 { t7=1 } } 190 if t7==1 { tm_puts("T7 PASS artifact written (snow | skin, both tonemapped), "); tm_pn(szp[0]); tm_puts(" bytes\n" as *u8) } 191 else { fails=fails+1; tm_puts("T7 FAIL png\n" as *u8) } 192 193 if fails==0 { tm_puts("GX6-TONEMAP GREEN -- seq260 fixed: highlights roll off instead of clipping, distinct radiances stay distinct\n" as *u8); sys_exit(0); return 0 } 194 tm_puts("GX6-TONEMAP RED fails="); tm_pn(fails); tm_puts("\n" as *u8) 195 sys_exit(1) 196 return 1 197}