@woosh/meep-engine 3.12.0 → 3.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/package.json +1 -1
  2. package/src/engine/graphics3/decal/GPUDecalRenderer.js +3 -3
  3. package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
  4. package/src/engine/graphics3/decal/shader_decal_composite.js +4 -2
  5. package/src/engine/graphics3/highlight/GPUHighlightRenderer.d.ts.map +1 -1
  6. package/src/engine/graphics3/highlight/GPUHighlightRenderer.js +2 -3
  7. package/src/engine/graphics3/highlight/shader_highlight_outline.js +150 -150
  8. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  9. package/src/shade/RENDERER_CONTRACT.md +2 -1
  10. package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts +18 -0
  11. package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts.map +1 -1
  12. package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.js +73 -0
  13. package/src/shade/device/PASS_ENCODER_PROPOSAL_2026_08_31.md +750 -0
  14. package/src/shade/device/ShadeGPUCommandContext.d.ts +10 -16
  15. package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
  16. package/src/shade/device/ShadeGPUCommandContext.js +801 -994
  17. package/src/shade/device/pass/ShadeGPUComputePassEncoder.d.ts +107 -0
  18. package/src/shade/device/pass/ShadeGPUComputePassEncoder.d.ts.map +1 -0
  19. package/src/shade/device/pass/ShadeGPUComputePassEncoder.js +222 -0
  20. package/src/shade/device/pass/ShadeGPURenderPassEncoder.d.ts +168 -0
  21. package/src/shade/device/pass/ShadeGPURenderPassEncoder.d.ts.map +1 -0
  22. package/src/shade/device/pass/ShadeGPURenderPassEncoder.js +317 -0
  23. package/src/shade/device/timing/GPUTimerArray.d.ts +4 -9
  24. package/src/shade/device/timing/GPUTimerArray.d.ts.map +1 -1
  25. package/src/shade/device/timing/GPUTimerArray.js +261 -289
  26. package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +20 -2
  27. package/src/shade/device/timing/ShadeGPUContextProfiler.d.ts +59 -0
  28. package/src/shade/device/timing/ShadeGPUContextProfiler.d.ts.map +1 -0
  29. package/src/shade/device/timing/ShadeGPUContextProfiler.js +182 -0
  30. package/src/shade/device/timing/profile/GPUProfileSession.d.ts +4 -4
  31. package/src/shade/device/timing/profile/GPUProfileSession.js +4 -4
  32. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_leaf_triangles.d.ts.map +1 -1
  33. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_leaf_triangles.js +3 -1
  34. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_points.d.ts.map +1 -1
  35. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_points.js +3 -1
  36. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_visibility.d.ts.map +1 -1
  37. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_visibility.js +3 -1
  38. package/src/shade/playground/skinned_blas_refit/verify_leaf_triangles.js +2 -2
  39. package/src/shade/playground/skinned_blas_refit/verify_traversal_reachability.js +2 -2
  40. package/src/shade/renderer/Renderer.d.ts +0 -6
  41. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  42. package/src/shade/renderer/Renderer.js +0 -29
  43. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  44. package/src/shade/renderer/animation/GPUAnimationManager.js +1 -3
  45. package/src/shade/renderer/animation/shader_animation_tick.d.ts.map +1 -1
  46. package/src/shade/renderer/animation/shader_animation_tick.js +128 -125
  47. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts.map +1 -1
  48. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +8 -18
  49. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_geometry_sphere_derive.d.ts.map +1 -1
  50. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_geometry_sphere_derive.js +100 -98
  51. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_reduce.d.ts.map +1 -1
  52. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_reduce.js +215 -212
  53. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_scatter.d.ts +1 -1
  54. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_scatter.d.ts.map +1 -1
  55. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_scatter.js +135 -120
  56. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_skinned_mesh_bounds_refresh.d.ts.map +1 -1
  57. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_skinned_mesh_bounds_refresh.js +77 -75
  58. package/src/shade/renderer/animation/skinning/shader_meshlet_apply_skinning.d.ts.map +1 -1
  59. package/src/shade/renderer/animation/skinning/shader_meshlet_apply_skinning.js +375 -371
  60. package/src/shade/renderer/atmosphere/shader_transmittance_lut.js +3 -3
  61. package/src/shade/renderer/buffer/graph_inspect_gpu_buffer.js +1 -1
  62. package/src/shade/renderer/buffer/table/GPUDatabase.d.ts.map +1 -1
  63. package/src/shade/renderer/buffer/table/GPUDatabase.js +14 -19
  64. package/src/shade/renderer/buffer/table/gpu_table_element_upload_shader.d.ts.map +1 -1
  65. package/src/shade/renderer/buffer/table/gpu_table_element_upload_shader.js +7 -2
  66. package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.d.ts.map +1 -1
  67. package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.js +325 -319
  68. package/src/shade/renderer/buffer/util/shader_buffer_copy_storage_to_storage.d.ts.map +1 -1
  69. package/src/shade/renderer/buffer/util/shader_buffer_copy_storage_to_storage.js +3 -1
  70. package/src/shade/renderer/deferred/shader_tonemap_HDR.d.ts.map +1 -1
  71. package/src/shade/renderer/deferred/shader_tonemap_HDR.js +4 -1
  72. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.d.ts +2 -2
  73. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.d.ts.map +1 -1
  74. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +167 -167
  75. package/src/shade/renderer/fow/GPUFogOfWarRenderer.js +3 -3
  76. package/src/shade/renderer/fow/shader_fog_of_war.d.ts.map +1 -1
  77. package/src/shade/renderer/fow/shader_fog_of_war.js +3 -1
  78. package/src/shade/renderer/geometry/bvh/record_blas_refit.js +102 -102
  79. package/src/shade/renderer/geometry/bvh/shader_blas_refit_leaf_bounds.d.ts.map +1 -1
  80. package/src/shade/renderer/geometry/bvh/shader_blas_refit_leaf_bounds.js +125 -121
  81. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
  82. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +766 -774
  83. package/src/shade/renderer/geometry/meshlet/shader_meshlet_metadata_address_patch.d.ts.map +1 -1
  84. package/src/shade/renderer/geometry/meshlet/shader_meshlet_metadata_address_patch.js +72 -69
  85. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts.map +1 -1
  86. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +5 -10
  87. package/src/shade/renderer/geometry/sdf/shader_build_sdf.js +129 -129
  88. package/src/shade/renderer/geometry/sdf/shader_sdf_visualize.d.ts.map +1 -1
  89. package/src/shade/renderer/geometry/sdf/shader_sdf_visualize.js +5 -3
  90. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_probes.d.ts.map +1 -1
  91. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_probes.js +4 -1
  92. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_resolve_probes.d.ts.map +1 -1
  93. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_resolve_probes.js +3 -1
  94. package/src/shade/renderer/global_illumination/clipmap/shader_update_clipmap_cascade.d.ts.map +1 -1
  95. package/src/shade/renderer/global_illumination/clipmap/shader_update_clipmap_cascade.js +114 -112
  96. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_lpv_store_sh.d.ts.map +1 -1
  97. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_lpv_store_sh.js +4 -1
  98. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_lvp_atlas_to_sh.d.ts.map +1 -1
  99. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_lvp_atlas_to_sh.js +187 -184
  100. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_reduce_atlas_to_sh.d.ts.map +1 -1
  101. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_reduce_atlas_to_sh.js +4 -1
  102. package/src/shade/renderer/global_illumination/sharc/SpatialHashRadianceCache.d.ts.map +1 -1
  103. package/src/shade/renderer/global_illumination/sharc/SpatialHashRadianceCache.js +6 -8
  104. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.d.ts.map +1 -1
  105. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.js +3 -1
  106. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_update.d.ts.map +1 -1
  107. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_update.js +3 -1
  108. package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +3 -3
  109. package/src/shade/renderer/gpu_primitive/bvh/PHASE1_HANDOVER.md +3 -3
  110. package/src/shade/renderer/gpu_primitive/bvh/graph_bvh_build_lbvh.js +217 -217
  111. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_emit_hierarchy.d.ts.map +1 -1
  112. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_emit_hierarchy.js +145 -142
  113. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_morton.d.ts.map +1 -1
  114. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_morton.js +87 -82
  115. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_refit.d.ts.map +1 -1
  116. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_refit.js +143 -140
  117. package/src/shade/renderer/gpu_primitive/bvh/verify_gpu_bvh_build.d.ts +2 -2
  118. package/src/shade/renderer/gpu_primitive/bvh/verify_gpu_bvh_build.d.ts.map +1 -1
  119. package/src/shade/renderer/gpu_primitive/bvh/verify_gpu_bvh_build.js +43 -8
  120. package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.d.ts.map +1 -1
  121. package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.js +4 -5
  122. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram.d.ts.map +1 -1
  123. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram.js +88 -84
  124. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter.d.ts.map +1 -1
  125. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter.js +194 -191
  126. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  127. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +33 -69
  128. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
  129. package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +6 -1
  130. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +1 -1
  131. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.js +23 -7
  132. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts.map +1 -1
  133. package/src/shade/renderer/light/environment/execute_filter_environment_map.js +1 -2
  134. package/src/shade/renderer/light/environment/shader_convolve_reflection_map.d.ts.map +1 -1
  135. package/src/shade/renderer/light/environment/shader_convolve_reflection_map.js +6 -1
  136. package/src/shade/renderer/lightmap/bake/raster/graph_draw_lightmap_viz_buffer.d.ts.map +1 -1
  137. package/src/shade/renderer/lightmap/bake/raster/graph_draw_lightmap_viz_buffer.js +234 -219
  138. package/src/shade/renderer/lightmap/bake/raster/shader_lightmap_to_viz_buffer.js +2 -2
  139. package/src/shade/renderer/lightmap/bake/shader_lightmap_denoise_sh.js +191 -191
  140. package/src/shade/renderer/lightmap/bake/tile/shader_lightmap_accummulate_tile.d.ts.map +1 -1
  141. package/src/shade/renderer/lightmap/bake/tile/shader_lightmap_accummulate_tile.js +85 -82
  142. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  143. package/src/shade/renderer/particles/graph_particles.js +5 -3
  144. package/src/shade/renderer/particles/prototypeParticleSystem.js +3 -4
  145. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  146. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +132 -117
  147. package/src/shade/renderer/particles/shaders/shader_particle_finalize.d.ts.map +1 -1
  148. package/src/shade/renderer/particles/shaders/shader_particle_finalize.js +79 -77
  149. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  150. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +111 -99
  151. package/src/shade/renderer/particles/sort/shader_particle_sort.d.ts.map +1 -1
  152. package/src/shade/renderer/particles/sort/shader_particle_sort.js +187 -158
  153. package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.d.ts.map +1 -1
  154. package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.js +7 -10
  155. package/src/shade/renderer/path_tracer/accumulating/shader_accumulating_path_tracer.d.ts.map +1 -1
  156. package/src/shade/renderer/path_tracer/accumulating/shader_accumulating_path_tracer.js +4 -1
  157. package/src/shade/renderer/postprocess/bloom/shader_bloom_mix.d.ts.map +1 -1
  158. package/src/shade/renderer/postprocess/bloom/shader_bloom_mix.js +3 -1
  159. package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.d.ts.map +1 -1
  160. package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.js +4 -2
  161. package/src/shade/renderer/postprocess/denoise/fragment_shader_denoise_ao.js +147 -147
  162. package/src/shade/renderer/postprocess/denoise/graph_atorus_denoise_luma.d.ts.map +1 -1
  163. package/src/shade/renderer/postprocess/denoise/graph_atorus_denoise_luma.js +121 -119
  164. package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.d.ts.map +1 -1
  165. package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.js +1 -2
  166. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.d.ts.map +1 -1
  167. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.js +4 -1
  168. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.d.ts.map +1 -1
  169. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.js +4 -1
  170. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.d.ts.map +1 -1
  171. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.js +4 -1
  172. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.d.ts.map +1 -1
  173. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.js +4 -1
  174. package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.d.ts.map +1 -1
  175. package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +2 -4
  176. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.d.ts.map +1 -1
  177. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.js +4 -1
  178. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.d.ts.map +1 -1
  179. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.js +4 -1
  180. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch.d.ts.map +1 -1
  181. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch.js +4 -1
  182. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.d.ts.map +1 -1
  183. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +269 -266
  184. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.d.ts.map +1 -1
  185. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +273 -270
  186. package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.d.ts.map +1 -1
  187. package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.js +4 -1
  188. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.d.ts.map +1 -1
  189. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.js +8 -11
  190. package/src/shade/renderer/postprocess/eye/shader_autoexposure_adapt.d.ts.map +1 -1
  191. package/src/shade/renderer/postprocess/eye/shader_autoexposure_adapt.js +5 -1
  192. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts.map +1 -1
  193. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.js +11 -8
  194. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.d.ts.map +1 -1
  195. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.js +7 -8
  196. package/src/shade/renderer/postprocess/motion_blur/shader_motion_blur_reconstruct.d.ts.map +1 -1
  197. package/src/shade/renderer/postprocess/motion_blur/shader_motion_blur_reconstruct.js +3 -1
  198. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  199. package/src/shade/renderer/postprocess/nss/shader_nss_concat.d.ts.map +1 -1
  200. package/src/shade/renderer/postprocess/nss/shader_nss_concat.js +70 -68
  201. package/src/shade/renderer/postprocess/ssr/depth_max_shader.d.ts.map +1 -1
  202. package/src/shade/renderer/postprocess/ssr/depth_max_shader.js +70 -68
  203. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_IBL.d.ts.map +1 -1
  204. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_IBL.js +3 -1
  205. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_LPV.d.ts.map +1 -1
  206. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_LPV.js +4 -1
  207. package/src/shade/renderer/postprocess/ssr/shader_downsample_encoded_normal.d.ts.map +1 -1
  208. package/src/shade/renderer/postprocess/ssr/shader_downsample_encoded_normal.js +103 -101
  209. package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.d.ts.map +1 -1
  210. package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.js +4 -2
  211. package/src/shade/renderer/postprocess/ssr/ssr_trace_shader.d.ts.map +1 -1
  212. package/src/shade/renderer/postprocess/ssr/ssr_trace_shader.js +3 -1
  213. package/src/shade/renderer/postprocess/taa/shader_taa.d.ts.map +1 -1
  214. package/src/shade/renderer/postprocess/taa/shader_taa.js +4 -1
  215. package/src/shade/renderer/postprocess/upscale-dither/fragment_shader_upscale_dither.js +5 -5
  216. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  217. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +339 -341
  218. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.d.ts.map +1 -1
  219. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.js +114 -109
  220. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.d.ts.map +1 -1
  221. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.js +118 -115
  222. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.d.ts.map +1 -1
  223. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.js +69 -65
  224. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.js +722 -722
  225. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
  226. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +1 -9
  227. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
  228. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +1 -6
  229. package/src/shade/renderer/restir/di/graph_restir_di.d.ts.map +1 -1
  230. package/src/shade/renderer/restir/di/graph_restir_di.js +2 -3
  231. package/src/shade/renderer/restir/di/graph_restir_di_denoise.d.ts.map +1 -1
  232. package/src/shade/renderer/restir/di/graph_restir_di_denoise.js +1 -2
  233. package/src/shade/renderer/restir/di/shader_restir_di_denoise.d.ts.map +1 -1
  234. package/src/shade/renderer/restir/di/shader_restir_di_denoise.js +139 -136
  235. package/src/shade/renderer/restir/di/shader_restir_di_resample.d.ts.map +1 -1
  236. package/src/shade/renderer/restir/di/shader_restir_di_resample.js +131 -129
  237. package/src/shade/renderer/restir/di/shader_restir_di_resolve.d.ts.map +1 -1
  238. package/src/shade/renderer/restir/di/shader_restir_di_resolve.js +101 -99
  239. package/src/shade/renderer/scene/GPUInstancesAccelerationStructure.d.ts.map +1 -1
  240. package/src/shade/renderer/scene/GPUInstancesAccelerationStructure.js +379 -383
  241. package/src/shade/renderer/scene/shader_bvh_extract_instance_leaves.d.ts.map +1 -1
  242. package/src/shade/renderer/scene/shader_bvh_extract_instance_leaves.js +147 -144
  243. package/src/shade/renderer/shader/ComputeShader.d.ts +2 -2
  244. package/src/shade/renderer/shader/ComputeShader.d.ts.map +1 -1
  245. package/src/shade/renderer/shader/ComputeShader.js +14 -17
  246. package/src/shade/renderer/shader/pass/RenderPassDescriptor.d.ts +2 -2
  247. package/src/shade/renderer/shader/pass/RenderPassDescriptor.d.ts.map +1 -1
  248. package/src/shade/renderer/shader/pass/RenderPassDescriptor.js +189 -189
  249. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts.map +1 -1
  250. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.js +3 -1
  251. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  252. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +14 -18
  253. package/src/shade/renderer/shadow/map/shader/shader_shadowmap_remap_cube_to_octahedral.d.ts.map +1 -1
  254. package/src/shade/renderer/shadow/map/shader/shader_shadowmap_remap_cube_to_octahedral.js +5 -1
  255. package/src/shade/renderer/shadow/ray/shader_shadow_upscale.d.ts.map +1 -1
  256. package/src/shade/renderer/shadow/ray/shader_shadow_upscale.js +3 -1
  257. package/src/shade/renderer/shadow/ray/staged/gen/graph_gen_rays.js +73 -73
  258. package/src/shade/renderer/shadow/ray/staged/gen/shader_generate_rays.d.ts.map +1 -1
  259. package/src/shade/renderer/shadow/ray/staged/gen/shader_generate_rays.js +151 -148
  260. package/src/shade/renderer/shadow/ray/staged/trace/graph_trace_rays.d.ts.map +1 -1
  261. package/src/shade/renderer/shadow/ray/staged/trace/graph_trace_rays.js +4 -2
  262. package/src/shade/renderer/shadow/ray/staged/trace/shader_trace_rays.d.ts.map +1 -1
  263. package/src/shade/renderer/shadow/ray/staged/trace/shader_trace_rays.js +18 -4
  264. package/src/shade/renderer/shadow/sdf/cascade/CascadedSceneSDF.d.ts.map +1 -1
  265. package/src/shade/renderer/shadow/sdf/cascade/CascadedSceneSDF.js +7 -8
  266. package/src/shade/renderer/shadow/sdf/cascade/shader_build_scene_sdf_f32.d.ts.map +1 -1
  267. package/src/shade/renderer/shadow/sdf/cascade/shader_build_scene_sdf_f32.js +6 -2
  268. package/src/shade/renderer/shadow/sdf/cascade/shader_scene_sdf_debug_viz.d.ts.map +1 -1
  269. package/src/shade/renderer/shadow/sdf/cascade/shader_scene_sdf_debug_viz.js +129 -127
  270. package/src/shade/renderer/shadow/sdf/graph_draw_sdf_shadows.d.ts.map +1 -1
  271. package/src/shade/renderer/shadow/sdf/graph_draw_sdf_shadows.js +5 -3
  272. package/src/shade/renderer/texture/virtual/VirtualTextureManager.d.ts.map +1 -1
  273. package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +2 -5
  274. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.d.ts.map +1 -1
  275. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +233 -231
  276. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
  277. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +2 -4
  278. package/src/shade/wgsl/validate_wgsl_source.d.ts.map +1 -1
  279. package/src/shade/wgsl/validate_wgsl_source.js +8 -3
  280. package/src/shade/device/timing/profile/make_profiling_pass_encoder.d.ts +0 -21
  281. package/src/shade/device/timing/profile/make_profiling_pass_encoder.d.ts.map +0 -1
  282. package/src/shade/device/timing/profile/make_profiling_pass_encoder.js +0 -114
  283. package/src/shade/device/timing/profile/parse_workgroup_size.d.ts +0 -20
  284. package/src/shade/device/timing/profile/parse_workgroup_size.d.ts.map +0 -1
  285. package/src/shade/device/timing/profile/parse_workgroup_size.js +0 -43
