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