nx_swgpu.nx source
↩ module page · 599 lines · 29145 B
1// nx_swgpu.nx -- the SOVEREIGN SOFTWARE GPU core (operator 2026-07-04: "build our OWN, beyond WebGL2/WebGPU,
2// be FIRST ... nishi os + browser + game state of the art on our own"). A CPU that IS a GPU: a general MESH
3// renderer -- arbitrary world verts + normals + triangles -> a TILED rasterizer (fast/SIMD/MT-ready, the
4// llvmpipe/SwiftShader model, fully ours) with PER-PIXEL lighting (barycentric normals -> integer Blinn-Phong
5// + rim + tonemap-ish). ALL INTEGER (nx_itrig) = deterministic + VM-vettable. Same pipeline Path B runs on the
6// real GPU (NVIDIA/AMD/FPGA) via our driver. Primitives: sg_add_ellipsoid (sphere/limb/torso) -> compose any
7// body. license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_itrig.nx"
10const O_MAGIC_4096: i64 = 4096
11const O_MAGIC_65536: i64 = 65536
12const O_MAGIC_4950: i64 = 4950
13const O_MAGIC_1080: i64 = 1080
14const O_MAGIC_3950: i64 = 3950
15const O_MAGIC_3050: i64 = 3050
16const O_MAGIC_1450: i64 = 1450
17const O_MAGIC_1250: i64 = 1250
18const O_MAGIC_2100: i64 = 2100
19const O_MAGIC_1300: i64 = 1300
20const O_MAGIC_1200: i64 = 1200
21const O_MAGIC_1400: i64 = 1400
22const O_MAGIC_1050: i64 = 1050
23const O_MAGIC_1550: i64 = 1550
24const O_MAGIC_2900: i64 = 2900
25const O_MAGIC_1780: i64 = 1780
26const O_MAGIC_1150: i64 = 1150
27
28const W: i64 = 512
29const H: i64 = 384
30const HW: i64 = 256
31const HH: i64 = 192
32const TILE: i64 = 64
33const NTX: i64 = 8
34const NTY: i64 = 6
35const FOCAL: i64 = 520
36const ZFAR: i64 = 1073741824
37const MAXV: i64 = 8192
38const MAXT: i64 = 16384
39
40const O_FB: i64 = 0
41const O_ZB: i64 = 1572864 // W*H*8
42const O_PX: i64 = 3145728 // world vert positions fx4096 (MAXV each)
43const O_PY: i64 = 3211264
44const O_PZ: i64 = 3276800
45const O_NX: i64 = 3342336 // vert normals (fx4096 unit, -> fx256 view at project)
46const O_NY: i64 = 3407872
47const O_NZ: i64 = 3473408
48const O_SX: i64 = 3538944 // screen x int
49const O_SY: i64 = 3604480
50const O_SZ: i64 = 3670016 // depth key
51const O_VIS: i64 = 3735552
52const O_TA: i64 = 3801088 // MAXT*3 i64 tri indices
53const O_TN: i64 = 4194304 // tri count
54const O_VC: i64 = 4194312 // vert count
55// ---- near-plane CLIPPING working set (seq249; appended, so swgpu_bytes() grows for every caller) ----
56const O_VVX: i64 = 4194376 // view-space vert x fx4096 (MAXV) -- kept so we clip BEFORE projecting
57const O_VVY: i64 = 4259912
58const O_VVZ: i64 = 4325448
59const O_TA2: i64 = 4390984 // scratch tri list the clipper emits into (MAXT*3). In-place is UNSAFE:
60 // one input tri can emit TWO output tris, so the write index outruns read.
61const O_CLIP: i64 = 4784200 // [0]=emitted tris [1]=verts the clip added [2]=envelope-hit flag
62const SG_NEAR: i64 = 2048 // near plane 0.5 world units in front of the eye (fx4096 view z)
63func swgpu_bytes() -> i64 { return O_CLIP + 64 }
64
65func sg_fb(base: i64) -> *i64 { return (base + O_FB) as *i64 }
66func sg_zb(base: i64) -> *i64 { return (base + O_ZB) as *i64 }
67func sg_isqrt(v: i64) -> i64 { if v <= 0 { return 0 } var x: i64 = v; var y: i64 = (x + 1) / 2; while y < x { x = y; y = (x + v / x) / 2 } return x }
68func sg_imin(a: i64, b: i64) -> i64 { if a < b { return a } return b }
69func sg_imax(a: i64, b: i64) -> i64 { if a > b { return a } return b }
70
71// EXPOSURE and WHITE POINT, both DERIVED rather than dialled in by eye:
72// This pipeline's true peak radiance is diffuse(albedo*238/256, up to ~231) + rim(46, x1.5 = 69 on blue)
73// + cubed specular(60) = ~360, i.e. 1.41 of 255. Plain Reinhard maps that range into 0..1 by compressing
74// EVERYTHING, which cost ~20 percent of midtone brightness (measured by eye on the first build: correct
75// highlights, but the whole image went dull). EXPOSURE pre-scales so midtones land back where they were,
76// and WHITE is then set to peak x exposure so the top of the range still lands exactly on 255 and cannot
77// clip. Gate reports the full transfer curve so this trade-off stays visible instead of being asserted.
78const SG_TM_EXPOSURE10: i64 = 12 // x1.2 midtone restore
79const SG_TM_WHITE10: i64 = 17 // 1.41 peak x 1.2 exposure ~= 1.7 -> nothing reaches the clamp
80
81// sg_tonemap -- EXTENDED REINHARD, all-integer: out = x(1 + x/W^2)/(1 + x) with x = v/255, W = white point.
82// Replaces the hard `if v > 255 { v = 255 }` clamp that was destroying every highlight (seq260: 50.4 percent
83// of a real game frame clamped to pure white, erasing all material detail there). Highlights now ROLL OFF --
84// distinct input radiances stay distinct output values -- and by construction the result can never exceed
85// 255, so the clamp is not merely relocated, it is impossible to reach. Monotonic (gate-asserted): brighter
86// in is never darker out, so shading order is preserved and no detail inverts.
87func sg_tonemap(v: i64) -> i64 {
88 if v <= 0 { return 0 }
89 let x: i64 = v * SG_TM_EXPOSURE10 / 10 // exposure first, so midtones keep their level
90 let w2: i64 = SG_TM_WHITE10 * SG_TM_WHITE10 // W^2, scaled x100
91 var o: i64 = x * (w2 * 255 + 100 * x) / (w2 * (255 + x))
92 if o > 255 { o = 255 } // exact-white guard only; unreachable below W
93 return o
94}
95
96func sg_reset(base: i64) -> i64 {
97 let tn: *i64 = (base + O_TN) as *i64
98 let vc: *i64 = (base + O_VC) as *i64
99 tn[0] = 0
100 vc[0] = 0
101 return 0
102}
103
104// append an ELLIPSOID (sphere/limb/torso) at center (fx4096) with radii (fx4096); a body = a union of these.
105func sg_add_ellipsoid(base: i64, cx: i64, cy: i64, cz: i64, rx: i64, ry: i64, rz: i64, seg: i64) -> i64 {
106 let px: *i64 = (base + O_PX) as *i64
107 let py: *i64 = (base + O_PY) as *i64
108 let pz: *i64 = (base + O_PZ) as *i64
109 let nx: *i64 = (base + O_NX) as *i64
110 let ny: *i64 = (base + O_NY) as *i64
111 let nz: *i64 = (base + O_NZ) as *i64
112 let ta: *i64 = (base + O_TA) as *i64
113 let vcp: *i64 = (base + O_VC) as *i64
114 let tnp: *i64 = (base + O_TN) as *i64
115 let v0: i64 = vcp[0]
116 var vc: i64 = v0
117 var y: i64 = 0
118 while y <= seg {
119 let ph: i64 = y * IT_PI / seg
120 let sp: i64 = it_sin4096(ph)
121 let cp: i64 = it_cos4096(ph)
122 var x: i64 = 0
123 while x <= seg {
124 if vc < MAXV {
125 let th: i64 = x * 2 * IT_PI / seg
126 let st: i64 = it_sin4096(th)
127 let ct: i64 = it_cos4096(th)
128 let ux: i64 = sp * ct / O_MAGIC_4096 // unit dir fx4096
129 let uy: i64 = cp
130 let uz: i64 = sp * st / O_MAGIC_4096
131 px[vc] = cx + ux * rx / O_MAGIC_4096 // surface point = center + dir*radius
132 py[vc] = cy + uy * ry / O_MAGIC_4096
133 pz[vc] = cz + uz * rz / O_MAGIC_4096
134 // ellipsoid normal ~ (ux/rx, uy/ry, uz/rz), normalized to fx4096
135 var mnx: i64 = 0
136 var mny: i64 = 0
137 var mnz: i64 = 0
138 if rx > 0 { mnx = ux * O_MAGIC_4096 / rx }
139 if ry > 0 { mny = uy * O_MAGIC_4096 / ry }
140 if rz > 0 { mnz = uz * O_MAGIC_4096 / rz }
141 let ml: i64 = sg_isqrt(mnx * mnx + mny * mny + mnz * mnz)
142 if ml > 0 { nx[vc] = mnx * O_MAGIC_4096 / ml; ny[vc] = mny * O_MAGIC_4096 / ml; nz[vc] = mnz * O_MAGIC_4096 / ml }
143 else { nx[vc] = 0; ny[vc] = O_MAGIC_4096; nz[vc] = 0 }
144 vc = vc + 1
145 }
146 x = x + 1
147 }
148 y = y + 1
149 }
150 var tc: i64 = tnp[0]
151 y = 0
152 while y < seg {
153 var x2: i64 = 0
154 while x2 < seg {
155 if tc + 1 < MAXT {
156 let a: i64 = v0 + y * (seg + 1) + x2
157 let b: i64 = a + seg + 1
158 ta[tc * 3] = a; ta[tc * 3 + 1] = b; ta[tc * 3 + 2] = a + 1; tc = tc + 1
159 ta[tc * 3] = b; ta[tc * 3 + 1] = b + 1; ta[tc * 3 + 2] = a + 1; tc = tc + 1
160 }
161 x2 = x2 + 1
162 }
163 y = y + 1
164 }
165 vcp[0] = vc
166 tnp[0] = tc
167 return 0
168}
169
170func sg_sphere(base: i64, seg: i64) -> i64 {
171 sg_reset(base)
172 sg_add_ellipsoid(base, 0, 0, 0, O_MAGIC_4096, O_MAGIC_4096, O_MAGIC_4096, seg)
173 let vc: *i64 = (base + O_VC) as *i64
174 return vc[0]
175}
176
177// project: rotate about Y, camera translate, perspective -> screen int; normals -> fx256 view.
178func sg_project(base: i64, yaw: i64, camz_units: i64) -> i64 {
179 let px: *i64 = (base + O_PX) as *i64
180 let py: *i64 = (base + O_PY) as *i64
181 let pz: *i64 = (base + O_PZ) as *i64
182 let nxp: *i64 = (base + O_NX) as *i64
183 let nyp: *i64 = (base + O_NY) as *i64
184 let nzp: *i64 = (base + O_NZ) as *i64
185 let sx: *i64 = (base + O_SX) as *i64
186 let sy: *i64 = (base + O_SY) as *i64
187 let sz: *i64 = (base + O_SZ) as *i64
188 let vis: *i64 = (base + O_VIS) as *i64
189 let vc: *i64 = (base + O_VC) as *i64
190 let nv: i64 = vc[0]
191 let c: i64 = it_cos4096(yaw)
192 let s: i64 = it_sin4096(yaw)
193 let camz: i64 = camz_units * O_MAGIC_4096
194 var i: i64 = 0
195 while i < nv {
196 let vx: i64 = px[i]
197 let vy: i64 = py[i]
198 let vz0: i64 = pz[i]
199 let rx: i64 = (c * vx + s * vz0) / O_MAGIC_4096
200 let rz: i64 = (0 - s * vx + c * vz0) / O_MAGIC_4096
201 // rotate normal
202 let mnx: i64 = nxp[i]
203 let mnz: i64 = nzp[i]
204 nxp[i] = ((c * mnx + s * mnz) / O_MAGIC_4096) / 16 // fx256 view normal
205 nyp[i] = nyp[i] / 16
206 nzp[i] = ((0 - s * mnx + c * mnz) / O_MAGIC_4096) / 16
207 let vz: i64 = rz - camz
208 if vz <= 0 { vis[i] = 0 } else {
209 vis[i] = 1
210 sx[i] = HW + FOCAL * rx / vz
211 sy[i] = HH - FOCAL * vy / vz
212 sz[i] = vz
213 }
214 i = i + 1
215 }
216 return 0
217}
218
219// interpolate a NEW vert on the segment inside->outside at the point where view z crosses SG_NEAR, writing it
220// at index k. POSITION and NORMAL are both interpolated, so shading stays continuous across the cut (a
221// position-only clip would band the lighting along the seam).
222func sg_clip_vert(base: i64, ins: i64, outv: i64, k: i64) -> i64 {
223 let nxp: *i64 = (base + O_NX) as *i64
224 let nyp: *i64 = (base + O_NY) as *i64
225 let nzp: *i64 = (base + O_NZ) as *i64
226 let vvx: *i64 = (base + O_VVX) as *i64
227 let vvy: *i64 = (base + O_VVY) as *i64
228 let vvz: *i64 = (base + O_VVZ) as *i64
229 var den: i64 = vvz[ins] - vvz[outv]
230 if den == 0 { den = 1 }
231 let num: i64 = SG_NEAR - vvz[outv]
232 vvx[k] = vvx[outv] + (vvx[ins] - vvx[outv]) * num / den
233 vvy[k] = vvy[outv] + (vvy[ins] - vvy[outv]) * num / den
234 vvz[k] = SG_NEAR
235 nxp[k] = nxp[outv] + (nxp[ins] - nxp[outv]) * num / den
236 nyp[k] = nyp[outv] + (nyp[ins] - nyp[outv]) * num / den
237 nzp[k] = nzp[outv] + (nzp[ins] - nzp[outv]) * num / den
238 return 0
239}
240
241// sg_project_clip -- sg_project WITH real near-plane clipping (Sutherland-Hodgman against z=SG_NEAR in view
242// space). Fixes seq249: sg_project alone marks a vert invisible when its view z <= 0 and the rasterizers then
243// drop the ENTIRE triangle, so surfaces straddling the camera plane vanish and pop as you walk into them.
244// This splits those triangles instead -- 1 vert inside -> 1 clipped tri, 2 inside -> 2 tris (the quad) -- so
245// geometry stays continuous right up to the eye. Drop-in for sg_project; sg_project itself is untouched.
246// ENVELOPE (declared, never silent): <= MAXV verts and <= MAXT emitted tris; on overflow clip[2] is set to 1
247// and emission stops -- callers can read (base + O_CLIP) for [tris, added_verts, envelope_hit].
248// yaw-only wrapper -- every existing caller keeps working unchanged. NishiLang does NOT check call arity,
249// so widening the original signature would have silently mis-bound every call site; adding a verb is safe.
250func sg_project_clip(base: i64, yaw: i64, camz_units: i64) -> i64 {
251 return sg_project_clip_pitch(base, yaw, 0, camz_units)
252}
253
254// PITCH (look up / down) as well as yaw. Without it a first-person camera on any raised ground sees nothing
255// but sky -- found by eyeballing the Gx-7 win frame from the mountain top. pitch > 0 looks DOWN; units are
256// rad*4096 like every other angle here.
257func sg_project_clip_pitch(base: i64, yaw: i64, pitch: i64, camz_units: i64) -> i64 {
258 let px: *i64 = (base + O_PX) as *i64
259 let py: *i64 = (base + O_PY) as *i64
260 let pz: *i64 = (base + O_PZ) as *i64
261 let nxp: *i64 = (base + O_NX) as *i64
262 let nyp: *i64 = (base + O_NY) as *i64
263 let nzp: *i64 = (base + O_NZ) as *i64
264 let sx: *i64 = (base + O_SX) as *i64
265 let sy: *i64 = (base + O_SY) as *i64
266 let sz: *i64 = (base + O_SZ) as *i64
267 let vis: *i64 = (base + O_VIS) as *i64
268 let ta: *i64 = (base + O_TA) as *i64
269 let tn: *i64 = (base + O_TN) as *i64
270 let vc: *i64 = (base + O_VC) as *i64
271 let vvx: *i64 = (base + O_VVX) as *i64
272 let vvy: *i64 = (base + O_VVY) as *i64
273 let vvz: *i64 = (base + O_VVZ) as *i64
274 let ta2: *i64 = (base + O_TA2) as *i64
275 let clip: *i64 = (base + O_CLIP) as *i64
276 clip[0] = 0
277 clip[1] = 0
278 clip[2] = 0
279 let c: i64 = it_cos4096(yaw)
280 let s: i64 = it_sin4096(yaw)
281 let cp: i64 = it_cos4096(pitch)
282 let sp: i64 = it_sin4096(pitch)
283 let camz: i64 = camz_units * O_MAGIC_4096
284 let nv0: i64 = vc[0]
285 var nv: i64 = nv0
286 // phase 1: world -> VIEW space (positions retained; normals rotated to fx256 exactly as sg_project does).
287 // Yaw about Y first, then PITCH about X -- so pitch is applied in the already-turned frame, which is what
288 // makes "turn then look down" behave the way a player expects.
289 var i: i64 = 0
290 while i < nv0 {
291 let wx: i64 = px[i]
292 let wz: i64 = pz[i]
293 let mnx: i64 = nxp[i]
294 let mnz: i64 = nzp[i]
295 let ynx: i64 = ((c * mnx + s * mnz) / O_MAGIC_4096) / 16
296 let yny: i64 = nyp[i] / 16
297 let ynz: i64 = ((0 - s * mnx + c * mnz) / O_MAGIC_4096) / 16
298 nxp[i] = ynx
299 nyp[i] = (yny * cp + ynz * sp) / O_MAGIC_4096
300 nzp[i] = ((0 - yny) * sp + ynz * cp) / O_MAGIC_4096
301 let yx: i64 = (c * wx + s * wz) / O_MAGIC_4096
302 let yy: i64 = py[i]
303 let yz: i64 = ((0 - s * wx + c * wz) / O_MAGIC_4096) - camz
304 vvx[i] = yx
305 vvy[i] = (yy * cp + yz * sp) / O_MAGIC_4096
306 vvz[i] = ((0 - yy) * sp + yz * cp) / O_MAGIC_4096
307 i = i + 1
308 }
309 // phase 2: clip every tri against vvz >= SG_NEAR, emitting into the scratch list
310 let ntri: i64 = tn[0]
311 var outn: i64 = 0
312 var t: i64 = 0
313 while t < ntri {
314 let a: i64 = ta[t * 3]
315 let b: i64 = ta[t * 3 + 1]
316 let d: i64 = ta[t * 3 + 2]
317 var nin: i64 = 0
318 if vvz[a] >= SG_NEAR { nin = nin + 1 }
319 if vvz[b] >= SG_NEAR { nin = nin + 1 }
320 if vvz[d] >= SG_NEAR { nin = nin + 1 }
321 if nin == 3 {
322 if outn < MAXT { ta2[outn * 3] = a; ta2[outn * 3 + 1] = b; ta2[outn * 3 + 2] = d; outn = outn + 1 }
323 else { clip[2] = 1 }
324 }
325 // ONE inside: the clipped region is the triangle (ins, cross(ins->next), cross(prev->ins)).
326 if nin == 1 {
327 var ins: i64 = a
328 var o1: i64 = b
329 var o2: i64 = d
330 if vvz[b] >= SG_NEAR { ins = b; o1 = d; o2 = a }
331 if vvz[d] >= SG_NEAR { ins = d; o1 = a; o2 = b }
332 if nv + 2 <= MAXV {
333 if outn < MAXT {
334 sg_clip_vert(base, ins, o1, nv)
335 sg_clip_vert(base, ins, o2, nv + 1)
336 ta2[outn * 3] = ins; ta2[outn * 3 + 1] = nv; ta2[outn * 3 + 2] = nv + 1
337 outn = outn + 1
338 nv = nv + 2
339 } else { clip[2] = 1 }
340 } else { clip[2] = 1 }
341 }
342 // TWO inside: the clipped region is a QUAD (i1, i2, cross(i2->out), cross(i1->out)) -> two tris.
343 if nin == 2 {
344 var outv: i64 = d
345 var i1: i64 = a
346 var i2: i64 = b
347 if vvz[a] < SG_NEAR { outv = a; i1 = b; i2 = d }
348 if vvz[b] < SG_NEAR { outv = b; i1 = d; i2 = a }
349 if nv + 2 <= MAXV {
350 if outn + 1 < MAXT {
351 sg_clip_vert(base, i2, outv, nv)
352 sg_clip_vert(base, i1, outv, nv + 1)
353 ta2[outn * 3] = i1; ta2[outn * 3 + 1] = i2; ta2[outn * 3 + 2] = nv
354 outn = outn + 1
355 ta2[outn * 3] = i1; ta2[outn * 3 + 1] = nv; ta2[outn * 3 + 2] = nv + 1
356 outn = outn + 1
357 nv = nv + 2
358 } else { clip[2] = 1 }
359 } else { clip[2] = 1 }
360 }
361 t = t + 1
362 }
363 // phase 3: project every vert (original + interpolated) from view space to screen
364 i = 0
365 while i < nv {
366 let vz: i64 = vvz[i]
367 if vz < SG_NEAR { vis[i] = 0 } else {
368 vis[i] = 1
369 sx[i] = HW + FOCAL * vvx[i] / vz
370 sy[i] = HH - FOCAL * vvy[i] / vz
371 sz[i] = vz
372 }
373 i = i + 1
374 }
375 // commit the clipped tri list over the caller's
376 var k: i64 = 0
377 while k < outn * 3 { ta[k] = ta2[k]; k = k + 1 }
378 tn[0] = outn
379 vc[0] = nv
380 clip[0] = outn
381 clip[1] = nv - nv0
382 return 0
383}
384
385// sg_raster -- THE rasterizer, single copy. Its body was duplicated verbatim into sg_render_pass when
386// multi-material passes landed (~100 lines of my own DRY debt): two places to fix any shading bug, two
387// places to add any feature. Unified here. clear=1 wipes fb+zbuf first (a frame's opening pass); clear=0
388// draws OVER them so later passes share the z-buffer and occlude correctly across materials.
389// The extraction is PROVEN behaviour-preserving, not assumed: the walk gate (16691393) and game gate
390// (42851779) golden checksums must hold bit-exact across it.
391func sg_raster(base: i64, skin_r: i64, skin_g: i64, skin_b: i64, clear: i64) -> i64 {
392 let fb: *i64 = sg_fb(base)
393 let zb: *i64 = sg_zb(base)
394 if clear == 1 {
395 var p: i64 = 0
396 while p < W * H {
397 let yy0: i64 = p / W
398 fb[p] = (26 + yy0 * 36 / H) + (28 + yy0 * 34 / H) * 256 + (42 + yy0 * 30 / H) * O_MAGIC_65536
399 zb[p] = ZFAR
400 p = p + 1
401 }
402 }
403 let sx: *i64 = (base + O_SX) as *i64
404 let sy: *i64 = (base + O_SY) as *i64
405 let sz: *i64 = (base + O_SZ) as *i64
406 let nxp: *i64 = (base + O_NX) as *i64
407 let nyp: *i64 = (base + O_NY) as *i64
408 let nzp: *i64 = (base + O_NZ) as *i64
409 let vis: *i64 = (base + O_VIS) as *i64
410 let ta: *i64 = (base + O_TA) as *i64
411 let tn: *i64 = (base + O_TN) as *i64
412 let ntri: i64 = tn[0]
413 let Lx: i64 = 130
414 let Ly: i64 = 205
415 let Lz: i64 = 0 - 130
416 var tyi: i64 = 0
417 while tyi < NTY {
418 var txi: i64 = 0
419 while txi < NTX {
420 let tx0: i64 = txi * TILE
421 let ty0: i64 = tyi * TILE
422 let tx1: i64 = tx0 + TILE - 1
423 let ty1: i64 = ty0 + TILE - 1
424 var ti: i64 = 0
425 while ti < ntri {
426 let ia: i64 = ta[ti * 3]
427 let ib: i64 = ta[ti * 3 + 1]
428 let ic: i64 = ta[ti * 3 + 2]
429 if vis[ia] == 1 { if vis[ib] == 1 { if vis[ic] == 1 {
430 let ax: i64 = sx[ia]
431 let ay: i64 = sy[ia]
432 let bx: i64 = sx[ib]
433 let by: i64 = sy[ib]
434 let cx: i64 = sx[ic]
435 let cy: i64 = sy[ic]
436 let area: i64 = (bx - ax) * (cy - ay) - (cx - ax) * (by - ay)
437 if area > 0 {
438 let minx: i64 = sg_imax(tx0, sg_imin(ax, sg_imin(bx, cx)))
439 let maxx: i64 = sg_imin(tx1, sg_imax(ax, sg_imax(bx, cx)))
440 let miny: i64 = sg_imax(ty0, sg_imin(ay, sg_imin(by, cy)))
441 let maxy: i64 = sg_imin(ty1, sg_imax(ay, sg_imax(by, cy)))
442 var yy: i64 = miny
443 while yy <= maxy {
444 var xx: i64 = minx
445 while xx <= maxx {
446 let w0: i64 = (bx - xx) * (cy - yy) - (cx - xx) * (by - yy)
447 let w1: i64 = (cx - xx) * (ay - yy) - (ax - xx) * (cy - yy)
448 let w2: i64 = (ax - xx) * (by - yy) - (bx - xx) * (ay - yy)
449 if w0 >= 0 { if w1 >= 0 { if w2 >= 0 {
450 let d: i64 = (w0 * sz[ia] + w1 * sz[ib] + w2 * sz[ic]) / area
451 let idx: i64 = yy * W + xx
452 if d < zb[idx] {
453 zb[idx] = d
454 var inx: i64 = (w0 * nxp[ia] + w1 * nxp[ib] + w2 * nxp[ic]) / area
455 var iny: i64 = (w0 * nyp[ia] + w1 * nyp[ib] + w2 * nyp[ic]) / area
456 var inz: i64 = (w0 * nzp[ia] + w1 * nzp[ib] + w2 * nzp[ic]) / area
457 let nl: i64 = sg_isqrt(inx * inx + iny * iny + inz * inz)
458 if nl > 0 { inx = inx * 256 / nl; iny = iny * 256 / nl; inz = inz * 256 / nl }
459 var diff: i64 = (inx * Lx + iny * Ly + inz * Lz) / 256
460 if diff < 0 { diff = 0 }
461 if diff > 256 { diff = 256 }
462 var az: i64 = inz
463 if az < 0 { az = 0 - az }
464 var rim: i64 = 256 - az
465 if rim < 0 { rim = 0 }
466 rim = rim * rim / 256 * 46 / 256
467 let lit: i64 = 70 + diff * 168 / 256 // brighter ambient+diffuse
468 var r: i64 = skin_r * lit / 256 + rim
469 var g: i64 = skin_g * lit / 256 + rim
470 var b: i64 = skin_b * lit / 256 + rim * 3 / 2
471 let sp2: i64 = diff * diff / 256 * diff / 256
472 r = r + sp2 * 60 / 256
473 g = g + sp2 * 60 / 256
474 b = b + sp2 * 60 / 256
475 r = sg_tonemap(r)
476 g = sg_tonemap(g)
477 b = sg_tonemap(b)
478 fb[idx] = r + g * 256 + b * O_MAGIC_65536
479 }
480 } } }
481 xx = xx + 1
482 }
483 yy = yy + 1
484 }
485 }
486 } } }
487 ti = ti + 1
488 }
489 txi = txi + 1
490 }
491 tyi = tyi + 1
492 }
493 return 0
494}
495
496// ---- AMBIENT OCCLUSION over the finished depth buffer (contact darkening) ----
497// Without it everything floats: a beacon resting on ground and a beacon hovering above it shade identically,
498// because direct lighting alone carries no proximity cue. Runs as a POST-PASS on the z-buffer the rasterizer
499// already filled, so it adds NOTHING to the raster inner loop and cannot regress it.
500// ★THE PLANE PROBLEM, and why this samples in PAIRS: a naive "is my neighbour nearer?" test darkens every
501// receding surface, because on any sloped or receding plane the downhill neighbour is always nearer -- the
502// classic SSAO false positive that greys out floors. Real engines avoid it with per-pixel normals, which we
503// would have to write from the inner loop. Instead we test OPPOSITE PAIRS: in a genuine cavity BOTH sides are
504// nearer; on a plane or slope one side is nearer and the other is farther, so it scores zero. Same effect,
505// no normal buffer, no inner-loop cost.
506const SG_AO_BIAS: i64 = 300 // below ~0.07 world units it is depth noise, not a cavity
507const SG_AO_FAR: i64 = 24576 // beyond 6 world units it is a DIFFERENT surface, not an occluder --
508 // this is what stops silhouettes being ringed with false shadow
509func sg_ssao(base: i64, strength: i64) -> i64 {
510 let fb: *i64 = sg_fb(base)
511 let zb: *i64 = sg_zb(base)
512 let ax: *i64 = sys_mmap(8 * 16) as *i64
513 let ay: *i64 = sys_mmap(8 * 16) as *i64
514 // 8 opposite pairs: entries s and s+8 are mirrors. Two radii -- the tight ring finds contact seams,
515 // the wide ring finds broad cavities.
516 ax[0]=4; ay[0]=0; ax[1]=3; ay[1]=3; ax[2]=0; ay[2]=4; ax[3]=0-3; ay[3]=3
517 ax[4]=9; ay[4]=0; ax[5]=6; ay[5]=6; ax[6]=0; ay[6]=9; ax[7]=0-6; ay[7]=6
518 ax[8]=0-4; ay[8]=0; ax[9]=0-3; ay[9]=0-3; ax[10]=0; ay[10]=0-4; ax[11]=3; ay[11]=0-3
519 ax[12]=0-9; ay[12]=0; ax[13]=0-6; ay[13]=0-6; ax[14]=0; ay[14]=0-9; ax[15]=6; ay[15]=0-6
520 var y: i64 = 0
521 while y < H {
522 var x: i64 = 0
523 while x < W {
524 let idx: i64 = y * W + x
525 let d: i64 = zb[idx]
526 if d < ZFAR {
527 var occ: i64 = 0
528 var s: i64 = 0
529 while s < 8 {
530 var near1: i64 = 0
531 var near2: i64 = 0
532 let x1: i64 = x + ax[s]
533 let y1: i64 = y + ay[s]
534 if x1 >= 0 { if x1 < W { if y1 >= 0 { if y1 < H {
535 let d1: i64 = zb[y1 * W + x1]
536 if d1 < ZFAR { let df: i64 = d - d1; if df > SG_AO_BIAS { if df < SG_AO_FAR { near1 = 1 } } }
537 } } } }
538 let x2: i64 = x + ax[s + 8]
539 let y2: i64 = y + ay[s + 8]
540 if x2 >= 0 { if x2 < W { if y2 >= 0 { if y2 < H {
541 let d2: i64 = zb[y2 * W + x2]
542 if d2 < ZFAR { let df2: i64 = d - d2; if df2 > SG_AO_BIAS { if df2 < SG_AO_FAR { near2 = 1 } } }
543 } } } }
544 if near1 == 1 { if near2 == 1 { occ = occ + 1 } } // BOTH sides nearer = a real cavity
545 s = s + 1
546 }
547 if occ > 0 {
548 var att: i64 = 256 - occ * strength / 8
549 if att < 64 { att = 64 } // never crush a cavity to black
550 let v: i64 = fb[idx]
551 let r: i64 = (v % 256) * att / 256
552 let g: i64 = ((v / 256) % 256) * att / 256
553 let b: i64 = ((v / O_MAGIC_65536) % 256) * att / 256
554 fb[idx] = r + g * 256 + b * O_MAGIC_65536
555 }
556 }
557 x = x + 1
558 }
559 y = y + 1
560 }
561 return 0
562}
563
564// The two verbs every caller already uses -- now thin wrappers over the single rasterizer.
565func sg_render(base: i64, skin_r: i64, skin_g: i64, skin_b: i64) -> i64 { return sg_raster(base, skin_r, skin_g, skin_b, 1) }
566func sg_render_pass(base: i64, skin_r: i64, skin_g: i64, skin_b: i64) -> i64 { return sg_raster(base, skin_r, skin_g, skin_b, 0) }
567
568// compose a HUMANOID from ellipsoids (fx4096 world units; ~2.4 tall, centered near origin, feet down -y).
569func sg_humanoid(base: i64, seg: i64) -> i64 {
570 sg_reset(base)
571 // head + neck (neck overlaps chest)
572 sg_add_ellipsoid(base, 0, O_MAGIC_4950, 0, 900, O_MAGIC_1080, 950, seg)
573 sg_add_ellipsoid(base, 0, O_MAGIC_3950, 0, 520, 700, 520, seg)
574 // torso: overlapping chest -> waist stack (parts intersect so seams are internal = a continuous body)
575 sg_add_ellipsoid(base, 0, O_MAGIC_3050, 0, O_MAGIC_1450, O_MAGIC_1250, 950, seg)
576 sg_add_ellipsoid(base, 0, O_MAGIC_2100, 0, O_MAGIC_1300, 1000, 880, seg)
577 sg_add_ellipsoid(base, 0, O_MAGIC_1300, 0, O_MAGIC_1200, 950, 820, seg)
578 // breasts
579 sg_add_ellipsoid(base, 0 - 560, O_MAGIC_3050, 820, 640, 640, 600, seg)
580 sg_add_ellipsoid(base, 560, O_MAGIC_3050, 820, 640, 640, 600, seg)
581 // hips (overlap the waist above + thighs below)
582 sg_add_ellipsoid(base, 0, 550, 0, O_MAGIC_1400, O_MAGIC_1050, 950, seg)
583 // arms (upper overlaps shoulder, fore overlaps upper) -- fatter, angled inward at the wrist
584 sg_add_ellipsoid(base, 0 - O_MAGIC_1550, O_MAGIC_2900, 0, 640, O_MAGIC_1200, 620, seg)
585 sg_add_ellipsoid(base, 0 - O_MAGIC_1780, O_MAGIC_1250, 0, 540, O_MAGIC_1150, 540, seg)
586 sg_add_ellipsoid(base, O_MAGIC_1550, O_MAGIC_2900, 0, 640, O_MAGIC_1200, 620, seg)
587 sg_add_ellipsoid(base, O_MAGIC_1780, O_MAGIC_1250, 0, 540, O_MAGIC_1150, 540, seg)
588 // legs (thigh overlaps hips, shin overlaps thigh) -- fatter
589 sg_add_ellipsoid(base, 0 - 680, 0 - 550, 0, 760, O_MAGIC_1450, 760, seg)
590 sg_add_ellipsoid(base, 0 - 700, 0 - O_MAGIC_3050, 0, 620, O_MAGIC_1400, 620, seg)
591 sg_add_ellipsoid(base, 680, 0 - 550, 0, 760, O_MAGIC_1450, 760, seg)
592 sg_add_ellipsoid(base, 700, 0 - O_MAGIC_3050, 0, 620, O_MAGIC_1400, 620, seg)
593 let vc: *i64 = (base + O_VC) as *i64
594 return vc[0]
595}
596
597func ww() -> i64 { return W }
598func hh() -> i64 { return H }
599func fb_off() -> i64 { return O_FB }