nx_gsplat_tile_lib.nx
buildroot/runtime/nx_gsplat_tile_lib.nx
about
nx_gsplat_tile_lib.nx -- ★TILED RASTERIZATION: a second POLICY over the SAME three rasterizer stages,
not a second rasterizer. It calls gs_project_sort, gs_blend_rect and gs_resolve -- the identical
projection, the identical conic blend, the identical resolve that the full-screen path uses. Nothing
about the maths is restated here, so the two renderers CANNOT drift.
★★THE ACCEPT RULE, DECLARED BEFORE THE EXPERIMENT (and enforced by nx_gsplat_tile_gate):
CORRECTNESS -- the tiled frame must be BYTE-IDENTICAL to the full-screen frame. Not "close", not
"within tolerance": identical. This optimisation is only allowed to change COST.
COST -- it must perform STRICTLY FEWER conic evaluations on a scene with occlusion.
If either fails, it does not ship. A tiled renderer that is merely as fast, or that differs by a
rounding nobody can see, is not an improvement -- it is a second ruler.
WHY BYTE-IDENTITY IS ACHIEVABLE RATHER THAN LUCKY: every pixel belongs to exactly one tile; within a
tile the splats are visited in the SAME global depth order (the list is built by walking the sorted
order and appending); and the early-out skips a splat only when EVERY pixel of the tile is already
saturated -- pixels the blend would have skipped anyway on its own `tr > 1` test. So no contribution
is dropped and none is reordered. The saving is work that provably could not have changed a pixel.
⚠WHAT IS AND IS NOT CLAIMED: this is a serial CPU renderer. Tiling here buys OCCLUSION SKIPPING and
memory locality; it does NOT buy parallelism, because there is no thread or GPU to spread tiles across.
The parallel dispatch that tiling exists for on a GPU is a NAMED GAP with a named blocker.
license_tier: ORIGINAL No hw writes (Rule 26).
dependencies 2 imports · 2 importers
imports: nx_syscalls.nxnx_gsplat.nx
imported by: nx_gsplat_perf.nxnx_gsplat_tile_gate.nx
structs
| none |
consts
| 26 | const GT_ERRFD: i64 = 2 |
| 27 | const GT_W64: i64 = 8 |
| 31 | const GT_EDGE_MIN: i64 = 1 |
| 32 | const GT_E_TILE: i64 = 3 |
| 33 | const GT_E_ENTRIES: i64 = 4 |
| 34 | const GT_E_INIT: i64 = 5 |
| 49 | const GT_WORKERS_MIN: i64 = 1 |
| 50 | const GT_E_WORKERS: i64 = 6 |
functions
| 63 | func gt_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 64 | func gt_err(s: *u8) -> i64 { sys_write(GT_ERRFD, s, gt_slen(s)); return 0 } |
| 66 | func gt_alloc(n: i64) -> i64 { GT_ALLOC = GT_ALLOC + n; return sys_mmap(n) as i64 } |
| 72 | func gt_init_at(tile_edge: i64, max_entries: i64, max_workers: i64, vw: i64, vh: i64) -> i64 |
| 106 | func gt_init(tile_edge: i64, max_entries: i64, max_workers: i64) -> i64 |
| 109 | func gt_tiles_x() -> i64 { return GT_TX } |
| 110 | func gt_tiles_y() -> i64 { return GT_TY } |
| 111 | func gt_tiles() -> i64 { return GT_TX * GT_TY } called by 1: main |
| 112 | func gt_edge() -> i64 { return GT_TE } |
| 113 | func gt_edge_min() -> i64 { return GT_EDGE_MIN } called by 1: main |
| 114 | func gt_bytes() -> i64 { return GT_ALLOC } called by 1: main |
| 116 | func gt_evals() -> i64 { return GT_EVALS } |
| 118 | func gt_entries() -> i64 { return GT_ENT } |
| 120 | func gt_skipped() -> i64 { return GT_SKIP } called by 1: main |
| 126 | func gt_blend_tile(ti: i64, gauss: *i64, sxb: *i64, syb: *i64, pa: *i64, pb: *i64, pc: *i64, pdet: *i64, acc: *i64, trans: *i64, explut: *i64, W: i64, H: i64, satp: *i64, skipout: *i64) -> i64 |
| 172 | func gt_workers() -> i64 { return GT_MAXW } |
| 174 | func gt_worker_evals(w: i64) -> i64 { let a: *i64 = GT_WEVAL as *i64; return a[w*2] } |
| 175 | func gt_worker_skipped(w: i64) -> i64 { let a: *i64 = GT_WEVAL as *i64; return a[w*2+1] } |
| 177 | func gt_render(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, workers: i64) -> i64 called by 2: mainmain calls 12: gt_errgs_focal_forgs_project_sort_atgs_splat_rxgs_splat_rygs_clear_at+6 |