code wiki / (root) / nx_gsplat.nx

nx_gsplat.nx source

↩ module page · 895 lines · 52043 B

1// nx_gsplat.nx -- ★SOVEREIGN 3D GAUSSIAN SPLATTING (the frontier rendering rep for photoreal avatars, confirmed 2// #1 by the 2024-2025 SOTA scan: GaussianAvatars/VRGaussianAvatar/HumanSplat all use it). A scene = a cloud of 3// 3D Gaussians {position, scale(σ), colour, opacity}; render = PROJECT each to a 2D splat, DEPTH-SORT, and 4// FRONT-TO-BACK alpha-composite. ALL INTEGER (fx1024 positions, fx256 opacity/transmittance, an integer exp-LUT 5// for the Gaussian falloff), deterministic, VM-vettable. ★The RENDERER needs NO trained weights (only a 6// generator would) -- so it is fully sovereign-buildable TODAY. v0 = ISOTROPIC Gaussians (spherical σ -> a 7// circular screen splat); anisotropic 3D covariance (the Jacobian projection) + SH view-dependent colour are the 8// next rungs. Camera convention MATCHES nx_sdfrender (yaw orbit, FOCAL 586) so splats align with our SDF/mesh. 9// license_tier: ORIGINAL 10import "nx_syscalls.nx" 11import "nx_itrig.nx" 12import "nx_vecmath.nx" 13const K_MAGIC_4096: i64 = 4096 14const K_MAGIC_3970: i64 = 3970 15const K_MAGIC_2000000000: i64 = 2000000000 16const K_MAGIC_65536: i64 = 65536 17const K_MAGIC_999999: i64 = 999999 18 19const GW: i64 = 512 20const GH: i64 = 384 21const GHW: i64 = 256 22const GHH: i64 = 192 23const GFOCAL: i64 = 586 24const GFX: i64 = 1024 // model-unit fixed point (matches sdf) 25const GEXPN: i64 = 176 // exp-LUT entries: k=0..175 -> u=k/16 in [0,11) (11σ² = past 3σ) 26const GNB: i64 = 2048 // depth-sort buckets 27 28// ★THE FIXED-POINT AND LAYOUT FACTS THIS RENDERER DEFINES -- EXPORTED so no consumer re-declares them. 29// A consumer that hardcodes 256, 16 or 12 becomes a SECOND RULER: change the value here and the copy 30// silently disagrees while still compiling and still producing a plausible-looking frame. That failure has 31// no symptom, which is exactly why the numbers have to leave the consumers and live at their one source. 32const GFXA: i64 = 256 // opacity / normal / transmittance fixed point 33const GLUTU: i64 = 16 // exp-LUT sub-steps per unit of u: explut[k] = GFXA*exp(-k/(2*GLUTU)) 34const GSTA: i64 = 12 // stride of the ANISOTROPIC record gs_render_aniso consumes 35const GSTI: i64 = 8 // stride of the ISOTROPIC record gs_render consumes 36// the model-unit fixed point. Exported for the same reason GFXA is: a consumer that re-declares 1024 37// becomes a SECOND RULER for world scale, and the two disagree silently while still rendering a frame. 38func gs_fx() -> i64 { return GFX } 39func gs_fxa() -> i64 { return GFXA } 40func gs_lutu() -> i64 { return GLUTU } 41func gs_stride_aniso() -> i64 { return GSTA } 42func gs_stride_iso() -> i64 { return GSTI } 43 44// RETIRED ONTO THE SHARED OWNER 2026-08-24: this was a private copy of the same Newton floor-sqrt; vm_isqrt is 45// gate-proven exact over 20,000 inputs. The alias keeps every call site untouched. 46func gs_isqrt(v: i64) -> i64 { return vm_isqrt(v) } 47 48// exp-LUT: explut[k] = round(256 * exp(-0.5 * k/16)), built by an integer recurrence (factor exp(-0.5/16) 49// ~= 3970/4096 in fx4096, then scaled to fx256). No float, deterministic. 50func gs_build_explut(explut: *i64) -> i64 { 51 var v: i64 = K_MAGIC_4096 // fx4096, exp(0)=1 52 var k: i64 = 0 53 while k < GEXPN { 54 explut[k] = v * 256 / K_MAGIC_4096 // -> fx256 55 v = v * K_MAGIC_3970 / K_MAGIC_4096 // *= exp(-0.5/16) 56 k = k + 1 57 } 58 return 0 59} 60 61// clear the per-pixel accumulators: acc = 0 (fx256 colour sum), trans = 256 (fx256 transmittance = 1.0) 62func gs_clear_at(acc: *i64, trans: *i64, npx: i64) -> i64 { 63 var i: i64 = 0 64 while i < npx { acc[i*3] = 0; acc[i*3+1] = 0; acc[i*3+2] = 0; trans[i] = GFXA; i = i + 1 } 65 return 0 66} 67func gs_clear(acc: *i64, trans: *i64) -> i64 { return gs_clear_at(acc, trans, GW * GH) } 68 69// render the Gaussian cloud. gauss = ng*8 i64 {x,y,z,scale,r,g,b,opacity(fx256)}; camera (yaw it4096, camz units). 70// scratch: acc(GW*GH*3), trans(GW*GH), depth(ng), sx(ng), sy(ng), sig(ng), order(ng), count(GNB+1), explut(GEXPN). 71// writes fb (GW*GH packed rgb). Returns the number of splatted (visible) gaussians. 72func gs_render(gauss: *i64, ng: i64, yaw: i64, camz: i64, fb: *i64, acc: *i64, trans: *i64, depth: *i64, sxb: *i64, syb: *i64, sigb: *i64, order: *i64, count: *i64, explut: *i64, bgr: i64, bgg: i64, bgb: i64) -> i64 { 73 let sy4: i64 = it_sin4096(yaw) 74 let cy4: i64 = it_cos4096(yaw) 75 let R: i64 = camz * GFX 76 // --- project all gaussians; mark culled with depth = -1 --- 77 var dmin: i64 = K_MAGIC_2000000000 78 var dmax: i64 = 0 - K_MAGIC_2000000000 79 var vis: i64 = 0 80 var i: i64 = 0 81 while i < ng { 82 let x: i64 = gauss[i*8] 83 let y: i64 = gauss[i*8+1] 84 let z: i64 = gauss[i*8+2] 85 let sc: i64 = gauss[i*8+3] 86 let cz: i64 = (x * sy4 + z * cy4) / K_MAGIC_4096 + R // camera-forward depth 87 if cz > 64 { 88 let cx: i64 = (x * cy4 - z * sy4) / K_MAGIC_4096 89 let sx: i64 = GHW + GFOCAL * cx / cz 90 let sy: i64 = GHH - GFOCAL * y / cz 91 var sig: i64 = GFOCAL * sc / cz // screen-space sigma (px) 92 if sig < 1 { sig = 1 } 93 depth[i] = cz; sxb[i] = sx; syb[i] = sy; sigb[i] = sig 94 if cz < dmin { dmin = cz } 95 if cz > dmax { dmax = cz } 96 vis = vis + 1 97 } else { depth[i] = 0 - 1 } 98 i = i + 1 99 } 100 if vis == 0 { return 0 } 101 // --- counting sort visible gaussians by depth (near->far = ascending cz) --- 102 let span: i64 = dmax - dmin + 1 103 var b: i64 = 0 104 while b <= GNB { count[b] = 0; b = b + 1 } 105 i = 0 106 while i < ng { if depth[i] >= 0 { var bk: i64 = (depth[i] - dmin) * GNB / span; if bk < 0 { bk = 0 } if bk >= GNB { bk = GNB - 1 } count[bk] = count[bk] + 1 } i = i + 1 } 107 var acc2: i64 = 0 // prefix sum -> bucket start offsets 108 b = 0 109 while b < GNB { let c: i64 = count[b]; count[b] = acc2; acc2 = acc2 + c; b = b + 1 } 110 i = 0 111 while i < ng { if depth[i] >= 0 { var bk: i64 = (depth[i] - dmin) * GNB / span; if bk < 0 { bk = 0 } if bk >= GNB { bk = GNB - 1 } order[count[bk]] = i; count[bk] = count[bk] + 1 } i = i + 1 } 112 // --- clear + composite front-to-back --- 113 gs_clear(acc, trans) 114 var oi: i64 = 0 115 while oi < vis { 116 let g: i64 = order[oi] 117 let sx: i64 = sxb[g] 118 let sy: i64 = syb[g] 119 let sig: i64 = sigb[g] 120 let rad: i64 = 3 * sig 121 let s2: i64 = sig * sig 122 let gr: i64 = gauss[g*8+4] 123 let gg: i64 = gauss[g*8+5] 124 let gb: i64 = gauss[g*8+6] 125 let op: i64 = gauss[g*8+7] 126 var py: i64 = sy - rad 127 if py < 0 { py = 0 } 128 var pye: i64 = sy + rad 129 if pye >= GH { pye = GH - 1 } 130 while py <= pye { 131 let dy: i64 = py - sy 132 var px: i64 = sx - rad 133 if px < 0 { px = 0 } 134 var pxe: i64 = sx + rad 135 if pxe >= GW { pxe = GW - 1 } 136 while px <= pxe { 137 let dx: i64 = px - sx 138 let d2: i64 = dx * dx + dy * dy 139 var k: i64 = d2 * 16 / s2 // u=d2/sig2 in 1/16ths 140 if k < GEXPN { 141 let pix: i64 = py * GW + px 142 let tr: i64 = trans[pix] 143 if tr > 1 { 144 let alpha: i64 = op * explut[k] / 256 // fx256 splat opacity at this pixel 145 let contrib: i64 = tr * alpha / 256 // transmittance-weighted (fx256) 146 acc[pix*3] = acc[pix*3] + gr * contrib 147 acc[pix*3+1] = acc[pix*3+1] + gg * contrib 148 acc[pix*3+2] = acc[pix*3+2] + gb * contrib 149 trans[pix] = tr - tr * alpha / 256 // *= (1-alpha) 150 } 151 } 152 px = px + 1 153 } 154 py = py + 1 155 } 156 oi = oi + 1 157 } 158 // --- resolve: colour = accum/256 + background * leftover transmittance --- 159 i = 0 160 while i < GW * GH { 161 let tr: i64 = trans[i] 162 var r: i64 = acc[i*3] / 256 + bgr * tr / 256 163 var gg2: i64 = acc[i*3+1] / 256 + bgg * tr / 256 164 var bb: i64 = acc[i*3+2] / 256 + bgb * tr / 256 165 if r > 255 { r = 255 } 166 if gg2 > 255 { gg2 = 255 } 167 if bb > 255 { bb = 255 } 168 fb[i] = r + gg2 * 256 + bb * K_MAGIC_65536 169 i = i + 1 170 } 171 return vis 172} 173 174// set one isotropic gaussian 175func gs_set(gauss: *i64, i: i64, x: i64, y: i64, z: i64, sc: i64, r: i64, g: i64, b: i64, op: i64) -> i64 { 176 gauss[i*8]=x; gauss[i*8+1]=y; gauss[i*8+2]=z; gauss[i*8+3]=sc; gauss[i*8+4]=r; gauss[i*8+5]=g; gauss[i*8+6]=b; gauss[i*8+7]=op 177 return 0 178} 179 180// ===== ★ANISOTROPIC SURFACE SPLATTING (the defining 3DGS feature: oriented ELLIPSE splats, not blobs) ===== 181// each gaussian = a DISK in the tangent plane (a "surfel") with normal n + in-plane radius rtan -> projects to a 182// 2D ELLIPSE (EWA-style). This aligns splats to the surface -> smooth coverage between mesh verts (kills the 183// isotropic-blob grid/dotty look). 12 i64/gaussian: {x,y,z, nx,ny,nz(fx256 unit), rtan, r,g,b, op, _spare}. 184func gs_set_aniso(gauss: *i64, i: i64, x: i64, y: i64, z: i64, nx: i64, ny: i64, nz: i64, rtan: i64, r: i64, g: i64, b: i64, op: i64) -> i64 { 185 gauss[i*12]=x; gauss[i*12+1]=y; gauss[i*12+2]=z; gauss[i*12+3]=nx; gauss[i*12+4]=ny; gauss[i*12+5]=nz 186 gauss[i*12+6]=rtan; gauss[i*12+7]=r; gauss[i*12+8]=g; gauss[i*12+9]=b; gauss[i*12+10]=op; gauss[i*12+11]=0 187 return 0 188} 189// ★★RESOLUTION IS A PARAMETER, NOT A COMPILE-TIME FACT (widened 2026-08-23). 190// Until today every stage read the module consts GW/GH/GHW/GHH/GFOCAL, so this rasterizer -- AND 191// EVERY "second renderer" the three-stage decomposition above exists to enable (nx_gsplat_tile_lib 192// is one, measured: it calls gs_project_sort/gs_blend_rect/gs_resolve) -- could only ever produce 193// 512x384 = 196,608 pixels. A beauty-tier frame is 1920x1080 (10.6x those pixels) or 3840x2160 194// (42x). THE CEILING WAS NEVER A GRAPHICS LIMIT; IT WAS FIVE CONSTANTS READ FROM MODULE SCOPE. 195// The inner stages were ALREADY viewport-agnostic -- gs_blend_rect takes stride_w, gs_resolve takes 196// npx -- so only the projection and the entry points were nailed down. The _at variants below take 197// the viewport explicitly; the historic names are now thin wrappers passing the historic values, so 198// every existing caller renders BYTE-IDENTICALLY and the widening is neutral BY CONSTRUCTION. 199// 200// ★FOCAL IS DERIVED, NEVER PICKED. gs_focal_for(h) scales the incumbent focal by h/GH, which holds 201// tan(vfov/2) = (h/2)/focal EXACTLY equal to the historic (GH/2)/GFOCAL -- the SAME field of view at 202// any height. Picking a focal instead would silently change the LENS, which is a different decision 203// and has to be made on purpose. Half-extents are DERIVED from the viewport (hw=w/2, hh=h/2) at the 204// entry point rather than passed alongside it, so a caller cannot hand in a centre that disagrees 205// with its own width -- a second ruler this file already refuses everywhere else. 206func gs_focal_for(h: i64) -> i64 { return GFOCAL * h / GH } 207func gs_proj_x_at(x: i64, y: i64, z: i64, sy4: i64, cy4: i64, R: i64, hw: i64, focal: i64) -> i64 { 208 let cz: i64 = (x*sy4 + z*cy4)/K_MAGIC_4096 + R 209 if cz <= 64 { return 0 - K_MAGIC_999999 } 210 let cx: i64 = (x*cy4 - z*sy4)/K_MAGIC_4096 211 return hw + focal*cx/cz 212} 213func gs_proj_y_at(x: i64, y: i64, z: i64, sy4: i64, cy4: i64, R: i64, hh: i64, focal: i64) -> i64 { 214 let cz: i64 = (x*sy4 + z*cy4)/K_MAGIC_4096 + R 215 if cz <= 64 { return 0 - K_MAGIC_999999 } 216 return hh - focal*y/cz 217} 218func gs_proj_x(x: i64, y: i64, z: i64, sy4: i64, cy4: i64, R: i64) -> i64 { 219 return gs_proj_x_at(x, y, z, sy4, cy4, R, GHW, GFOCAL) 220} 221func gs_proj_y(x: i64, y: i64, z: i64, sy4: i64, cy4: i64, R: i64) -> i64 { 222 return gs_proj_y_at(x, y, z, sy4, cy4, R, GHH, GFOCAL) 223} 224// ★★THE RASTERIZER, DECOMPOSED INTO ITS THREE STAGES so a second renderer (tiled, foveated, stereo) can 225// be built WITHOUT retyping any of them. A retyped projection or blend would drift silently: two renderers 226// would disagree by rounding nobody could see in a frame. These are the one implementation of each stage. 227const GNEARZ: i64 = 64 // near-clip in camera depth units: nearer than this is culled 228const GUPTHRESH: i64 = 210 // |n.y| (fx256) above which the up-vector must be swapped to avoid 229 // a degenerate cross product when the normal IS the up axis 230const GSIGMA_CUT: i64 = 3 // AABB half-extent in sigma; the LUT carries weight to sqrt(GEXPN/GLUTU) 231 232// STAGE 1: project every gaussian to a screen position + 2D conic, and depth-sort the visible ones. 233// depth[i] = -1 marks culled. Returns the visible count; order[0..vis) is near-to-far. 234func gs_project_sort_at(gauss: *i64, ng: i64, yaw: i64, camz: i64, depth: *i64, sxb: *i64, syb: *i64, pa: *i64, pb: *i64, pc: *i64, pdet: *i64, order: *i64, count: *i64, hw: i64, hh: i64, focal: i64) -> i64 { 235 let sy4: i64 = it_sin4096(yaw) 236 let cy4: i64 = it_cos4096(yaw) 237 let R: i64 = camz * GFX 238 var dmin: i64 = K_MAGIC_2000000000 239 var dmax: i64 = 0 - K_MAGIC_2000000000 240 var vis: i64 = 0 241 var i: i64 = 0 242 while i < ng { 243 let x: i64 = gauss[i*GSTA] 244 let y: i64 = gauss[i*GSTA+1] 245 let z: i64 = gauss[i*GSTA+2] 246 let cz: i64 = (x*sy4 + z*cy4)/K_MAGIC_4096 + R 247 if cz > GNEARZ { 248 let nx: i64 = gauss[i*GSTA+3] 249 let ny: i64 = gauss[i*GSTA+4] 250 let nz: i64 = gauss[i*GSTA+5] 251 let rt: i64 = gauss[i*GSTA+6] 252 var ux: i64 = 0 253 var uy: i64 = GFXA 254 var uz: i64 = 0 255 if ny > GUPTHRESH { ux = GFXA; uy = 0; uz = 0 } 256 if ny < 0 - GUPTHRESH { ux = GFXA; uy = 0; uz = 0 } 257 var t1x: i64 = (ny*uz - nz*uy)/GFXA 258 var t1y: i64 = (nz*ux - nx*uz)/GFXA 259 var t1z: i64 = (nx*uy - ny*ux)/GFXA 260 let l1: i64 = gs_isqrt(t1x*t1x + t1y*t1y + t1z*t1z) 261 if l1 > 0 { t1x = t1x*GFXA/l1; t1y = t1y*GFXA/l1; t1z = t1z*GFXA/l1 } 262 var t2x: i64 = (ny*t1z - nz*t1y)/GFXA 263 var t2y: i64 = (nz*t1x - nx*t1z)/GFXA 264 var t2z: i64 = (nx*t1y - ny*t1x)/GFXA 265 let l2: i64 = gs_isqrt(t2x*t2x + t2y*t2y + t2z*t2z) 266 if l2 > 0 { t2x = t2x*GFXA/l2; t2y = t2y*GFXA/l2; t2z = t2z*GFXA/l2 } 267 let a1x: i64 = t1x*rt/GFXA 268 let a1y: i64 = t1y*rt/GFXA 269 let a1z: i64 = t1z*rt/GFXA 270 let a2x: i64 = t2x*rt/GFXA 271 let a2y: i64 = t2y*rt/GFXA 272 let a2z: i64 = t2z*rt/GFXA 273 let sx: i64 = gs_proj_x_at(x, y, z, sy4, cy4, R, hw, focal) 274 let sy: i64 = gs_proj_y_at(x, y, z, sy4, cy4, R, hh, focal) 275 let e1x: i64 = gs_proj_x_at(x+a1x, y+a1y, z+a1z, sy4, cy4, R, hw, focal) - sx 276 let e1y: i64 = gs_proj_y_at(x+a1x, y+a1y, z+a1z, sy4, cy4, R, hh, focal) - sy 277 let e2x: i64 = gs_proj_x_at(x+a2x, y+a2y, z+a2z, sy4, cy4, R, hw, focal) - sx 278 let e2y: i64 = gs_proj_y_at(x+a2x, y+a2y, z+a2z, sy4, cy4, R, hh, focal) - sy 279 var ca: i64 = e1x*e1x + e2x*e2x + 1 280 let cb: i64 = e1x*e1y + e2x*e2y 281 var cc: i64 = e1y*e1y + e2y*e2y + 1 282 var det: i64 = ca*cc - cb*cb 283 if det < 1 { det = 1 } 284 depth[i] = cz; sxb[i] = sx; syb[i] = sy; pa[i] = ca; pb[i] = cb; pc[i] = cc; pdet[i] = det 285 if cz < dmin { dmin = cz } 286 if cz > dmax { dmax = cz } 287 vis = vis + 1 288 } else { depth[i] = 0 - 1 } 289 i = i + 1 290 } 291 if vis == 0 { return 0 } 292 let span: i64 = dmax - dmin + 1 293 var b: i64 = 0 294 while b <= GNB { count[b] = 0; b = b + 1 } 295 i = 0 296 while i < ng { if depth[i] >= 0 { var bk: i64 = (depth[i]-dmin)*GNB/span; if bk<0{bk=0} if bk>=GNB{bk=GNB-1} count[bk]=count[bk]+1 } i=i+1 } 297 var pre: i64 = 0 298 b = 0 299 while b < GNB { let c: i64 = count[b]; count[b] = pre; pre = pre + c; b = b + 1 } 300 i = 0 301 while i < ng { if depth[i] >= 0 { var bk: i64 = (depth[i]-dmin)*GNB/span; if bk<0{bk=0} if bk>=GNB{bk=GNB-1} order[count[bk]]=i; count[bk]=count[bk]+1 } i=i+1 } 302 return vis 303} 304// The historic entry point: the viewport IS the module default. Both call sites of this stage keep 305// their signature and their exact output (this file's gs_render_aniso and nx_gsplat_tile_lib's tiled 306// walk -- the complete population, corpus_complete=1 over 59,540 files); only a caller that WANTS a 307// bigger frame passes one. One implementation, two entry widths -- never a second sort. 308func gs_project_sort(gauss: *i64, ng: i64, yaw: i64, camz: i64, depth: *i64, sxb: *i64, syb: *i64, pa: *i64, pb: *i64, pc: *i64, pdet: *i64, order: *i64, count: *i64) -> i64 { 309 return gs_project_sort_at(gauss, ng, yaw, camz, depth, sxb, syb, pa, pb, pc, pdet, order, count, GHW, GHH, GFOCAL) 310} 311 312// the screen-space AABB half-extents of a splat's conic. Derived from the conic, not chosen. 313func gs_splat_rx(ca: i64) -> i64 { return GSIGMA_CUT * gs_isqrt(ca) } 314func gs_splat_ry(cc: i64) -> i64 { return GSIGMA_CUT * gs_isqrt(cc) } 315 316// STAGE 2: blend one splat into an ALREADY-CLIPPED pixel rectangle, front-to-back. 317// ★Returns the number of CONIC EVALUATIONS performed -- the cost unit. Any renderer built on this reports 318// its work in the same currency, so two renderers can be compared without a stopwatch or a guess. 319// satcnt[0] is INCREMENTED once for each pixel that transitions from live to saturated inside this call. 320// A tiled renderer uses it to stop feeding a region that can no longer change; the full-screen policy 321// simply ignores the number. Counting it HERE, at the only place a pixel can saturate, is what makes the 322// two policies share one blend instead of one growing a private copy with an early-out bolted on. 323func gs_blend_rect(gauss: *i64, g: i64, sx: i64, sy: i64, ca: i64, cb: i64, cc: i64, det: i64, x0: i64, x1: i64, y0: i64, y1: i64, acc: *i64, trans: *i64, explut: *i64, stride_w: i64, satcnt: *i64) -> i64 { 324 let gr: i64 = gauss[g*GSTA+7] 325 let gg: i64 = gauss[g*GSTA+8] 326 let gb: i64 = gauss[g*GSTA+9] 327 let op: i64 = gauss[g*GSTA+10] 328 var evals: i64 = 0 329 var py: i64 = y0 330 while py <= y1 { 331 let dy: i64 = py - sy 332 var px: i64 = x0 333 while px <= x1 { 334 let dx: i64 = px - sx 335 // power = d^T Sigma2d^-1 d = (cc dx^2 - 2 cb dx dy + ca dy^2)/det ; LUT idx k = power*GLUTU 336 let pnum: i64 = cc*dx*dx - 2*cb*dx*dy + ca*dy*dy 337 let k: i64 = pnum * GLUTU / det 338 evals = evals + 1 339 if k < GEXPN { if k >= 0 { 340 let pix: i64 = py*stride_w + px 341 let tr: i64 = trans[pix] 342 if tr > 1 { 343 let alpha: i64 = op * explut[k] / GFXA 344 let contrib: i64 = tr * alpha / GFXA 345 acc[pix*3] = acc[pix*3] + gr*contrib 346 acc[pix*3+1] = acc[pix*3+1] + gg*contrib 347 acc[pix*3+2] = acc[pix*3+2] + gb*contrib 348 let ntr: i64 = tr - tr*alpha/GFXA 349 trans[pix] = ntr 350 if ntr <= 1 { satcnt[0] = satcnt[0] + 1 } 351 } 352 } } 353 px = px + 1 354 } 355 py = py + 1 356 } 357 return evals 358} 359 360// STAGE 3: resolve accumulated colour + leftover transmittance against the background. 361func gs_resolve(fb: *i64, acc: *i64, trans: *i64, npx: i64, bgr: i64, bgg: i64, bgb: i64) -> i64 { 362 var i: i64 = 0 363 while i < npx { 364 let tr: i64 = trans[i] 365 var r: i64 = acc[i*3]/GFXA + bgr*tr/GFXA 366 var g2: i64 = acc[i*3+1]/GFXA + bgg*tr/GFXA 367 var bb: i64 = acc[i*3+2]/GFXA + bgb*tr/GFXA 368 if r>255{r=255} if g2>255{g2=255} if bb>255{bb=255} 369 fb[i] = r + g2*256 + bb*K_MAGIC_65536 370 i = i + 1 371 } 372 return 0 373} 374 375// anisotropic render: pa/pb/pc/pdet = per-gaussian 2D-covariance scratch (ng each). Same sort + front-to-back 376// composite as the isotropic path; only the per-splat kernel is an oriented ellipse. 377// ★NOW COMPOSED FROM THE THREE STAGES ABOVE -- this function is the FULL-SCREEN policy over them, and the 378// tiled renderer is a different policy over the SAME stages. Neither can drift from the other. 379func gs_render_aniso_at(gauss: *i64, ng: i64, yaw: i64, camz: i64, fb: *i64, acc: *i64, trans: *i64, depth: *i64, sxb: *i64, syb: *i64, pa: *i64, pb: *i64, pc: *i64, pdet: *i64, order: *i64, count: *i64, explut: *i64, bgr: i64, bgg: i64, bgb: i64, w: i64, h: i64, focal: i64) -> i64 { 380 // hw/hh are DERIVED from the viewport, never passed beside it: a centre that disagrees with its 381 // own width is a second ruler, and this file refuses those everywhere else. 382 let hw: i64 = w / 2 383 let hh: i64 = h / 2 384 let vis: i64 = gs_project_sort_at(gauss, ng, yaw, camz, depth, sxb, syb, pa, pb, pc, pdet, order, count, hw, hh, focal) 385 if vis == 0 { return 0 } 386 gs_clear_at(acc, trans, w * h) 387 var oi: i64 = 0 388 while oi < vis { 389 let g: i64 = order[oi] 390 let sx: i64 = sxb[g] 391 let sy: i64 = syb[g] 392 let ca: i64 = pa[g] 393 let cb: i64 = pb[g] 394 let cc: i64 = pc[g] 395 let det: i64 = pdet[g] 396 let rx: i64 = gs_splat_rx(ca) 397 let ry: i64 = gs_splat_ry(cc) 398 var y0: i64 = sy - ry 399 if y0 < 0 { y0 = 0 } 400 var y1: i64 = sy + ry 401 if y1 >= h { y1 = h - 1 } 402 var x0: i64 = sx - rx 403 if x0 < 0 { x0 = 0 } 404 var x1: i64 = sx + rx 405 if x1 >= w { x1 = w - 1 } 406 // the full-screen policy has no early-out, so it discards the saturation count into the slot the 407 // sort scratch already reserves past its own range (callers allocate GNB+2). No extra allocation 408 // per frame, which a long-running renderer would otherwise pay a page for. 409 gs_blend_rect(gauss, g, sx, sy, ca, cb, cc, det, x0, x1, y0, y1, acc, trans, explut, w, ((count as i64) + (GNB+1)*8) as *i64) 410 oi = oi + 1 411 } 412 gs_resolve(fb, acc, trans, w*h, bgr, bgg, bgb) 413 return vis 414} 415// The historic full-screen entry point, unchanged for every existing caller: the module viewport, the 416// module focal, byte-identical output. A caller wanting a beauty-tier frame calls gs_render_aniso_at 417// with its own w/h and gs_focal_for(h) -- same lens, more pixels. SCRATCH IS THE CALLER'S CONTRACT: 418// acc needs w*h*3 and trans w*h i64, so a wider viewport must allocate wider buffers; the renderer 419// cannot check that for you, and the sizes are stated here rather than discovered by a fault. 420func gs_render_aniso(gauss: *i64, ng: i64, yaw: i64, camz: i64, fb: *i64, acc: *i64, trans: *i64, depth: *i64, sxb: *i64, syb: *i64, pa: *i64, pb: *i64, pc: *i64, pdet: *i64, order: *i64, count: *i64, explut: *i64, bgr: i64, bgg: i64, bgb: i64) -> i64 { 421 return gs_render_aniso_at(gauss, ng, yaw, camz, fb, acc, trans, depth, sxb, syb, pa, pb, pc, pdet, order, count, explut, bgr, bgg, bgb, GW, GH, GFOCAL) 422} 423// ===== ★GAUSSIAN-PARAMETER OPTIMIZER (the generator/"training" side = the real photoreal lift) ===== 424// v0 = DIFFERENTIABLE COLOUR fit: a splatted pixel is LINEAR in the Gaussian colours (pixel = Σ w_i·c_i), so the 425// image-loss gradient wrt each colour is ANALYTIC (dL/dc_i = Σ_px (render-target)·w_i). One gradient-descent 426// step: forward-render, accumulate the per-Gaussian colour gradient over its splat footprint, take a 427// least-squares-preconditioned step (÷ Σw²). Integer, deterministic. This is the 3DGS training mechanism (colour 428// channel); position/covariance optimization + adaptive density = the next rungs. Returns the L1 image loss. 429func gs_color_grad_step(gauss: *i64, ng: i64, target: *i64, yaw: i64, camz: i64, fb: *i64, acc: *i64, trans: *i64, depth: *i64, sxb: *i64, syb: *i64, sigb: *i64, order: *i64, count: *i64, explut: *i64, gradbuf: *i64, wnorm: *i64) -> i64 { 430 gs_render(gauss, ng, yaw, camz, fb, acc, trans, depth, sxb, syb, sigb, order, count, explut, 26, 28, 44) 431 // L1 loss + zero the grad accumulators 432 var loss: i64 = 0 433 var i: i64 = 0 434 while i < GW*GH { 435 var dr: i64 = (fb[i]&255) - (target[i]&255); if dr<0 {dr=0-dr} 436 var dg: i64 = ((fb[i]>>8)&255) - ((target[i]>>8)&255); if dg<0 {dg=0-dg} 437 var db: i64 = ((fb[i]>>16)&255) - ((target[i]>>16)&255); if db<0 {db=0-db} 438 loss = loss + dr + dg + db 439 i = i + 1 440 } 441 i = 0 442 while i < ng { gradbuf[i*3]=0; gradbuf[i*3+1]=0; gradbuf[i*3+2]=0; wnorm[i]=0; i=i+1 } 443 // accumulate per-gaussian colour gradient over its splat footprint (uses the projections gs_render just filled) 444 i = 0 445 while i < ng { 446 if depth[i] >= 0 { 447 let sx: i64 = sxb[i] 448 let sy: i64 = syb[i] 449 let sig: i64 = sigb[i] 450 let rad: i64 = 3*sig 451 let s2: i64 = sig*sig 452 let op: i64 = gauss[i*8+7] 453 var py: i64 = sy-rad 454 if py<0 {py=0} 455 var pye: i64 = sy+rad 456 if pye>=GH {pye=GH-1} 457 while py <= pye { 458 let dy: i64 = py-sy 459 var px: i64 = sx-rad 460 if px<0 {px=0} 461 var pxe: i64 = sx+rad 462 if pxe>=GW {pxe=GW-1} 463 while px <= pxe { 464 let dx: i64 = px-sx 465 let d2: i64 = dx*dx+dy*dy 466 let k: i64 = d2*16/s2 467 if k < GEXPN { 468 let pix: i64 = py*GW+px 469 let w: i64 = op*explut[k]/256 // this gaussian's splat weight (fx256) 470 gradbuf[i*3] = gradbuf[i*3] + ((fb[pix]&255) - (target[pix]&255)) * w 471 gradbuf[i*3+1] = gradbuf[i*3+1] + (((fb[pix]>>8)&255) - ((target[pix]>>8)&255)) * w 472 gradbuf[i*3+2] = gradbuf[i*3+2] + (((fb[pix]>>16)&255) - ((target[pix]>>16)&255)) * w 473 wnorm[i] = wnorm[i] + w*w/256 474 } 475 px = px + 1 476 } 477 py = py + 1 478 } 479 // least-squares-preconditioned step (lr 0.75): c -= 3*grad/(4*wnorm) 480 let nd: i64 = wnorm[i]*4 + 1 481 var nr: i64 = gauss[i*8+4] - gradbuf[i*3]*3/nd 482 var nge: i64 = gauss[i*8+5] - gradbuf[i*3+1]*3/nd 483 var nb: i64 = gauss[i*8+6] - gradbuf[i*3+2]*3/nd 484 if nr<0 {nr=0} if nr>255 {nr=255} 485 if nge<0 {nge=0} if nge>255 {nge=255} 486 if nb<0 {nb=0} if nb>255 {nb=255} 487 gauss[i*8+4]=nr; gauss[i*8+5]=nge; gauss[i*8+6]=nb 488 } 489 i = i + 1 490 } 491 return loss 492} 493 494// ===== ★POSITION GRADIENT: the leg that turns a RENDERER into a RECONSTRUCTOR ===== 495// The colour step above is analytic because a pixel is LINEAR in colour. Position is NOT: moving a 496// gaussian changes WHICH pixels it covers and by HOW MUCH. But the Gaussian carries its own derivative 497// in closed form, so this needs neither finite differences nor a second render: 498// w = op * exp(-d^2 / (2 sigma^2)) => dw/dsx = w * dx / sigma^2, dx = px - sx 499// A pixel's contribution from gaussian i is c * w/GFXA, so per channel 500// J_ch = c_ch * (w/GFXA) * dx / sigma^2 501// and the Gauss-Newton step is the ratio of two sums the same pixel walk already visits: 502// dL/dsx = SUM resid_ch * J_ch curv = SUM J_ch^2 dsx = -dL/dsx / curv 503// ★THE STEP SIZE IS DERIVED, NOT TUNED. Gauss-Newton fixes the magnitude; the only free choice is 504// damping, and it is set to 1/2 because the model is NONLINEAR in position (unlike colour, where the 505// incumbent can afford 3/4 precisely because the model is linear there). The screen correction is 506// returned to world units through the projection Jacobian INVERTED, dx_world = dsx * cz/GFOCAL -- 507// world-units-per-pixel AT THAT GAUSSIAN'S DEPTH, read from the camera rather than picked. 508// ⚠DECLARED APPROXIMATION, the same one the colour step makes and names: the gradient ignores 509// transmittance, i.e. it treats each gaussian's footprint independently of what occludes it. That is 510// exact for a non-overlapping scene and an approximation elsewhere; it is stated here rather than 511// discovered later, and it is why the fitting gate's fixture is separated in depth. 512func gs_pos_grad_step(gauss: *i64, ng: i64, target: *i64, yaw: i64, camz: i64, fb: *i64, acc: *i64, trans: *i64, depth: *i64, sxb: *i64, syb: *i64, sigb: *i64, order: *i64, count: *i64, explut: *i64, gradbuf: *i64, wnorm: *i64) -> i64 { 513 gs_render(gauss, ng, yaw, camz, fb, acc, trans, depth, sxb, syb, sigb, order, count, explut, 26, 28, 44) 514 var loss: i64 = 0 515 var i: i64 = 0 516 while i < GW*GH { 517 var dr: i64 = (fb[i]&255) - (target[i]&255); if dr<0 {dr=0-dr} 518 var dg: i64 = ((fb[i]>>8)&255) - ((target[i]>>8)&255); if dg<0 {dg=0-dg} 519 var db: i64 = ((fb[i]>>16)&255) - ((target[i]>>16)&255); if db<0 {db=0-db} 520 loss = loss + dr + dg + db 521 i = i + 1 522 } 523 i = 0 524 while i < ng { gradbuf[i*3]=0; gradbuf[i*3+1]=0; gradbuf[i*3+2]=0; wnorm[i]=0; i=i+1 } 525 let sy4: i64 = it_sin4096(yaw) 526 let cy4: i64 = it_cos4096(yaw) 527 let R: i64 = camz * GFX 528 i = 0 529 while i < ng { 530 if depth[i] >= 0 { 531 let sx: i64 = sxb[i] 532 let sy: i64 = syb[i] 533 let sig: i64 = sigb[i] 534 let rad: i64 = 3*sig 535 let s2: i64 = sig*sig 536 let op: i64 = gauss[i*8+7] 537 let cr: i64 = gauss[i*8+4] 538 let cg: i64 = gauss[i*8+5] 539 let cb: i64 = gauss[i*8+6] 540 let cm2: i64 = cr*cr + cg*cg + cb*cb 541 var py: i64 = sy-rad 542 if py<0 {py=0} 543 var pye: i64 = sy+rad 544 if pye>=GH {pye=GH-1} 545 while py <= pye { 546 let dy: i64 = py-sy 547 var px: i64 = sx-rad 548 if px<0 {px=0} 549 var pxe: i64 = sx+rad 550 if pxe>=GW {pxe=GW-1} 551 while px <= pxe { 552 let dx: i64 = px-sx 553 let d2: i64 = dx*dx+dy*dy 554 let k: i64 = d2*GLUTU/s2 555 if k < GEXPN { 556 let pix: i64 = py*GW+px 557 let w: i64 = op*explut[k]/GFXA 558 let rc: i64 = ((fb[pix]&255) - (target[pix]&255))*cr + (((fb[pix]>>8)&255) - ((target[pix]>>8)&255))*cg + (((fb[pix]>>16)&255) - ((target[pix]>>16)&255))*cb 559 let ufx: i64 = w*dx*GFXA/s2 560 let ufy: i64 = w*dy*GFXA/s2 561 gradbuf[i*3] = gradbuf[i*3] + rc*ufx 562 gradbuf[i*3+1] = gradbuf[i*3+1] + rc*ufy 563 gradbuf[i*3+2] = gradbuf[i*3+2] + cm2*ufx*ufx/K_MAGIC_65536 564 wnorm[i] = wnorm[i] + cm2*ufy*ufy/K_MAGIC_65536 565 } 566 px = px + 1 567 } 568 py = py + 1 569 } 570 // world step through the INVERTED projection Jacobian, damped by 2 (see header). 571 let cz: i64 = (gauss[i*8]*sy4 + gauss[i*8+2]*cy4)/K_MAGIC_4096 + R 572 if cz > GNEARZ { 573 let cxd: i64 = gradbuf[i*3+2]*2*GFOCAL + 1 574 let cyd: i64 = wnorm[i]*2*GFOCAL + 1 575 gauss[i*8] = gauss[i*8] - gradbuf[i*3]*cz/cxd 576 gauss[i*8+1] = gauss[i*8+1] + gradbuf[i*3+1]*cz/cyd 577 } 578 } 579 i = i + 1 580 } 581 return loss 582} 583 584// ===== ★ANISOTROPIC POSITION GRADIENT: fitting on the path that carries QUALITY ===== 585// gs_pos_grad_step fits the ISOTROPIC path (stride GSTI). That is the blob path. Real surfels live on 586// the ANISOTROPIC path (stride GSTA), and until now it could be RENDERED but never FITTED -- a 587// reconstructor that stops exactly where the representation starts to matter. 588// ★THE DERIVATIVE IS THE RENDERER'S OWN ARITHMETIC, DIFFERENTIATED -- not a re-derivation that could 589// drift from it. gs_blend_rect evaluates the conic as 590// pnum = cc*dx^2 - 2*cb*dx*dy + ca*dy^2, u = pnum/det, w = op*exp(-u/2) 591// so, with dx = px - sx (hence d(dx)/dsx = -1): 592// du/dsx = -2*(cc*dx - cb*dy)/det => dw/dsx = w*(cc*dx - cb*dy)/det 593// du/dsy = -2*(ca*dy - cb*dx)/det => dw/dsy = w*(ca*dy - cb*dx)/det 594// ★AND IT REDUCES TO THE ISOTROPIC CASE EXACTLY, which is the check that it is the same mathematics and 595// not a second one: with ca = cc = sigma^2 and cb = 0, det = sigma^4, dw/dsx = w*dx/sigma^2 -- the 596// isotropic formula above, recovered term for term. 597// ⚠DECLARED PRECISION: the per-pixel derivative is accumulated as an integer of order 10..100, so it 598// carries ~1 percent truncation. That is a magnitude error in a DIRECTION, and Gauss-Newton re-measures 599// the direction every step, so it costs iterations and not correctness. Stated rather than discovered. 600// ⚠DECLARED APPROXIMATION, INHERITED AND UNCHANGED: like both incumbent steps, this ignores 601// transmittance -- exact for a non-overlapping scene, an approximation under occlusion. It is why the 602// fitting fixtures separate gaussians in depth, and it is the thing to revisit FIRST when adaptive 603// density starts producing overlapping splats. 604// ★★TRANSMITTANCE IS NOW A PARAMETER, SO ITS COST IS MEASURABLE INSTEAD OF ASSERTED (2026-08-23). 605// Both incumbent gradient steps DROP transmittance: they weight every splat's footprint as if nothing 606// occluded it. That is exact for a non-overlapping scene and an approximation elsewhere -- and 607// ADAPTIVE DENSITY PRODUCES OVERLAP BY CONSTRUCTION, so densifying against the approximation would 608// tune the fit through a ruler its own output invalidates (the same shape as judging beauty at 512x384). 609// So the approximation and the exact direct form live in ONE implementation, selected by usetrans: 610// usetrans=0 -> T is treated as full (GFXA). Byte-identical to the incumbent behaviour. 611// usetrans=1 -> T is the REAL front-to-back transmittance at that pixel before this splat. 612// usetrans=2 -> direct + INDIRECT. ⚠⚠QUARANTINED, DO NOT SHIP. Measured 2026-08-23: it is not 613// merely non-beneficial, it is NUMERICALLY UNSTABLE. Single-view understated it (100 614// against 79, which reads as a mild loss); under MULTI-VIEW the same code diverged to 615// 46,128,546,235 against a 52-pixel floor -- ten orders of magnitude, ending far 616// worse than it began. The mathematics in the branch is believed right (it reduces to 617// the direct form and its units were checked twice); the instability is unfound, and 618// the likely suspects are the S_i subtraction losing significance when acc_final and 619// the prefix are close, and the 1/(GFXA-alpha) divisor near full opacity where the 620// clamp at 1 is doing real work. LEFT IN, NOT DELETED, because the arm is the only 621// way to re-measure it -- but nothing may depend on it until it is bounded. 622// A splat's contribution to a pixel is c*T*alpha/GFXA^2, so the exact direct derivative just carries the 623// T/GFXA factor: J_ch = c_ch * T * vs / GFXA^2. With T=GFXA that is c_ch*vs/GFXA -- the incumbent form, 624// recovered exactly, which is why usetrans=0 is a true control and not an approximation of the control. 625// ⚠THE INDIRECT TERM IS STILL ABSENT AND IS NAMED HERE: moving splat i also changes how much light 626// reaches every splat BEHIND it (dT_j/dparam_i for j after i). That needs a back-to-front suffix pass 627// and is the next rung; what lands here is the direct term, which is the dominant one and the one that 628// makes an occluded splat stop pulling as hard as an unoccluded one. 629// ⚠AND THE WALK ORDER IS NOW LOAD-BEARING: transmittance only means anything front-to-back, so the 630// accumulation walks order[] (the renderer's own depth sort) rather than gaussian index. 631// ⚠⚠ARITY CEILING, HIT AND HONOURED. The compiler REFUSED this function at 25 parameters: "a call with 632// more arguments than operand slots would silently truncate -- refusing is the only honest answer" 633// (cap 23, already raised 4 -> 8 -> 16 -> 24 slots; capability=call-arity-ceiling, tracked on 634// /compare/lang). That is a REAL ceiling with a stated rationale, not a phony cap: the alternative is a 635// silently truncated call. Its named fix is a parameter struct, which is the right long-term shape and 636// is owed. Two parameters were removed instead, and BOTH removals are improvements in their own right: 637// focal -- now DERIVED as gs_focal_for(vh) rather than passed, so caller and callee can no longer 638// disagree about the lens. That disagreement is exactly the second-ruler defect this file 639// refuses everywhere else, so the ceiling forced the better design. 640// tcur -- the transmittance scratch reuses acc, which is DEAD inside this function from the moment 641// the render returns (the gradient reads fb and target, never acc) and is npx*3 >= npx. 642func gs_pos_grad_step_aniso(gauss: *i64, ng: i64, target: *i64, yaw: i64, camz: i64, fb: *i64, acc: *i64, trans: *i64, depth: *i64, sxb: *i64, syb: *i64, pa: *i64, pb: *i64, pc: *i64, pdet: *i64, order: *i64, count: *i64, explut: *i64, gradbuf: *i64, wnorm: *i64, vw: i64, vh: i64, usetrans: i64) -> i64 { 643 let focal: i64 = gs_focal_for(vh) 644 // ★SCRATCH LAYOUT (usetrans=2 needs the suffix colour, so the buffers are re-cast): 645 // trans[0..npx) -- RUNNING transmittance. Its post-render value is dead here, so the walk 646 // re-uses it rather than demanding a parameter the arity ceiling forbids. 647 // acc[0..3npx) -- acc_final, the render's accumulated colour. PRESERVED (v1 aliased tcur 648 // onto acc and destroyed exactly this; the indirect term needs it). 649 // acc[3npx..6npx) -- P, the running PREFIX colour. Caller must size acc npx*6 for usetrans=2. 650 // ★★AND THIS IS WHY NO BACKWARD PASS IS NEEDED. The textbook backward pass reconstructs T by 651 // dividing it out, which in integers divides by (GFXA-alpha) and loses precision exactly where 652 // alpha is large. The suffix is available in the SAME forward walk by arithmetic instead: 653 // S_i = acc_final - P_(i-1) - (this splat's own contribution) 654 // one subtraction, no reconstruction, no division by a near-zero, and it cannot drift from the 655 // renderer because acc_final IS the renderer's own output. 656 let tcur: *i64 = trans 657 let pbase: i64 = vw*vh*3 658 let vis: i64 = gs_render_aniso_at(gauss, ng, yaw, camz, fb, acc, trans, depth, sxb, syb, pa, pb, pc, pdet, order, count, explut, 26, 28, 44, vw, vh, focal) 659 let npx: i64 = vw * vh 660 var loss: i64 = 0 661 var i: i64 = 0 662 while i < npx { 663 var dr: i64 = (fb[i]&255) - (target[i]&255); if dr<0 {dr=0-dr} 664 var dg: i64 = ((fb[i]>>8)&255) - ((target[i]>>8)&255); if dg<0 {dg=0-dg} 665 var db: i64 = ((fb[i]>>16)&255) - ((target[i]>>16)&255); if db<0 {db=0-db} 666 loss = loss + dr + dg + db 667 i = i + 1 668 } 669 i = 0 670 while i < ng { gradbuf[i*3]=0; gradbuf[i*3+1]=0; gradbuf[i*3+2]=0; wnorm[i]=0; i=i+1 } 671 // transmittance starts FULL and is attenuated exactly as the renderer attenuates it, so the value 672 // this walk sees at a pixel is the value that pixel actually had when the splat was composited. 673 i = 0 674 while i < npx { tcur[i] = GFXA; i = i + 1 } 675 // the prefix starts empty: before any splat composites, nothing is in front of anything. 676 if usetrans == 2 { i = 0; while i < npx*3 { acc[pbase+i] = 0; i = i + 1 } } 677 // the prefix starts empty: before any splat composites, nothing is in front of anything. 678 if usetrans == 2 { i = 0; while i < npx*3 { acc[pbase+i] = 0; i = i + 1 } } 679 let sy4: i64 = it_sin4096(yaw) 680 let cy4: i64 = it_cos4096(yaw) 681 let R: i64 = camz * GFX 682 var oi: i64 = 0 683 while oi < vis { 684 let g: i64 = order[oi] 685 if depth[g] >= 0 { 686 let sx: i64 = sxb[g] 687 let sy: i64 = syb[g] 688 let ca: i64 = pa[g] 689 let cb: i64 = pb[g] 690 let cc: i64 = pc[g] 691 let det: i64 = pdet[g] 692 let op: i64 = gauss[g*GSTA+10] 693 let cr: i64 = gauss[g*GSTA+7] 694 let cg: i64 = gauss[g*GSTA+8] 695 let cbl: i64 = gauss[g*GSTA+9] 696 let cm2: i64 = cr*cr + cg*cg + cbl*cbl 697 let rx: i64 = gs_splat_rx(ca) 698 let ry: i64 = gs_splat_ry(cc) 699 var py: i64 = sy-ry 700 if py<0 {py=0} 701 var pye: i64 = sy+ry 702 if pye>=vh {pye=vh-1} 703 while py <= pye { 704 let dy: i64 = py-sy 705 var px: i64 = sx-rx 706 if px<0 {px=0} 707 var pxe: i64 = sx+rx 708 if pxe>=vw {pxe=vw-1} 709 while px <= pxe { 710 let dx: i64 = px-sx 711 let pnum: i64 = cc*dx*dx - 2*cb*dx*dy + ca*dy*dy 712 let k: i64 = pnum * GLUTU / det 713 if k < GEXPN { if k >= 0 { 714 let pix: i64 = py*vw+px 715 let w: i64 = op*explut[k]/GFXA 716 let rr: i64 = (fb[pix]&255) - (target[pix]&255) 717 let rg: i64 = ((fb[pix]>>8)&255) - ((target[pix]>>8)&255) 718 let rb: i64 = ((fb[pix]>>16)&255) - ((target[pix]>>16)&255) 719 let rc: i64 = rr*cr + rg*cg + rb*cbl 720 // T is the light still available at this pixel when THIS splat composites. 721 // usetrans=0 pins it full, which reproduces the incumbent arithmetic exactly. 722 var tw: i64 = GFXA 723 if usetrans > 0 { tw = tcur[pix] } 724 let dadx: i64 = w*(cc*dx - cb*dy)/det 725 let dady: i64 = w*(ca*dy - cb*dx)/det 726 if usetrans == 2 { 727 // ★THE INDIRECT TERM. Moving this splat changes the light reaching EVERY splat 728 // behind it: dT_j/dalpha_i = -T_j/(GFXA-alpha_i), so summing over j>i gives 729 // dC/dalpha_i = c_i*T_i/GFXA - S_i/(GFXA-alpha_i) 730 // with S_i the colour accumulated BEHIND this splat. S_i needs no backward 731 // pass: it is acc_final minus the running prefix minus this splat's own 732 // contribution -- three known quantities in a forward walk. 733 let cn: i64 = tw*w/GFXA 734 let ownr: i64 = cr*cn 735 let owng: i64 = cg*cn 736 let ownb: i64 = cbl*cn 737 let sr: i64 = acc[pix*3] - acc[pbase+pix*3] - ownr 738 let sg: i64 = acc[pix*3+1] - acc[pbase+pix*3+1] - owng 739 let sb: i64 = acc[pix*3+2] - acc[pbase+pix*3+2] - ownb 740 // a fully opaque splat leaves nothing behind it to disturb; clamping the 741 // divisor at 1 is the honest floor, not a tuned epsilon. 742 var dn: i64 = GFXA - w 743 if dn < 1 { dn = 1 } 744 // ⚠UNITS. v1 wrote sr/dn and the arm came out WORSE than direct-only (81 vs 79), 745 // which is what sent me to the algebra instead of to the tooth. In pixel units 746 // d(pixel)/d(alpha_i) = [ c_i*T_i - S_i/(GFXA-alpha_i) ] / GFXA^2 747 // and this accumulator carries the DIRECT part as c*T/GFXA, i.e. already one 748 // factor of GFXA down. The suffix therefore needs the SAME division or it 749 // enters 256x oversized -- a term that dominates the very gradient it corrects. 750 let er: i64 = cr*tw/GFXA - sr/dn/GFXA 751 let eg: i64 = cg*tw/GFXA - sg/dn/GFXA 752 let eb: i64 = cbl*tw/GFXA - sb/dn/GFXA 753 let coef: i64 = rr*er + rg*eg + rb*eb 754 let em2: i64 = er*er + eg*eg + eb*eb 755 gradbuf[g*3] = gradbuf[g*3] + coef*dadx 756 gradbuf[g*3+1] = gradbuf[g*3+1] + coef*dady 757 gradbuf[g*3+2] = gradbuf[g*3+2] + (em2*dadx/GFXA/GFXA)*dadx 758 wnorm[g] = wnorm[g] + (em2*dady/GFXA/GFXA)*dady 759 acc[pbase+pix*3] = acc[pbase+pix*3] + ownr 760 acc[pbase+pix*3+1] = acc[pbase+pix*3+1] + owng 761 acc[pbase+pix*3+2] = acc[pbase+pix*3+2] + ownb 762 } else { 763 let vsx: i64 = dadx*tw/GFXA 764 let vsy: i64 = dady*tw/GFXA 765 gradbuf[g*3] = gradbuf[g*3] + rc*vsx 766 gradbuf[g*3+1] = gradbuf[g*3+1] + rc*vsy 767 // squared term reduced BEFORE squaring so a near-degenerate conic cannot overflow 768 gradbuf[g*3+2] = gradbuf[g*3+2] + (cm2*vsx/GFXA)*vsx/GFXA 769 wnorm[g] = wnorm[g] + (cm2*vsy/GFXA)*vsy/GFXA 770 } 771 // attenuate for the splats behind this one, mirroring gs_blend_rect exactly 772 let tr: i64 = tcur[pix] 773 tcur[pix] = tr - tr*w/GFXA 774 } } 775 px = px + 1 776 } 777 py = py + 1 778 } 779 let cz: i64 = (gauss[g*GSTA]*sy4 + gauss[g*GSTA+2]*cy4)/K_MAGIC_4096 + R 780 if cz > GNEARZ { 781 let dnx: i64 = gradbuf[g*3+2]*2*GFXA*focal + 1 782 let dny: i64 = wnorm[g]*2*GFXA*focal + 1 783 gauss[g*GSTA] = gauss[g*GSTA] - gradbuf[g*3]*cz/dnx 784 gauss[g*GSTA+1] = gauss[g*GSTA+1] + gradbuf[g*3+1]*cz/dny 785 } 786 } 787 oi = oi + 1 788 } 789 return loss 790} 791 792func gs_w() -> i64 { return GW } 793func gs_h() -> i64 { return GH } 794func gs_expn() -> i64 { return GEXPN } 795func gs_nb() -> i64 { return GNB } 796 797// ===== ★MESH -> GAUSSIANS: THE INGEST HALF OF THE SPLAT LANE (2026-09-04) ===== 798// The estate could RENDER splats, FIT them by integer gradient descent, ADAPT their density and BIND 799// them to a driving cage -- but nothing could turn an authored or bought mesh INTO them, so a sculpt 800// could not enter this representation at all. This is that missing edge, and it deliberately emits 801// ANISOTROPIC SURFELS rather than isotropic blobs: a triangle KNOWS its normal exactly, so throwing 802// that away and re-deriving orientation later would be inventing work and losing precision. The file's 803// own aniso comment says why -- oriented disks give smooth coverage between verts instead of the dotty 804// blob look. 805// 806// ★THE ORACLE, AND WHY THIS IS NOT A PORT OF IT: EA SEED's Mesh2Splat converts glb to 3DGS in 807// milliseconds by exploiting the rasterizer's interpolator. It is BSD-3-Clause plus an EA marks clause 808// and it stays OUTSIDE our build path as a bar, never a dependency. It also emits ONE splat set per 809// model; the exceed axis declared on procgen.matrix before any run is PER-PART emission, which this 810// signature supports by construction because the caller passes one triangle range at a time. 811// 812// ★EVERY QUANTITY IS DERIVED, NONE IS PICKED (Rule 11). Sample count comes from DENSITY x AREA, not a 813// constant. The surfel radius is the radius whose disk area equals the surface each sample owns, so 814// splats just cover the face at any density -- a picked radius would gap at low density and overdraw at 815// high. Sampling is a stratified lattice folded into the triangle by reflection, so it is deterministic 816// and has no RNG to seed. 817// 818// ⚠NO SILENT CAP: stats[] reports what was REQUESTED beside what was WRITTEN, and truncation is decided 819// by comparing them rather than by a flag set mid-loop, so a buffer that happens to fill exactly is not 820// misreported as truncated. A cap reached in silence becomes a measurement nobody knows is partial. 821const GTRI_VS: i64 = 6 // per-vertex stride inside a triangle record: x y z r g b 822const GTRI_STRIDE: i64 = 18 // 3 * GTRI_VS -- the whole triangle record 823const GTRI_CO: i64 = 3 // colour offset inside one vertex record 824const GFM_HALF: i64 = 2 // the cross-product magnitude is TWICE the triangle area 825const GFM_MIN_SIDE: i64 = 1 // a face never vanishes: every triangle yields at least one surfel 826const GFM_MIN_RTAN: i64 = 1 // a zero-radius surfel would render as nothing at all 827const GFM_STATS: i64 = 4 // slots the caller must provide 828const GFM_ST_VISITED: i64 = 0 829const GFM_ST_TRUNC: i64 = 1 830const GFM_ST_WANT: i64 = 2 831const GFM_ST_CLAMP: i64 = 3 832 833func gs_from_mesh(tris: *i64, ntri: i64, gauss: *i64, cap: i64, density: i64, opacity: i64, stats: *i64) -> i64 { 834 stats[GFM_ST_VISITED] = 0 835 stats[GFM_ST_TRUNC] = 0 836 stats[GFM_ST_WANT] = 0 837 stats[GFM_ST_CLAMP] = 0 838 var ng: i64 = 0 839 var t: i64 = 0 840 while t < ntri { 841 let b: i64 = t * GTRI_STRIDE 842 let b1: i64 = b + GTRI_VS 843 let b2: i64 = b + GTRI_VS * 2 844 let e1x: i64 = tris[b1] - tris[b] 845 let e1y: i64 = tris[b1+1] - tris[b+1] 846 let e1z: i64 = tris[b1+2] - tris[b+2] 847 let e2x: i64 = tris[b2] - tris[b] 848 let e2y: i64 = tris[b2+1] - tris[b+1] 849 let e2z: i64 = tris[b2+2] - tris[b+2] 850 let cnx: i64 = e1y*e2z - e1z*e2y 851 let cny: i64 = e1z*e2x - e1x*e2z 852 let cnz: i64 = e1x*e2y - e1y*e2x 853 let clen: i64 = gs_isqrt(cnx*cnx + cny*cny + cnz*cnz) 854 if clen > 0 { 855 var side: i64 = gs_isqrt(density * clen / (GFM_HALF * GFX * GFX)) 856 if side < GFM_MIN_SIDE { side = GFM_MIN_SIDE; stats[GFM_ST_CLAMP] = stats[GFM_ST_CLAMP] + 1 } 857 let nsamp: i64 = side * side 858 var rtan: i64 = gs_isqrt(clen / (GFM_HALF * nsamp)) 859 if rtan < GFM_MIN_RTAN { rtan = GFM_MIN_RTAN } 860 let unx: i64 = cnx * GFXA / clen 861 let uny: i64 = cny * GFXA / clen 862 let unz: i64 = cnz * GFXA / clen 863 stats[GFM_ST_WANT] = stats[GFM_ST_WANT] + nsamp 864 var i: i64 = 0 865 while i < side { 866 var j: i64 = 0 867 while j < side { 868 if ng < cap { 869 var u: i64 = (2*i+1) * GFX / (2*side) 870 var v: i64 = (2*j+1) * GFX / (2*side) 871 if u + v > GFX { u = GFX - u; v = GFX - v } 872 let w: i64 = GFX - u - v 873 let px: i64 = tris[b] + (e1x*u + e2x*v)/GFX 874 let py: i64 = tris[b+1] + (e1y*u + e2y*v)/GFX 875 let pz: i64 = tris[b+2] + (e1z*u + e2z*v)/GFX 876 let cr: i64 = (tris[b+GTRI_CO]*w + tris[b1+GTRI_CO]*u + tris[b2+GTRI_CO]*v)/GFX 877 let cg: i64 = (tris[b+GTRI_CO+1]*w + tris[b1+GTRI_CO+1]*u + tris[b2+GTRI_CO+1]*v)/GFX 878 let cb: i64 = (tris[b+GTRI_CO+2]*w + tris[b1+GTRI_CO+2]*u + tris[b2+GTRI_CO+2]*v)/GFX 879 gs_set_aniso(gauss, ng, px, py, pz, unx, uny, unz, rtan, cr, cg, cb, opacity) 880 ng = ng + 1 881 } 882 j = j + 1 883 } 884 i = i + 1 885 } 886 } 887 stats[GFM_ST_VISITED] = stats[GFM_ST_VISITED] + 1 888 t = t + 1 889 } 890 if stats[GFM_ST_WANT] > ng { stats[GFM_ST_TRUNC] = 1 } 891 return ng 892} 893 894func gs_tri_stride() -> i64 { return GTRI_STRIDE } 895func gs_fm_stats() -> i64 { return GFM_STATS }