@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.
- package/package.json +1 -1
- package/src/core/geom/3d/hash-grid/PointHashGrid3.d.ts +118 -0
- package/src/core/geom/3d/hash-grid/PointHashGrid3.d.ts.map +1 -0
- package/src/core/geom/3d/hash-grid/PointHashGrid3.js +283 -0
- package/src/core/geom/3d/shape/BoxShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/BoxShape3D.js +6 -17
- package/src/core/geom/3d/shape/CapsuleShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/CapsuleShape3D.js +3 -14
- package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/CylinderShape3D.js +3 -21
- package/src/core/geom/3d/shape/PlaneShape3D.d.ts +105 -0
- package/src/core/geom/3d/shape/PlaneShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/PlaneShape3D.js +251 -0
- package/src/core/geom/3d/shape/SphereShape3D.js +3 -3
- package/src/core/geom/3d/shape/json/shape_to_type.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/shape_to_type.js +3 -0
- package/src/core/geom/3d/shape/json/type_adapters.d.ts +14 -0
- package/src/core/geom/3d/shape/json/type_adapters.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/type_adapters.js +15 -0
- package/src/core/geom/3d/shape/sdf/sdf3_box.d.ts +51 -0
- package/src/core/geom/3d/shape/sdf/sdf3_box.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_box.js +113 -0
- package/src/core/geom/3d/shape/sdf/sdf3_capsule.d.ts +46 -0
- package/src/core/geom/3d/shape/sdf/sdf3_capsule.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_capsule.js +82 -0
- package/src/core/geom/3d/shape/sdf/sdf3_cylinder.d.ts +60 -0
- package/src/core/geom/3d/shape/sdf/sdf3_cylinder.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_cylinder.js +126 -0
- package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.d.ts +50 -0
- package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.js +53 -0
- package/src/core/geom/3d/shape/sdf/sdf3_plane.d.ts +52 -0
- package/src/core/geom/3d/shape/sdf/sdf3_plane.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_plane.js +61 -0
- package/src/core/geom/3d/shape/sdf/sdf3_sphere.d.ts +41 -0
- package/src/core/geom/3d/shape/sdf/sdf3_sphere.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_sphere.js +71 -0
- package/src/core/process/ConcurrencyGate.d.ts +103 -0
- package/src/core/process/ConcurrencyGate.d.ts.map +1 -0
- package/src/core/process/ConcurrencyGate.js +207 -0
- package/src/engine/asset/AssetManager.d.ts +10 -0
- package/src/engine/asset/AssetManager.d.ts.map +1 -1
- package/src/engine/asset/AssetManager.js +10 -0
- package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +30 -13
- package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts +26 -0
- package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts.map +1 -0
- package/src/engine/graphics3/pose/collect_entity_playbacks.js +40 -0
- package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts.map +1 -1
- package/src/engine/graphics3/pose/query_entity_node_world_pose.js +74 -101
- package/src/engine/physics/cloth/MEASUREMENTS.md +1287 -4
- package/src/engine/physics/cloth/PLAN.md +1967 -1581
- package/src/engine/physics/cloth/build/ClothProxy.d.ts +147 -0
- package/src/engine/physics/cloth/build/ClothProxy.d.ts.map +1 -0
- package/src/engine/physics/cloth/build/ClothProxy.js +207 -0
- package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts +70 -0
- package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts.map +1 -0
- package/src/engine/physics/cloth/build/cloth_proxy_from_joints.js +256 -0
- package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +176 -0
- package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/ClothColliderIndex.js +435 -0
- package/src/engine/physics/cloth/collider/ClothColliderKind.d.ts +12 -0
- package/src/engine/physics/cloth/collider/ClothColliderKind.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/ClothColliderKind.js +38 -0
- package/src/engine/physics/cloth/collider/ClothColliderRecord.d.ts +120 -0
- package/src/engine/physics/cloth/collider/ClothColliderRecord.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/ClothColliderRecord.js +137 -0
- package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts +25 -0
- package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/cloth_collider_bounds.js +190 -0
- package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts +44 -0
- package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/cloth_collider_pack.js +173 -0
- package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts +94 -0
- package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/cloth_collider_sdf.js +275 -0
- package/src/engine/physics/cloth/ecs/ClothCollider.d.ts +126 -0
- package/src/engine/physics/cloth/ecs/ClothCollider.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothCollider.js +199 -0
- package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts +9 -0
- package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothColliderFlags.js +32 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts +114 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.js +243 -0
- package/src/engine/physics/cloth/ecs/ClothDynamicsFlags.js +66 -57
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +109 -1
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothInstance.js +479 -363
- package/src/engine/physics/cloth/ecs/ClothRig.d.ts +95 -0
- package/src/engine/physics/cloth/ecs/ClothRig.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothRig.js +140 -0
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +30 -1
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothSystem.js +1021 -773
- package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts +90 -0
- package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_build_rows.js +374 -0
- package/src/engine/physics/cloth/ecs/cloth_dynamics_library.d.ts +32 -3
- package/src/engine/physics/cloth/ecs/cloth_dynamics_library.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_dynamics_library.js +283 -254
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +59 -0
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +328 -0
- package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts +13 -0
- package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.js +288 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +58 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +199 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts +8 -63
- package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +317 -645
- package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts +61 -0
- package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_write_back.js +212 -449
- package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts +49 -0
- package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_write_back_rig.js +115 -0
- package/src/engine/physics/cloth/playground/README.md +155 -1
- package/src/engine/physics/cloth/playground/collide.html +178 -0
- package/src/engine/physics/cloth/playground/collide_build.d.ts +95 -0
- package/src/engine/physics/cloth/playground/collide_build.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/collide_build.js +315 -0
- package/src/engine/physics/cloth/playground/collide_main.d.ts +2 -0
- package/src/engine/physics/cloth/playground/collide_main.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/collide_main.js +720 -0
- package/src/engine/physics/cloth/playground/garment.html +182 -0
- package/src/engine/physics/cloth/playground/garment_build.d.ts +78 -0
- package/src/engine/physics/cloth/playground/garment_build.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/garment_build.js +359 -0
- package/src/engine/physics/cloth/playground/garment_main.d.ts +2 -0
- package/src/engine/physics/cloth/playground/garment_main.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/garment_main.js +633 -0
- package/src/engine/physics/cloth/solver/ClothState.d.ts +208 -5
- package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/ClothState.js +302 -8
- package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts +87 -0
- package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/cloth_contact_find.js +343 -0
- package/src/engine/physics/cloth/solver/cloth_self_find.d.ts +112 -0
- package/src/engine/physics/cloth/solver/cloth_self_find.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/cloth_self_find.js +448 -0
- package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts +4 -1
- package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_solve_vertex.js +16 -1
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +112 -5
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_solver_constants.js +117 -6
- package/src/engine/physics/cloth/solver/cloth_step.d.ts +40 -9
- package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_step.js +122 -10
- package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts +95 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.js +285 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts +129 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.js +501 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts +75 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_self_row.js +202 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/playground/vgeo_viewer/README.md +18 -0
- package/src/shade/playground/vgeo_viewer/main.js +87 -50
- package/src/shade/renderer/buffer/table/GPUDatabase.d.ts.map +1 -1
- package/src/shade/renderer/buffer/table/GPUDatabase.js +191 -62
- package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts +94 -2
- package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
- package/src/shade/renderer/buffer/table/GPUTypedTable.js +414 -2
- package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts +14 -0
- package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.js +13 -0
- package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.d.ts +17 -0
- package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.d.ts.map +1 -0
- package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.js +116 -0
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts +9 -4
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +843 -734
- package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.d.ts +12 -0
- package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.d.ts.map +1 -1
- package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.js +13 -0
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts +17 -0
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +185 -30
- package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +17 -0
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +141 -24
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +204 -64
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +110 -10
- package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +19 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts +19 -6
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.js +26 -7
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts +173 -14
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.js +458 -51
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts +49 -13
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.js +52 -14
- package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.d.ts +20 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.js +19 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.d.ts +27 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.js +48 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.d.ts +29 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.js +60 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts +8 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.js +32 -2
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts +258 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.js +723 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts +49 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.js +50 -0
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.d.ts +3 -0
- package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.d.ts.map +1 -1
- package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.js +121 -21
- package/src/shade/renderer/texture/virtual/README.md +6 -4
- package/src/shade/renderer/texture/virtual/VirtualTextureManager.d.ts.map +1 -1
- package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +7 -0
- package/src/shade/renderer/texture/virtual/source/VTSourceTiled.d.ts +3 -3
- package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +3 -3
- package/src/engine/ecs/system/SystemExecutionPhase.d.ts +0 -9
- package/src/engine/ecs/system/SystemExecutionPhase.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/NOTES.md +0 -128
- package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.d.ts +0 -6
- package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.js +0 -5
- package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts +0 -49
- package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.js +0 -264
- package/src/engine/graphics/texture/virtual/VirtualTextureUsage.d.ts +0 -44
- package/src/engine/graphics/texture/virtual/VirtualTextureUsage.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/VirtualTextureUsage.js +0 -228
- package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.d.ts +0 -3
- package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.js +0 -73
- package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.d.ts +0 -14
- package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.js +0 -85
- package/src/engine/graphics/texture/virtual/debug/UsageDebugView.d.ts +0 -12
- package/src/engine/graphics/texture/virtual/debug/UsageDebugView.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/debug/UsageDebugView.js +0 -63
- package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.d.ts +0 -12
- package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.js +0 -243
- package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.d.ts +0 -30
- package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.js +0 -39
- package/src/engine/graphics/texture/virtual/tile/compose_finger_print.d.ts +0 -9
- package/src/engine/graphics/texture/virtual/tile/compose_finger_print.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/compose_finger_print.js +0 -24
- package/src/engine/graphics/texture/virtual/tile/compose_tile_address.d.ts +0 -9
- package/src/engine/graphics/texture/virtual/tile/compose_tile_address.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/compose_tile_address.js +0 -26
- package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.d.ts +0 -11
- package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.js +0 -12
- package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.d.ts +0 -7
- package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.js +0 -16
- package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.d.ts +0 -7
- package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.d.ts.map +0 -1
- 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
|
-
*
|
|
486
|
-
*
|
|
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
|
+
});
|