@woosh/meep-engine 3.22.0 → 3.23.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 (267) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/hash-grid/PointHashGrid3.d.ts +118 -0
  3. package/src/core/geom/3d/hash-grid/PointHashGrid3.d.ts.map +1 -0
  4. package/src/core/geom/3d/hash-grid/PointHashGrid3.js +283 -0
  5. package/src/core/geom/3d/shape/BoxShape3D.d.ts.map +1 -1
  6. package/src/core/geom/3d/shape/BoxShape3D.js +6 -17
  7. package/src/core/geom/3d/shape/CapsuleShape3D.d.ts.map +1 -1
  8. package/src/core/geom/3d/shape/CapsuleShape3D.js +3 -14
  9. package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
  10. package/src/core/geom/3d/shape/CylinderShape3D.js +3 -21
  11. package/src/core/geom/3d/shape/PlaneShape3D.d.ts +105 -0
  12. package/src/core/geom/3d/shape/PlaneShape3D.d.ts.map +1 -0
  13. package/src/core/geom/3d/shape/PlaneShape3D.js +251 -0
  14. package/src/core/geom/3d/shape/SphereShape3D.js +3 -3
  15. package/src/core/geom/3d/shape/json/shape_to_type.d.ts.map +1 -1
  16. package/src/core/geom/3d/shape/json/shape_to_type.js +3 -0
  17. package/src/core/geom/3d/shape/json/type_adapters.d.ts +14 -0
  18. package/src/core/geom/3d/shape/json/type_adapters.d.ts.map +1 -1
  19. package/src/core/geom/3d/shape/json/type_adapters.js +15 -0
  20. package/src/core/geom/3d/shape/sdf/sdf3_box.d.ts +51 -0
  21. package/src/core/geom/3d/shape/sdf/sdf3_box.d.ts.map +1 -0
  22. package/src/core/geom/3d/shape/sdf/sdf3_box.js +113 -0
  23. package/src/core/geom/3d/shape/sdf/sdf3_capsule.d.ts +46 -0
  24. package/src/core/geom/3d/shape/sdf/sdf3_capsule.d.ts.map +1 -0
  25. package/src/core/geom/3d/shape/sdf/sdf3_capsule.js +82 -0
  26. package/src/core/geom/3d/shape/sdf/sdf3_cylinder.d.ts +60 -0
  27. package/src/core/geom/3d/shape/sdf/sdf3_cylinder.d.ts.map +1 -0
  28. package/src/core/geom/3d/shape/sdf/sdf3_cylinder.js +126 -0
  29. package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.d.ts +50 -0
  30. package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.d.ts.map +1 -0
  31. package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.js +53 -0
  32. package/src/core/geom/3d/shape/sdf/sdf3_plane.d.ts +52 -0
  33. package/src/core/geom/3d/shape/sdf/sdf3_plane.d.ts.map +1 -0
  34. package/src/core/geom/3d/shape/sdf/sdf3_plane.js +61 -0
  35. package/src/core/geom/3d/shape/sdf/sdf3_sphere.d.ts +41 -0
  36. package/src/core/geom/3d/shape/sdf/sdf3_sphere.d.ts.map +1 -0
  37. package/src/core/geom/3d/shape/sdf/sdf3_sphere.js +71 -0
  38. package/src/core/process/ConcurrencyGate.d.ts +103 -0
  39. package/src/core/process/ConcurrencyGate.d.ts.map +1 -0
  40. package/src/core/process/ConcurrencyGate.js +207 -0
  41. package/src/engine/asset/AssetManager.d.ts +10 -0
  42. package/src/engine/asset/AssetManager.d.ts.map +1 -1
  43. package/src/engine/asset/AssetManager.js +10 -0
  44. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +30 -13
  45. package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts +26 -0
  46. package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts.map +1 -0
  47. package/src/engine/graphics3/pose/collect_entity_playbacks.js +40 -0
  48. package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts.map +1 -1
  49. package/src/engine/graphics3/pose/query_entity_node_world_pose.js +74 -101
  50. package/src/engine/physics/cloth/MEASUREMENTS.md +1287 -4
  51. package/src/engine/physics/cloth/PLAN.md +1967 -1581
  52. package/src/engine/physics/cloth/build/ClothProxy.d.ts +147 -0
  53. package/src/engine/physics/cloth/build/ClothProxy.d.ts.map +1 -0
  54. package/src/engine/physics/cloth/build/ClothProxy.js +207 -0
  55. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts +70 -0
  56. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts.map +1 -0
  57. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.js +256 -0
  58. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +176 -0
  59. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -0
  60. package/src/engine/physics/cloth/collider/ClothColliderIndex.js +435 -0
  61. package/src/engine/physics/cloth/collider/ClothColliderKind.d.ts +12 -0
  62. package/src/engine/physics/cloth/collider/ClothColliderKind.d.ts.map +1 -0
  63. package/src/engine/physics/cloth/collider/ClothColliderKind.js +38 -0
  64. package/src/engine/physics/cloth/collider/ClothColliderRecord.d.ts +120 -0
  65. package/src/engine/physics/cloth/collider/ClothColliderRecord.d.ts.map +1 -0
  66. package/src/engine/physics/cloth/collider/ClothColliderRecord.js +137 -0
  67. package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts +25 -0
  68. package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts.map +1 -0
  69. package/src/engine/physics/cloth/collider/cloth_collider_bounds.js +190 -0
  70. package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts +44 -0
  71. package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts.map +1 -0
  72. package/src/engine/physics/cloth/collider/cloth_collider_pack.js +173 -0
  73. package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts +94 -0
  74. package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts.map +1 -0
  75. package/src/engine/physics/cloth/collider/cloth_collider_sdf.js +275 -0
  76. package/src/engine/physics/cloth/ecs/ClothCollider.d.ts +126 -0
  77. package/src/engine/physics/cloth/ecs/ClothCollider.d.ts.map +1 -0
  78. package/src/engine/physics/cloth/ecs/ClothCollider.js +199 -0
  79. package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts +9 -0
  80. package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts.map +1 -0
  81. package/src/engine/physics/cloth/ecs/ClothColliderFlags.js +32 -0
  82. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts +114 -0
  83. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts.map +1 -0
  84. package/src/engine/physics/cloth/ecs/ClothColliderSystem.js +243 -0
  85. package/src/engine/physics/cloth/ecs/ClothDynamicsFlags.js +66 -57
  86. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +109 -1
  87. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  88. package/src/engine/physics/cloth/ecs/ClothInstance.js +479 -363
  89. package/src/engine/physics/cloth/ecs/ClothRig.d.ts +95 -0
  90. package/src/engine/physics/cloth/ecs/ClothRig.d.ts.map +1 -0
  91. package/src/engine/physics/cloth/ecs/ClothRig.js +140 -0
  92. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +30 -1
  93. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
  94. package/src/engine/physics/cloth/ecs/ClothSystem.js +1021 -773
  95. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts +90 -0
  96. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -0
  97. package/src/engine/physics/cloth/ecs/cloth_build_rows.js +374 -0
  98. package/src/engine/physics/cloth/ecs/cloth_dynamics_library.d.ts +32 -3
  99. package/src/engine/physics/cloth/ecs/cloth_dynamics_library.d.ts.map +1 -1
  100. package/src/engine/physics/cloth/ecs/cloth_dynamics_library.js +283 -254
  101. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +59 -0
  102. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -0
  103. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +328 -0
  104. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts +13 -0
  105. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts.map +1 -0
  106. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.js +288 -0
  107. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +58 -0
  108. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -0
  109. package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +199 -0
  110. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts +8 -63
  111. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
  112. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +317 -645
  113. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts +61 -0
  114. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts.map +1 -1
  115. package/src/engine/physics/cloth/ecs/cloth_write_back.js +212 -449
  116. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts +49 -0
  117. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts.map +1 -0
  118. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.js +115 -0
  119. package/src/engine/physics/cloth/playground/README.md +155 -1
  120. package/src/engine/physics/cloth/playground/collide.html +178 -0
  121. package/src/engine/physics/cloth/playground/collide_build.d.ts +95 -0
  122. package/src/engine/physics/cloth/playground/collide_build.d.ts.map +1 -0
  123. package/src/engine/physics/cloth/playground/collide_build.js +315 -0
  124. package/src/engine/physics/cloth/playground/collide_main.d.ts +2 -0
  125. package/src/engine/physics/cloth/playground/collide_main.d.ts.map +1 -0
  126. package/src/engine/physics/cloth/playground/collide_main.js +720 -0
  127. package/src/engine/physics/cloth/playground/garment.html +182 -0
  128. package/src/engine/physics/cloth/playground/garment_build.d.ts +78 -0
  129. package/src/engine/physics/cloth/playground/garment_build.d.ts.map +1 -0
  130. package/src/engine/physics/cloth/playground/garment_build.js +359 -0
  131. package/src/engine/physics/cloth/playground/garment_main.d.ts +2 -0
  132. package/src/engine/physics/cloth/playground/garment_main.d.ts.map +1 -0
  133. package/src/engine/physics/cloth/playground/garment_main.js +633 -0
  134. package/src/engine/physics/cloth/solver/ClothState.d.ts +208 -5
  135. package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
  136. package/src/engine/physics/cloth/solver/ClothState.js +302 -8
  137. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts +87 -0
  138. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts.map +1 -0
  139. package/src/engine/physics/cloth/solver/cloth_contact_find.js +343 -0
  140. package/src/engine/physics/cloth/solver/cloth_self_find.d.ts +112 -0
  141. package/src/engine/physics/cloth/solver/cloth_self_find.d.ts.map +1 -0
  142. package/src/engine/physics/cloth/solver/cloth_self_find.js +448 -0
  143. package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts +4 -1
  144. package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts.map +1 -1
  145. package/src/engine/physics/cloth/solver/cloth_solve_vertex.js +16 -1
  146. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +112 -5
  147. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
  148. package/src/engine/physics/cloth/solver/cloth_solver_constants.js +117 -6
  149. package/src/engine/physics/cloth/solver/cloth_step.d.ts +40 -9
  150. package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
  151. package/src/engine/physics/cloth/solver/cloth_step.js +122 -10
  152. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts +95 -0
  153. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts.map +1 -0
  154. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.js +285 -0
  155. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts +129 -0
  156. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts.map +1 -0
  157. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.js +501 -0
  158. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts +75 -0
  159. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts.map +1 -0
  160. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.js +202 -0
  161. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  162. package/src/shade/playground/vgeo_viewer/README.md +18 -0
  163. package/src/shade/playground/vgeo_viewer/main.js +87 -50
  164. package/src/shade/renderer/buffer/table/GPUDatabase.d.ts.map +1 -1
  165. package/src/shade/renderer/buffer/table/GPUDatabase.js +191 -62
  166. package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts +94 -2
  167. package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
  168. package/src/shade/renderer/buffer/table/GPUTypedTable.js +414 -2
  169. package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts +14 -0
  170. package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts.map +1 -0
  171. package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.js +13 -0
  172. package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.d.ts +17 -0
  173. package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.d.ts.map +1 -0
  174. package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.js +116 -0
  175. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts +9 -4
  176. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
  177. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +843 -734
  178. package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.d.ts +12 -0
  179. package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.d.ts.map +1 -1
  180. package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.js +13 -0
  181. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts +17 -0
  182. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
  183. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +185 -30
  184. package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +17 -0
  185. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +141 -24
  186. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +204 -64
  187. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +110 -10
  188. package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +19 -0
  189. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts +19 -6
  190. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts.map +1 -1
  191. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.js +26 -7
  192. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts +173 -14
  193. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts.map +1 -1
  194. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.js +458 -51
  195. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts +49 -13
  196. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts.map +1 -1
  197. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.js +52 -14
  198. package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.d.ts +20 -0
  199. package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.d.ts.map +1 -0
  200. package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.js +19 -0
  201. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.d.ts +27 -0
  202. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.d.ts.map +1 -0
  203. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.js +48 -0
  204. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.d.ts +29 -0
  205. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.d.ts.map +1 -0
  206. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.js +60 -0
  207. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts +8 -1
  208. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts.map +1 -1
  209. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.js +32 -2
  210. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts +258 -0
  211. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts.map +1 -0
  212. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.js +723 -0
  213. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts +49 -0
  214. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts.map +1 -0
  215. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.js +50 -0
  216. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  217. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  218. package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.d.ts +3 -0
  219. package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.d.ts.map +1 -1
  220. package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.js +121 -21
  221. package/src/shade/renderer/texture/virtual/README.md +6 -4
  222. package/src/shade/renderer/texture/virtual/VirtualTextureManager.d.ts.map +1 -1
  223. package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +7 -0
  224. package/src/shade/renderer/texture/virtual/source/VTSourceTiled.d.ts +3 -3
  225. package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +3 -3
  226. package/src/engine/ecs/system/SystemExecutionPhase.d.ts +0 -9
  227. package/src/engine/ecs/system/SystemExecutionPhase.d.ts.map +0 -1
  228. package/src/engine/graphics/texture/virtual/NOTES.md +0 -128
  229. package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.d.ts +0 -6
  230. package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.d.ts.map +0 -1
  231. package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.js +0 -5
  232. package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts +0 -49
  233. package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts.map +0 -1
  234. package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.js +0 -264
  235. package/src/engine/graphics/texture/virtual/VirtualTextureUsage.d.ts +0 -44
  236. package/src/engine/graphics/texture/virtual/VirtualTextureUsage.d.ts.map +0 -1
  237. package/src/engine/graphics/texture/virtual/VirtualTextureUsage.js +0 -228
  238. package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.d.ts +0 -3
  239. package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.d.ts.map +0 -1
  240. package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.js +0 -73
  241. package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.d.ts +0 -14
  242. package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.d.ts.map +0 -1
  243. package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.js +0 -85
  244. package/src/engine/graphics/texture/virtual/debug/UsageDebugView.d.ts +0 -12
  245. package/src/engine/graphics/texture/virtual/debug/UsageDebugView.d.ts.map +0 -1
  246. package/src/engine/graphics/texture/virtual/debug/UsageDebugView.js +0 -63
  247. package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.d.ts +0 -12
  248. package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.d.ts.map +0 -1
  249. package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.js +0 -243
  250. package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.d.ts +0 -30
  251. package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.d.ts.map +0 -1
  252. package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.js +0 -39
  253. package/src/engine/graphics/texture/virtual/tile/compose_finger_print.d.ts +0 -9
  254. package/src/engine/graphics/texture/virtual/tile/compose_finger_print.d.ts.map +0 -1
  255. package/src/engine/graphics/texture/virtual/tile/compose_finger_print.js +0 -24
  256. package/src/engine/graphics/texture/virtual/tile/compose_tile_address.d.ts +0 -9
  257. package/src/engine/graphics/texture/virtual/tile/compose_tile_address.d.ts.map +0 -1
  258. package/src/engine/graphics/texture/virtual/tile/compose_tile_address.js +0 -26
  259. package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.d.ts +0 -11
  260. package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.d.ts.map +0 -1
  261. package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.js +0 -12
  262. package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.d.ts +0 -7
  263. package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.d.ts.map +0 -1
  264. package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.js +0 -16
  265. package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.d.ts +0 -7
  266. package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.d.ts.map +0 -1
  267. package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.js +0 -35
