code wiki / (root) / nx_textured_tri_fast.nx

nx_textured_tri_fast.nx source

↩ module page · 195 lines · 8644 B

1// nx_textured_tri_fast.nx -- E1-R1 of the raster perf ladder (board S18): the SAME textured-triangle 2// semantics as nx_textured_tri.txtri_render, made incremental. BIT-EXACT by construction: 3// (a) edge functions are affine -> per-pixel re-evaluation (6 muls) becomes +=dE/dx steps (exact ints); 4// (b) UV numerators Nu=w0*u0+w1*u1+w2*u2 are affine too -> row-DDA increments (exact ints), the /area 5// truncating division stays per-fragment so u,v match the reference EXACTLY; 6// (c) bilinear sampling inlines nx_txs_sample_bilinear's CLAMP arithmetic verbatim, with direct 7// row-pointer loads (the wrap-clamp already guarantees in-bounds -- nx_image_get's checks never 8// fire on this path) and >>10 replacing /1024 ONLY where operands are provably non-negative. 9// The equivalence gate lives in nx_raster_bench (fast frame must checksum IDENTICAL to the reference; 10// neg-control: a perturbed scene must NOT match). license_tier: ORIGINAL 11import "nx_syscalls.nx" 12import "nx_textured_tri.nx" 13 14// exact-equivalent CLAMP bilinear (channel 0; tch = tex.channels so indexing matches nx_image_get) 15func txsf_bilinear(tp: *u8, w: i64, h: i64, stride: i64, tch: i64, u_q10: i64, v_q10: i64) -> i64 { 16 let u_t: i64 = u_q10 * w - 512 17 let v_t: i64 = v_q10 * h - 512 18 // exact FLOOR(x/1024) without idiv: >>10 for x>=0; -((-x+1023)>>10) for x<0 (== the reference's 19 // trunc-then-correct for ALL inputs, remainder-0 negatives included) 20 var tx0: i64 = 0 21 if u_t >= 0 { tx0 = u_t >> 10 } else { tx0 = 0 - (((0 - u_t) + 1023) >> 10) } 22 var ty0: i64 = 0 23 if v_t >= 0 { ty0 = v_t >> 10 } else { ty0 = 0 - (((0 - v_t) + 1023) >> 10) } 24 let fu: i64 = u_t - tx0 * 1024 25 let fv: i64 = v_t - ty0 * 1024 26 var x0c: i64 = tx0 27 if x0c < 0 { x0c = 0 } 28 if x0c >= w { x0c = w - 1 } 29 var y0c: i64 = ty0 30 if y0c < 0 { y0c = 0 } 31 if y0c >= h { y0c = h - 1 } 32 var x1c: i64 = tx0 + 1 33 if x1c < 0 { x1c = 0 } 34 if x1c >= w { x1c = w - 1 } 35 var y1c: i64 = ty0 + 1 36 if y1c < 0 { y1c = 0 } 37 if y1c >= h { y1c = h - 1 } 38 let r0: i64 = y0c * stride 39 let r1: i64 = y1c * stride 40 let t00: i64 = tp[r0 + x0c*tch] as i64 41 let t10: i64 = tp[r0 + x1c*tch] as i64 42 let t01: i64 = tp[r1 + x0c*tch] as i64 43 let t11: i64 = tp[r1 + x1c*tch] as i64 44 let omu: i64 = 1024 - fu 45 let omv: i64 = 1024 - fv 46 let top: i64 = (t00 * omu + t10 * fu) >> 10 47 let bot: i64 = (t01 * omu + t11 * fu) >> 10 48 return (top * omv + bot * fv) >> 10 49} 50 51func txtri_render_fast_clip(fb: *Image, tex: *Image, x0: i64, y0: i64, u0: i64, v0: i64, x1: i64, y1: i64, u1: i64, v1: i64, x2: i64, y2: i64, u2: i64, v2: i64, y0clip: i64, y1clip: i64) -> i64 { 52 var area: i64 = tt_edge(x0, y0, x1, y1, x2, y2) 53 if area == 0 { return 0 } 54 var sgn: i64 = 1 55 if area < 0 { sgn = 0 - 1; area = 0 - area } 56 var minx: i64 = x0 57 if x1 < minx { minx = x1 } 58 if x2 < minx { minx = x2 } 59 var maxx: i64 = x0 60 if x1 > maxx { maxx = x1 } 61 if x2 > maxx { maxx = x2 } 62 var miny: i64 = y0 63 if y1 < miny { miny = y1 } 64 if y2 < miny { miny = y2 } 65 var maxy: i64 = y0 66 if y1 > maxy { maxy = y1 } 67 if y2 > maxy { maxy = y2 } 68 if minx < 0 { minx = 0 } 69 if miny < 0 { miny = 0 } 70 if maxx >= fb.width { maxx = fb.width - 1 } 71 if maxy >= fb.height { maxy = fb.height - 1 } 72 if y1clip > 0 { if miny < y0clip { miny = y0clip } if maxy > y1clip - 1 { maxy = y1clip - 1 } } // band clip (threaded raster) 73 if maxx < minx { return 0 } 74 if maxy < miny { return 0 } 75 // edge steps: dE/dx = -(by-ay), dE/dy = (bx-ax), sign-folded 76 let a0: i64 = (0 - (y2 - y1)) * sgn 77 let b0: i64 = (x2 - x1) * sgn 78 let a1: i64 = (0 - (y0 - y2)) * sgn 79 let b1: i64 = (x0 - x2) * sgn 80 let a2: i64 = (0 - (y1 - y0)) * sgn 81 let b2: i64 = (x1 - x0) * sgn 82 // row-start edge values at (minx,miny) 83 var w0r: i64 = tt_edge(x1, y1, x2, y2, minx, miny) * sgn 84 var w1r: i64 = tt_edge(x2, y2, x0, y0, minx, miny) * sgn 85 var w2r: i64 = tt_edge(x0, y0, x1, y1, minx, miny) * sgn 86 // UV numerator DDA (exact: increments are the same integers the reference sums per pixel) 87 let nua: i64 = a0*u0 + a1*u1 + a2*u2 88 let nub: i64 = b0*u0 + b1*u1 + b2*u2 89 let nva: i64 = a0*v0 + a1*v1 + a2*v2 90 let nvb: i64 = b0*v0 + b1*v1 + b2*v2 91 var nur: i64 = w0r*u0 + w1r*u1 + w2r*u2 92 var nvr: i64 = w0r*v0 + w1r*v1 + w2r*v2 93 let tw: i64 = tex.width 94 let th: i64 = tex.height 95 let tstride: i64 = tex.stride 96 let tch: i64 = tex.channels 97 let tp: *u8 = tex.pixels 98 let px: *u8 = fb.pixels 99 let fstride: i64 = fb.stride 100 // R2a: EXACT reciprocal for /area (round-up method): sh=31+ceil(log2(area)), m=floor(2^sh/area)+1 101 // -> floor(n/area) == (n*m)>>sh for ALL 0<=n<2^31, because m*area = 2^sh + (area - r) with 102 // 0 < area-r <= 2^ceil(log2 area). GUARD keeps it in-domain (n=nu<=area*1023 < 2^31 and n*m < 2^63): 103 // all six UVs in [0,1023] and area<=2^21; anything else uses the reference's per-fragment division 104 // (identical expression -> bit-exact both ways, negatives keep trunc semantics). 105 var rf: i64 = 1 106 if area > 2097152 { rf = 0 } 107 if u0 < 0 { rf = 0 } 108 if u0 > 1023 { rf = 0 } 109 if v0 < 0 { rf = 0 } 110 if v0 > 1023 { rf = 0 } 111 if u1 < 0 { rf = 0 } 112 if u1 > 1023 { rf = 0 } 113 if v1 < 0 { rf = 0 } 114 if v1 > 1023 { rf = 0 } 115 if u2 < 0 { rf = 0 } 116 if u2 > 1023 { rf = 0 } 117 if v2 < 0 { rf = 0 } 118 if v2 > 1023 { rf = 0 } 119 var l2: i64 = 0 120 while (1 << l2) < area { l2 = l2 + 1 } 121 let sh: i64 = 31 + l2 122 var mrec: i64 = 0 123 if rf == 1 { mrec = ((1 << sh) / area) + 1 } 124 let txmax: i64 = tw - 1 125 let tymax: i64 = th - 1 126 var py: i64 = miny 127 while py <= maxy { 128 var w0: i64 = w0r 129 var w1: i64 = w1r 130 var w2: i64 = w2r 131 var nu: i64 = nur 132 var nv: i64 = nvr 133 var off: i64 = py * fstride + minx * 3 134 var pxi: i64 = minx 135 while pxi <= maxx { 136 if w0 >= 0 { if w1 >= 0 { if w2 >= 0 { 137 var u: i64 = 0 138 var v: i64 = 0 139 if rf == 1 { u = (nu * mrec) >> sh; v = (nv * mrec) >> sh } else { u = nu / area; v = nv / area } 140 // R2b: INTERIOR fast path (the common case) -- when the 2x2 footprint is fully inside 141 // the texture, every clamp is the identity -> direct taps, inlined (no call, no branches). 142 // Border/negative samples fall back to txsf_bilinear (the proven exact general form). 143 let u_t: i64 = u * tw - 512 144 let v_t: i64 = v * th - 512 145 var g: i64 = 0 146 var inr: i64 = 0 147 var tx0: i64 = 0 148 var ty0: i64 = 0 149 if u_t >= 0 { if v_t >= 0 { 150 tx0 = u_t >> 10 151 ty0 = v_t >> 10 152 if tx0 < txmax { if ty0 < tymax { inr = 1 } } 153 } } 154 if inr == 1 { 155 let fu: i64 = u_t - (tx0 << 10) 156 let fv: i64 = v_t - (ty0 << 10) 157 let r0: i64 = ty0 * tstride + tx0 * tch 158 let t00: i64 = tp[r0] as i64 159 let t10: i64 = tp[r0 + tch] as i64 160 let t01: i64 = tp[r0 + tstride] as i64 161 let t11: i64 = tp[r0 + tstride + tch] as i64 162 let omu: i64 = 1024 - fu 163 let top: i64 = (t00 * omu + t10 * fu) >> 10 164 let bot: i64 = (t01 * omu + t11 * fu) >> 10 165 g = (top * (1024 - fv) + bot * fv) >> 10 166 } else { 167 g = txsf_bilinear(tp, tw, th, tstride, tch, u, v) 168 } 169 px[off] = g as u8 170 px[off + 1] = g as u8 171 px[off + 2] = g as u8 172 } } } 173 w0 = w0 + a0 174 w1 = w1 + a1 175 w2 = w2 + a2 176 nu = nu + nua 177 nv = nv + nva 178 off = off + 3 179 pxi = pxi + 1 180 } 181 w0r = w0r + b0 182 w1r = w1r + b1 183 w2r = w2r + b2 184 nur = nur + nub 185 nvr = nvr + nvb 186 py = py + 1 187 } 188 return 0 189} 190 191// thin full-frame wrapper -- every existing caller (bench + game) stays byte-unchanged; only the threaded 192// raster passes a real [y0clip,y1clip) band. y1clip<=0 => no band clip = whole frame. 193func txtri_render_fast(fb: *Image, tex: *Image, x0: i64, y0: i64, u0: i64, v0: i64, x1: i64, y1: i64, u1: i64, v1: i64, x2: i64, y2: i64, u2: i64, v2: i64) -> i64 { 194 return txtri_render_fast_clip(fb, tex, x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2, 0, 0) 195}