@woosh/meep-engine 3.11.3 → 3.12.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 (401) hide show
  1. package/README.md +14 -18
  2. package/editor/SelectionVisualizer.js +127 -127
  3. package/editor/entity_world_bounds.js +27 -27
  4. package/editor/particles/rebuildParticleEmitter.js +31 -31
  5. package/editor/process/SymbolicDisplayProcess.js +1 -1
  6. package/editor/process/symbolic/CameraSymbolicDisplay.js +146 -146
  7. package/editor/process/symbolic/LightSymbolicDisplay.js +172 -172
  8. package/editor/prototypeEditorShell.js +156 -156
  9. package/editor/selection/editor_pick.js +130 -130
  10. package/editor/selection/screen_rect_frustum.js +119 -119
  11. package/editor/tools/v2/TransformControls.js +971 -971
  12. package/package.json +1 -1
  13. package/src/engine/Engine.d.ts +3 -3
  14. package/src/engine/Engine.d.ts.map +1 -1
  15. package/src/engine/Engine.js +726 -726
  16. package/src/engine/EngineHarness.d.ts +1 -1
  17. package/src/engine/EngineHarness.js +590 -590
  18. package/src/engine/ecs/HIERARCHY_REVIEW.md +717 -717
  19. package/src/engine/ecs/terrain/ecs/BuildLightTexture.d.ts +1 -1
  20. package/src/engine/ecs/terrain/ecs/BuildLightTexture.js +27 -27
  21. package/src/engine/ecs/terrain/ecs/Terrain.d.ts +1 -5
  22. package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
  23. package/src/engine/ecs/terrain/ecs/Terrain.js +790 -790
  24. package/src/engine/ecs/terrain/util/loadVisibleTerrainTiles.js +129 -129
  25. package/src/engine/graphics/ecs/camera/camera_find_active.d.ts +1 -1
  26. package/src/engine/graphics/ecs/camera/camera_find_active.js +28 -28
  27. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.d.ts +1 -1
  28. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +428 -428
  29. package/src/engine/graphics/ecs/water/Water.d.ts +1 -1
  30. package/src/engine/graphics/ecs/water/Water.js +104 -104
  31. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +1 -1
  32. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1 -1
  33. package/src/engine/graphics3/AnimationGraphSystem.d.ts +65 -0
  34. package/src/engine/graphics3/AnimationGraphSystem.d.ts.map +1 -0
  35. package/src/engine/graphics3/AnimationGraphSystem.js +329 -0
  36. package/src/engine/graphics3/AnimationGraphSystem3.d.ts +6 -61
  37. package/src/engine/graphics3/AnimationGraphSystem3.d.ts.map +1 -1
  38. package/src/engine/graphics3/AnimationGraphSystem3.js +6 -325
  39. package/src/engine/graphics3/AnimationSystem.d.ts +76 -0
  40. package/src/engine/graphics3/AnimationSystem.d.ts.map +1 -0
  41. package/src/engine/graphics3/AnimationSystem.js +352 -0
  42. package/src/engine/graphics3/AnimationSystem3.d.ts +6 -72
  43. package/src/engine/graphics3/AnimationSystem3.d.ts.map +1 -1
  44. package/src/engine/graphics3/AnimationSystem3.js +6 -348
  45. package/src/engine/graphics3/CameraSystem.d.ts +49 -0
  46. package/src/engine/graphics3/CameraSystem.d.ts.map +1 -0
  47. package/src/engine/graphics3/CameraSystem.js +170 -0
  48. package/src/engine/graphics3/CameraSystem3.d.ts +6 -45
  49. package/src/engine/graphics3/CameraSystem3.d.ts.map +1 -1
  50. package/src/engine/graphics3/CameraSystem3.js +6 -166
  51. package/src/engine/graphics3/DebugDrawSystem.d.ts +72 -0
  52. package/src/engine/graphics3/DebugDrawSystem.d.ts.map +1 -0
  53. package/src/engine/graphics3/DebugDrawSystem.js +205 -0
  54. package/src/engine/graphics3/DebugDrawSystem3.d.ts +6 -68
  55. package/src/engine/graphics3/DebugDrawSystem3.d.ts.map +1 -1
  56. package/src/engine/graphics3/DebugDrawSystem3.js +6 -201
  57. package/src/engine/graphics3/DecalSystem.d.ts +81 -0
  58. package/src/engine/graphics3/DecalSystem.d.ts.map +1 -0
  59. package/src/engine/graphics3/DecalSystem.js +441 -0
  60. package/src/engine/graphics3/DecalSystem3.d.ts +6 -77
  61. package/src/engine/graphics3/DecalSystem3.d.ts.map +1 -1
  62. package/src/engine/graphics3/DecalSystem3.js +6 -437
  63. package/src/engine/graphics3/FogOfWarSystem.d.ts +74 -0
  64. package/src/engine/graphics3/FogOfWarSystem.d.ts.map +1 -0
  65. package/src/engine/graphics3/FogOfWarSystem.js +265 -0
  66. package/src/engine/graphics3/FogOfWarSystem3.d.ts +6 -70
  67. package/src/engine/graphics3/FogOfWarSystem3.d.ts.map +1 -1
  68. package/src/engine/graphics3/FogOfWarSystem3.js +6 -261
  69. package/src/engine/graphics3/GraphicsEngine.d.ts +340 -0
  70. package/src/engine/graphics3/GraphicsEngine.d.ts.map +1 -0
  71. package/src/engine/graphics3/GraphicsEngine.js +790 -0
  72. package/src/engine/graphics3/GraphicsEngine3.d.ts +6 -336
  73. package/src/engine/graphics3/GraphicsEngine3.d.ts.map +1 -1
  74. package/src/engine/graphics3/GraphicsEngine3.js +6 -786
  75. package/src/engine/graphics3/HighlightSystem.d.ts +82 -0
  76. package/src/engine/graphics3/HighlightSystem.d.ts.map +1 -0
  77. package/src/engine/graphics3/HighlightSystem.js +241 -0
  78. package/src/engine/graphics3/HighlightSystem3.d.ts +6 -78
  79. package/src/engine/graphics3/HighlightSystem3.d.ts.map +1 -1
  80. package/src/engine/graphics3/HighlightSystem3.js +6 -237
  81. package/src/engine/graphics3/LightSystem.d.ts +40 -0
  82. package/src/engine/graphics3/LightSystem.d.ts.map +1 -0
  83. package/src/engine/graphics3/LightSystem.js +211 -0
  84. package/src/engine/graphics3/LightSystem3.d.ts +6 -36
  85. package/src/engine/graphics3/LightSystem3.d.ts.map +1 -1
  86. package/src/engine/graphics3/LightSystem3.js +6 -207
  87. package/src/engine/graphics3/MeshSystem.d.ts +106 -0
  88. package/src/engine/graphics3/MeshSystem.d.ts.map +1 -0
  89. package/src/engine/graphics3/MeshSystem.js +647 -0
  90. package/src/engine/graphics3/MeshSystem3.d.ts +6 -102
  91. package/src/engine/graphics3/MeshSystem3.d.ts.map +1 -1
  92. package/src/engine/graphics3/MeshSystem3.js +6 -642
  93. package/src/engine/graphics3/ParticipatingMedia.d.ts +2 -2
  94. package/src/engine/graphics3/ParticipatingMedia.js +203 -203
  95. package/src/engine/graphics3/ParticipatingMediaSystem.d.ts +87 -0
  96. package/src/engine/graphics3/ParticipatingMediaSystem.d.ts.map +1 -0
  97. package/src/engine/graphics3/ParticipatingMediaSystem.js +184 -0
  98. package/src/engine/graphics3/ParticipatingMediaSystem3.d.ts +6 -83
  99. package/src/engine/graphics3/ParticipatingMediaSystem3.d.ts.map +1 -1
  100. package/src/engine/graphics3/ParticipatingMediaSystem3.js +6 -180
  101. package/src/engine/graphics3/ParticleEmitterSystem.d.ts +76 -0
  102. package/src/engine/graphics3/ParticleEmitterSystem.d.ts.map +1 -0
  103. package/src/engine/graphics3/ParticleEmitterSystem.js +289 -0
  104. package/src/engine/graphics3/ParticleEmitterSystem3.d.ts +6 -72
  105. package/src/engine/graphics3/ParticleEmitterSystem3.d.ts.map +1 -1
  106. package/src/engine/graphics3/ParticleEmitterSystem3.js +6 -285
  107. package/src/engine/graphics3/PathDisplaySystem.d.ts +70 -0
  108. package/src/engine/graphics3/PathDisplaySystem.d.ts.map +1 -0
  109. package/src/engine/graphics3/PathDisplaySystem.js +309 -0
  110. package/src/engine/graphics3/PathDisplaySystem3.d.ts +6 -66
  111. package/src/engine/graphics3/PathDisplaySystem3.d.ts.map +1 -1
  112. package/src/engine/graphics3/PathDisplaySystem3.js +6 -305
  113. package/src/engine/graphics3/Pickable.d.ts +1 -1
  114. package/src/engine/graphics3/Pickable.js +65 -65
  115. package/src/engine/graphics3/PickingQuery.d.ts +1 -1
  116. package/src/engine/graphics3/PickingQuery.js +48 -48
  117. package/src/engine/graphics3/PickingSystem.d.ts +67 -0
  118. package/src/engine/graphics3/PickingSystem.d.ts.map +1 -0
  119. package/src/engine/graphics3/PickingSystem.js +273 -0
  120. package/src/engine/graphics3/PickingSystem3.d.ts +6 -63
  121. package/src/engine/graphics3/PickingSystem3.d.ts.map +1 -1
  122. package/src/engine/graphics3/PickingSystem3.js +6 -269
  123. package/src/engine/graphics3/ShadedGeometrySystem.d.ts +47 -0
  124. package/src/engine/graphics3/ShadedGeometrySystem.d.ts.map +1 -0
  125. package/src/engine/graphics3/ShadedGeometrySystem.js +169 -0
  126. package/src/engine/graphics3/ShadedGeometrySystem3.d.ts +6 -43
  127. package/src/engine/graphics3/ShadedGeometrySystem3.d.ts.map +1 -1
  128. package/src/engine/graphics3/ShadedGeometrySystem3.js +6 -165
  129. package/src/engine/graphics3/TerrainSystem.d.ts +80 -0
  130. package/src/engine/graphics3/TerrainSystem.d.ts.map +1 -0
  131. package/src/engine/graphics3/TerrainSystem.js +566 -0
  132. package/src/engine/graphics3/TerrainSystem3.d.ts +6 -76
  133. package/src/engine/graphics3/TerrainSystem3.d.ts.map +1 -1
  134. package/src/engine/graphics3/TerrainSystem3.js +6 -562
  135. package/src/engine/graphics3/TooltipComponentSystem.d.ts +65 -0
  136. package/src/engine/graphics3/TooltipComponentSystem.d.ts.map +1 -0
  137. package/src/engine/graphics3/TooltipComponentSystem.js +328 -0
  138. package/src/engine/graphics3/TooltipComponentSystem3.d.ts +6 -61
  139. package/src/engine/graphics3/TooltipComponentSystem3.d.ts.map +1 -1
  140. package/src/engine/graphics3/TooltipComponentSystem3.js +6 -324
  141. package/src/engine/graphics3/Trail3DSystem.d.ts +73 -0
  142. package/src/engine/graphics3/Trail3DSystem.d.ts.map +1 -0
  143. package/src/engine/graphics3/Trail3DSystem.js +221 -0
  144. package/src/engine/graphics3/Trail3DSystem3.d.ts +6 -69
  145. package/src/engine/graphics3/Trail3DSystem3.d.ts.map +1 -1
  146. package/src/engine/graphics3/Trail3DSystem3.js +6 -217
  147. package/src/engine/graphics3/VolumetricLightMap.d.ts +2 -2
  148. package/src/engine/graphics3/VolumetricLightMap.js +2 -2
  149. package/src/engine/graphics3/VolumetricLightMapSystem.d.ts +98 -0
  150. package/src/engine/graphics3/VolumetricLightMapSystem.d.ts.map +1 -0
  151. package/src/engine/graphics3/VolumetricLightMapSystem.js +261 -0
  152. package/src/engine/graphics3/VolumetricLightMapSystem3.d.ts +6 -94
  153. package/src/engine/graphics3/VolumetricLightMapSystem3.d.ts.map +1 -1
  154. package/src/engine/graphics3/VolumetricLightMapSystem3.js +10 -261
  155. package/src/engine/graphics3/WaterSystem.d.ts +59 -0
  156. package/src/engine/graphics3/WaterSystem.d.ts.map +1 -0
  157. package/src/engine/graphics3/WaterSystem.js +293 -0
  158. package/src/engine/graphics3/WaterSystem3.d.ts +6 -55
  159. package/src/engine/graphics3/WaterSystem3.d.ts.map +1 -1
  160. package/src/engine/graphics3/WaterSystem3.js +6 -289
  161. package/src/engine/graphics3/animation/AnimationGraphController.d.ts +1 -1
  162. package/src/engine/graphics3/animation/AnimationGraphController.js +518 -518
  163. package/src/engine/graphics3/animation/ClipListPlayer.d.ts +1 -1
  164. package/src/engine/graphics3/animation/ClipListPlayer.js +263 -263
  165. package/src/engine/graphics3/animation/ClipPlayback.d.ts +2 -2
  166. package/src/engine/graphics3/animation/ClipPlayback.js +84 -84
  167. package/src/engine/graphics3/decal/graph_build_decal_clusters.d.ts.map +1 -1
  168. package/src/engine/graphics3/decal/graph_build_decal_clusters.js +6 -8
  169. package/src/engine/graphics3/decal/shader_cluster_assign_decals.d.ts +0 -7
  170. package/src/engine/graphics3/decal/shader_cluster_assign_decals.d.ts.map +1 -1
  171. package/src/engine/graphics3/decal/shader_cluster_assign_decals.js +1 -1
  172. package/src/engine/graphics3/decal/shader_cull_decals_frustum.d.ts +0 -4
  173. package/src/engine/graphics3/decal/shader_cull_decals_frustum.d.ts.map +1 -1
  174. package/src/engine/graphics3/decal/shader_cull_decals_frustum.js +1 -1
  175. package/src/engine/graphics3/highlight/pack_highlight_table.d.ts +2 -2
  176. package/src/engine/graphics3/highlight/pack_highlight_table.d.ts.map +1 -1
  177. package/src/engine/graphics3/highlight/pack_highlight_table.js +74 -74
  178. package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts +1 -1
  179. package/src/engine/graphics3/pose/query_entity_node_world_pose.js +101 -101
  180. package/src/engine/graphics3/preview/make_model_thumbnail.d.ts +3 -3
  181. package/src/engine/graphics3/preview/make_model_thumbnail.d.ts.map +1 -1
  182. package/src/engine/graphics3/preview/make_model_thumbnail.js +48 -48
  183. package/src/engine/graphics3/shade_camera_projection_ray.d.ts +1 -1
  184. package/src/engine/graphics3/shade_camera_projection_ray.js +77 -77
  185. package/src/engine/graphics3/shade_node_to_entity_composition.d.ts +1 -1
  186. package/src/engine/graphics3/shade_node_to_entity_composition.js +84 -84
  187. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_leaf_triangles.d.ts.map +1 -1
  188. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_leaf_triangles.js +0 -1
  189. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_points.d.ts.map +1 -1
  190. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_points.js +0 -1
  191. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_visibility.d.ts.map +1 -1
  192. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_visibility.js +133 -134
  193. package/src/shade/playground/skinned_blas_refit/verify_leaf_triangles.js +202 -202
  194. package/src/shade/playground/skinned_blas_refit/verify_traversal_reachability.js +249 -249
  195. package/src/shade/renderer/STATIC_GRAPHICS_ENGINE_ASSETS.js +1 -1
  196. package/src/shade/renderer/animation/pose/PosePlayback.d.ts +2 -1
  197. package/src/shade/renderer/animation/pose/PosePlayback.d.ts.map +1 -1
  198. package/src/shade/renderer/animation/pose/PosePlayback.js +2 -1
  199. package/src/shade/renderer/animation/shader_animation_apply.d.ts.map +1 -1
  200. package/src/shade/renderer/animation/shader_animation_apply.js +128 -129
  201. package/src/shade/renderer/animation/shader_animation_pose_resolve.d.ts.map +1 -1
  202. package/src/shade/renderer/animation/shader_animation_pose_resolve.js +176 -177
  203. package/src/shade/renderer/animation/shader_animation_tick.d.ts.map +1 -1
  204. package/src/shade/renderer/animation/shader_animation_tick.js +125 -126
  205. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_geometry_sphere_derive.d.ts.map +1 -1
  206. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_geometry_sphere_derive.js +98 -99
  207. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_reduce.d.ts.map +1 -1
  208. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_reduce.js +212 -213
  209. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_scatter.d.ts.map +1 -1
  210. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_scatter.js +120 -121
  211. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_skinned_mesh_bounds_refresh.d.ts.map +1 -1
  212. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_skinned_mesh_bounds_refresh.js +75 -76
  213. package/src/shade/renderer/animation/skinning/shader_meshlet_apply_skinning.d.ts.map +1 -1
  214. package/src/shade/renderer/animation/skinning/shader_meshlet_apply_skinning.js +371 -372
  215. package/src/shade/renderer/animation/skinning/shader_skin_matrix_prep.d.ts.map +1 -1
  216. package/src/shade/renderer/animation/skinning/shader_skin_matrix_prep.js +104 -105
  217. package/src/shade/renderer/atmosphere/shader_multiscatter_lut.d.ts.map +1 -1
  218. package/src/shade/renderer/atmosphere/shader_multiscatter_lut.js +0 -1
  219. package/src/shade/renderer/buffer/graph_inspect_gpu_buffer.js +136 -136
  220. package/src/shade/renderer/buffer/table/GPUDatabase.d.ts.map +1 -1
  221. package/src/shade/renderer/buffer/table/GPUDatabase.js +2 -3
  222. package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.js +319 -319
  223. package/src/shade/renderer/buffer/util/shader_buffer_copy_storage_to_storage.d.ts.map +1 -1
  224. package/src/shade/renderer/buffer/util/shader_buffer_copy_storage_to_storage.js +41 -42
  225. package/src/shade/renderer/buffer/util/shader_buffer_intra_copy_regions.d.ts.map +1 -1
  226. package/src/shade/renderer/buffer/util/shader_buffer_intra_copy_regions.js +83 -84
  227. package/src/shade/renderer/dynamic/shader_dynamic_mesh.d.ts.map +1 -1
  228. package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +177 -170
  229. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +918 -918
  230. package/src/shade/renderer/geometry/bvh/record_blas_refit.js +4 -4
  231. package/src/shade/renderer/geometry/bvh/shader_blas_refit_leaf_bounds.d.ts.map +1 -1
  232. package/src/shade/renderer/geometry/bvh/shader_blas_refit_leaf_bounds.js +121 -122
  233. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +774 -774
  234. package/src/shade/renderer/geometry/meshlet/shader_meshlet_metadata_address_patch.d.ts +1 -1
  235. package/src/shade/renderer/geometry/meshlet/shader_meshlet_metadata_address_patch.d.ts.map +1 -1
  236. package/src/shade/renderer/geometry/meshlet/shader_meshlet_metadata_address_patch.js +69 -70
  237. package/src/shade/renderer/global_illumination/lpv/placement/graph_generate_probe_locations.js +367 -367
  238. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_lvp_atlas_to_sh.js +184 -184
  239. package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +1115 -1115
  240. package/src/shade/renderer/gpu_primitive/bvh/graph_bvh_build_lbvh.js +217 -217
  241. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_emit_hierarchy.d.ts.map +1 -1
  242. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_emit_hierarchy.js +142 -143
  243. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_morton.d.ts.map +1 -1
  244. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_morton.js +0 -1
  245. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_refit.d.ts.map +1 -1
  246. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_refit.js +140 -141
  247. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram.d.ts.map +1 -1
  248. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram.js +0 -1
  249. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter.d.ts.map +1 -1
  250. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter.js +0 -1
  251. package/src/shade/renderer/light/cluster/cull/graph_cull_lights.d.ts.map +1 -1
  252. package/src/shade/renderer/light/cluster/cull/graph_cull_lights.js +191 -198
  253. package/src/shade/renderer/light/cluster/cull/shader_cull_lights_frustum.d.ts +0 -6
  254. package/src/shade/renderer/light/cluster/cull/shader_cull_lights_frustum.d.ts.map +1 -1
  255. package/src/shade/renderer/light/cluster/cull/shader_cull_lights_frustum.js +120 -120
  256. package/src/shade/renderer/light/cluster/cull/shader_cull_lights_hzb.d.ts +0 -4
  257. package/src/shade/renderer/light/cluster/cull/shader_cull_lights_hzb.d.ts.map +1 -1
  258. package/src/shade/renderer/light/cluster/cull/shader_cull_lights_hzb.js +112 -112
  259. package/src/shade/renderer/light/cluster/graph_assign_clsuters.d.ts.map +1 -1
  260. package/src/shade/renderer/light/cluster/graph_assign_clsuters.js +4 -7
  261. package/src/shade/renderer/light/cluster/shader_cluster_assign_lights.d.ts +0 -6
  262. package/src/shade/renderer/light/cluster/shader_cluster_assign_lights.d.ts.map +1 -1
  263. package/src/shade/renderer/light/cluster/shader_cluster_assign_lights.js +1 -1
  264. package/src/shade/renderer/lightmap/bake/raster/shader_lightmap_to_viz_buffer.js +222 -222
  265. package/src/shade/renderer/lightmap/bake/tile/shader_lightmap_tile_composit.js +81 -81
  266. package/src/shade/renderer/material/GPUMaterialContext.js +380 -380
  267. package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
  268. package/src/shade/renderer/object_property/shader_object_property_process.d.ts.map +1 -1
  269. package/src/shade/renderer/object_property/shader_object_property_process.js +83 -84
  270. package/src/shade/renderer/object_property/shader_refresh_all_mesh_bounds.d.ts.map +1 -1
  271. package/src/shade/renderer/object_property/shader_refresh_all_mesh_bounds.js +57 -58
  272. package/src/shade/renderer/particles/prototypeParticleSystem.js +2 -3
  273. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  274. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +117 -118
  275. package/src/shade/renderer/particles/shaders/shader_particle_finalize.d.ts.map +1 -1
  276. package/src/shade/renderer/particles/shaders/shader_particle_finalize.js +77 -79
  277. package/src/shade/renderer/particles/shaders/shader_particle_render.d.ts.map +1 -1
  278. package/src/shade/renderer/particles/shaders/shader_particle_render.js +190 -183
  279. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  280. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +99 -100
  281. package/src/shade/renderer/particles/sort/shader_particle_sort.d.ts.map +1 -1
  282. package/src/shade/renderer/particles/sort/shader_particle_sort.js +158 -161
  283. package/src/shade/renderer/path_tracer/graph_path_trace_scene.js +184 -184
  284. package/src/shade/renderer/postprocess/denoise/graph_atorus_denoise_luma.js +119 -119
  285. package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +3 -4
  286. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.d.ts.map +1 -1
  287. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +266 -267
  288. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.d.ts.map +1 -1
  289. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +270 -271
  290. package/src/shade/renderer/postprocess/nss/shader_nss_concat.d.ts.map +1 -1
  291. package/src/shade/renderer/postprocess/nss/shader_nss_concat.js +68 -69
  292. package/src/shade/renderer/postprocess/nss/shader_nss_conv_layer.d.ts.map +1 -1
  293. package/src/shade/renderer/postprocess/nss/shader_nss_conv_layer.js +225 -226
  294. package/src/shade/renderer/postprocess/nss/shader_nss_extract_feedback.d.ts.map +1 -1
  295. package/src/shade/renderer/postprocess/nss/shader_nss_extract_feedback.js +49 -50
  296. package/src/shade/renderer/postprocess/nss/shader_nss_postprocess.d.ts.map +1 -1
  297. package/src/shade/renderer/postprocess/nss/shader_nss_postprocess.js +291 -292
  298. package/src/shade/renderer/postprocess/nss/shader_nss_preprocess.d.ts.map +1 -1
  299. package/src/shade/renderer/postprocess/nss/shader_nss_preprocess.js +230 -231
  300. package/src/shade/renderer/postprocess/ssr/reproject/shader_ffx_denoiser_reflections_reproject.d.ts +16 -2
  301. package/src/shade/renderer/postprocess/ssr/reproject/shader_ffx_denoiser_reflections_reproject.d.ts.map +1 -1
  302. package/src/shade/renderer/postprocess/ssr/reproject/shader_ffx_denoiser_reflections_reproject.js +12 -12
  303. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +2 -2
  304. package/src/shade/renderer/rasterize/bucket/shader_count_meshlets_by_material.d.ts.map +1 -1
  305. package/src/shade/renderer/rasterize/bucket/shader_count_meshlets_by_material.js +68 -69
  306. package/src/shade/renderer/rasterize/bucket/shader_make_meshlet_indirect_draw_commands_by_material.d.ts.map +1 -1
  307. package/src/shade/renderer/rasterize/bucket/shader_make_meshlet_indirect_draw_commands_by_material.js +59 -60
  308. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
  309. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +0 -1
  310. package/src/shade/renderer/rasterize/bucket/shader_sort_meshlets_by_material.d.ts.map +1 -1
  311. package/src/shade/renderer/rasterize/bucket/shader_sort_meshlets_by_material.js +64 -65
  312. package/src/shade/renderer/rasterize/compute/graph_mesh_frustum_cull.d.ts.map +1 -1
  313. package/src/shade/renderer/rasterize/compute/graph_mesh_frustum_cull.js +168 -169
  314. package/src/shade/renderer/rasterize/compute/graph_mesh_sphere_cull.d.ts.map +1 -1
  315. package/src/shade/renderer/rasterize/compute/graph_mesh_sphere_cull.js +160 -161
  316. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.d.ts.map +1 -1
  317. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.js +109 -110
  318. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.d.ts.map +1 -1
  319. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.js +115 -116
  320. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_classify_bucket.d.ts.map +1 -1
  321. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_classify_bucket.js +139 -140
  322. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_count_by_rasterization_bucket.d.ts.map +1 -1
  323. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_count_by_rasterization_bucket.js +91 -92
  324. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.d.ts.map +1 -1
  325. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.js +88 -89
  326. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_count_by_rasterization_bucket.d.ts.map +1 -1
  327. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_count_by_rasterization_bucket.js +90 -91
  328. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.d.ts.map +1 -1
  329. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.js +65 -66
  330. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.d.ts.map +1 -1
  331. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.js +89 -90
  332. package/src/shade/renderer/rasterize/expand/graph_mesh_filter_one_way.d.ts.map +1 -1
  333. package/src/shade/renderer/rasterize/expand/graph_mesh_filter_one_way.js +169 -170
  334. package/src/shade/renderer/rasterize/expand/mesh/shader_mesh_expand_to_meshlets_prefix.d.ts.map +1 -1
  335. package/src/shade/renderer/rasterize/expand/mesh/shader_mesh_expand_to_meshlets_prefix.js +89 -90
  336. package/src/shade/renderer/rasterize/expand/mesh/shader_meshes_to_meshlet_counts.d.ts.map +1 -1
  337. package/src/shade/renderer/rasterize/expand/mesh/shader_meshes_to_meshlet_counts.js +61 -62
  338. package/src/shade/renderer/rasterize/expand/pass1/filter_meshlets.2-way.compute.d.ts.map +1 -1
  339. package/src/shade/renderer/rasterize/expand/pass1/filter_meshlets.2-way.compute.js +153 -154
  340. package/src/shade/renderer/rasterize/native/depth/shadow_rasterization_alpha_tested_pass_descriptor.js +144 -144
  341. package/src/shade/renderer/rasterize/native/depth/shadow_rasterization_pass_descriptor.js +88 -88
  342. package/src/shade/renderer/rasterize/native/oit/shader_oit_generate_moments.js +217 -217
  343. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments.js +179 -179
  344. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +178 -178
  345. package/src/shade/renderer/rasterize/native/viz/viz_rasterization_alpha_tested_pass_descriptor.js +194 -194
  346. package/src/shade/renderer/rasterize/native/viz/viz_rasterization_opaque_pass_descriptor.js +76 -76
  347. package/src/shade/renderer/restir/di/shader_restir_di_denoise.d.ts.map +1 -1
  348. package/src/shade/renderer/restir/di/shader_restir_di_denoise.js +136 -137
  349. package/src/shade/renderer/restir/di/shader_restir_di_resample.d.ts.map +1 -1
  350. package/src/shade/renderer/restir/di/shader_restir_di_resample.js +129 -130
  351. package/src/shade/renderer/restir/di/shader_restir_di_resolve.d.ts.map +1 -1
  352. package/src/shade/renderer/restir/di/shader_restir_di_resolve.js +99 -100
  353. package/src/shade/renderer/restir/di/shader_restir_di_spatial.d.ts.map +1 -1
  354. package/src/shade/renderer/restir/di/shader_restir_di_spatial.js +116 -117
  355. package/src/shade/renderer/scene/GPUInstancesAccelerationStructure.js +383 -383
  356. package/src/shade/renderer/scene/hierarchy/graph_scene_update_transform_hierarchy_naive.js +123 -123
  357. package/src/shade/renderer/scene/hierarchy/shader_nodes_record_input_positions.d.ts.map +1 -1
  358. package/src/shade/renderer/scene/hierarchy/shader_nodes_record_input_positions.js +64 -65
  359. package/src/shade/renderer/scene/hierarchy/shader_nodes_update_transform_hierarchy.d.ts.map +1 -1
  360. package/src/shade/renderer/scene/hierarchy/shader_nodes_update_transform_hierarchy.js +372 -373
  361. package/src/shade/renderer/scene/hierarchy/shader_nodes_update_transform_hierarchy_naive.d.ts.map +1 -1
  362. package/src/shade/renderer/scene/hierarchy/shader_nodes_update_transform_hierarchy_naive.js +111 -112
  363. package/src/shade/renderer/scene/shader_bvh_extract_instance_leaves.d.ts.map +1 -1
  364. package/src/shade/renderer/scene/shader_bvh_extract_instance_leaves.js +0 -1
  365. package/src/shade/renderer/shader/ComputeShader.d.ts +24 -3
  366. package/src/shade/renderer/shader/ComputeShader.d.ts.map +1 -1
  367. package/src/shade/renderer/shader/ComputeShader.js +350 -296
  368. package/src/shade/renderer/shader/ImageShader.js +217 -217
  369. package/src/shade/renderer/shader/ShaderDescriptor.d.ts +5 -0
  370. package/src/shade/renderer/shader/ShaderDescriptor.d.ts.map +1 -1
  371. package/src/shade/renderer/shader/ShaderDescriptor.js +20 -0
  372. package/src/shade/renderer/shader/util/compute_infer_workgroup_size_from_source.d.ts +47 -0
  373. package/src/shade/renderer/shader/util/compute_infer_workgroup_size_from_source.d.ts.map +1 -0
  374. package/src/shade/renderer/shader/util/compute_infer_workgroup_size_from_source.js +448 -0
  375. package/src/shade/renderer/shadow/map/csm/graph_csm_setup_cascades.js +3 -3
  376. package/src/shade/renderer/shadow/ray/staged/gen/graph_gen_rays.js +3 -3
  377. package/src/shade/renderer/shadow/ray/staged/trace/graph_trace_rays.js +2 -2
  378. package/src/shade/renderer/shadow/sdf/cascade/CascadedSceneSDF.js +4 -4
  379. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.d.ts.map +1 -1
  380. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +231 -232
  381. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +1 -1
  382. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
  383. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +12 -15
  384. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts +3 -2
  385. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
  386. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +3 -2
  387. package/src/shade/renderer/volumetrics/taa/shader_volumetrics_taa.d.ts +0 -2
  388. package/src/shade/renderer/volumetrics/taa/shader_volumetrics_taa.d.ts.map +1 -1
  389. package/src/shade/renderer/volumetrics/taa/shader_volumetrics_taa.js +17 -6
  390. package/src/shade/renderer/water/shader_water.js +246 -246
  391. package/src/shade/vitest.setup.mjs +1 -1
  392. package/src/shade/wgsl/emulator/ComputeShaderEmulator.js +291 -291
  393. package/src/view/minimap/Minimap.d.ts +2 -2
  394. package/src/view/minimap/Minimap.d.ts.map +1 -1
  395. package/src/view/minimap/Minimap.js +225 -225
  396. package/src/view/minimap/dom/MinimapCameraView.d.ts +3 -3
  397. package/src/view/minimap/dom/MinimapCameraView.d.ts.map +1 -1
  398. package/src/view/minimap/dom/MinimapCameraView.js +206 -206
  399. package/src/shade/renderer/shadow/map/csm/shader/shader_setup_cascades.d.ts +0 -3
  400. package/src/shade/renderer/shadow/map/csm/shader/shader_setup_cascades.d.ts.map +0 -1
  401. package/src/shade/renderer/shadow/map/csm/shader/shader_setup_cascades.js +0 -20