@@ -5,12 +5,15 @@ import { platform_compute_endianness } from "../../../../core/binary/platform_co
5
5
  import { read_wgsl_type_value } from "../../shader/type/serde/read_gpu_typed_buffer.js";
6
6
  import { write_wgsl_type_value } from "../../shader/type/serde/write_gpu_typed_buffer.js";
7
7
  import { u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
8
+ import { GPU_DATABASE_BUFFER_ELEMENT_TYPE } from "./GPU_DATABASE_BUFFER_ELEMENT_TYPE.js";
8
9
  import { GPU_DATABASE_UPLOAD_BUFFER_LIMIT } from "./GPU_DATABASE_UPLOAD_BUFFER_LIMIT.js";
10
+ import { GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS } from "./GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.js";
9
11
  import { GPUPageMetadata } from "./GPUPageMetadata.js";
10
12
 
11
13
  /**
12
14
  * @typedef {import("./GPUTypedTableDescriptor.js").GPUTypedTableDescriptor} GPUTypedTableDescriptor
13
15
  * @typedef {import("./PageBufferPool.js").PageBufferPool} PageBufferPool
16
+ * @typedef {import("../../shader/type/WebGPUType.js").WebGPUType} WebGPUType
14
17
  */
15
18
 
16
19
  const SCRATCH_BUFFER = new BinaryBuffer();
@@ -20,6 +23,37 @@ const SCRATCH_BUFFER = new BinaryBuffer();
20
23
  // (e.g. the upload buffer, where the value section follows the u32 element id).
21
24
  const READ_STAGE_BUFFER = BinaryBuffer.fromEndianness(platform_compute_endianness());
22
25
 
26
+ // Stages one field / sub-struct for `set_range`, at position 0 so `write_wgsl_type_value`'s
27
+ // top-level alignment round-up is a no-op.
28
+ const WRITE_STAGE_BUFFER = BinaryBuffer.fromEndianness(platform_compute_endianness());
29
+
30
+ /**
31
+ * Byte stride of one entry in {@link GPUTypedTable.partial_upload_descriptors}.
32
+ * @type {number}
33
+ */
34
+ const PARTIAL_DESCRIPTOR_BYTES = GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS * GPU_DATABASE_BUFFER_ELEMENT_TYPE.size;
35
+
36
+ /**
37
+ * Index of the `word_count` word within one partial-upload descriptor — the one word retiring a
38
+ * descriptor has to reach.
39
+ * @type {number}
40
+ */
41
+ const PARTIAL_DESCRIPTOR_WORD_COUNT = 2;
42
+
43
+ /**
44
+ * A staging buffer that holds nothing until something is written to it. `BinaryBuffer` allocates a
45
+ * kilobyte up front, and a table that never takes a partial write must not pay for two of those.
46
+ *
47
+ * @returns {BinaryBuffer}
48
+ */
49
+ function empty_upload_buffer() {
50
+ const buffer = BinaryBuffer.fromEndianness(platform_compute_endianness());
51
+
52
+ buffer.setCapacity(0);
53
+
54
+ return buffer;
55
+ }
56
+
23
57
  /**
24
58
  * How far {@link GPUTypedTable.set} walks back through
25
59
  * {@link GPUTypedTable.element_upload_buffer} looking for an existing
@@ -38,6 +72,12 @@ const READ_STAGE_BUFFER = BinaryBuffer.fromEndianness(platform_compute_endiannes
38
72
  * happen in practice; if it ever does, swap this linear scan for a tiny
39
73
  * bloom filter or a sparse `id → record_offset` map.
40
74
  *
75
+ * The partial-write stream took the map route from the start — see
76
+ * {@link GPUTypedTable#set_bytes} — not because its hazard is worse but
77
+ * because a row can own several ranges at once and every write has to
78
+ * find all of them, so a scan deep enough to be safe would have cost
79
+ * more than the feature saves.
80
+ *
41
81
  * @type {number}
42
82
  */
43
83
  const UPLOAD_BUFFER_DEDUP_LOOKBACK = 256;
@@ -67,6 +107,53 @@ export class GPUTypedTable {
67
107
  */
68
108
  element_upload_buffer = BinaryBuffer.fromEndianness(platform_compute_endianness());
69
109
 
110
+ /**
111
+ * Sub-record ranges waiting to be uploaded, as fixed-stride descriptors:
112
+ * `[u32 element_index, u32 word_offset, u32 word_count, u32 data_offset]`, where `word_offset`
113
+ * is relative to the start of the record and `data_offset` indexes {@link partial_upload_data}.
114
+ *
115
+ * Separate from {@link element_upload_buffer} because ranges are variable length, and the
116
+ * whole-record shader can only index its buffer by thread id while every record is the same
117
+ * size. See {@link set_bytes} for how the two streams stay out of each other's way.
118
+ *
119
+ * Consumed and rewound by {@link GPUDatabase.update}.
120
+ * @type {BinaryBuffer}
121
+ */
122
+ partial_upload_descriptors = empty_upload_buffer();
123
+
124
+ /**
125
+ * The words {@link partial_upload_descriptors} point at, appended in descriptor order — so a
126
+ * run of descriptors always owns a contiguous slice of this blob, which is what lets the
127
+ * upload be chunked.
128
+ * @type {BinaryBuffer}
129
+ */
130
+ partial_upload_data = empty_upload_buffer();
131
+
132
+ /**
133
+ * Index of the most recent pending descriptor for a row — the head of that row's chain
134
+ * through {@link #partial_descriptor_previous}.
135
+ *
136
+ * {@link set}'s dedupe can afford a backwards scan because it only ever looks for one record;
137
+ * a row here can own several descriptors at once, and every write has to find all of them. A
138
+ * scan deep enough to be safe would cost more than the whole feature saves, so the stream
139
+ * carries an index instead — which also makes reconciliation exact rather than windowed.
140
+ *
141
+ * Empty for a table that never takes a partial write, which is the only thing {@link set} and
142
+ * {@link remove} have to check.
143
+ *
144
+ * @type {Map<number,number>}
145
+ */
146
+ #partial_descriptor_head = new Map();
147
+
148
+ /**
149
+ * For descriptor `i`, the index of the previous pending descriptor targeting the same row, or
150
+ * -1 at the end of the chain. Parallel to {@link partial_upload_descriptors}, and kept on this
151
+ * side rather than in a fifth descriptor word so the GPU-side stride stays at four.
152
+ *
153
+ * @type {number[]}
154
+ */
155
+ #partial_descriptor_previous = [];
156
+
70
157
  /**
71
158
  * When a page is updated - it is marked as dirty.
72
159
  *
@@ -236,6 +323,10 @@ export class GPUTypedTable {
236
323
 
237
324
  const upload_buffer = this.element_upload_buffer;
238
325
 
326
+ // A whole-record write supersedes any sub-record range queued for the row earlier in the
327
+ // frame — and a zero record most of all, since the row is being freed.
328
+ this.#retire_partial_records(element_index);
329
+
239
330
  // Dedupe like set() does: same-frame slot recycling (set -> remove -> add -> set)
240
331
  // must not leave both the old value record and this zero record alive in one
241
332
  // dispatch, or the GPU row becomes schedule-dependent.
@@ -454,6 +545,11 @@ export class GPUTypedTable {
454
545
  // a workgroup (last-writer-wins is non-deterministic). The CPU
455
546
  // shadow above is already idempotent w.r.t. repeated set() calls,
456
547
  // so the GPU side is the only thing that needed the dedupe.
548
+ //
549
+ // A whole record also supersedes any sub-record range queued for this row earlier in the
550
+ // frame — see set_bytes for the ordering rules across the two streams.
551
+ this.#retire_partial_records(element_index);
552
+
457
553
  const upload_buffer = this.element_upload_buffer;
458
554
  const value_offset = u32.size;
459
555
  const step_size = value_offset + record_size;
@@ -482,13 +578,329 @@ export class GPUTypedTable {
482
578
  }
483
579
 
484
580
  /**
485
- * Trim the upload buffer if it exceeds the configured limit.
486
- * Called after the upload buffer has been consumed during {@link GPUDatabase.update}.
581
+ * Overwrite one field (or sub-struct) of an existing record, leaving the rest of it untouched
582
+ * — on the CPU shadow and on the GPU alike.
583
+ *
584
+ * `byte_offset` says where the value lives inside the record; a caller takes it from the
585
+ * `offset` that its {@link WebGPUStruct} field already carries. Naming an offset rather than a
586
+ * field is deliberate: the table knows nothing about what its type means, and a caller
587
+ * updating several adjacent fields can hand over one covering range instead of three writes.
588
+ *
589
+ * @param {number} element_index row to patch; must already be occupied
590
+ * @param {number} byte_offset offset of the range within the record
591
+ * @param {*} value
592
+ * @param {WebGPUType} type type of `value` as it is laid out at `byte_offset`
593
+ */
594
+ set_range(element_index, byte_offset, value, type) {
595
+ assert.defined(type, 'type');
596
+ assert.equal(type.isWebGPUType, true, 'type.isWebGPUType !== true');
597
+ assert.isNonNegativeInteger(byte_offset, 'byte_offset');
598
+ assert.equal(byte_offset % type.align, 0, `byte_offset (${byte_offset}) is not aligned to '${type}' (align ${type.align}) — a value of that type never sits there`);
599
+
600
+ const size = type.size;
601
+
602
+ WRITE_STAGE_BUFFER.ensureCapacity(size);
603
+
604
+ // `write_wgsl_type_value` stops at the last field, so a type carrying internal or trailing
605
+ // padding would leave those bytes holding whatever the previous caller staged. The GPU
606
+ // never reads them, but the shadow and the upload would stop being a function of `value`.
607
+ new Uint8Array(WRITE_STAGE_BUFFER.data, 0, size).fill(0);
608
+
609
+ WRITE_STAGE_BUFFER.position = 0;
610
+ write_wgsl_type_value(value, WRITE_STAGE_BUFFER, type);
611
+
612
+ this.set_bytes(element_index, byte_offset, WRITE_STAGE_BUFFER.data, 0, size);
613
+ }
614
+
615
+ /**
616
+ * Overwrite a byte range of an existing record from raw bytes — the primitive
617
+ * {@link set_range} is built on.
618
+ *
619
+ * The range is written to the CPU shadow and queued for the GPU as a sub-record ("partial")
620
+ * upload: a descriptor in {@link partial_upload_descriptors} plus its words in
621
+ * {@link partial_upload_data}, applied by `gpu_table_partial_upload_shader`. Nothing outside
622
+ * `[byte_offset, byte_offset + byte_length)` is serialized, uploaded, or touched, which is the
623
+ * entire point — a caller that owns twelve words of a forty-four word record should pay for
624
+ * twelve.
625
+ *
626
+ * The range must be a whole number of u32 words at a 4-byte-aligned offset. The database
627
+ * buffer is an `array<u32>`, so anything finer would need a masked read-modify-write on the
628
+ * GPU; every WGSL field is at least 4-byte aligned, so the restriction costs a real caller
629
+ * nothing.
630
+ *
631
+ * The row must already be occupied. A partial write patches a record, it never creates one:
632
+ * allocating a page here would publish a row whose other fields nobody ever wrote.
633
+ *
634
+ * ## Ordering
635
+ *
636
+ * Within one update cycle a word of a row must end up with exactly one pending writer, or the
637
+ * writes race — each upload entry is consumed by its own thread within a single dispatch, and
638
+ * the GPU is free to retire threads in any order. In append order:
639
+ *
640
+ * - **partial after whole-record** — the pending whole record is patched in place and nothing
641
+ * is appended, so the row is not in both streams at once. This is the one case bounded by
642
+ * {@link UPLOAD_BUFFER_DEDUP_LOOKBACK}, since it borrows {@link set}'s backwards scan. A
643
+ * whole record further back than the window is missed and the range is queued beside it —
644
+ * still correct, because {@link GPUDatabase.update} dispatches a table's whole records
645
+ * before its ranges, so the range lands on top of the record exactly as append order said.
646
+ * - **whole-record after partial** — {@link set} retires the row's pending descriptors (blanks
647
+ * their word count, making those threads no-ops); the whole record supersedes them.
648
+ * - **partial after partial, the same range or a sub-range of a pending one** — the pending
649
+ * descriptor's words are patched in place.
650
+ * - **partial after partial, disjoint ranges** — both stay. Different words, different
651
+ * threads, no race. This is what keeps a caller updating three scattered fields cheap.
652
+ * - **partial after partial, overlapping without containment** — the overlapping descriptors
653
+ * are retired and one covering their union is appended, its words read back out of the CPU
654
+ * shadow, which is already correct by then.
655
+ *
656
+ * Every case but the first is exact rather than windowed: a row's pending descriptors are
657
+ * indexed by id, so they are found however long the stream has grown.
658
+ *
659
+ * @param {number} element_index row to patch; must already be occupied
660
+ * @param {number} byte_offset offset of the range within the record, 4-byte aligned
661
+ * @param {ArrayBuffer|ArrayBufferView} source
662
+ * @param {number} [source_offset] byte offset into `source`
663
+ * @param {number} [byte_length] length of the range, a whole number of u32 words
664
+ */
665
+ set_bytes(element_index, byte_offset, source, source_offset = 0, byte_length = source.byteLength - source_offset) {
666
+ assert.isNonNegativeInteger(element_index, 'element_index');
667
+ assert.isNonNegativeInteger(byte_offset, 'byte_offset');
668
+ assert.isNonNegativeInteger(source_offset, 'source_offset');
669
+ assert.isPositiveInteger(byte_length, 'byte_length');
670
+
671
+ const descriptor = this.descriptor;
672
+ const record_size = descriptor.packed_element_size_bytes;
673
+ const word_size = GPU_DATABASE_BUFFER_ELEMENT_TYPE.size;
674
+
675
+ assert.equal(byte_offset % word_size, 0, `byte_offset (${byte_offset}) must be a multiple of ${word_size} — the database buffer is an array<u32> and a partial write is not masked`);
676
+ assert.equal(byte_length % word_size, 0, `byte_length (${byte_length}) must be a multiple of ${word_size} — the database buffer is an array<u32> and a partial write is not masked`);
677
+ assert.lessThanOrEqual(byte_offset + byte_length, record_size, `range [${byte_offset}, ${byte_offset + byte_length}) reaches past the end of a ${record_size} byte record of '${descriptor.name}'`);
678
+
679
+ assert.ok(this.occupancy.get(element_index), `element ${element_index} of '${descriptor.name}' is not occupied — a partial write patches a record, it does not create one`);
680
+
681
+ const source_bytes = source instanceof ArrayBuffer
682
+ ? new Uint8Array(source)
683
+ : new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
684
+
685
+ assert.lessThanOrEqual(source_offset + byte_length, source_bytes.byteLength, `source holds ${source_bytes.byteLength} bytes, short of the ${source_offset + byte_length} the range asks for`);
686
+
687
+ const elements_per_page = descriptor.elements_per_page;
688
+ const page_index = Math.floor(element_index / elements_per_page);
689
+ const page = this.pages.get(page_index);
690
+
691
+ assert.defined(page, `page ${page_index} of '${descriptor.name}'`);
692
+
693
+ // A partial write is a read-modify-write of a record that already exists, so it has
694
+ // nowhere to run against a page whose shadow was released — materializing a zeroed one
695
+ // the way `set` does would silently blank every field this call doesn't write.
696
+ assert.defined(page.cpu_data_buffer, `page ${page_index} of '${descriptor.name}' has no CPU shadow — a partial write cannot run against an unmapped page`);
697
+
698
+ // Patch the CPU shadow first. Everything the GPU is told below is then read back out of
699
+ // it rather than out of `source`, so the two cannot disagree by construction — and the
700
+ // merge path, which has to upload words this call never supplied, has somewhere to get
701
+ // them from.
702
+ const record_bytes = new Uint8Array(
703
+ page.cpu_data_buffer,
704
+ page.cpu_data_address + (element_index % elements_per_page) * record_size,
705
+ record_size
706
+ );
707
+
708
+ record_bytes.set(
709
+ source_bytes.subarray(source_offset, source_offset + byte_length),
710
+ byte_offset
711
+ );
712
+
713
+ page.cpu_version++;
714
+
715
+ // A pending whole record for this row wins the frame outright: patch its bytes and append
716
+ // nothing, so the row stays in exactly one stream.
717
+ const upload_buffer = this.element_upload_buffer;
718
+ const upload_tail = upload_buffer.position;
719
+ const pending_record = this.#find_upload_record(element_index);
720
+
721
+ upload_buffer.position = upload_tail;
722
+
723
+ if (pending_record >= 0) {
724
+ new Uint8Array(upload_buffer.data, pending_record + u32.size + byte_offset, byte_length)
725
+ .set(record_bytes.subarray(byte_offset, byte_offset + byte_length));
726
+
727
+ return;
728
+ }
729
+
730
+ this.#stage_partial_range(element_index, byte_offset / word_size, byte_length / word_size, record_bytes);
731
+ }
732
+
733
+ /**
734
+ * Queue `[word_offset, word_offset + word_count)` of a row for the partial upload stream,
735
+ * merging with whatever is already pending for that row. See {@link set_bytes} for the rules.
736
+ *
737
+ * @param {number} element_index
738
+ * @param {number} word_offset within the record
739
+ * @param {number} word_count
740
+ * @param {Uint8Array} record_bytes the row's CPU shadow, already patched
741
+ */
742
+ #stage_partial_range(element_index, word_offset, word_count, record_bytes) {
743
+ const descriptors = this.partial_upload_descriptors;
744
+ const data = this.partial_upload_data;
745
+ const previous = this.#partial_descriptor_previous;
746
+ const word_size = GPU_DATABASE_BUFFER_ELEMENT_TYPE.size;
747
+
748
+ const tail = descriptors.position;
749
+ const head = this.#partial_descriptor_head.get(element_index);
750
+
751
+ let lo = word_offset;
752
+ let hi = word_offset + word_count;
753
+ let overlaps = false;
754
+
755
+ for (let i = head === undefined ? -1 : head; i >= 0; i = previous[i]) {
756
+ descriptors.position = i * PARTIAL_DESCRIPTOR_BYTES + u32.size;
757
+
758
+ const candidate_lo = descriptors.readUint32();
759
+ const candidate_count = descriptors.readUint32();
760
+
761
+ if (candidate_count === 0) {
762
+ // retired by a whole-record write, or absorbed into a union below
763
+ continue;
764
+ }
765
+
766
+ const candidate_hi = candidate_lo + candidate_count;
767
+
768
+ if (candidate_lo <= lo && candidate_hi >= hi) {
769
+ // Contains the new range — patch its words in place. Nothing else pending for the
770
+ // row can overlap the new range, because it would have to overlap this descriptor
771
+ // too, and the invariant says it doesn't.
772
+ const candidate_data = descriptors.readUint32();
773
+
774
+ descriptors.position = tail;
775
+
776
+ new Uint8Array(data.data, (candidate_data + (lo - candidate_lo)) * word_size, word_count * word_size)
777
+ .set(record_bytes.subarray(lo * word_size, hi * word_size));
778
+
779
+ return;
780
+ }
781
+
782
+ if (candidate_hi > lo && candidate_lo < hi) {
783
+ overlaps = true;
784
+ }
785
+ }
786
+
787
+ // Overlapping without containment: retire what overlaps and let the new descriptor cover
788
+ // the union. Absorbing one range can bring the union into contact with another, so the
789
+ // chain is walked until a pass finds nothing left to absorb — over a chain of a handful of
790
+ // entries, which is what a row that is patched field by field has.
791
+ for (let absorbing = overlaps; absorbing;) {
792
+ absorbing = false;
793
+
794
+ for (let i = head === undefined ? -1 : head; i >= 0; i = previous[i]) {
795
+ descriptors.position = i * PARTIAL_DESCRIPTOR_BYTES + u32.size;
796
+
797
+ const candidate_lo = descriptors.readUint32();
798
+ const candidate_count = descriptors.readUint32();
799
+
800
+ if (candidate_count === 0) {
801
+ continue;
802
+ }
803
+
804
+ const candidate_hi = candidate_lo + candidate_count;
805
+
806
+ if (candidate_hi <= lo || candidate_lo >= hi) {
807
+ // disjoint — different words, different thread, no race to resolve
808
+ continue;
809
+ }
810
+
811
+ lo = Math.min(lo, candidate_lo);
812
+ hi = Math.max(hi, candidate_hi);
813
+
814
+ descriptors.position = i * PARTIAL_DESCRIPTOR_BYTES + PARTIAL_DESCRIPTOR_WORD_COUNT * u32.size;
815
+ descriptors.writeUint32(0);
816
+
817
+ absorbing = true;
818
+ }
819
+ }
820
+
821
+ descriptors.position = tail;
822
+
823
+ const data_offset = data.position / word_size;
824
+
825
+ // The words come out of the shadow rather than out of the caller's `source`, so a union
826
+ // wider than what this call supplied still carries the right values.
827
+ data.writeBytes(record_bytes, lo * word_size, (hi - lo) * word_size);
828
+
829
+ descriptors.writeUint32(element_index);
830
+ descriptors.writeUint32(lo);
831
+ descriptors.writeUint32(hi - lo);
832
+ descriptors.writeUint32(data_offset);
833
+
834
+ const index = tail / PARTIAL_DESCRIPTOR_BYTES;
835
+
836
+ previous[index] = head === undefined ? -1 : head;
837
+ this.#partial_descriptor_head.set(element_index, index);
838
+ }
839
+
840
+ /**
841
+ * Blank the word count of every pending partial descriptor targeting a row, making its thread
842
+ * a no-op, and drop the row's chain. Called when a whole-record write supersedes them.
843
+ *
844
+ * Exact, not windowed — the chain reaches every descriptor for the row however long the stream
845
+ * has grown — and it costs a table that never takes a partial write one comparison.
846
+ *
847
+ * @param {number} element_index
848
+ */
849
+ #retire_partial_records(element_index) {
850
+ const heads = this.#partial_descriptor_head;
851
+
852
+ if (heads.size === 0) {
853
+ return;
854
+ }
855
+
856
+ const head = heads.get(element_index);
857
+
858
+ if (head === undefined) {
859
+ return;
860
+ }
861
+
862
+ const descriptors = this.partial_upload_descriptors;
863
+ const previous = this.#partial_descriptor_previous;
864
+ const tail = descriptors.position;
865
+
866
+ for (let i = head; i >= 0; i = previous[i]) {
867
+ descriptors.position = i * PARTIAL_DESCRIPTOR_BYTES + PARTIAL_DESCRIPTOR_WORD_COUNT * u32.size;
868
+ descriptors.writeUint32(0);
869
+ }
870
+
871
+ descriptors.position = tail;
872
+
873
+ heads.delete(element_index);
874
+ }
875
+
876
+ /**
877
+ * Drop the partial upload stream, its per-row index included. Called by
878
+ * {@link GPUDatabase.update} once the stream has been dispatched.
879
+ */
880
+ rewind_partial_upload() {
881
+ this.partial_upload_descriptors.position = 0;
882
+ this.partial_upload_data.position = 0;
883
+
884
+ this.#partial_descriptor_head.clear();
885
+ this.#partial_descriptor_previous.length = 0;
886
+ }
887
+
888
+ /**
889
+ * Trim the upload buffers if they exceed the configured limit.
890
+ * Called after the upload buffers have been consumed during {@link GPUDatabase.update}.
487
891
  */
