nx_nxa_gpu_lib.nx source
↩ module page · 318 lines · 12516 B
1// Native NXA geometry conversion for GPU upload; no platform API or allocator dependency.
2import "nx_nxa.nx"
3
4const NGP_COMPONENTS: i64 = 3 // VERT xyz and TRIS corners in the NXA v1 schema.
5const NGP_GPU_WORD: i64 = 4 // WebGPU float32/uint32 wire representation.
6const NGP_U32_MAX: i64 = 4294967295
7const NGP_E_OUTPUT: i64 = 0-4
8const NGP_E_INDEX: i64 = 0-5
9const NGP_E_OVERLAP: i64 = 0-6
10
11const NGP_SKIN_INFLUENCES: i64 = 4 // NXA v1: four joint indices, then four Q12 weights.
12const NGP_SKIN_WORDS: i64 = NGP_SKIN_INFLUENCES*2
13const NGP_SKEL_WORDS: i64 = 8 // NXA v1 parent, bind translation xyz, quaternion xyzw.
14const NGP_WEIGHT_ONE: i64 = 4096 // NXA v1 Q12 weight denominator.
15const NGP_F32_INTEGER_BITS: i64 = 24 // IEEE-754 binary32 significand precision.
16const NGP_E_SKIN_COUNT: i64 = 0-7
17const NGP_E_SKIN_JOINT: i64 = 0-8
18const NGP_E_SKIN_WEIGHT: i64 = 0-9
19const NGP_E_TEXC_SHAPE: i64=0-10
20const NGP_E_TEXC_VALUE: i64=0-11
21const NGP_F32_ABS_MASK: i64=2147483647
22const NGP_F32_SIGN_MASK: i64=2147483648
23
24func ngp_reason(rc: i64) -> *u8 {
25 if rc==0-1 { return "NXA identity or required section absent" as *u8 }
26 if rc==0-2 { return "NXA version newer than this reader" as *u8 }
27 if rc==0-3 { return "NXA checksum, extent or element shape invalid" as *u8 }
28 if rc==NGP_E_OUTPUT { return "output extent insufficient; allocate the reported geometry byte requirement" as *u8 }
29 if rc==NGP_E_INDEX { return "triangle index outside vertex range or uint32 representation" as *u8 }
30 if rc==NGP_E_OVERLAP { return "output overlaps immutable source; provide a distinct upload buffer" as *u8 }
31 if rc==NGP_E_SKIN_COUNT { return "SKIN vertex count differs from VERT; repair the source binding" as *u8 }
32 if rc==NGP_E_SKIN_JOINT { return "SKIN joint is outside SKEL or exact GPU index representation" as *u8 }
33 if rc==NGP_E_SKIN_WEIGHT { return "SKIN Q12 weights are out of range or do not sum to one" as *u8 }
34 if rc==NGP_E_TEXC_SHAPE { return "TEXC vertex count, stride or extent differs from the source geometry" as *u8 }
35 if rc==NGP_E_TEXC_VALUE { return "TEXC UV or owning joint is outside the NXA v1 atlas representation" as *u8 }
36 return "asset buffers packed" as *u8
37}
38func ngp_section(b: *u8,n: i64,tag: *u8) -> i64 {
39 return nxa_counted_section(b,n,nxa_tag4(tag),NGP_COMPONENTS)
40}
41func ngp_bytes(b: *u8,n: i64,tag: *u8) -> i64 {
42 let at: i64=ngp_section(b,n,tag)
43 if at<0 { return at }
44 let h: *i64=b as *i64
45 // Validated i64 payload fits n; its float32/uint32 representation is half its size.
46 return h[at]*NGP_COMPONENTS*NGP_GPU_WORD
47}
48func ngp_output(b: *u8,n: i64,out: *u8,cap: i64,need: i64) -> i64 {
49 if cap<need { return NGP_E_OUTPUT }
50 if need==0 { return 0 }
51 if (out as i64)<=0 { return NGP_E_OUTPUT }
52 let src: i64=b as i64
53 let dst: i64=out as i64
54 if dst>=src {
55 if dst-src<n { return NGP_E_OVERLAP }
56 } else {
57 if src-dst<need { return NGP_E_OVERLAP }
58 }
59 return 0
60}
61func ngp_vertices(b: *u8,n: i64,out: *u8,cap: i64) -> i64 {
62 let at: i64=ngp_section(b,n,"VERT" as *u8)
63 if at<0 { return at }
64 let h: *i64=b as *i64
65 let count: i64=h[at]*NGP_COMPONENTS
66 let need: i64=count*NGP_GPU_WORD
67 let valid: i64=ngp_output(b,n,out,cap,need)
68 if valid<0 { return valid }
69 let dst: *u32=out as *u32
70 var i: i64=0
71 while i<count { dst[i]=__f32_from_i64(h[at+1+i]) as u32; i=i+1 }
72 return need
73}
74func ngp_indices(b: *u8,n: i64,out: *u8,cap: i64) -> i64 {
75 let va: i64=ngp_section(b,n,"VERT" as *u8)
76 if va<0 { return va }
77 let ta: i64=ngp_section(b,n,"TRIS" as *u8)
78 if ta<0 { return ta }
79 let h: *i64=b as *i64
80 let nv: i64=h[va]
81 let count: i64=h[ta]*NGP_COMPONENTS
82 let need: i64=count*NGP_GPU_WORD
83 let valid: i64=ngp_output(b,n,out,cap,need)
84 if valid<0 { return valid }
85 // Validate every index before modifying the destination, including late invalid elements.
86 var i: i64=0
87 while i<count {
88 let v: i64=h[ta+1+i]
89 if v<0 { return NGP_E_INDEX }
90 if v>=nv { return NGP_E_INDEX }
91 if v>NGP_U32_MAX { return NGP_E_INDEX }
92 i=i+1
93 }
94 let dst: *u32=out as *u32
95 i=0
96 while i<count { dst[i]=h[ta+1+i] as u32; i=i+1 }
97 return need
98}
99
100// Validate source-to-skeleton binding before any derived GPU allocation or write.
101func ngp_skin_section(b: *u8,n: i64) -> i64 {
102 let va: i64=ngp_section(b,n,"VERT" as *u8)
103 if va<0 { return va }
104 let sa: i64=nxa_counted_section(b,n,nxa_tag4("SKEL" as *u8),NGP_SKEL_WORDS)
105 if sa<0 { return sa }
106 let ka: i64=nxa_counted_section(b,n,nxa_tag4("SKIN" as *u8),NGP_SKIN_WORDS)
107 if ka<0 { return ka }
108 let h: *i64=b as *i64
109 if h[ka]!=h[va] { return NGP_E_SKIN_COUNT }
110 let count: i64=h[ka]
111 let joints: i64=h[sa]
112 var v: i64=0
113 while v<count {
114 let at: i64=ka+1+v*NGP_SKIN_WORDS
115 var sum: i64=0
116 var k: i64=0
117 while k<NGP_SKIN_INFLUENCES {
118 let joint: i64=h[at+k]
119 if joint<0 { return NGP_E_SKIN_JOINT }
120 if joint>=joints { return NGP_E_SKIN_JOINT }
121 if joint>(1<<NGP_F32_INTEGER_BITS) { return NGP_E_SKIN_JOINT }
122 let weight: i64=h[at+NGP_SKIN_INFLUENCES+k]
123 if weight<0 { return NGP_E_SKIN_WEIGHT }
124 if weight>NGP_WEIGHT_ONE { return NGP_E_SKIN_WEIGHT }
125 sum=sum+weight
126 k=k+1
127 }
128 if sum!=NGP_WEIGHT_ONE { return NGP_E_SKIN_WEIGHT }
129 v=v+1
130 }
131 return ka
132}
133func ngp_skin_bytes(b: *u8,n: i64) -> i64 {
134 let at: i64=ngp_skin_section(b,n)
135 if at<0 { return at }
136 let h: *i64=b as *i64
137 return h[at]*NGP_SKIN_WORDS*NGP_GPU_WORD
138}
139// Interleaved float32x4 joints + float32x4 weights; offsets/stride derive from the v1 schema.
140// Q12 weights convert exactly to binary32; never silently normalize malformed source weights.
141func ngp_skin(b: *u8,n: i64,out: *u8,cap: i64) -> i64 {
142 let at: i64=ngp_skin_section(b,n)
143 if at<0 { return at }
144 let h: *i64=b as *i64
145 let count: i64=h[at]
146 let need: i64=count*NGP_SKIN_WORDS*NGP_GPU_WORD
147 let valid: i64=ngp_output(b,n,out,cap,need)
148 if valid<0 { return valid }
149 let dst: *u32=out as *u32
150 let denominator: i64=__f32_from_i64(NGP_WEIGHT_ONE)
151 var v: i64=0
152 while v<count {
153 let src: i64=at+1+v*NGP_SKIN_WORDS
154 let target: i64=v*NGP_SKIN_WORDS
155 var k: i64=0
156 while k<NGP_SKIN_INFLUENCES {
157 dst[target+k]=__f32_from_i64(h[src+k]) as u32
158 dst[target+NGP_SKIN_INFLUENCES+k]=__f32_div(__f32_from_i64(h[src+NGP_SKIN_INFLUENCES+k]),denominator) as u32
159 k=k+1
160 }
161 v=v+1
162 }
163 return need
164}
165
166const NGP_TEXC_HEADER: i64=4 // NXA TEXC nv, stride, atlas grid, optional region table offset.
167const NGP_TEXC_STRIDE: i64=3 // u Q16, v Q16, owning joint.
168const NGP_TEXC_UV: i64=2
169const NGP_TEXC_ONE: i64=65536
170
171// Read UVs from the source atlas; optional region metadata remains in the immutable NXA.
172func ngp_texcoord_section(b: *u8,n: i64) -> i64 {
173 let va: i64=ngp_section(b,n,"VERT" as *u8)
174 if va<0 { return va }
175 let sk: i64=nxa_counted_section(b,n,nxa_tag4("SKEL" as *u8),NGP_SKEL_WORDS)
176 if sk<0 { return sk }
177 let entry: i64=nxa_section_entry(b,n,nxa_tag4("TEXC" as *u8))
178 if entry<0 { return entry }
179 let h: *i64=b as *i64
180 let words: i64=h[entry+2]
181 if words<NGP_TEXC_HEADER { return NGP_E_TEXC_SHAPE }
182 let at: i64=h[entry+1]/8
183 let count: i64=h[at]
184 if count!=h[va] || h[at+1]!=NGP_TEXC_STRIDE { return NGP_E_TEXC_SHAPE }
185 if count>(words-NGP_TEXC_HEADER)/NGP_TEXC_STRIDE { return NGP_E_TEXC_SHAPE }
186 var v: i64=0
187 while v<count {
188 let row: i64=at+NGP_TEXC_HEADER+v*NGP_TEXC_STRIDE
189 var k: i64=0
190 while k<NGP_TEXC_UV {
191 if h[row+k]<0 || h[row+k]>=NGP_TEXC_ONE { return NGP_E_TEXC_VALUE }
192 k=k+1
193 }
194 if h[row+NGP_TEXC_UV]<0 || h[row+NGP_TEXC_UV]>=h[sk] { return NGP_E_TEXC_VALUE }
195 v=v+1
196 }
197 return at
198}
199func ngp_texcoord_bytes(b: *u8,n: i64) -> i64 {
200 let at: i64=ngp_texcoord_section(b,n)
201 if at<0 { return at }
202 let h: *i64=b as *i64
203 return h[at]*NGP_TEXC_UV*NGP_GPU_WORD
204}
205func ngp_texcoords(b: *u8,n: i64,out: *u8,cap: i64) -> i64 {
206 let at: i64=ngp_texcoord_section(b,n)
207 if at<0 { return at }
208 let h: *i64=b as *i64
209 let count: i64=h[at]
210 let need: i64=count*NGP_TEXC_UV*NGP_GPU_WORD
211 let valid: i64=ngp_output(b,n,out,cap,need)
212 if valid<0 { return valid }
213 let dst: *u32=out as *u32
214 let denominator: i64=__f32_from_i64(NGP_TEXC_ONE)
215 var v: i64=0
216 while v<count {
217 var k: i64=0
218 while k<NGP_TEXC_UV {
219 let value: i64=h[at+NGP_TEXC_HEADER+v*NGP_TEXC_STRIDE+k]
220 dst[v*NGP_TEXC_UV+k]=__f32_div(__f32_from_i64(value),denominator) as u32
221 k=k+1
222 }
223 v=v+1
224 }
225 return need
226}
227
228// Normal sums are scaled before squaring, so length squared is in [1,3].
229// Newton refinement stops on identical binary32 bits; precision bounds the iterations.
230func ngp_normal_length(square: i64) -> i64 {
231 var root: i64=__f32_from_i64(1)
232 let two: i64=__f32_from_i64(2)
233 var i: i64=0
234 while i<NGP_F32_INTEGER_BITS {
235 let next: i64=__f32_div(__f32_add(root,__f32_div(square,root)),two)
236 if next==root { return root }
237 root=next;i=i+1
238 }
239 return root
240}
241func ngp_normal_edge(h: *i64,va: i64,a: i64,b: i64,k: i64,scale: i64) -> i64 {
242 let av: i64=__f32_div(__f32_from_i64(h[va+1+a*NGP_COMPONENTS+k]),scale)
243 let bv: i64=__f32_div(__f32_from_i64(h[va+1+b*NGP_COMPONENTS+k]),scale)
244 return ngp_float_difference(bv,av)
245}
246func ngp_float_difference(a: i64,b: i64) -> i64 { return __f32_add(a,b^NGP_F32_SIGN_MASK) }
247// Area-weighted normals for a mesh lacking authored normals. Never weld source vertices:
248// duplicated seam/hard-edge vertices remain independent. Degenerate sums remain zero.
249func ngp_normals(b: *u8,n: i64,out: *u8,cap: i64) -> i64 {
250 let va: i64=ngp_section(b,n,"VERT" as *u8)
251 if va<0 { return va }
252 let ta: i64=ngp_section(b,n,"TRIS" as *u8)
253 if ta<0 { return ta }
254 let h: *i64=b as *i64
255 let nv: i64=h[va]
256 let count: i64=h[ta]*NGP_COMPONENTS
257 let need: i64=nv*NGP_COMPONENTS*NGP_GPU_WORD
258 let valid: i64=ngp_output(b,n,out,cap,need)
259 if valid<0 { return valid }
260 var i: i64=0
261 while i<count {
262 let index: i64=h[ta+1+i]
263 if index<0 || index>=nv || index>NGP_U32_MAX { return NGP_E_INDEX }
264 i=i+1
265 }
266 // One common scale preserves relative face areas while keeping cross products finite.
267 var scale: i64=__f32_from_i64(1)
268 i=0
269 while i<nv*NGP_COMPONENTS {
270 let magnitude: i64=__f32_from_i64(h[va+1+i])&NGP_F32_ABS_MASK
271 if magnitude>scale { scale=magnitude }
272 i=i+1
273 }
274 let dst: *u32=out as *u32
275 i=0
276 while i<nv*NGP_COMPONENTS { dst[i]=0 as u32;i=i+1 }
277 i=0
278 while i<count {
279 let a: i64=h[ta+1+i];let c: i64=h[ta+2+i];let d: i64=h[ta+3+i]
280 var k: i64=0
281 while k<NGP_COMPONENTS {
282 let u: i64=(k+1)%NGP_COMPONENTS;let v: i64=(k+2)%NGP_COMPONENTS
283 let face: i64=ngp_float_difference(__f32_mul(ngp_normal_edge(h,va,a,c,u,scale),ngp_normal_edge(h,va,a,d,v,scale)),__f32_mul(ngp_normal_edge(h,va,a,c,v,scale),ngp_normal_edge(h,va,a,d,u,scale)))
284 var corner: i64=0
285 while corner<NGP_COMPONENTS {
286 let at: i64=h[ta+1+i+corner]*NGP_COMPONENTS+k
287 dst[at]=__f32_add(dst[at] as i64,face) as u32
288 corner=corner+1
289 }
290 k=k+1
291 }
292 i=i+NGP_COMPONENTS
293 }
294 i=0
295 while i<nv {
296 var largest: i64=0;var k: i64=0
297 while k<NGP_COMPONENTS {
298 let magnitude: i64=(dst[i*NGP_COMPONENTS+k] as i64)&NGP_F32_ABS_MASK
299 if magnitude>largest { largest=magnitude }
300 k=k+1
301 }
302 if largest>0 {
303 var square: i64=0;k=0
304 while k<NGP_COMPONENTS {
305 let at: i64=i*NGP_COMPONENTS+k
306 let value: i64=__f32_div(dst[at] as i64,largest)
307 dst[at]=value as u32;square=__f32_add(square,__f32_mul(value,value));k=k+1
308 }
309 let length: i64=ngp_normal_length(square);k=0
310 while k<NGP_COMPONENTS {
311 let at: i64=i*NGP_COMPONENTS+k
312 dst[at]=__f32_div(dst[at] as i64,length) as u32;k=k+1
313 }
314 }
315 i=i+1
316 }
317 return need
318}