@@ -1,371 +1,375 @@
1
- import { mat4x4f, u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
2
- import { WebGPUArray } from "../../shader/type/WebGPUArray.js";
3
- import {
4
- MESHLET_METADATA_FLAGS
5
- } from "../../geometry/meshlet/encoding/MESHLET_METADATA_FLAGS.js";
6
- import {
7
- MESHLET_METADATA_STRUCT
8
- } from "../../geometry/meshlet/encoding/MESHLET_METADATA_STRUCT.js";
9
- import { chunk_scene_database_access } from "../../scene/chunk_scene_database_access.js";
10
- import { chunk_mat4_inverse } from "../../shader/chunk/geometry/mat4/chunk_mat4_inverse.js";
11
- import {
12
- chunk_meshlet_compute_attribute_section_offset
13
- } from "../../shader/chunk/geometry/meshlet/chunk_meshlet_compute_attribute_section_offset.js";
14
- import {
15
- chunk_decode_vertex_normal
16
- } from "../../shader/chunk/geometry/normal/chunk_decode_vertex_normal.js";
17
- import {
18
- chunk_encode_vertex_normal
19
- } from "../../shader/chunk/geometry/normal/chunk_encode_vertex_normal.js";
20
- import {
21
- chunk_decode_vertex_tangent
22
- } from "../../shader/chunk/geometry/normal/tangent/chunk_decode_vertex_tangent.js";
23
- import {
24
- chunk_encode_vertex_tangent
25
- } from "../../shader/chunk/geometry/normal/tangent/chunk_encode_vertex_tangent.js";
26
- import {
27
- chunk_dual_quat_transform_direction
28
- } from "../../shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_direction.js";
29
- import {
30
- chunk_dual_quat_transform_point
31
- } from "../../shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js";
32
- import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
33
- import { ComputeShader } from "../../shader/ComputeShader.js";
34
- import { ShaderResourceSetDescriptor } from "../../shader/resource/ShaderResourceSetDescriptor.js";
35
- import { chunk_skin_blend_mesh_local } from "./chunk_skin_blend_mesh_local.js";
36
- import { SKINNING_BINDING_STRUCT } from "./SKINNING_BINDING_STRUCT.js";
37
-
38
- /**
39
- * Workgroup size — also the per-meshlet vertex stride. Each thread
40
- * handles vertex `local_id`, then loops by `WORKGROUP_SIZE` for
41
- * meshlets larger than the workgroup.
42
- */
43
- export const SKINNING_WORKGROUP_SIZE = 64;
44
-
45
- const resources = new ShaderResourceSetDescriptor();
46
- resources.createGroup()
47
- // First workgroup of the current sub-dispatch. WebGPU caps
48
- // `dispatchWorkgroups` along each dimension at the device's
49
- // `maxComputeWorkgroupsPerDimension` (65535 minimum per the
50
- // spec, often higher in practice), so the host paginates large
51
- // totals into multiple dispatches and increments this offset
52
- // between them. The shader adds it to `wg_id.x` before indexing
53
- // `dispatch_lookup`, so the per-pair mapping stays a flat array
54
- // regardless of how many sub-dispatches it gets split into.
55
- .addUniform('dispatch_offset', u32)
56
- .addStorageBuffer('meshlet_data', WebGPUArray.from(u32), true)
57
- .addStorageBuffer('meshlet_metadata', WebGPUArray.from(MESHLET_METADATA_STRUCT), false)
58
- .addStorageBuffer('skin_matrices', WebGPUArray.from(mat4x4f), false)
59
- .addStorageBuffer('prev_skin_matrices', WebGPUArray.from(mat4x4f), false)
60
- .addStorageBuffer('skinning_bindings', WebGPUArray.from(SKINNING_BINDING_STRUCT), false)
61
- .addStorageBuffer('dispatch_lookup', WebGPUArray.from(u32), false)
62
- // Per-meshlet base u32-word offset into the side-buffer that
63
- // holds previous-frame mesh-local positions. Sentinel
64
- // `0xFFFFFFFF` for non-clone meshlets — those skip the
65
- // prev-position write entirely, since the velocity post-pass
66
- // will fall back to per-object math for them.
67
- .addStorageBuffer('prev_position_offsets', WebGPUArray.from(u32), false)
68
- .addStorageBuffer('prev_positions', WebGPUArray.from(u32), true)
69
- // Scene-graph state read-only. We need the SkinnedMesh's
70
- // `transforms` row to source the live `mesh.transform_global`
71
- // for the post-DQ position cancellation (see commentary in main).
72
- .addStorageBuffer('scene_database', WebGPUArray.from(u32), false);
73
-
74
- // One workgroup per (binding, meshlet) pair. The host builds
75
- // `dispatch_lookup` as a flat (binding_index, meshlet_local) pair
76
- // table and dispatches one workgroup per pair, so workgroup_id.x
77
- // directly indexes the table.
78
- //
79
- // Skinning math is dual-quaternion: each of the 4 per-vertex joints
80
- // contributes a DQ (built from its skin matrix), the four DQs blend
81
- // by weight (with antipodal correction), and the result transforms
82
- // position via the full screw motion and normal/tangent via just the
83
- // rotation part. DQ blending avoids the candy-wrapper artifacts of
84
- // linear matrix blending and lets normals + tangents transform with
85
- // the same blended frame, no matrix inversion or gimbal-style
86
- // interpolation issues.
87
- //
88
- // Per-attribute compression handling: Position is always
89
- // uncompressed (one entry per vertex, vertex_count*3 words). For
90
- // Normal and Tangent, the meshlet flags say whether they were
91
- // collapsed to a single shared entry across all vertices. Per-vertex
92
- // skinning into a shared slot would race, so the shader skips the
93
- // write for any attribute where the flag is set — the cloned slot
94
- // keeps the source's value and lighting on those vertices uses an
95
- // undeformed direction. For typical character meshes
96
- // normals/tangents vary per vertex and so don't get the all-equal
97
- // collapse, so this skip rarely engages in practice.
98
- //
99
- // SkinningJoints + SkinningWeights are read-only here. They CAN be
100
- // compressed a meshlet where every vertex shares the same joint
101
- // palette (e.g. simple test meshes that bind every vertex to the
102
- // same two bones) collapses Joints to one shared u32-pair, and the
103
- // shader then has to broadcast that single entry across all vertex
104
- // reads instead of striding by 2 words per vertex. Same logic for
105
- // Weights (less common in practice weights usually vary per
106
- // vertex even on simple meshes but the flag handling is free).
107
- const body = CodeChunk.from(
108
- //language=WGSL
109
- `
110
- @compute @workgroup_size(${SKINNING_WORKGROUP_SIZE},1,1)
111
- fn main(
112
- @builtin(workgroup_id) wg_id: vec3<u32>,
113
- @builtin(local_invocation_index) local_id: u32,
114
- ) {
115
- // dispatch_offset lets the host paginate large dispatches —
116
- // WebGPU's per-dimension cap is 65535 workgroups, but a busy
117
- // scene's (binding, meshlet) pair total can exceed that.
118
- let pair_index = wg_id.x + dispatch_offset;
119
- let lookup_base = pair_index * 2u;
120
- let binding_index = dispatch_lookup[lookup_base];
121
- let meshlet_local = dispatch_lookup[lookup_base + 1u];
122
-
123
- let binding = skinning_bindings[binding_index];
124
-
125
- // The glTF skinning formula's leftmost factor is
126
- // inverse(meshNode.worldMatrix), which lands the per-vertex
127
- // output in mesh-local space ready for the rasterizer's
128
- // mesh.world re-application (see
129
- // chunk_mesh_resolve_vertex_position_world.js). It goes into
130
- // each *matrix*, not onto the blended position — see
131
- // chunk_skin_blend_mesh_local for why the difference is the
132
- // whole ball game. Both positions and directions therefore come
133
- // out of the blend already in mesh-local space, and the
134
- // rasterizer's own transforms take them to world.
135
- //
136
- // mesh_instance_index is the row of the SkinnedMesh in the
137
- // meshes table; mesh.node is its transforms-table row.
138
- // Workgroup-uniform (one binding per workgroup), so the load +
139
- // inverse amortize across every vertex this workgroup
140
- // processes.
141
- let mesh = scene_read_mesh(&scene_database, binding.mesh_instance_index);
142
- let mesh_node = scene_read_node(&scene_database, mesh.node);
143
- let inv_mesh_world = mat4_inverse(mesh_node.global);
144
-
145
- let source_meshlet = meshlet_metadata[binding.source_meshlet_metadata_offset + meshlet_local];
146
- let clone_meshlet = meshlet_metadata[binding.clone_meshlet_metadata_offset + meshlet_local];
147
-
148
- let vertex_count = source_meshlet.vertex_count;
149
-
150
- // Both source and clone share an attribute layout (the clone is a
151
- // memcpy of the source's bytes with addresses patched), so attribute
152
- // offsets relative to each meshlet's attribute section are identical.
153
- let source_attr_section = meshlet_compute_attribute_section_offset(source_meshlet);
154
- let clone_attr_section = meshlet_compute_attribute_section_offset(clone_meshlet);
155
-
156
- // Walk the per-attribute layout, capturing the offset of each
157
- // attribute we'll need. Order matches ENCODED_ORDERED_ATTRIBUTES:
158
- // Position, Normal, Tangent, Color, UV0, UV1, SkinningJoints,
159
- // SkinningWeights. Conditional skips for the compressible ones —
160
- // when collapsed, the slot is a single shared entry rather than
161
- // vertex_count entries.
162
- let position_offset = 0u;
163
- var attr_offset: u32 = vertex_count * 3u;
164
-
165
- let normal_offset = attr_offset;
166
- let normal_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_NORMAL}u) != 0u;
167
- attr_offset = attr_offset + select(vertex_count, 1u, normal_compressed);
168
-
169
- let tangent_offset = attr_offset;
170
- let tangent_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_TANGENT}u) != 0u;
171
- attr_offset = attr_offset + select(vertex_count, 1u, tangent_compressed);
172
-
173
- let color_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_COLOR}u) != 0u;
174
- attr_offset = attr_offset + select(vertex_count, 1u, color_compressed);
175
-
176
- let uv0_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_TEXTURE_COORDINATES_0}u) != 0u;
177
- attr_offset = attr_offset + select(vertex_count * 2u, 2u, uv0_compressed);
178
-
179
- let uv1_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_TEXTURE_COORDINATES_1}u) != 0u;
180
- attr_offset = attr_offset + select(vertex_count, 1u, uv1_compressed);
181
-
182
- let joints_offset = attr_offset;
183
- let joints_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_SKINNING_JOINTS}u) != 0u;
184
- attr_offset = attr_offset + select(vertex_count * 2u, 2u, joints_compressed);
185
-
186
- let weights_offset = attr_offset;
187
- let weights_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_SKINNING_WEIGHTS}u) != 0u;
188
-
189
- let skin_base = binding.skin_matrix_offset;
190
-
191
- // Resolve the clone meshlet's per-meshlet base offset in the
192
- // prev_positions side-buffer (or sentinel if this meshlet
193
- // somehow isn't a clone — shouldn't happen in practice since
194
- // the skinning shader only runs on (clone, skinned) pairs, but
195
- // the check costs one load and one branch). Lifted out of the
196
- // vertex loop because it's per-meshlet, not per-vertex.
197
- let clone_meshlet_abs = binding.clone_meshlet_metadata_offset + meshlet_local;
198
- let prev_pos_meshlet_base = prev_position_offsets[clone_meshlet_abs];
199
- let has_prev_positions = prev_pos_meshlet_base != 0xFFFFFFFFu;
200
-
201
- // Stride loop one thread per WORKGROUP_SIZE-stride'd vertex slot.
202
- // Most meshlets fit in a single iteration (vertex_count <=
203
- // WORKGROUP_SIZE); larger ones cycle the workgroup over them.
204
- var v: u32 = local_id;
205
- loop {
206
- if v >= vertex_count { break; }
207
-
208
- // Joint indices: vec4<u16> packed into 2 u32s. When the
209
- // attribute is compressed (every vertex of this meshlet
210
- // shares the same palette), there's one shared pair at
211
- // joints_offset rather than per-vertex strides; broadcast
212
- // that single entry to every thread.
213
- let joints_local_offset = select(v * 2u, 0u, joints_compressed);
214
- let joints_addr = source_attr_section + joints_offset + joints_local_offset;
215
- let raw_j0 = meshlet_data[joints_addr];
216
- let raw_j1 = meshlet_data[joints_addr + 1u];
217
- let j0 = raw_j0 & 0xFFFFu;
218
- let j1 = (raw_j0 >> 16u) & 0xFFFFu;
219
- let j2 = raw_j1 & 0xFFFFu;
220
- let j3 = (raw_j1 >> 16u) & 0xFFFFu;
221
-
222
- // Joint weights: vec4<unorm8> packed into 1 u32. Half the
223
- // bandwidth of the previous f16×4 layout, sufficient
224
- // precision for typical character skinning (step size
225
- // ~0.4%, well under any visible artefact threshold).
226
- // unpack4x8unorm returns vec4<f32> in [0, 1] with
227
- // components matching byte 0..3 of the packed u32.
228
- let weights_local_offset = select(v, 0u, weights_compressed);
229
- let weights_addr = source_attr_section + weights_offset + weights_local_offset;
230
- let weights = unpack4x8unorm(meshlet_data[weights_addr]);
231
-
232
- // Build per-joint dual quaternions from the joint matrices, then
233
- // blend with antipodal correction. The result represents the
234
- // weighted-average screw motion the four matrices encode.
235
- let dq_blend = skin_blend_mesh_local(
236
- inv_mesh_world,
237
- skin_matrices[skin_base + j0],
238
- skin_matrices[skin_base + j1],
239
- skin_matrices[skin_base + j2],
240
- skin_matrices[skin_base + j3],
241
- weights,
242
- );
243
-
244
- // Position: full screw motion (rotation + translation).
245
- let pos_addr = source_attr_section + position_offset + v * 3u;
246
- let p = vec3f(
247
- bitcast<f32>(meshlet_data[pos_addr]),
248
- bitcast<f32>(meshlet_data[pos_addr + 1u]),
249
- bitcast<f32>(meshlet_data[pos_addr + 2u]),
250
- );
251
- // Position: full screw motion, already mesh-local — the
252
- // rasterizer reapplies mesh.world to every mesh's vertices,
253
- // skinned or not.
254
- let p_skinned = dual_quat_transform_point(dq_blend, p);
255
-
256
- let clone_pos_addr = clone_attr_section + position_offset + v * 3u;
257
- meshlet_data[clone_pos_addr] = bitcast<u32>(p_skinned.x);
258
- meshlet_data[clone_pos_addr + 1u] = bitcast<u32>(p_skinned.y);
259
- meshlet_data[clone_pos_addr + 2u] = bitcast<u32>(p_skinned.z);
260
-
261
- // Previous-frame mesh-local position: same dual-quat math,
262
- // but driven by prev_skin_matrices instead of skin_matrices.
263
- // Written to the side-buffer at the per-meshlet base
264
- // offset + v * 3 u32 words. The velocity post-pass reads
265
- // these to interpolate per-vertex skin deformation across
266
- // the rasterized triangle (vs. just the rigid mesh.global
267
- // mesh.prev_global delta that Stage 1's path captures).
268
- //
269
- // Skipping the write when there's no slot for this meshlet
270
- // (non-clone shouldn't be hit by this shader's dispatch
271
- // set, but the guard is cheap). The dual-quat build is
272
- // also skipped to save the per-joint work.
273
- if has_prev_positions {
274
- // Mesh-local for the same reason the current-frame
275
- // position is the velocity pass reads these through
276
- // the same rasterizer transform, so the space they live
277
- // in has to match.
278
- let prev_dq_blend = skin_blend_mesh_local(
279
- inv_mesh_world,
280
- prev_skin_matrices[skin_base + j0],
281
- prev_skin_matrices[skin_base + j1],
282
- prev_skin_matrices[skin_base + j2],
283
- prev_skin_matrices[skin_base + j3],
284
- weights,
285
- );
286
-
287
- let p_prev_skinned = dual_quat_transform_point(prev_dq_blend, p);
288
-
289
- let prev_addr = prev_pos_meshlet_base + v * 3u;
290
- prev_positions[prev_addr] = bitcast<u32>(p_prev_skinned.x);
291
- prev_positions[prev_addr + 1u] = bitcast<u32>(p_prev_skinned.y);
292
- prev_positions[prev_addr + 2u] = bitcast<u32>(p_prev_skinned.z);
293
- }
294
-
295
- // Normal: rotation only. Skip when the source compressed it
296
- // down to a single shared slot — per-vertex writes to that
297
- // slot would race, and the rotated direction would be
298
- // mostly-meaningless anyway since every vertex would compute
299
- // a different one.
300
- //
301
- // Mesh-local like the position, and left there: the
302
- // rasterizer applies the inverse-transpose of mesh.world's
303
- // 3x3 to it, which is the glTF normal transform and takes a
304
- // mesh-local direction to world (see fragment_gbuffer.js).
305
- if !normal_compressed {
306
- let n_raw_addr = source_attr_section + normal_offset + v;
307
- let n = decode_vertex_normal(meshlet_data[n_raw_addr]);
308
- let n_skinned = normalize(dual_quat_transform_direction(dq_blend, n));
309
-
310
- let clone_n_addr = clone_attr_section + normal_offset + v;
311
- meshlet_data[clone_n_addr] = encode_vertex_normal(n_skinned);
312
- }
313
-
314
- // Tangent: rotation only on the xyz; w (handedness) is a
315
- // topological property, copied through unchanged.
316
- if !tangent_compressed {
317
- let t_raw_addr = source_attr_section + tangent_offset + v;
318
- let t = decode_vertex_tangent(meshlet_data[t_raw_addr]);
319
- let t_skinned_dir = normalize(dual_quat_transform_direction(dq_blend, t.xyz));
320
-
321
- let clone_t_addr = clone_attr_section + tangent_offset + v;
322
- meshlet_data[clone_t_addr] = encode_vertex_tangent(vec4f(t_skinned_dir, t.w));
323
- }
324
-
325
- v = v + ${SKINNING_WORKGROUP_SIZE}u;
326
- }
327
- }
328
- `, [
329
- MESHLET_METADATA_STRUCT.declaration_chunk,
330
- SKINNING_BINDING_STRUCT.declaration_chunk,
331
- chunk_meshlet_compute_attribute_section_offset,
332
- chunk_skin_blend_mesh_local,
333
- chunk_dual_quat_transform_point,
334
- chunk_dual_quat_transform_direction,
335
- chunk_decode_vertex_normal,
336
- chunk_encode_vertex_normal,
337
- chunk_decode_vertex_tangent,
338
- chunk_encode_vertex_tangent,
339
- chunk_scene_database_access,
340
- chunk_mat4_inverse,
341
- ]
342
- );
343
-
344
- /**
345
- * Compute shader that deforms cloned meshlet vertex regions in place
346
- * using the per-frame skin matrices the prep pass produced. One
347
- * workgroup per (binding, meshlet) pair, dispatched against a flat
348
- * `dispatch_lookup` table the host builds when bindings change.
349
- *
350
- * Skins position (full transform) plus normal and tangent (rotation
351
- * only) per vertex via dual-quaternion blending the blended DQ
352
- * captures the four per-vertex joints' screw motions weighted by
353
- * the per-vertex `SkinningWeights`, and applies cleanly to both
354
- * positions and unit-direction attributes without the matrix-blend
355
- * candy-wrapper artifacts that would otherwise show up in the
356
- * normal-transformed lighting.
357
- *
358
- * Source bytes stay untouched other instances pointed at the
359
- * source render normally. Per-meshlet bounds aren't refreshed by
360
- * this pass; meshlets that animate outside the source bounds may
361
- * cull incorrectly until a future bounds-refresh phase lands.
362
- *
363
- * Dispatch via `shader.dispatch({ group_count_x: total_meshlet_count, ... })`
364
- * where `total_meshlet_count` is the sum of `meshlet_count` across
365
- * every registered binding. Skip when `total_meshlet_count === 0`.
366
- */
367
- export const shader_meshlet_apply_skinning = ComputeShader.from({
368
- label: "Skinning/MeshletApplySkinning",
369
- resources,
370
- body,
371
- });
1
+ import { mat4x4f, u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
2
+ import { WebGPUArray } from "../../shader/type/WebGPUArray.js";
3
+ import {
4
+ MESHLET_METADATA_FLAGS
5
+ } from "../../geometry/meshlet/encoding/MESHLET_METADATA_FLAGS.js";
6
+ import {
7
+ MESHLET_METADATA_STRUCT
8
+ } from "../../geometry/meshlet/encoding/MESHLET_METADATA_STRUCT.js";
9
+ import { chunk_scene_database_access } from "../../scene/chunk_scene_database_access.js";
10
+ import { chunk_mat4_inverse } from "../../shader/chunk/geometry/mat4/chunk_mat4_inverse.js";
11
+ import {
12
+ chunk_meshlet_compute_attribute_section_offset
13
+ } from "../../shader/chunk/geometry/meshlet/chunk_meshlet_compute_attribute_section_offset.js";
14
+ import {
15
+ chunk_decode_vertex_normal
16
+ } from "../../shader/chunk/geometry/normal/chunk_decode_vertex_normal.js";
17
+ import {
18
+ chunk_encode_vertex_normal
19
+ } from "../../shader/chunk/geometry/normal/chunk_encode_vertex_normal.js";
20
+ import {
21
+ chunk_decode_vertex_tangent
22
+ } from "../../shader/chunk/geometry/normal/tangent/chunk_decode_vertex_tangent.js";
23
+ import {
24
+ chunk_encode_vertex_tangent
25
+ } from "../../shader/chunk/geometry/normal/tangent/chunk_encode_vertex_tangent.js";
26
+ import {
27
+ chunk_dual_quat_transform_direction
28
+ } from "../../shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_direction.js";
29
+ import {
30
+ chunk_dual_quat_transform_point
31
+ } from "../../shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js";
32
+ import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
33
+ import { ComputeShader } from "../../shader/ComputeShader.js";
34
+ import { ShaderResourceSetDescriptor } from "../../shader/resource/ShaderResourceSetDescriptor.js";
35
+ import { chunk_skin_blend_mesh_local } from "./chunk_skin_blend_mesh_local.js";
36
+ import { SKINNING_BINDING_STRUCT } from "./SKINNING_BINDING_STRUCT.js";
37
+
38
+ /**
39
+ * Workgroup size — also the per-meshlet vertex stride. Each thread
40
+ * handles vertex `local_id`, then loops by `WORKGROUP_SIZE` for
41
+ * meshlets larger than the workgroup.
42
+ */
43
+ export const SKINNING_WORKGROUP_SIZE = 64;
44
+
45
+ const resources = new ShaderResourceSetDescriptor();
46
+
47
+ // per-dispatch, four bytes, read-only: immediate data rather than a uniform buffer. Skinning is
48
+ // paginated into sub-dispatches when the workload exceeds maxComputeWorkgroupsPerDimension, so the
49
+ // buffer was allocated once per chunk to carry one integer.
50
+ resources.setImmediate("dispatch_offset", u32);
51
+ resources.createGroup()
52
+ // First workgroup of the current sub-dispatch. WebGPU caps
53
+ // `dispatchWorkgroups` along each dimension at the device's
54
+ // `maxComputeWorkgroupsPerDimension` (65535 minimum per the
55
+ // spec, often higher in practice), so the host paginates large
56
+ // totals into multiple dispatches and increments this offset
57
+ // between them. The shader adds it to `wg_id.x` before indexing
58
+ // `dispatch_lookup`, so the per-pair mapping stays a flat array
59
+ // regardless of how many sub-dispatches it gets split into.
60
+ .addStorageBuffer('meshlet_data', WebGPUArray.from(u32), true)
61
+ .addStorageBuffer('meshlet_metadata', WebGPUArray.from(MESHLET_METADATA_STRUCT), false)
62
+ .addStorageBuffer('skin_matrices', WebGPUArray.from(mat4x4f), false)
63
+ .addStorageBuffer('prev_skin_matrices', WebGPUArray.from(mat4x4f), false)
64
+ .addStorageBuffer('skinning_bindings', WebGPUArray.from(SKINNING_BINDING_STRUCT), false)
65
+ .addStorageBuffer('dispatch_lookup', WebGPUArray.from(u32), false)
66
+ // Per-meshlet base u32-word offset into the side-buffer that
67
+ // holds previous-frame mesh-local positions. Sentinel
68
+ // `0xFFFFFFFF` for non-clone meshlets — those skip the
69
+ // prev-position write entirely, since the velocity post-pass
70
+ // will fall back to per-object math for them.
71
+ .addStorageBuffer('prev_position_offsets', WebGPUArray.from(u32), false)
72
+ .addStorageBuffer('prev_positions', WebGPUArray.from(u32), true)
73
+ // Scene-graph state — read-only. We need the SkinnedMesh's
74
+ // `transforms` row to source the live `mesh.transform_global`
75
+ // for the post-DQ position cancellation (see commentary in main).
76
+ .addStorageBuffer('scene_database', WebGPUArray.from(u32), false);
77
+
78
+ // One workgroup per (binding, meshlet) pair. The host builds
79
+ // `dispatch_lookup` as a flat (binding_index, meshlet_local) pair
80
+ // table and dispatches one workgroup per pair, so workgroup_id.x
81
+ // directly indexes the table.
82
+ //
83
+ // Skinning math is dual-quaternion: each of the 4 per-vertex joints
84
+ // contributes a DQ (built from its skin matrix), the four DQs blend
85
+ // by weight (with antipodal correction), and the result transforms
86
+ // position via the full screw motion and normal/tangent via just the
87
+ // rotation part. DQ blending avoids the candy-wrapper artifacts of
88
+ // linear matrix blending and lets normals + tangents transform with
89
+ // the same blended frame, no matrix inversion or gimbal-style
90
+ // interpolation issues.
91
+ //
92
+ // Per-attribute compression handling: Position is always
93
+ // uncompressed (one entry per vertex, vertex_count*3 words). For
94
+ // Normal and Tangent, the meshlet flags say whether they were
95
+ // collapsed to a single shared entry across all vertices. Per-vertex
96
+ // skinning into a shared slot would race, so the shader skips the
97
+ // write for any attribute where the flag is set — the cloned slot
98
+ // keeps the source's value and lighting on those vertices uses an
99
+ // undeformed direction. For typical character meshes
100
+ // normals/tangents vary per vertex and so don't get the all-equal
101
+ // collapse, so this skip rarely engages in practice.
102
+ //
103
+ // SkinningJoints + SkinningWeights are read-only here. They CAN be
104
+ // compressed a meshlet where every vertex shares the same joint
105
+ // palette (e.g. simple test meshes that bind every vertex to the
106
+ // same two bones) collapses Joints to one shared u32-pair, and the
107
+ // shader then has to broadcast that single entry across all vertex
108
+ // reads instead of striding by 2 words per vertex. Same logic for
109
+ // Weights (less common in practice — weights usually vary per
110
+ // vertex even on simple meshes — but the flag handling is free).
111
+ const body = CodeChunk.from(
112
+ //language=WGSL
113
+ `
114
+ @compute @workgroup_size(${SKINNING_WORKGROUP_SIZE},1,1)
115
+ fn main(
116
+ @builtin(workgroup_id) wg_id: vec3<u32>,
117
+ @builtin(local_invocation_index) local_id: u32,
118
+ ) {
119
+ // dispatch_offset lets the host paginate large dispatches —
120
+ // WebGPU's per-dimension cap is 65535 workgroups, but a busy
121
+ // scene's (binding, meshlet) pair total can exceed that.
122
+ let pair_index = wg_id.x + dispatch_offset;
123
+ let lookup_base = pair_index * 2u;
124
+ let binding_index = dispatch_lookup[lookup_base];
125
+ let meshlet_local = dispatch_lookup[lookup_base + 1u];
126
+
127
+ let binding = skinning_bindings[binding_index];
128
+
129
+ // The glTF skinning formula's leftmost factor is
130
+ // inverse(meshNode.worldMatrix), which lands the per-vertex
131
+ // output in mesh-local space ready for the rasterizer's
132
+ // mesh.world re-application (see
133
+ // chunk_mesh_resolve_vertex_position_world.js). It goes into
134
+ // each *matrix*, not onto the blended position — see
135
+ // chunk_skin_blend_mesh_local for why the difference is the
136
+ // whole ball game. Both positions and directions therefore come
137
+ // out of the blend already in mesh-local space, and the
138
+ // rasterizer's own transforms take them to world.
139
+ //
140
+ // mesh_instance_index is the row of the SkinnedMesh in the
141
+ // meshes table; mesh.node is its transforms-table row.
142
+ // Workgroup-uniform (one binding per workgroup), so the load +
143
+ // inverse amortize across every vertex this workgroup
144
+ // processes.
145
+ let mesh = scene_read_mesh(&scene_database, binding.mesh_instance_index);
146
+ let mesh_node = scene_read_node(&scene_database, mesh.node);
147
+ let inv_mesh_world = mat4_inverse(mesh_node.global);
148
+
149
+ let source_meshlet = meshlet_metadata[binding.source_meshlet_metadata_offset + meshlet_local];
150
+ let clone_meshlet = meshlet_metadata[binding.clone_meshlet_metadata_offset + meshlet_local];
151
+
152
+ let vertex_count = source_meshlet.vertex_count;
153
+
154
+ // Both source and clone share an attribute layout (the clone is a
155
+ // memcpy of the source's bytes with addresses patched), so attribute
156
+ // offsets relative to each meshlet's attribute section are identical.
157
+ let source_attr_section = meshlet_compute_attribute_section_offset(source_meshlet);
158
+ let clone_attr_section = meshlet_compute_attribute_section_offset(clone_meshlet);
159
+
160
+ // Walk the per-attribute layout, capturing the offset of each
161
+ // attribute we'll need. Order matches ENCODED_ORDERED_ATTRIBUTES:
162
+ // Position, Normal, Tangent, Color, UV0, UV1, SkinningJoints,
163
+ // SkinningWeights. Conditional skips for the compressible ones —
164
+ // when collapsed, the slot is a single shared entry rather than
165
+ // vertex_count entries.
166
+ let position_offset = 0u;
167
+ var attr_offset: u32 = vertex_count * 3u;
168
+
169
+ let normal_offset = attr_offset;
170
+ let normal_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_NORMAL}u) != 0u;
171
+ attr_offset = attr_offset + select(vertex_count, 1u, normal_compressed);
172
+
173
+ let tangent_offset = attr_offset;
174
+ let tangent_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_TANGENT}u) != 0u;
175
+ attr_offset = attr_offset + select(vertex_count, 1u, tangent_compressed);
176
+
177
+ let color_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_COLOR}u) != 0u;
178
+ attr_offset = attr_offset + select(vertex_count, 1u, color_compressed);
179
+
180
+ let uv0_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_TEXTURE_COORDINATES_0}u) != 0u;
181
+ attr_offset = attr_offset + select(vertex_count * 2u, 2u, uv0_compressed);
182
+
183
+ let uv1_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_TEXTURE_COORDINATES_1}u) != 0u;
184
+ attr_offset = attr_offset + select(vertex_count, 1u, uv1_compressed);
185
+
186
+ let joints_offset = attr_offset;
187
+ let joints_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_SKINNING_JOINTS}u) != 0u;
188
+ attr_offset = attr_offset + select(vertex_count * 2u, 2u, joints_compressed);
189
+
190
+ let weights_offset = attr_offset;
191
+ let weights_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_SKINNING_WEIGHTS}u) != 0u;
192
+
193
+ let skin_base = binding.skin_matrix_offset;
194
+
195
+ // Resolve the clone meshlet's per-meshlet base offset in the
196
+ // prev_positions side-buffer (or sentinel if this meshlet
197
+ // somehow isn't a clone — shouldn't happen in practice since
198
+ // the skinning shader only runs on (clone, skinned) pairs, but
199
+ // the check costs one load and one branch). Lifted out of the
200
+ // vertex loop because it's per-meshlet, not per-vertex.
201
+ let clone_meshlet_abs = binding.clone_meshlet_metadata_offset + meshlet_local;
202
+ let prev_pos_meshlet_base = prev_position_offsets[clone_meshlet_abs];
203
+ let has_prev_positions = prev_pos_meshlet_base != 0xFFFFFFFFu;
204
+
205
+ // Stride loop — one thread per WORKGROUP_SIZE-stride'd vertex slot.
206
+ // Most meshlets fit in a single iteration (vertex_count <=
207
+ // WORKGROUP_SIZE); larger ones cycle the workgroup over them.
208
+ var v: u32 = local_id;
209
+ loop {
210
+ if v >= vertex_count { break; }
211
+
212
+ // Joint indices: vec4<u16> packed into 2 u32s. When the
213
+ // attribute is compressed (every vertex of this meshlet
214
+ // shares the same palette), there's one shared pair at
215
+ // joints_offset rather than per-vertex strides; broadcast
216
+ // that single entry to every thread.
217
+ let joints_local_offset = select(v * 2u, 0u, joints_compressed);
218
+ let joints_addr = source_attr_section + joints_offset + joints_local_offset;
219
+ let raw_j0 = meshlet_data[joints_addr];
220
+ let raw_j1 = meshlet_data[joints_addr + 1u];
221
+ let j0 = raw_j0 & 0xFFFFu;
222
+ let j1 = (raw_j0 >> 16u) & 0xFFFFu;
223
+ let j2 = raw_j1 & 0xFFFFu;
224
+ let j3 = (raw_j1 >> 16u) & 0xFFFFu;
225
+
226
+ // Joint weights: vec4<unorm8> packed into 1 u32. Half the
227
+ // bandwidth of the previous f16×4 layout, sufficient
228
+ // precision for typical character skinning (step size
229
+ // ~0.4%, well under any visible artefact threshold).
230
+ // unpack4x8unorm returns vec4<f32> in [0, 1] with
231
+ // components matching byte 0..3 of the packed u32.
232
+ let weights_local_offset = select(v, 0u, weights_compressed);
233
+ let weights_addr = source_attr_section + weights_offset + weights_local_offset;
234
+ let weights = unpack4x8unorm(meshlet_data[weights_addr]);
235
+
236
+ // Build per-joint dual quaternions from the joint matrices, then
237
+ // blend with antipodal correction. The result represents the
238
+ // weighted-average screw motion the four matrices encode.
239
+ let dq_blend = skin_blend_mesh_local(
240
+ inv_mesh_world,
241
+ skin_matrices[skin_base + j0],
242
+ skin_matrices[skin_base + j1],
243
+ skin_matrices[skin_base + j2],
244
+ skin_matrices[skin_base + j3],
245
+ weights,
246
+ );
247
+
248
+ // Position: full screw motion (rotation + translation).
249
+ let pos_addr = source_attr_section + position_offset + v * 3u;
250
+ let p = vec3f(
251
+ bitcast<f32>(meshlet_data[pos_addr]),
252
+ bitcast<f32>(meshlet_data[pos_addr + 1u]),
253
+ bitcast<f32>(meshlet_data[pos_addr + 2u]),
254
+ );
255
+ // Position: full screw motion, already mesh-local — the
256
+ // rasterizer reapplies mesh.world to every mesh's vertices,
257
+ // skinned or not.
258
+ let p_skinned = dual_quat_transform_point(dq_blend, p);
259
+
260
+ let clone_pos_addr = clone_attr_section + position_offset + v * 3u;
261
+ meshlet_data[clone_pos_addr] = bitcast<u32>(p_skinned.x);
262
+ meshlet_data[clone_pos_addr + 1u] = bitcast<u32>(p_skinned.y);
263
+ meshlet_data[clone_pos_addr + 2u] = bitcast<u32>(p_skinned.z);
264
+
265
+ // Previous-frame mesh-local position: same dual-quat math,
266
+ // but driven by prev_skin_matrices instead of skin_matrices.
267
+ // Written to the side-buffer at the per-meshlet base
268
+ // offset + v * 3 u32 words. The velocity post-pass reads
269
+ // these to interpolate per-vertex skin deformation across
270
+ // the rasterized triangle (vs. just the rigid mesh.global
271
+ // mesh.prev_global delta that Stage 1's path captures).
272
+ //
273
+ // Skipping the write when there's no slot for this meshlet
274
+ // (non-clone shouldn't be hit by this shader's dispatch
275
+ // set, but the guard is cheap). The dual-quat build is
276
+ // also skipped to save the per-joint work.
277
+ if has_prev_positions {
278
+ // Mesh-local for the same reason the current-frame
279
+ // position is — the velocity pass reads these through
280
+ // the same rasterizer transform, so the space they live
281
+ // in has to match.
282
+ let prev_dq_blend = skin_blend_mesh_local(
283
+ inv_mesh_world,
284
+ prev_skin_matrices[skin_base + j0],
285
+ prev_skin_matrices[skin_base + j1],
286
+ prev_skin_matrices[skin_base + j2],
287
+ prev_skin_matrices[skin_base + j3],
288
+ weights,
289
+ );
290
+
291
+ let p_prev_skinned = dual_quat_transform_point(prev_dq_blend, p);
292
+
293
+ let prev_addr = prev_pos_meshlet_base + v * 3u;
294
+ prev_positions[prev_addr] = bitcast<u32>(p_prev_skinned.x);
295
+ prev_positions[prev_addr + 1u] = bitcast<u32>(p_prev_skinned.y);
296
+ prev_positions[prev_addr + 2u] = bitcast<u32>(p_prev_skinned.z);
297
+ }
298
+
299
+ // Normal: rotation only. Skip when the source compressed it
300
+ // down to a single shared slot — per-vertex writes to that
301
+ // slot would race, and the rotated direction would be
302
+ // mostly-meaningless anyway since every vertex would compute
303
+ // a different one.
304
+ //
305
+ // Mesh-local like the position, and left there: the
306
+ // rasterizer applies the inverse-transpose of mesh.world's
307
+ // 3x3 to it, which is the glTF normal transform and takes a
308
+ // mesh-local direction to world (see fragment_gbuffer.js).
309
+ if !normal_compressed {
310
+ let n_raw_addr = source_attr_section + normal_offset + v;
311
+ let n = decode_vertex_normal(meshlet_data[n_raw_addr]);
312
+ let n_skinned = normalize(dual_quat_transform_direction(dq_blend, n));
313
+
314
+ let clone_n_addr = clone_attr_section + normal_offset + v;
315
+ meshlet_data[clone_n_addr] = encode_vertex_normal(n_skinned);
316
+ }
317
+
318
+ // Tangent: rotation only on the xyz; w (handedness) is a
319
+ // topological property, copied through unchanged.
320
+ if !tangent_compressed {
321
+ let t_raw_addr = source_attr_section + tangent_offset + v;
322
+ let t = decode_vertex_tangent(meshlet_data[t_raw_addr]);
323
+ let t_skinned_dir = normalize(dual_quat_transform_direction(dq_blend, t.xyz));
324
+
325
+ let clone_t_addr = clone_attr_section + tangent_offset + v;
326
+ meshlet_data[clone_t_addr] = encode_vertex_tangent(vec4f(t_skinned_dir, t.w));
327
+ }
328
+
329
+ v = v + ${SKINNING_WORKGROUP_SIZE}u;
330
+ }
331
+ }
332
+ `, [
333
+ MESHLET_METADATA_STRUCT.declaration_chunk,
334
+ SKINNING_BINDING_STRUCT.declaration_chunk,
335
+ chunk_meshlet_compute_attribute_section_offset,
336
+ chunk_skin_blend_mesh_local,
337
+ chunk_dual_quat_transform_point,
338
+ chunk_dual_quat_transform_direction,
339
+ chunk_decode_vertex_normal,
340
+ chunk_encode_vertex_normal,
341
+ chunk_decode_vertex_tangent,
342
+ chunk_encode_vertex_tangent,
343
+ chunk_scene_database_access,
344
+ chunk_mat4_inverse,
345
+ ]
346
+ );
347
+
348
+ /**
349
+ * Compute shader that deforms cloned meshlet vertex regions in place
350
+ * using the per-frame skin matrices the prep pass produced. One
351
+ * workgroup per (binding, meshlet) pair, dispatched against a flat
352
+ * `dispatch_lookup` table the host builds when bindings change.
353
+ *
354
+ * Skins position (full transform) plus normal and tangent (rotation
355
+ * only) per vertex via dual-quaternion blending the blended DQ
356
+ * captures the four per-vertex joints' screw motions weighted by
357
+ * the per-vertex `SkinningWeights`, and applies cleanly to both
358
+ * positions and unit-direction attributes without the matrix-blend
359
+ * candy-wrapper artifacts that would otherwise show up in the
360
+ * normal-transformed lighting.
361
+ *
362
+ * Source bytes stay untouched — other instances pointed at the
363
+ * source render normally. Per-meshlet bounds aren't refreshed by
364
+ * this pass; meshlets that animate outside the source bounds may
365
+ * cull incorrectly until a future bounds-refresh phase lands.
366
+ *
367
+ * Dispatch via `shader.dispatch({ group_count_x: total_meshlet_count, ... })`
368
+ * where `total_meshlet_count` is the sum of `meshlet_count` across
369
+ * every registered binding. Skip when `total_meshlet_count === 0`.
370
+ */
371
+ export const shader_meshlet_apply_skinning = ComputeShader.from({
372
+ label: "Skinning/MeshletApplySkinning",
373
+ resources,
374
+ body,
375
+ });