@@ -1,775 +1,775 @@
1
- import { assert } from "../../../../core/assert.js";
2
- import { ALLOCATOR_NO_SPACE, OffsetAllocator } from "../../../../core/binary/allocator/OffsetAllocator.js";
3
- import { BinaryBuffer } from "../../../../core/binary/BinaryBuffer.js";
4
- import { platform_compute_endianness } from "../../../../core/binary/platform_compute_endianness.js";
5
- import Signal from "../../../../core/events/signal/Signal.js";
6
- import { MEGABYTE } from "../../../../core/science/units/memory/MEGABYTE.js";
7
- import { read_wgsl_type_value } from "../../shader/type/serde/read_gpu_typed_buffer.js";
8
- import { write_wgsl_type_value } from "../../shader/type/serde/write_gpu_typed_buffer.js";
9
- import { u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
10
- import { inspect_gpu_buffer_typed } from "../../buffer/inspect_gpu_buffer_typed.js";
11
- import {
12
- BUFFER_INTRA_COPY_REGION_STRUCT,
13
- shader_buffer_intra_copy_regions
14
- } from "../../buffer/util/shader_buffer_intra_copy_regions.js";
15
- import { MESHLET_METADATA_STRUCT } from "./encoding/MESHLET_METADATA_STRUCT.js";
16
- import { shader_meshlet_metadata_address_patch } from "./shader_meshlet_metadata_address_patch.js";
17
-
18
- /**
19
- * In meshlets
20
- * @type {number}
21
- */
22
- const DEFAULT_METADATA_CAPACITY = 128 * 1024;
23
-
24
- /**
25
- * In bytes
26
- * @type {number}
27
- */
28
- const DEFAULT_DATA_CAPACITY = 32 * MEGABYTE;
29
-
30
- class MeshletBatchAllocationRecord {
31
- /**
32
- * @type {Allocation}
33
- */
34
- metadata
35
- /**
36
- * @type {Allocation}
37
- */
38
- data
39
-
40
- /**
41
- * Number of words
42
- * @type {number}
43
- */
44
- data_size = 0;
45
-
46
- /**
47
- * Meshlet count
48
- * @type {number}
49
- */
50
- meshlet_count = 0;
51
-
52
- /**
53
- *
54
- * @type {ArrayBuffer}
55
- */
56
- metadata_buffer_original;
57
-
58
- /**
59
- *
60
- * @type {Signal<>}
61
- */
62
- changed = new Signal()
63
- }
64
-
65
- export class GPUMeshletManager {
66
-
67
- /**
68
- * Allocation sizes in words of 4 bytes
69
- * @type {OffsetAllocator}
70
- */
71
- #allocator_data = new OffsetAllocator(1024);
72
-
73
- /**
74
- * Allocation sizes in meshlets
75
- * @type {OffsetAllocator}
76
- */
77
- #allocator_metadata = new OffsetAllocator(1024);
78
-
79
- /**
80
- * @type {GPUBuffer}
81
- */
82
- #meshlet_data_buffer;
83
-
84
- get buffer_data() {
85
- return this.#meshlet_data_buffer;
86
- }
87
-
88
- /**
89
- * @type {GPUBuffer}
90
- */
91
- #meshlet_metadata_buffer;
92
-
93
- get buffer_metadata() {
94
- return this.#meshlet_metadata_buffer;
95
- }
96
-
97
- /**
98
- * @type {GPUDevice}
99
- */
100
- #device;
101
-
102
- /**
103
- *
104
- * @type {number}
105
- */
106
- #deallocations_since_last_rebuild = 0;
107
-
108
- /**
109
- *
110
- * @type {MeshletBatchAllocationRecord[]}
111
- */
112
- #records = [];
113
-
114
- /**
115
- * Queue of GPU-side clones that have been allocated but not yet
116
- * copied + patched. Drained by {@link update}. Each entry holds the
117
- * minimum bookkeeping needed to issue two `copyBufferToBuffer`
118
- * commands and N `(record_index, address_delta)` patch entries.
119
- *
120
- * Entries name the two records, not the addresses they held when the
121
- * clone was queued: a {@link compact} or {@link grow} in between
122
- * migrates both, and {@link update} reads where they ended up. What
123
- * an entry does require is that neither record is handed back to
124
- * {@link remove_batch} before the flush.
125
- *
126
- * @type {{
127
- * source: MeshletBatchAllocationRecord,
128
- * clone: MeshletBatchAllocationRecord,
129
- * }[]}
130
- */
131
- #pending_clones = [];
132
-
133
- /**
134
- *
135
- * @param {GPUDevice} device
136
- * @param {number} [initial_data_capacity] in bytes
137
- * @param {number} [initial_metadata_capacity] in meshlets
138
- */
139
- constructor({
140
- device,
141
- initial_data_capacity = DEFAULT_DATA_CAPACITY,
142
- initial_metadata_capacity = DEFAULT_METADATA_CAPACITY,
143
- }) {
144
- assert.defined(device, 'device');
145
-
146
- assert.isNonNegativeInteger(initial_data_capacity, 'initial_data_capacity');
147
- assert.isNonNegativeInteger(initial_metadata_capacity, 'initial_metadata_capacity');
148
-
149
- assert.greaterThan(initial_data_capacity, 0, 'initial_data_capacity');
150
- assert.greaterThan(initial_metadata_capacity, 0, 'initial_data_capacity');
151
-
152
- this.#device = device;
153
-
154
- const data_buffer_size_words = Math.ceil(initial_data_capacity / 4);
155
-
156
- // TODO manage max-allocations as well
157
- this.#allocator_data = new OffsetAllocator(data_buffer_size_words);
158
- this.#allocator_metadata = new OffsetAllocator(initial_metadata_capacity);
159
-
160
- this.#meshlet_data_buffer = device.createBuffer({
161
- label: 'GPUMeshletManager/data',
162
- size: data_buffer_size_words * 4,
163
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
164
- });
165
-
166
- this.#meshlet_metadata_buffer = device.createBuffer({
167
- label: 'GPUMeshletManager/metadata',
168
- size: initial_metadata_capacity * MESHLET_METADATA_STRUCT.aligned_size,
169
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
170
- });
171
-
172
- }
173
-
174
- compact() {
175
- this.#rebuild(
176
- this.#meshlet_data_buffer.size >>> 2,
177
- Math.ceil(this.#meshlet_metadata_buffer.size / MESHLET_METADATA_STRUCT.aligned_size)
178
- );
179
- }
180
-
181
- /**
182
- *
183
- * @param {number} extra_data_size in words (4 bytes)
184
- * @param {number} extra_metadata_size in metadata elements
185
- */
186
- grow(extra_data_size, extra_metadata_size) {
187
- assert.isNonNegativeInteger(extra_data_size, 'extra_data_size');
188
- assert.isNonNegativeInteger(extra_metadata_size, 'extra_metadata_size');
189
-
190
- if (extra_data_size === 0 && extra_metadata_size === 0) {
191
- // nothing to do
192
- return;
193
- }
194
-
195
- const current_data_buffer_size_bytes = this.#meshlet_data_buffer.size;
196
- const current_metadata_buffer_size = this.#meshlet_metadata_buffer.size;
197
-
198
- const current_buffer_size_words = current_data_buffer_size_bytes >> 2;
199
- const current_metadata_buffer_size_elements = Math.ceil(current_metadata_buffer_size / MESHLET_METADATA_STRUCT.aligned_size);
200
-
201
- const DATA_GROWTH_STEP = 16 * 1024;
202
- const METADATA_GROWTH_STEP = 256;
203
- const GROWTH_MULTIPLIER = 1.25;
204
-
205
- let new_data_size;
206
- if (extra_data_size === 0) {
207
- new_data_size = current_buffer_size_words;
208
- } else {
209
- // grow
210
- new_data_size = Math.max(
211
- current_buffer_size_words + extra_data_size,
212
- current_buffer_size_words + DATA_GROWTH_STEP,
213
- Math.ceil(current_buffer_size_words * GROWTH_MULTIPLIER)
214
- );
215
- }
216
-
217
- let new_metadata_size;
218
- if (extra_metadata_size === 0) {
219
- new_metadata_size = current_metadata_buffer_size_elements;
220
- } else {
221
- // grow
222
- new_metadata_size = Math.max(
223
- current_metadata_buffer_size_elements + extra_metadata_size,
224
- current_metadata_buffer_size_elements + METADATA_GROWTH_STEP,
225
- current_metadata_buffer_size_elements * GROWTH_MULTIPLIER
226
- );
227
- }
228
-
229
- this.#rebuild(
230
- new_data_size,
231
- new_metadata_size
232
- );
233
- }
234
-
235
- /**
236
- * Helper to patch pointers in metadata and upload to GPU
237
- * @param {ArrayBuffer} source_array_buffer
238
- * @param {number} meshlet_count
239
- * @param {number} data_offset_words
240
- * @param {number} metadata_offset_index
241
- * @param {GPUBuffer} target_buffer
242
- */
243
- #write_patched_metadata(
244
- source_array_buffer,
245
- meshlet_count,
246
- data_offset_words,
247
- metadata_offset_index,
248
- target_buffer
249
- ) {
250
- const temp_writer = BinaryBuffer.fromEndianness(platform_compute_endianness());
251
- temp_writer.setCapacity(meshlet_count * MESHLET_METADATA_STRUCT.size); // pre-allocate to avoid resizing
252
-
253
- const source_reader = BinaryBuffer.fromArrayBuffer(source_array_buffer);
254
- source_reader.endianness = platform_compute_endianness();
255
-
256
- // Iterate over meshlets in the batch, patch the address, and write to temp buffer
257
- for (let i = 0; i < meshlet_count; i++) {
258
- const header = read_wgsl_type_value(source_reader, MESHLET_METADATA_STRUCT);
259
-
260
- // PATCH: Add the allocation offset to the relative address
261
- header.address += data_offset_words;
262
-
263
- write_wgsl_type_value(header, temp_writer, MESHLET_METADATA_STRUCT);
264
- }
265
-
266
- // Upload
267
- this.#device.queue.writeBuffer(
268
- target_buffer,
269
- metadata_offset_index * MESHLET_METADATA_STRUCT.aligned_size,
270
- temp_writer.data,
271
- 0,
272
- meshlet_count * MESHLET_METADATA_STRUCT.aligned_size
273
- );
274
- }
275
-
276
- /**
277
- *
278
- * @param {number} new_data_size
279
- * @param {number} new_metadata_size
280
- */
281
- #rebuild(
282
- new_data_size,
283
- new_metadata_size
284
- ) {
285
-
286
- assert.isNonNegativeInteger(new_data_size, 'new_data_size');
287
- assert.isNonNegativeInteger(new_metadata_size, 'new_metadata_size');
288
-
289
- // Outstanding clone work survives this. A queued clone names its
290
- // source and destination records, both of which are migrated
291
- // below, and the flush reads their addresses when it runs — so a
292
- // rebuild moves the copy rather than invalidating it.
293
-
294
- // console.warn('GPUMeshletManager/rebuild');
295
-
296
- const device = this.#device;
297
-
298
- const records = this.#records;
299
- const record_count = records.length;
300
- const new_allocator_data = new OffsetAllocator(
301
- new_data_size,
302
- Math.max(record_count * 2, 128 * 1024)
303
- );
304
-
305
- const old_buffer_data = this.#meshlet_data_buffer;
306
- const new_buffer_data = device.createBuffer({
307
- label: old_buffer_data.label,
308
- usage: old_buffer_data.usage,
309
- size: new_data_size * 4,
310
- });
311
-
312
- const new_allocator_metadata = new OffsetAllocator(
313
- new_metadata_size,
314
- Math.max(record_count * 2, 128 * 1024)
315
- );
316
-
317
- const old_buffer_metadata = this.#meshlet_metadata_buffer;
318
- const new_buffer_metadata = device.createBuffer({
319
- label: old_buffer_metadata.label,
320
- usage: old_buffer_metadata.usage,
321
- size: new_metadata_size * MESHLET_METADATA_STRUCT.aligned_size,
322
- });
323
-
324
- const encoder = device.createCommandEncoder({
325
- label: "GPUMeshletManager/rebuild",
326
- });
327
-
328
- const moved_records = [];
329
-
330
- // move data
331
- for (let i = 0; i < record_count; i++) {
332
- const record = records[i];
333
-
334
- const new_allocation_data = new_allocator_data.allocate(record.data_size);
335
- const new_allocation_metadata = new_allocator_metadata.allocate(record.meshlet_count);
336
-
337
- assert.notEqual(new_allocation_data.offset, ALLOCATOR_NO_SPACE);
338
- assert.notEqual(new_allocation_metadata.offset, ALLOCATOR_NO_SPACE);
339
-
340
- encoder.copyBufferToBuffer(
341
- old_buffer_data, record.data.offset * 4,
342
- new_buffer_data, new_allocation_data.offset * 4,
343
- record.data_size * 4
344
- );
345
-
346
- const metadata_record_moved = record.data.offset !== new_allocation_data.offset;
347
-
348
- // B. Patch Metadata (CPU -> GPU Write)
349
- // Metadata contains `address` which points to the Data offset.
350
- // Since the Data offset changed, we must re-calculate and upload the metadata.
351
- // We reuse the helper to avoid code duplication.
352
- this.#write_patched_metadata(
353
- record.metadata_buffer_original,
354
- record.meshlet_count,
355
- new_allocation_data.offset, // The NEW data offset
356
- new_allocation_metadata.offset, // The NEW metadata location
357
- new_buffer_metadata
358
- );
359
-
360
- // update record
361
- record.data = new_allocation_data;
362
- record.metadata = new_allocation_metadata;
363
-
364
- if (metadata_record_moved) {
365
- moved_records.push(record);
366
- }
367
- }
368
-
369
- for (const record of moved_records) {
370
- // notify
371
- record.changed.send0();
372
- }
373
-
374
- const command_buffer = encoder.finish();
375
-
376
- device.queue.submit([command_buffer]);
377
-
378
- old_buffer_data.destroy();
379
- old_buffer_metadata.destroy();
380
-
381
- this.#meshlet_data_buffer = new_buffer_data;
382
- this.#meshlet_metadata_buffer = new_buffer_metadata;
383
-
384
- this.#allocator_data = new_allocator_data;
385
- this.#allocator_metadata = new_allocator_metadata;
386
-
387
- // reset count
388
- this.#deallocations_since_last_rebuild = 0;
389
- }
390
-
391
- /**
392
- * Allocate `data_size_words` in the data buffer and `meshlet_count`
393
- * in the metadata buffer, retrying with compact-then-grow on failure.
394
- * Throws if the third attempt still can't satisfy both. The two
395
- * allocations are kept in lockstep — partial successes are released
396
- * before retrying so we never leak.
397
- *
398
- * Compaction triggers a full {@link #rebuild} which migrates every
399
- * existing allocation to a new buffer pair. Queued clone work is
400
- * carried through it — {@link #pending_clones} names records, not
401
- * addresses — so an allocation made while a clone is pending is safe.
402
- *
403
- * @param {number} data_size_words
404
- * @param {number} meshlet_count
405
- * @returns {{data: Allocation, metadata: Allocation}}
406
- */
407
- #allocate_data_and_metadata(data_size_words, meshlet_count) {
408
- let data_allocation;
409
- let metadata_allocation;
410
-
411
- for (let try_index = 0; try_index < 3; try_index++) {
412
-
413
- data_allocation = this.#allocator_data.allocate(data_size_words);
414
- metadata_allocation = this.#allocator_metadata.allocate(meshlet_count);
415
-
416
- const data_ok = data_allocation.offset !== ALLOCATOR_NO_SPACE;
417
- const metadata_ok = metadata_allocation.offset !== ALLOCATOR_NO_SPACE;
418
-
419
- if (data_ok && metadata_ok) {
420
- break;
421
- }
422
-
423
- // release partial allocation
424
- if (data_ok) {
425
- this.#allocator_data.free(data_allocation);
426
-
427
- data_allocation.offset = ALLOCATOR_NO_SPACE;
428
- data_allocation.metadata = ALLOCATOR_NO_SPACE;
429
- }
430
- if (metadata_ok) {
431
- this.#allocator_metadata.free(metadata_allocation);
432
-
433
- metadata_allocation.offset = ALLOCATOR_NO_SPACE;
434
- metadata_allocation.metadata = ALLOCATOR_NO_SPACE;
435
- }
436
-
437
- if (this.#deallocations_since_last_rebuild > 0) {
438
- // try compaction
439
- this.compact();
440
- continue;
441
- }
442
-
443
- let grow_data_size = data_ok ? 0 : data_size_words;
444
- let grow_metadata_size = metadata_ok ? 0 : meshlet_count;
445
-
446
- this.grow(grow_data_size, grow_metadata_size);
447
-
448
- }
449
-
450
- if (data_allocation.offset === ALLOCATOR_NO_SPACE || metadata_allocation.offset === ALLOCATOR_NO_SPACE) {
451
- throw new Error('Failed to allocate GPU memory for meshlet batch');
452
- }
453
-
454
- return { data: data_allocation, metadata: metadata_allocation };
455
- }
456
-
457
- /**
458
- *
459
- * @param {MeshletBatch} batch
460
- * @returns {MeshletBatchAllocationRecord}
461
- */
462
- #allocate_for_batch(batch) {
463
- assert.defined(batch, 'batch');
464
-
465
- const data_buffer = batch.data_buffer;
466
-
467
- const data_size_words = Math.ceil(data_buffer.byteLength / 4);
468
-
469
- const meshlet_count = batch.count;
470
-
471
- const { data, metadata } = this.#allocate_data_and_metadata(data_size_words, meshlet_count);
472
-
473
- const record = new MeshletBatchAllocationRecord();
474
-
475
- record.data = data;
476
- record.metadata = metadata;
477
-
478
- record.meshlet_count = meshlet_count;
479
- record.data_size = data_size_words;
480
-
481
- record.metadata_buffer_original = batch.metadata_buffer;
482
-
483
- this.#records.push(record);
484
-
485
- return record;
486
- }
487
-
488
- /**
489
- * Release a batch allocation back to the data and metadata allocators. The GPU bytes are
490
- * not cleared — the freed slices are reclaimed lazily by the next {@link compact} (a
491
- * deallocation bumps the compaction counter the allocator consults under pressure).
492
- * Other batches' allocations are unaffected (the OffsetAllocator frees disjoint slices),
493
- * so callers only need to ensure nothing still references this batch's metadata rows
494
- * (the geometry table rebuild in {@link GPUGeometryManager} handles that).
495
- *
496
- * @param {MeshletBatchAllocationRecord} record a record returned by {@link add_batch} or
497
- * {@link clone_batch_allocation}
498
- * @returns {boolean} true if the record was owned and freed
499
- */
500
- remove_batch(record) {
501
- assert.defined(record, 'record');
502
-
503
- const index = this.#records.indexOf(record);
504
- if (index === -1) {
505
- return false;
506
- }
507
-
508
- this.#allocator_data.free(record.data);
509
- this.#allocator_metadata.free(record.metadata);
510
-
511
- this.#records.splice(index, 1);
512
- this.#deallocations_since_last_rebuild++;
513
-
514
- return true;
515
- }
516
-
517
- /**
518
- * Allocate a fresh (data, metadata) pair sized to mirror `source`,
519
- * and queue a GPU-side clone: the source's data and metadata are
520
- * copied into the new ranges via `copyBufferToBuffer`, then a
521
- * compute pass patches the cloned metadata records' `address`
522
- * fields to point at the new data range. Both the copies and the
523
- * patch dispatch land when the host next calls
524
- * {@link update update(cmd_ctx)}.
525
- *
526
- * The returned record can be wired into the geometry table
527
- * immediately — its `data.offset` and `metadata.offset` are valid
528
- * the moment this call returns. The GPU bytes at those addresses
529
- * just aren't populated yet; downstream passes that consume the
530
- * clone must run after the next `update`. Those addresses may still
531
- * move before the flush, which is why nothing outside the record is
532
- * allowed to remember them.
533
- *
534
- * Sharing the source's `metadata_buffer_original` lets a future
535
- * compaction patch the clone's metadata from the same un-patched
536
- * CPU bytes the source uses — the post-patch state on the GPU is
537
- * identical to the source aside from the data offset.
538
- *
539
- * @param {MeshletBatchAllocationRecord} source
540
- * @returns {MeshletBatchAllocationRecord}
541
- */
542
- clone_batch_allocation(source) {
543
- assert.defined(source, 'source');
544
- assert.greaterThan(source.meshlet_count, 0, 'source.meshlet_count');
545
-
546
- const { data, metadata } = this.#allocate_data_and_metadata(source.data_size, source.meshlet_count);
547
-
548
- const record = new MeshletBatchAllocationRecord();
549
-
550
- record.data = data;
551
- record.metadata = metadata;
552
-
553
- record.meshlet_count = source.meshlet_count;
554
- record.data_size = source.data_size;
555
-
556
- // Share the un-patched CPU bytes so a future compact rewrites
557
- // the clone's metadata correctly under its new data offset.
558
- record.metadata_buffer_original = source.metadata_buffer_original;
559
-
560
- this.#records.push(record);
561
-
562
- // The queue names the two records rather than the addresses they
563
- // hold right now. Both are in `#records`, so a rebuild between
564
- // this call and the flush moves them and the flush still reads
565
- // where they went — which is what lets a clone be queued in the
566
- // same frame as the growth another geometry's arrival forces.
567
- this.#pending_clones.push({ source, clone: record });
568
-
569
- return record;
570
- }
571
-
572
- /**
573
- * Flush any pending clone work into `cmd_ctx`. For each queued
574
- * clone, emits two `copyBufferToBuffer` commands (one for vertex
575
- * data, one for metadata records). After every queued clone's
576
- * copies are recorded, dispatches a single batched patch shader
577
- * pass that walks a flat `(record_index, address_delta)` table
578
- * — N clones → 1 patch dispatch, total threads = sum of clone
579
- * meshlet counts.
580
- *
581
- * Idempotent and cheap when nothing's queued.
582
- *
583
- * @param {ShadeGPUCommandContext} cmd_ctx
584
- */
585
- update(cmd_ctx) {
586
- const pending = this.#pending_clones;
587
- if (pending.length === 0) {
588
- return;
589
- }
590
-
591
- const data_buffer = this.#meshlet_data_buffer;
592
- const metadata_buffer = this.#meshlet_metadata_buffer;
593
- const metadata_stride = MESHLET_METADATA_STRUCT.aligned_size;
594
- const metadata_stride_words = metadata_stride / 4;
595
-
596
- // WebGPU forbids `copyBufferToBuffer` with src === dst even when
597
- // ranges don't overlap (the validation check is buffer-identity,
598
- // not range-overlap), so we relocate via a small compute-shader
599
- // memcpy bound to the buffer once as `read_write`. Two dispatches
600
- // — one over the data-buffer regions, one over the metadata-buffer
601
- // regions — handle every queued clone in a batched pass.
602
- //
603
- // Region (src, dst) ranges are non-overlapping by construction:
604
- // the OffsetAllocator hands out fresh disjoint slices, so the
605
- // memcpy shader can safely skip the per-thread overlap check.
606
- const region_size_bytes = BUFFER_INTRA_COPY_REGION_STRUCT.aligned_size;
607
-
608
- const data_regions_array = new Uint32Array(pending.length * (region_size_bytes / 4));
609
- const metadata_regions_array = new Uint32Array(pending.length * (region_size_bytes / 4));
610
-
611
- let total_records = 0;
612
- for (let i = 0; i < pending.length; i++) {
613
- const { source, clone } = pending[i];
614
-
615
- // Region struct is just three u32s — write directly. Order
616
- // matches the field declaration: src_offset, dst_offset,
617
- // word_count.
618
- const region_words = i * 3;
619
- data_regions_array[region_words] = source.data.offset;
620
- data_regions_array[region_words + 1] = clone.data.offset;
621
- data_regions_array[region_words + 2] = clone.data_size;
622
-
623
- metadata_regions_array[region_words] = source.metadata.offset * metadata_stride_words;
624
- metadata_regions_array[region_words + 1] = clone.metadata.offset * metadata_stride_words;
625
- metadata_regions_array[region_words + 2] = clone.meshlet_count * metadata_stride_words;
626
-
627
- total_records += clone.meshlet_count;
628
- }
629
-
630
- const data_regions_buffer = cmd_ctx.allocateTransientBufferAndLoad(
631
- data_regions_array.buffer,
632
- GPUBufferUsage.STORAGE,
633
- );
634
- shader_buffer_intra_copy_regions.dispatch({
635
- encoder: cmd_ctx,
636
- group_count_x: pending.length,
637
- bindings: {
638
- buffer: data_buffer,
639
- regions: data_regions_buffer,
640
- },
641
- });
642
-
643
- const metadata_regions_buffer = cmd_ctx.allocateTransientBufferAndLoad(
644
- metadata_regions_array.buffer,
645
- GPUBufferUsage.STORAGE,
646
- );
647
- shader_buffer_intra_copy_regions.dispatch({
648
- encoder: cmd_ctx,
649
- group_count_x: pending.length,
650
- bindings: {
651
- buffer: metadata_buffer,
652
- regions: metadata_regions_buffer,
653
- },
654
- });
655
-
656
- // Build the flat (record_index, address_delta) table. One pair
657
- // per cloned meshlet record. Uploaded as a transient storage
658
- // buffer the patch shader binds for one dispatch.
659
- const patches_array = new Uint32Array(total_records * 2);
660
- let p = 0;
661
- for (let i = 0; i < pending.length; i++) {
662
- const { source, clone } = pending[i];
663
-
664
- // Address delta is in u32 word units. When the clone's offset
665
- // is below the source's, the unsigned subtraction wraps — the
666
- // patch shader does the matching wrapping add, so the final
667
- // per-record `address` lands at the right absolute word offset
668
- // either way.
669
- const address_delta = (clone.data.offset - source.data.offset) >>> 0;
670
-
671
- for (let m = 0; m < clone.meshlet_count; m++) {
672
- patches_array[p++] = clone.metadata.offset + m;
673
- patches_array[p++] = address_delta;
674
- }
675
- }
676
-
677
- const patches_buffer = cmd_ctx.allocateTransientBufferAndLoad(
678
- patches_array.buffer,
679
- GPUBufferUsage.STORAGE,
680
- );
681
-
682
- // patch_count uniform is a bare u32 — see
683
- // shader_meshlet_metadata_address_patch.
684
- const patch_count_buffer = cmd_ctx.allocateTransientValueBuffer(
685
- u32,
686
- total_records,
687
- );
688
-
689
- shader_meshlet_metadata_address_patch.dispatchLinear({
690
- encoder: cmd_ctx,
691
- element_count: total_records,
692
- bindings: {
693
- meshlet_metadata: metadata_buffer,
694
- patches: patches_buffer,
695
- patch_count: patch_count_buffer,
696
- },
697
- });
698
-
699
- pending.length = 0;
700
- }
701
-
702
- destroy() {
703
- this.#meshlet_data_buffer.destroy();
704
- this.#meshlet_metadata_buffer.destroy();
705
- }
706
-
707
- /**
708
- *
709
- * @param {MeshletBatch} batch
710
- * @returns {MeshletBatchAllocationRecord}
711
- */
712
- add_batch(batch) {
713
- assert.defined(batch, 'batch');
714
- assert.notNull(batch, 'batch');
715
- assert.equal(batch.isMeshletBatch, true, 'batch.isMeshletBatch !== true');
716
-
717
- const record = this.#allocate_for_batch(batch);
718
-
719
- // plonk data
720
- const data_buffer = batch.data_buffer;
721
- const data_allocation = record.data;
722
-
723
- this.#device.queue.writeBuffer(
724
- this.#meshlet_data_buffer,
725
- data_allocation.offset << 2, // allocator works in words of 4 bytes
726
- data_buffer
727
- );
728
-
729
- // Write Metadata (Patch & Upload)
730
- this.#write_patched_metadata(
731
- record.metadata_buffer_original,
732
- record.meshlet_count,
733
- record.data.offset,
734
- record.metadata.offset,
735
- this.#meshlet_metadata_buffer
736
- );
737
-
738
- return record;
739
- }
740
-
741
- async debug_dump_metadata() {
742
- const device = this.#device;
743
-
744
- return await inspect_gpu_buffer_typed({
745
- buffer: this.#meshlet_metadata_buffer,
746
- device,
747
- type: MESHLET_METADATA_STRUCT,
748
- count: Math.floor(this.#meshlet_metadata_buffer.size / MESHLET_METADATA_STRUCT.aligned_size),
749
- });
750
- }
751
-
752
- get gpu_memory_usage_occupied() {
753
- let result = 0;
754
-
755
- for (const record of this.#records) {
756
- result += record.data_size * 4;
757
- }
758
-
759
- return result;
760
- }
761
-
762
- get gpu_memory_usage() {
763
- let result = 0;
764
-
765
- if (this.#meshlet_data_buffer) {
766
- result += this.#meshlet_data_buffer.size;
767
- }
768
-
769
- if (this.#meshlet_metadata_buffer) {
770
- result += this.#meshlet_metadata_buffer.size;
771
- }
772
-
773
- return result;
774
- }
1
+ import { assert } from "../../../../core/assert.js";
2
+ import { ALLOCATOR_NO_SPACE, OffsetAllocator } from "../../../../core/binary/allocator/OffsetAllocator.js";
3
+ import { BinaryBuffer } from "../../../../core/binary/BinaryBuffer.js";
4
+ import { platform_compute_endianness } from "../../../../core/binary/platform_compute_endianness.js";
5
+ import Signal from "../../../../core/events/signal/Signal.js";
6
+ import { MEGABYTE } from "../../../../core/science/units/memory/MEGABYTE.js";
7
+ import { read_wgsl_type_value } from "../../shader/type/serde/read_gpu_typed_buffer.js";
8
+ import { write_wgsl_type_value } from "../../shader/type/serde/write_gpu_typed_buffer.js";
9
+ import { u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
10
+ import { inspect_gpu_buffer_typed } from "../../buffer/inspect_gpu_buffer_typed.js";
11
+ import {
12
+ BUFFER_INTRA_COPY_REGION_STRUCT,
13
+ shader_buffer_intra_copy_regions
14
+ } from "../../buffer/util/shader_buffer_intra_copy_regions.js";
15
+ import { MESHLET_METADATA_STRUCT } from "./encoding/MESHLET_METADATA_STRUCT.js";
16
+ import { shader_meshlet_metadata_address_patch } from "./shader_meshlet_metadata_address_patch.js";
17
+
18
+ /**
19
+ * In meshlets
20
+ * @type {number}
21
+ */
22
+ const DEFAULT_METADATA_CAPACITY = 128 * 1024;
23
+
24
+ /**
25
+ * In bytes
26
+ * @type {number}
27
+ */
28
+ const DEFAULT_DATA_CAPACITY = 32 * MEGABYTE;
29
+
30
+ class MeshletBatchAllocationRecord {
31
+ /**
32
+ * @type {Allocation}
33
+ */
34
+ metadata
35
+ /**
36
+ * @type {Allocation}
37
+ */
38
+ data
39
+
40
+ /**
41
+ * Number of words
42
+ * @type {number}
43
+ */
44
+ data_size = 0;
45
+
46
+ /**
47
+ * Meshlet count
48
+ * @type {number}
49
+ */
50
+ meshlet_count = 0;
51
+
52
+ /**
53
+ *
54
+ * @type {ArrayBuffer}
55
+ */
56
+ metadata_buffer_original;
57
+
58
+ /**
59
+ *
60
+ * @type {Signal<>}
61
+ */
62
+ changed = new Signal()
63
+ }
64
+
65
+ export class GPUMeshletManager {
66
+
67
+ /**
68
+ * Allocation sizes in words of 4 bytes
69
+ * @type {OffsetAllocator}
70
+ */
71
+ #allocator_data = new OffsetAllocator(1024);
72
+
73
+ /**
74
+ * Allocation sizes in meshlets
75
+ * @type {OffsetAllocator}
76
+ */
77
+ #allocator_metadata = new OffsetAllocator(1024);
78
+
79
+ /**
80
+ * @type {GPUBuffer}
81
+ */
82
+ #meshlet_data_buffer;
83
+
84
+ get buffer_data() {
85
+ return this.#meshlet_data_buffer;
86
+ }
87
+
88
+ /**
89
+ * @type {GPUBuffer}
90
+ */
91
+ #meshlet_metadata_buffer;
92
+
93
+ get buffer_metadata() {
94
+ return this.#meshlet_metadata_buffer;
95
+ }
96
+
97
+ /**
98
+ * @type {GPUDevice}
99
+ */
100
+ #device;
101
+
102
+ /**
103
+ *
104
+ * @type {number}
105
+ */
106
+ #deallocations_since_last_rebuild = 0;
107
+
108
+ /**
109
+ *
110
+ * @type {MeshletBatchAllocationRecord[]}
111
+ */
112
+ #records = [];
113
+
114
+ /**
115
+ * Queue of GPU-side clones that have been allocated but not yet
116
+ * copied + patched. Drained by {@link update}. Each entry holds the
117
+ * minimum bookkeeping needed to issue two `copyBufferToBuffer`
118
+ * commands and N `(record_index, address_delta)` patch entries.
119
+ *
120
+ * Entries name the two records, not the addresses they held when the
121
+ * clone was queued: a {@link compact} or {@link grow} in between
122
+ * migrates both, and {@link update} reads where they ended up. What
123
+ * an entry does require is that neither record is handed back to
124
+ * {@link remove_batch} before the flush.
125
+ *
126
+ * @type {{
127
+ * source: MeshletBatchAllocationRecord,
128
+ * clone: MeshletBatchAllocationRecord,
129
+ * }[]}
130
+ */
131
+ #pending_clones = [];
132
+
133
+ /**
134
+ *
135
+ * @param {GPUDevice} device
136
+ * @param {number} [initial_data_capacity] in bytes
137
+ * @param {number} [initial_metadata_capacity] in meshlets
138
+ */
139
+ constructor({
140
+ device,
141
+ initial_data_capacity = DEFAULT_DATA_CAPACITY,
142
+ initial_metadata_capacity = DEFAULT_METADATA_CAPACITY,
143
+ }) {
144
+ assert.defined(device, 'device');
145
+
146
+ assert.isNonNegativeInteger(initial_data_capacity, 'initial_data_capacity');
147
+ assert.isNonNegativeInteger(initial_metadata_capacity, 'initial_metadata_capacity');
148
+
149
+ assert.greaterThan(initial_data_capacity, 0, 'initial_data_capacity');
150
+ assert.greaterThan(initial_metadata_capacity, 0, 'initial_data_capacity');
151
+
152
+ this.#device = device;
153
+
154
+ const data_buffer_size_words = Math.ceil(initial_data_capacity / 4);
155
+
156
+ // TODO manage max-allocations as well
157
+ this.#allocator_data = new OffsetAllocator(data_buffer_size_words);
158
+ this.#allocator_metadata = new OffsetAllocator(initial_metadata_capacity);
159
+
160
+ this.#meshlet_data_buffer = device.createBuffer({
161
+ label: 'GPUMeshletManager/data',
162
+ size: data_buffer_size_words * 4,
163
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
164
+ });
165
+
166
+ this.#meshlet_metadata_buffer = device.createBuffer({
167
+ label: 'GPUMeshletManager/metadata',
168
+ size: initial_metadata_capacity * MESHLET_METADATA_STRUCT.aligned_size,
169
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
170
+ });
171
+
172
+ }
173
+
174
+ compact() {
175
+ this.#rebuild(
176
+ this.#meshlet_data_buffer.size >>> 2,
177
+ Math.ceil(this.#meshlet_metadata_buffer.size / MESHLET_METADATA_STRUCT.aligned_size)
178
+ );
179
+ }
180
+
181
+ /**
182
+ *
183
+ * @param {number} extra_data_size in words (4 bytes)
184
+ * @param {number} extra_metadata_size in metadata elements
185
+ */
186
+ grow(extra_data_size, extra_metadata_size) {
187
+ assert.isNonNegativeInteger(extra_data_size, 'extra_data_size');
188
+ assert.isNonNegativeInteger(extra_metadata_size, 'extra_metadata_size');
189
+
190
+ if (extra_data_size === 0 && extra_metadata_size === 0) {
191
+ // nothing to do
192
+ return;
193
+ }
194
+
195
+ const current_data_buffer_size_bytes = this.#meshlet_data_buffer.size;
196
+ const current_metadata_buffer_size = this.#meshlet_metadata_buffer.size;
197
+
198
+ const current_buffer_size_words = current_data_buffer_size_bytes >> 2;
199
+ const current_metadata_buffer_size_elements = Math.ceil(current_metadata_buffer_size / MESHLET_METADATA_STRUCT.aligned_size);
200
+
201
+ const DATA_GROWTH_STEP = 16 * 1024;
202
+ const METADATA_GROWTH_STEP = 256;
203
+ const GROWTH_MULTIPLIER = 1.25;
204
+
205
+ let new_data_size;
206
+ if (extra_data_size === 0) {
207
+ new_data_size = current_buffer_size_words;
208
+ } else {
209
+ // grow
210
+ new_data_size = Math.max(
211
+ current_buffer_size_words + extra_data_size,
212
+ current_buffer_size_words + DATA_GROWTH_STEP,
213
+ Math.ceil(current_buffer_size_words * GROWTH_MULTIPLIER)
214
+ );
215
+ }
216
+
217
+ let new_metadata_size;
218
+ if (extra_metadata_size === 0) {
219
+ new_metadata_size = current_metadata_buffer_size_elements;
220
+ } else {
221
+ // grow
222
+ new_metadata_size = Math.max(
223
+ current_metadata_buffer_size_elements + extra_metadata_size,
224
+ current_metadata_buffer_size_elements + METADATA_GROWTH_STEP,
225
+ current_metadata_buffer_size_elements * GROWTH_MULTIPLIER
226
+ );
227
+ }
228
+
229
+ this.#rebuild(
230
+ new_data_size,
231
+ new_metadata_size
232
+ );
233
+ }
234
+
235
+ /**
236
+ * Helper to patch pointers in metadata and upload to GPU
237
+ * @param {ArrayBuffer} source_array_buffer
238
+ * @param {number} meshlet_count
239
+ * @param {number} data_offset_words
240
+ * @param {number} metadata_offset_index
241
+ * @param {GPUBuffer} target_buffer
242
+ */
243
+ #write_patched_metadata(
244
+ source_array_buffer,
245
+ meshlet_count,
246
+ data_offset_words,
247
+ metadata_offset_index,
248
+ target_buffer
249
+ ) {
250
+ const temp_writer = BinaryBuffer.fromEndianness(platform_compute_endianness());
251
+ temp_writer.setCapacity(meshlet_count * MESHLET_METADATA_STRUCT.size); // pre-allocate to avoid resizing
252
+
253
+ const source_reader = BinaryBuffer.fromArrayBuffer(source_array_buffer);
254
+ source_reader.endianness = platform_compute_endianness();
255
+
256
+ // Iterate over meshlets in the batch, patch the address, and write to temp buffer
257
+ for (let i = 0; i < meshlet_count; i++) {
258
+ const header = read_wgsl_type_value(source_reader, MESHLET_METADATA_STRUCT);
259
+
260
+ // PATCH: Add the allocation offset to the relative address
261
+ header.address += data_offset_words;
262
+
263
+ write_wgsl_type_value(header, temp_writer, MESHLET_METADATA_STRUCT);
264
+ }
265
+
266
+ // Upload
267
+ this.#device.queue.writeBuffer(
268
+ target_buffer,
269
+ metadata_offset_index * MESHLET_METADATA_STRUCT.aligned_size,
270
+ temp_writer.data,
271
+ 0,
272
+ meshlet_count * MESHLET_METADATA_STRUCT.aligned_size
273
+ );
274
+ }
275
+
276
+ /**
277
+ *
278
+ * @param {number} new_data_size
279
+ * @param {number} new_metadata_size
280
+ */
281
+ #rebuild(
282
+ new_data_size,
283
+ new_metadata_size
284
+ ) {
285
+
286
+ assert.isNonNegativeInteger(new_data_size, 'new_data_size');
287
+ assert.isNonNegativeInteger(new_metadata_size, 'new_metadata_size');
288
+
289
+ // Outstanding clone work survives this. A queued clone names its
290
+ // source and destination records, both of which are migrated
291
+ // below, and the flush reads their addresses when it runs — so a
292
+ // rebuild moves the copy rather than invalidating it.
293
+
294
+ // console.warn('GPUMeshletManager/rebuild');
295
+
296
+ const device = this.#device;
297
+
298
+ const records = this.#records;
299
+ const record_count = records.length;
300
+ const new_allocator_data = new OffsetAllocator(
301
+ new_data_size,
302
+ Math.max(record_count * 2, 128 * 1024)
303
+ );
304
+
305
+ const old_buffer_data = this.#meshlet_data_buffer;
306
+ const new_buffer_data = device.createBuffer({
307
+ label: old_buffer_data.label,
308
+ usage: old_buffer_data.usage,
309
+ size: new_data_size * 4,
310
+ });
311
+
312
+ const new_allocator_metadata = new OffsetAllocator(
313
+ new_metadata_size,
314
+ Math.max(record_count * 2, 128 * 1024)
315
+ );
316
+
317
+ const old_buffer_metadata = this.#meshlet_metadata_buffer;
318
+ const new_buffer_metadata = device.createBuffer({
319
+ label: old_buffer_metadata.label,
320
+ usage: old_buffer_metadata.usage,
321
+ size: new_metadata_size * MESHLET_METADATA_STRUCT.aligned_size,
322
+ });
323
+
324
+ const encoder = device.createCommandEncoder({
325
+ label: "GPUMeshletManager/rebuild",
326
+ });
327
+
328
+ const moved_records = [];
329
+
330
+ // move data
331
+ for (let i = 0; i < record_count; i++) {
332
+ const record = records[i];
333
+
334
+ const new_allocation_data = new_allocator_data.allocate(record.data_size);
335
+ const new_allocation_metadata = new_allocator_metadata.allocate(record.meshlet_count);
336
+
337
+ assert.notEqual(new_allocation_data.offset, ALLOCATOR_NO_SPACE);
338
+ assert.notEqual(new_allocation_metadata.offset, ALLOCATOR_NO_SPACE);
339
+
340
+ encoder.copyBufferToBuffer(
341
+ old_buffer_data, record.data.offset * 4,
342
+ new_buffer_data, new_allocation_data.offset * 4,
343
+ record.data_size * 4
344
+ );
345
+
346
+ const metadata_record_moved = record.data.offset !== new_allocation_data.offset;
347
+
348
+ // B. Patch Metadata (CPU -> GPU Write)
349
+ // Metadata contains `address` which points to the Data offset.
350
+ // Since the Data offset changed, we must re-calculate and upload the metadata.
351
+ // We reuse the helper to avoid code duplication.
352
+ this.#write_patched_metadata(
353
+ record.metadata_buffer_original,
354
+ record.meshlet_count,
355
+ new_allocation_data.offset, // The NEW data offset
356
+ new_allocation_metadata.offset, // The NEW metadata location
357
+ new_buffer_metadata
358
+ );
359
+
360
+ // update record
361
+ record.data = new_allocation_data;
362
+ record.metadata = new_allocation_metadata;
363
+
364
+ if (metadata_record_moved) {
365
+ moved_records.push(record);
366
+ }
367
+ }
368
+
369
+ for (const record of moved_records) {
370
+ // notify
371
+ record.changed.send0();
372
+ }
373
+
374
+ const command_buffer = encoder.finish();
375
+
376
+ device.queue.submit([command_buffer]);
377
+
378
+ old_buffer_data.destroy();
379
+ old_buffer_metadata.destroy();
380
+
381
+ this.#meshlet_data_buffer = new_buffer_data;
382
+ this.#meshlet_metadata_buffer = new_buffer_metadata;
383
+
384
+ this.#allocator_data = new_allocator_data;
385
+ this.#allocator_metadata = new_allocator_metadata;
386
+
387
+ // reset count
388
+ this.#deallocations_since_last_rebuild = 0;
389
+ }
390
+
391
+ /**
392
+ * Allocate `data_size_words` in the data buffer and `meshlet_count`
393
+ * in the metadata buffer, retrying with compact-then-grow on failure.
394
+ * Throws if the third attempt still can't satisfy both. The two
395
+ * allocations are kept in lockstep — partial successes are released
396
+ * before retrying so we never leak.
397
+ *
398
+ * Compaction triggers a full {@link #rebuild} which migrates every
399
+ * existing allocation to a new buffer pair. Queued clone work is
400
+ * carried through it — {@link #pending_clones} names records, not
401
+ * addresses — so an allocation made while a clone is pending is safe.
402
+ *
403
+ * @param {number} data_size_words
404
+ * @param {number} meshlet_count
405
+ * @returns {{data: Allocation, metadata: Allocation}}
406
+ */
407
+ #allocate_data_and_metadata(data_size_words, meshlet_count) {
408
+ let data_allocation;
409
+ let metadata_allocation;
410
+
411
+ for (let try_index = 0; try_index < 3; try_index++) {
412
+
413
+ data_allocation = this.#allocator_data.allocate(data_size_words);
414
+ metadata_allocation = this.#allocator_metadata.allocate(meshlet_count);
415
+
416
+ const data_ok = data_allocation.offset !== ALLOCATOR_NO_SPACE;
417
+ const metadata_ok = metadata_allocation.offset !== ALLOCATOR_NO_SPACE;
418
+
419
+ if (data_ok && metadata_ok) {
420
+ break;
421
+ }
422
+
423
+ // release partial allocation
424
+ if (data_ok) {
425
+ this.#allocator_data.free(data_allocation);
426
+
427
+ data_allocation.offset = ALLOCATOR_NO_SPACE;
428
+ data_allocation.metadata = ALLOCATOR_NO_SPACE;
429
+ }
430
+ if (metadata_ok) {
431
+ this.#allocator_metadata.free(metadata_allocation);
432
+
433
+ metadata_allocation.offset = ALLOCATOR_NO_SPACE;
434
+ metadata_allocation.metadata = ALLOCATOR_NO_SPACE;
435
+ }
436
+
437
+ if (this.#deallocations_since_last_rebuild > 0) {
438
+ // try compaction
439
+ this.compact();
440
+ continue;
441
+ }
442
+
443
+ let grow_data_size = data_ok ? 0 : data_size_words;
444
+ let grow_metadata_size = metadata_ok ? 0 : meshlet_count;
445
+
446
+ this.grow(grow_data_size, grow_metadata_size);
447
+
448
+ }
449
+
450
+ if (data_allocation.offset === ALLOCATOR_NO_SPACE || metadata_allocation.offset === ALLOCATOR_NO_SPACE) {
451
+ throw new Error('Failed to allocate GPU memory for meshlet batch');
452
+ }
453
+
454
+ return { data: data_allocation, metadata: metadata_allocation };
455
+ }
456
+
457
+ /**
458
+ *
459
+ * @param {MeshletBatch} batch
460
+ * @returns {MeshletBatchAllocationRecord}
461
+ */
462
+ #allocate_for_batch(batch) {
463
+ assert.defined(batch, 'batch');
464
+
465
+ const data_buffer = batch.data_buffer;
466
+
467
+ const data_size_words = Math.ceil(data_buffer.byteLength / 4);
468
+
469
+ const meshlet_count = batch.count;
470
+
471
+ const { data, metadata } = this.#allocate_data_and_metadata(data_size_words, meshlet_count);
472
+
473
+ const record = new MeshletBatchAllocationRecord();
474
+
475
+ record.data = data;
476
+ record.metadata = metadata;
477
+
478
+ record.meshlet_count = meshlet_count;
479
+ record.data_size = data_size_words;
480
+
481
+ record.metadata_buffer_original = batch.metadata_buffer;
482
+
483
+ this.#records.push(record);
484
+
485
+ return record;
486
+ }
487
+
488
+ /**
489
+ * Release a batch allocation back to the data and metadata allocators. The GPU bytes are
490
+ * not cleared — the freed slices are reclaimed lazily by the next {@link compact} (a
491
+ * deallocation bumps the compaction counter the allocator consults under pressure).
492
+ * Other batches' allocations are unaffected (the OffsetAllocator frees disjoint slices),
493
+ * so callers only need to ensure nothing still references this batch's metadata rows
494
+ * (the geometry table rebuild in {@link GPUGeometryManager} handles that).
495
+ *
496
+ * @param {MeshletBatchAllocationRecord} record a record returned by {@link add_batch} or
497
+ * {@link clone_batch_allocation}
498
+ * @returns {boolean} true if the record was owned and freed
499
+ */
500
+ remove_batch(record) {
501
+ assert.defined(record, 'record');
502
+
503
+ const index = this.#records.indexOf(record);
504
+ if (index === -1) {
505
+ return false;
506
+ }
507
+
508
+ this.#allocator_data.free(record.data);
509
+ this.#allocator_metadata.free(record.metadata);
510
+
511
+ this.#records.splice(index, 1);
512
+ this.#deallocations_since_last_rebuild++;
513
+
514
+ return true;
515
+ }
516
+
517
+ /**
518
+ * Allocate a fresh (data, metadata) pair sized to mirror `source`,
519
+ * and queue a GPU-side clone: the source's data and metadata are
520
+ * copied into the new ranges via `copyBufferToBuffer`, then a
521
+ * compute pass patches the cloned metadata records' `address`
522
+ * fields to point at the new data range. Both the copies and the
523
+ * patch dispatch land when the host next calls
524
+ * {@link update update(cmd_ctx)}.
525
+ *
526
+ * The returned record can be wired into the geometry table
527
+ * immediately — its `data.offset` and `metadata.offset` are valid
528
+ * the moment this call returns. The GPU bytes at those addresses
529
+ * just aren't populated yet; downstream passes that consume the
530
+ * clone must run after the next `update`. Those addresses may still
531
+ * move before the flush, which is why nothing outside the record is
532
+ * allowed to remember them.
533
+ *
534
+ * Sharing the source's `metadata_buffer_original` lets a future
535
+ * compaction patch the clone's metadata from the same un-patched
536
+ * CPU bytes the source uses — the post-patch state on the GPU is
537
+ * identical to the source aside from the data offset.
538
+ *
539
+ * @param {MeshletBatchAllocationRecord} source
540
+ * @returns {MeshletBatchAllocationRecord}
541
+ */
542
+ clone_batch_allocation(source) {
543
+ assert.defined(source, 'source');
544
+ assert.greaterThan(source.meshlet_count, 0, 'source.meshlet_count');
545
+
546
+ const { data, metadata } = this.#allocate_data_and_metadata(source.data_size, source.meshlet_count);
547
+
548
+ const record = new MeshletBatchAllocationRecord();
549
+
550
+ record.data = data;
551
+ record.metadata = metadata;
552
+
553
+ record.meshlet_count = source.meshlet_count;
554
+ record.data_size = source.data_size;
555
+
556
+ // Share the un-patched CPU bytes so a future compact rewrites
557
+ // the clone's metadata correctly under its new data offset.
558
+ record.metadata_buffer_original = source.metadata_buffer_original;
559
+
560
+ this.#records.push(record);
561
+
562
+ // The queue names the two records rather than the addresses they
563
+ // hold right now. Both are in `#records`, so a rebuild between
564
+ // this call and the flush moves them and the flush still reads
565
+ // where they went — which is what lets a clone be queued in the
566
+ // same frame as the growth another geometry's arrival forces.
567
+ this.#pending_clones.push({ source, clone: record });
568
+
569
+ return record;
570
+ }
571
+
572
+ /**
573
+ * Flush any pending clone work into `cmd_ctx`. For each queued
574
+ * clone, emits two `copyBufferToBuffer` commands (one for vertex
575
+ * data, one for metadata records). After every queued clone's
576
+ * copies are recorded, dispatches a single batched patch shader
577
+ * pass that walks a flat `(record_index, address_delta)` table
578
+ * — N clones → 1 patch dispatch, total threads = sum of clone
579
+ * meshlet counts.
580
+ *
581
+ * Idempotent and cheap when nothing's queued.
582
+ *
583
+ * @param {ShadeGPUCommandContext} cmd_ctx
584
+ */
585
+ update(cmd_ctx) {
586
+ const pending = this.#pending_clones;
587
+ if (pending.length === 0) {
588
+ return;
589
+ }
590
+
591
+ const data_buffer = this.#meshlet_data_buffer;
592
+ const metadata_buffer = this.#meshlet_metadata_buffer;
593
+ const metadata_stride = MESHLET_METADATA_STRUCT.aligned_size;
594
+ const metadata_stride_words = metadata_stride / 4;
595
+
596
+ // WebGPU forbids `copyBufferToBuffer` with src === dst even when
597
+ // ranges don't overlap (the validation check is buffer-identity,
598
+ // not range-overlap), so we relocate via a small compute-shader
599
+ // memcpy bound to the buffer once as `read_write`. Two dispatches
600
+ // — one over the data-buffer regions, one over the metadata-buffer
601
+ // regions — handle every queued clone in a batched pass.
602
+ //
603
+ // Region (src, dst) ranges are non-overlapping by construction:
604
+ // the OffsetAllocator hands out fresh disjoint slices, so the
605
+ // memcpy shader can safely skip the per-thread overlap check.
606
+ const region_size_bytes = BUFFER_INTRA_COPY_REGION_STRUCT.aligned_size;
607
+
608
+ const data_regions_array = new Uint32Array(pending.length * (region_size_bytes / 4));
609
+ const metadata_regions_array = new Uint32Array(pending.length * (region_size_bytes / 4));
610
+
611
+ let total_records = 0;
612
+ for (let i = 0; i < pending.length; i++) {
613
+ const { source, clone } = pending[i];
614
+
615
+ // Region struct is just three u32s — write directly. Order
616
+ // matches the field declaration: src_offset, dst_offset,
617
+ // word_count.
618
+ const region_words = i * 3;
619
+ data_regions_array[region_words] = source.data.offset;
620
+ data_regions_array[region_words + 1] = clone.data.offset;
621
+ data_regions_array[region_words + 2] = clone.data_size;
622
+
623
+ metadata_regions_array[region_words] = source.metadata.offset * metadata_stride_words;
624
+ metadata_regions_array[region_words + 1] = clone.metadata.offset * metadata_stride_words;
625
+ metadata_regions_array[region_words + 2] = clone.meshlet_count * metadata_stride_words;
626
+
627
+ total_records += clone.meshlet_count;
628
+ }
629
+
630
+ const data_regions_buffer = cmd_ctx.allocateTransientBufferAndLoad(
631
+ data_regions_array.buffer,
632
+ GPUBufferUsage.STORAGE,
633
+ );
634
+ shader_buffer_intra_copy_regions.dispatch({
635
+ encoder: cmd_ctx,
636
+ group_count_x: pending.length,
637
+ bindings: {
638
+ buffer: data_buffer,
639
+ regions: data_regions_buffer,
640
+ },
641
+ });
642
+
643
+ const metadata_regions_buffer = cmd_ctx.allocateTransientBufferAndLoad(
644
+ metadata_regions_array.buffer,
645
+ GPUBufferUsage.STORAGE,
646
+ );
647
+ shader_buffer_intra_copy_regions.dispatch({
648
+ encoder: cmd_ctx,
649
+ group_count_x: pending.length,
650
+ bindings: {
651
+ buffer: metadata_buffer,
652
+ regions: metadata_regions_buffer,
653
+ },
654
+ });
655
+
656
+ // Build the flat (record_index, address_delta) table. One pair
657
+ // per cloned meshlet record. Uploaded as a transient storage
658
+ // buffer the patch shader binds for one dispatch.
659
+ const patches_array = new Uint32Array(total_records * 2);
660
+ let p = 0;
661
+ for (let i = 0; i < pending.length; i++) {
662
+ const { source, clone } = pending[i];
663
+
664
+ // Address delta is in u32 word units. When the clone's offset
665
+ // is below the source's, the unsigned subtraction wraps — the
666
+ // patch shader does the matching wrapping add, so the final
667
+ // per-record `address` lands at the right absolute word offset
668
+ // either way.
669
+ const address_delta = (clone.data.offset - source.data.offset) >>> 0;
670
+
671
+ for (let m = 0; m < clone.meshlet_count; m++) {
672
+ patches_array[p++] = clone.metadata.offset + m;
673
+ patches_array[p++] = address_delta;
674
+ }
675
+ }
676
+
677
+ const patches_buffer = cmd_ctx.allocateTransientBufferAndLoad(
678
+ patches_array.buffer,
679
+ GPUBufferUsage.STORAGE,
680
+ );
681
+
682
+ // patch_count uniform is a bare u32 — see
683
+ // shader_meshlet_metadata_address_patch.
684
+ const patch_count_buffer = cmd_ctx.allocateTransientValueBuffer(
685
+ u32,
686
+ total_records,
687
+ );
688
+
689
+ shader_meshlet_metadata_address_patch.dispatchThreads({
690
+ encoder: cmd_ctx,
691
+ thread_count_x: total_records,
692
+ bindings: {
693
+ meshlet_metadata: metadata_buffer,
694
+ patches: patches_buffer,
695
+ patch_count: patch_count_buffer,
696
+ },
697
+ });
698
+
699
+ pending.length = 0;
700
+ }
701
+
702
+ destroy() {
703
+ this.#meshlet_data_buffer.destroy();
704
+ this.#meshlet_metadata_buffer.destroy();
705
+ }
706
+
707
+ /**
708
+ *
709
+ * @param {MeshletBatch} batch
710
+ * @returns {MeshletBatchAllocationRecord}
711
+ */
712
+ add_batch(batch) {
713
+ assert.defined(batch, 'batch');
714
+ assert.notNull(batch, 'batch');
715
+ assert.equal(batch.isMeshletBatch, true, 'batch.isMeshletBatch !== true');
716
+
717
+ const record = this.#allocate_for_batch(batch);
718
+
719
+ // plonk data
720
+ const data_buffer = batch.data_buffer;
721
+ const data_allocation = record.data;
722
+
723
+ this.#device.queue.writeBuffer(
724
+ this.#meshlet_data_buffer,
725
+ data_allocation.offset << 2, // allocator works in words of 4 bytes
726
+ data_buffer
727
+ );
728
+
729
+ // Write Metadata (Patch & Upload)
730
+ this.#write_patched_metadata(
731
+ record.metadata_buffer_original,
732
+ record.meshlet_count,
733
+ record.data.offset,
734
+ record.metadata.offset,
735
+ this.#meshlet_metadata_buffer
736
+ );
737
+
738
+ return record;
739
+ }
740
+
741
+ async debug_dump_metadata() {
742
+ const device = this.#device;
743
+
744
+ return await inspect_gpu_buffer_typed({
745
+ buffer: this.#meshlet_metadata_buffer,
746
+ device,
747
+ type: MESHLET_METADATA_STRUCT,
748
+ count: Math.floor(this.#meshlet_metadata_buffer.size / MESHLET_METADATA_STRUCT.aligned_size),
749
+ });
750
+ }
751
+
752
+ get gpu_memory_usage_occupied() {
753
+ let result = 0;
754
+
755
+ for (const record of this.#records) {
756
+ result += record.data_size * 4;
757
+ }
758
+
759
+ return result;
760
+ }
761
+
762
+ get gpu_memory_usage() {
763
+ let result = 0;
764
+
765
+ if (this.#meshlet_data_buffer) {
766
+ result += this.#meshlet_data_buffer.size;
767
+ }
768
+
769
+ if (this.#meshlet_metadata_buffer) {
770
+ result += this.#meshlet_metadata_buffer.size;
771
+ }
772
+
773
+ return result;
774
+ }
775
775
  }