code wiki / (root) / nx_nxa_gpu_lib.nx

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}