488
892
  trim_upload_buffer() {
489
893
  if (this.element_upload_buffer.capacity > GPU_DATABASE_UPLOAD_BUFFER_LIMIT) {
490
894
  this.element_upload_buffer.setCapacity(GPU_DATABASE_UPLOAD_BUFFER_LIMIT);
491
895
  }
896
+
897
+ if (this.partial_upload_descriptors.capacity > GPU_DATABASE_UPLOAD_BUFFER_LIMIT) {
898
+ this.partial_upload_descriptors.setCapacity(GPU_DATABASE_UPLOAD_BUFFER_LIMIT);
899
+ }
900
+
901
+ if (this.partial_upload_data.capacity > GPU_DATABASE_UPLOAD_BUFFER_LIMIT) {
902
+ this.partial_upload_data.setCapacity(GPU_DATABASE_UPLOAD_BUFFER_LIMIT);
903
+ }
492
904
  }
493
905
 
494
906
  /**
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Words per descriptor in the partial (sub-record) upload stream:
3
+ * `[element_index, word_offset, word_count, data_offset]`.
4
+ *
5
+ * `word_offset` is relative to the start of the record, `data_offset` is an absolute word index
6
+ * into the table's partial data blob — a dispatch rebases it by `settings.data_word_base` so a
7
+ * chunk can carry a slice of that blob rather than all of it.
8
+ *
9
+ * @see GPUTypedTable#set_bytes
10
+ * @see gpu_table_partial_upload_shader
11
+ * @type {number}
12
+ */
13
+ export const GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS: number;
14
+ //# sourceMappingURL=GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wDAFU,MAAM,CAE2C"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Words per descriptor in the partial (sub-record) upload stream:
3
+ * `[element_index, word_offset, word_count, data_offset]`.
4
+ *
5
+ * `word_offset` is relative to the start of the record, `data_offset` is an absolute word index
6
+ * into the table's partial data blob — a dispatch rebases it by `settings.data_word_base` so a
7
+ * chunk can carry a slice of that blob rather than all of it.
8
+ *
9
+ * @see GPUTypedTable#set_bytes
10
+ * @see gpu_table_partial_upload_shader
11
+ * @type {number}
12
+ */
13
+ export const GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS = 4;
@@ -0,0 +1,17 @@
1
+ export const GPU_TABLE_PARTIAL_UPLOAD_SETTINGS_STRUCT: WebGPUStruct;
2
+ /**
3
+ * Applies sub-record ("partial") writes to the database.
4
+ *
5
+ * The whole-record upload shader can index its buffer by thread id because every record there is
6
+ * the same size. Partial writes are variable length, so the stream is split in two: a fixed-stride
7
+ * descriptor array this shader indexes by thread id, and a data blob the descriptors point into.
8
+ * One thread per descriptor, copying `word_count` words.
9
+ *
10
+ * A descriptor with `word_count == 0` is a retired entry — {@link GPUTypedTable} blanks descriptors
11
+ * that a later whole-record write superseded rather than compacting the buffer — and its thread
12
+ * falls through the copy loop without writing anything.
13
+ */
14
+ export const gpu_table_partial_upload_shader: ComputeShader;
15
+ import { WebGPUStruct } from "../../shader/type/WebGPUStruct.js";
16
+ import { ComputeShader } from "../../shader/ComputeShader.js";
17
+ //# sourceMappingURL=gpu_table_partial_upload_shader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gpu_table_partial_upload_shader.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.js"],"names":[],"mappings":"AAUA,oEAoBG;AAcH;;;;;;;;;;;GAWG;AACH,4DA2DG;6BAjH0B,mCAAmC;8BAElC,+BAA+B"}
@@ -0,0 +1,116 @@
1
+ import { u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
2
+ import { WebGPUArray } from "../../shader/type/WebGPUArray.js";
3
+ import { WebGPUStruct } from "../../shader/type/WebGPUStruct.js";
4
+ import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
5
+ import { ComputeShader } from "../../shader/ComputeShader.js";
6
+ import { ShaderResourceSetDescriptor } from "../../shader/resource/ShaderResourceSetDescriptor.js";
7
+ import { GPU_DATABASE_BUFFER_ELEMENT_TYPE } from "./GPU_DATABASE_BUFFER_ELEMENT_TYPE.js";
8
+ import { GPU_DATABASE_UPLOAD_DISPATCH_GROUP_SIZE } from "./GPU_DATABASE_UPLOAD_DISPATCH_GROUP_SIZE.js";
9
+ import { GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS } from "./GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.js";
10
+
11
+ export const GPU_TABLE_PARTIAL_UPLOAD_SETTINGS_STRUCT = WebGPUStruct.from({
12
+ // number of descriptors in this dispatch, one thread each
13
+ count: u32,
14
+ // size of one record in GPU_DATABASE_BUFFER_ELEMENT_TYPE units (u32 words).
15
+ // Used as the in-page slot stride and as the bound a descriptor's range must
16
+ // stay inside.
17
+ record_size: u32,
18
+ elements_per_page: u32,
19
+ // u32 offset into the database buffer where this table's page address table
20
+ // begins.
21
+ page_lookup_address: u32,
22
+ // size of the per-page GPU header in u32 words; records start there.
23
+ page_header_words: u32,
24
+ // per-table maximum page count, used to drop element ids whose page index
25
+ // would walk past the end of this table's address region.
26
+ page_limit: u32,
27
+ // word index, within the table's partial data blob, of the first word this
28
+ // chunk's `data` binding holds. Descriptors carry absolute blob offsets, so
29
+ // the shader subtracts this to index the chunk.
30
+ data_word_base: u32,
31
+ });
32
+
33
+ const resources = new ShaderResourceSetDescriptor();
34
+
35
+ // per-dispatch, 28 bytes, read-only — same reasoning as the whole-record upload: a uniform would
36
+ // cost a transient buffer, a staging buffer and a copy per chunk to carry seven integers already
37
+ // known on the CPU at encode time.
38
+ resources.setImmediate('settings', GPU_TABLE_PARTIAL_UPLOAD_SETTINGS_STRUCT);
39
+
40
+ resources.createGroup()
41
+ .addStorageBuffer('descriptors', WebGPUArray.from(GPU_DATABASE_BUFFER_ELEMENT_TYPE), false)
42
+ .addStorageBuffer('data', WebGPUArray.from(GPU_DATABASE_BUFFER_ELEMENT_TYPE), false)
43
+ .addStorageBuffer('database', WebGPUArray.from(GPU_DATABASE_BUFFER_ELEMENT_TYPE), true);
44
+
45
+ /**
46
+ * Applies sub-record ("partial") writes to the database.
47
+ *
48
+ * The whole-record upload shader can index its buffer by thread id because every record there is
49
+ * the same size. Partial writes are variable length, so the stream is split in two: a fixed-stride
50
+ * descriptor array this shader indexes by thread id, and a data blob the descriptors point into.
51
+ * One thread per descriptor, copying `word_count` words.
52
+ *
53
+ * A descriptor with `word_count == 0` is a retired entry — {@link GPUTypedTable} blanks descriptors
54
+ * that a later whole-record write superseded rather than compacting the buffer — and its thread
55
+ * falls through the copy loop without writing anything.
56
+ */
57
+ export const gpu_table_partial_upload_shader = ComputeShader.from({
58
+ label: `GPUTypedTable/Upload partial records`,
59
+ body: CodeChunk.from(
60
+ //language=WGSL
61
+ `
62
+ @compute @workgroup_size(${GPU_DATABASE_UPLOAD_DISPATCH_GROUP_SIZE})
63
+ fn main(@builtin(global_invocation_id) global_id : vec3<u32>){
64
+ let descriptor_index = global_id.x;
65
+
66
+ if(descriptor_index >= settings.count){
67
+ return;
68
+ }
69
+
70
+ let descriptor_offset = descriptor_index * ${GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS}u;
71
+
72
+ let element_id = descriptors[descriptor_offset];
73
+ let word_offset = descriptors[descriptor_offset + 1u];
74
+ let word_count = descriptors[descriptor_offset + 2u];
75
+ let data_offset = descriptors[descriptor_offset + 3u] - settings.data_word_base;
76
+
77
+ // A word_count of 0 — a descriptor retired by a later whole-record write — needs no test of
78
+ // its own: the copy loop below runs zero times.
79
+
80
+ // A range that reaches past the record would write into the next slot. The CPU asserts this
81
+ // when the range is queued; the guard is here because the cost of getting it wrong is silent
82
+ // corruption of an unrelated row.
83
+ if(word_offset + word_count > settings.record_size){
84
+ return;
85
+ }
86
+
87
+ let page_index = element_id / settings.elements_per_page;
88
+ let page_offset = element_id % settings.elements_per_page;
89
+
90
+ if(page_index >= settings.page_limit){
91
+ // page index overflow
92
+ return;
93
+ }
94
+
95
+ let page_lookup_index = page_index + settings.page_lookup_address;
96
+ let page_address = database[page_lookup_index];
97
+
98
+ // Page may have been freed since this write was queued (its last element removed in the same
99
+ // update cycle). Without this guard the u32 add below wraps and write_offset lands inside the
100
+ // allocation-table region.
101
+ if(page_address == 0xFFFFFFFFu){
102
+ return;
103
+ }
104
+
105
+ let write_offset = page_address + settings.page_header_words + page_offset * settings.record_size + word_offset;
106
+
107
+ // copy the range
108
+ for(var i = 0u; i < word_count; i++){
109
+ database[write_offset + i] = data[data_offset + i];
110
+ }
111
+
112
+ }
113
+ `
114
+ ),
115
+ resources
116
+ });