code wiki / (root) / nx_swgpu.nx

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 }