code wiki / (root) / nx_swgpu.nx

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 }