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}