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}