@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
|
@@ -1,735 +1,844 @@
|
|
|
1
|
-
import { assert } from "../../../../core/assert.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* internal
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
*
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
*
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
*
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
*
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
* @
|
|
133
|
-
*/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
* @
|
|
151
|
-
*/
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
*
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
*
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
*
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
*
|
|
187
|
-
* @
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
*
|
|
193
|
-
* @
|
|
194
|
-
*/
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this.#
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
*
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
);
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
*
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1
|
+
import { assert } from "../../../../core/assert.js";
|
|
2
|
+
import { ALLOCATOR_NO_SPACE, OffsetAllocator } from "../../../../core/binary/allocator/OffsetAllocator.js";
|
|
3
|
+
import { platform_compute_endianness } from "../../../../core/binary/platform_compute_endianness.js";
|
|
4
|
+
import { array_buffer_copy } from "../../../../core/collection/array/typed/array_buffer_copy.js";
|
|
5
|
+
import { Deque } from "../../../../core/collection/queue/Deque.js";
|
|
6
|
+
import { round_up } from "../../../../core/math/round_up.js";
|
|
7
|
+
import { number_format_by_thousands } from "../../../../core/primitives/numbers/number_format_by_thousands.js";
|
|
8
|
+
import { u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
|
|
9
|
+
import { WebGPUStruct } from "../../shader/type/WebGPUStruct.js";
|
|
10
|
+
import { BVH2_NODE_STRUCT } from "../../shader/chunk/bvh/BVH2_NODE_STRUCT.js";
|
|
11
|
+
import {
|
|
12
|
+
BVH_NODE_BOUNDS_WORD,
|
|
13
|
+
BVH_NODE_CHILD_1_WORD,
|
|
14
|
+
BVH_NODE_CHILD_2_WORD
|
|
15
|
+
} from "../../gpu_primitive/bvh/BVH_BUFFER_LAYOUT.js";
|
|
16
|
+
import { ensure_buffer_capacity } from "../make_temp_gpu_buffer.js";
|
|
17
|
+
import { bvh2_derive_topology } from "./bvh2_derive_topology.js";
|
|
18
|
+
import { GPUGeometryMeshletsBVH } from "./GPUGeometryMeshletsBVH.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {import("../MeshletGeometry.js").MeshletGeometry} MeshletGeometry
|
|
22
|
+
* @typedef {import("../../../../core/binary/allocator/OffsetAllocator.js").Allocation} Allocation
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* One row of the lookup table the traversal shaders read as `blas_lookup`.
|
|
27
|
+
*
|
|
28
|
+
* The row holds the arena node index a tree's root sits at. What the table is *indexed* by is not
|
|
29
|
+
* this manager's business — see {@link GPUGeometryBVHManager#publish_lookup}.
|
|
30
|
+
*/
|
|
31
|
+
export const BLAS_METADATA_STRUCT = WebGPUStruct.from({
|
|
32
|
+
// index of root node for BLAS
|
|
33
|
+
address: u32
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Arena node index of the empty tree: what the lookup names for a geometry that has no tree yet.
|
|
38
|
+
*
|
|
39
|
+
* One internal node whose box is +inf below and -inf above. No ray enters it, so the ray queries
|
|
40
|
+
* pop it and miss; no point is within any distance of it, so the point queries prune it; and it is
|
|
41
|
+
* internal, so nothing ever fetches a triangle for it. Its children are itself, and are never
|
|
42
|
+
* descended into. Every traversal reads `blas_lookup[geometry_index]` and starts walking without a
|
|
43
|
+
* null check, so "no tree" has to be a tree — and it must not be whatever tree happens to sit at
|
|
44
|
+
* the arena's end, which is what a record resident over zero bytes used to point at.
|
|
45
|
+
*
|
|
46
|
+
* @type {number}
|
|
47
|
+
*/
|
|
48
|
+
export const BLAS_EMPTY_TREE_ADDRESS = 0;
|
|
49
|
+
|
|
50
|
+
const DATA_SIZE_ALIGNMENT = 1024;
|
|
51
|
+
const DATA_INITIAL_CAPACITY = round_up(1024, DATA_SIZE_ALIGNMENT);
|
|
52
|
+
|
|
53
|
+
const DATA_GROWTH_STEP = 1024;
|
|
54
|
+
const DATA_GROWTH_MULTIPLIER = 1.2;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Regions the node arena's allocator can describe at once — live trees plus the free gaps
|
|
58
|
+
* between them. A scene's live trees are one per geometry plus one per skinned instance, so
|
|
59
|
+
* this is thousands of instances of headroom.
|
|
60
|
+
*
|
|
61
|
+
* A ceiling rather than a starting point: {@link OffsetAllocator#grow} enlarges the arena but
|
|
62
|
+
* deliberately does not raise this, so it is picked once here.
|
|
63
|
+
*/
|
|
64
|
+
const NODE_ALLOCATION_LIMIT = 32 * 1024;
|
|
65
|
+
|
|
66
|
+
const endianness = platform_compute_endianness();
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Residency for BLAS node data: one arena buffer holding every registered tree's nodes, and a
|
|
70
|
+
* lookup buffer that maps *something else's* index onto an arena address.
|
|
71
|
+
*
|
|
72
|
+
* The split matters. This manager owns arena positions — it decides where a tree's nodes land and
|
|
73
|
+
* hands back the node index of its root. It does **not** own the index space the lookup is read by:
|
|
74
|
+
* every traversal shader does `blas_lookup[geometry_index]`, and geometry indices belong to
|
|
75
|
+
* {@link GPUGeometryManager}. So the table is published by whoever owns those indices, through
|
|
76
|
+
* {@link publish_lookup}; this manager only stores and uploads it.
|
|
77
|
+
*
|
|
78
|
+
* It used to allocate the lookup rows itself, from a pool of its own in BLAS-registration order.
|
|
79
|
+
* That agrees with geometry indices exactly until something takes a geometry index without taking a
|
|
80
|
+
* BLAS row — which per-instance clones do — after which every geometry resolved to another
|
|
81
|
+
* geometry's tree. See `blas_lookup_indexing.spec.js`.
|
|
82
|
+
*/
|
|
83
|
+
export class GPUGeometryBVHManager {
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
*
|
|
87
|
+
* @type {GPUBuffer}
|
|
88
|
+
*/
|
|
89
|
+
#buffer_metadata;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
*
|
|
93
|
+
* @returns {GPUBuffer}
|
|
94
|
+
*/
|
|
95
|
+
get buffer_metadata() {
|
|
96
|
+
return this.#buffer_metadata;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @type {GPUBuffer}
|
|
101
|
+
*/
|
|
102
|
+
#buffer_data;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
*
|
|
106
|
+
* @returns {GPUBuffer}
|
|
107
|
+
*/
|
|
108
|
+
get buffer_data() {
|
|
109
|
+
return this.#buffer_data;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Used range end pointer
|
|
114
|
+
* @type {number}
|
|
115
|
+
*/
|
|
116
|
+
/**
|
|
117
|
+
* Which parts of the node arena are spoken for.
|
|
118
|
+
*
|
|
119
|
+
* The arena used to be a bump pointer, and {@link remove} gave nothing back — a released
|
|
120
|
+
* tree's bytes stayed where they were and the next tree was appended after them, so a game
|
|
121
|
+
* respawning a skinned character grew this buffer for as long as it ran (MEEP-009).
|
|
122
|
+
*
|
|
123
|
+
* Reuse is safe here in a way it is not everywhere: a tree's bytes are position-independent.
|
|
124
|
+
* {@link #upload_pending_nodes} copies them into the arena verbatim and records the address
|
|
125
|
+
* separately, and every consumer reaches a tree through the published lookup rather than a
|
|
126
|
+
* baked-in offset — so a region handed back can be given to the next tree exactly as it
|
|
127
|
+
* lies, and could even be moved if anything ever wanted to compact this.
|
|
128
|
+
*
|
|
129
|
+
* Node {@link BLAS_EMPTY_TREE_ADDRESS} is allocated at construction and never freed, so the
|
|
130
|
+
* empty tree every unresident geometry traces against can never be handed to a real one.
|
|
131
|
+
*
|
|
132
|
+
* @type {OffsetAllocator}
|
|
133
|
+
*/
|
|
134
|
+
#allocator;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Parent of every node, and the node index of every leaf, for each *distinct topology* — one
|
|
138
|
+
* arena each, addressed like {@link #buffer_data}.
|
|
139
|
+
*
|
|
140
|
+
* Separate from the nodes because they are separate things. Node bounds are per instance: a
|
|
141
|
+
* skinned clone's are its own, recomputed every frame from its own deformed vertices. Parents
|
|
142
|
+
* and leaf positions are the shape of the tree, and every copy of a geometry has the same
|
|
143
|
+
* shape, so one derivation serves all of them.
|
|
144
|
+
*
|
|
145
|
+
* @type {GPUBuffer}
|
|
146
|
+
*/
|
|
147
|
+
#buffer_parents;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @returns {GPUBuffer}
|
|
151
|
+
*/
|
|
152
|
+
get buffer_parents() {
|
|
153
|
+
return this.#buffer_parents;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @type {GPUBuffer}
|
|
158
|
+
*/
|
|
159
|
+
#buffer_leaf_nodes;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @returns {GPUBuffer}
|
|
163
|
+
*/
|
|
164
|
+
get buffer_leaf_nodes() {
|
|
165
|
+
return this.#buffer_leaf_nodes;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#buffer_parents_end = 0;
|
|
169
|
+
#buffer_leaf_nodes_end = 0;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Topology by the geometry whose nodes it was derived from, shared by every copy of it.
|
|
173
|
+
*
|
|
174
|
+
* @type {Map<MeshletGeometry, {parents_offset: number, leaf_nodes_offset: number, leaf_count: number, node_count: number, resident: boolean, derived: {parents: Uint32Array, leaf_nodes: Uint32Array}}>}
|
|
175
|
+
*/
|
|
176
|
+
#topology = new Map();
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
*
|
|
180
|
+
* @type {number}
|
|
181
|
+
*/
|
|
182
|
+
#node_capacity = 0;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Moves whenever the answer of {@link get_refit_target} may have changed for any owner: a tree's
|
|
186
|
+
* nodes uploaded, a shape uploaded, an owner removed. Registration alone does not move it —
|
|
187
|
+
* nothing a refit can read has changed until {@link update} runs.
|
|
188
|
+
*
|
|
189
|
+
* A consumer that builds tables from those answers keeps them until this moves. Coarse on
|
|
190
|
+
* purpose: one number to compare per frame, against asking per owner per frame. The price is a
|
|
191
|
+
* rebuild on any tree's residency change, not only on the consumer's own — accepted, and pinned.
|
|
192
|
+
*
|
|
193
|
+
* @type {number}
|
|
194
|
+
*/
|
|
195
|
+
#residency_version = 0;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @returns {number}
|
|
199
|
+
*/
|
|
200
|
+
get residency_version() {
|
|
201
|
+
return this.#residency_version;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @type {GPUDevice}
|
|
206
|
+
*/
|
|
207
|
+
#device
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
*
|
|
211
|
+
* @type {Map<MeshletGeometry, GPUGeometryMeshletsBVH>}
|
|
212
|
+
*/
|
|
213
|
+
#map = new Map();
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The queue is processed during {@link update}
|
|
217
|
+
* @type {Deque<GPUGeometryMeshletsBVH>}
|
|
218
|
+
*/
|
|
219
|
+
#update_queue = new Deque();
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
*
|
|
223
|
+
* @param {GPUDevice} device
|
|
224
|
+
*/
|
|
225
|
+
constructor(device) {
|
|
226
|
+
this.#device = device;
|
|
227
|
+
|
|
228
|
+
this.#buffer_metadata = device.createBuffer({
|
|
229
|
+
label: "GPUGeometryBVHManager/metadata",
|
|
230
|
+
size: BLAS_METADATA_STRUCT.size * 16,
|
|
231
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
this.#buffer_data = device.createBuffer({
|
|
235
|
+
label: "GPUGeometryBVHManager/data",
|
|
236
|
+
size: DATA_INITIAL_CAPACITY,
|
|
237
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
this.#buffer_parents = device.createBuffer({
|
|
241
|
+
label: "GPUGeometryBVHManager/parents",
|
|
242
|
+
size: DATA_INITIAL_CAPACITY,
|
|
243
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
this.#buffer_leaf_nodes = device.createBuffer({
|
|
247
|
+
label: "GPUGeometryBVHManager/leaf_nodes",
|
|
248
|
+
size: DATA_INITIAL_CAPACITY,
|
|
249
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Node 0 is the empty tree, reserved before anything is appended — see BLAS_EMPTY_TREE_ADDRESS.
|
|
253
|
+
const empty_tree = new Uint32Array(BVH2_NODE_STRUCT.size / Uint32Array.BYTES_PER_ELEMENT);
|
|
254
|
+
|
|
255
|
+
new Float32Array(empty_tree.buffer, BVH_NODE_BOUNDS_WORD * Uint32Array.BYTES_PER_ELEMENT, 6)
|
|
256
|
+
.set([Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity]);
|
|
257
|
+
|
|
258
|
+
empty_tree[BVH_NODE_CHILD_1_WORD] = BLAS_EMPTY_TREE_ADDRESS;
|
|
259
|
+
empty_tree[BVH_NODE_CHILD_2_WORD] = BLAS_EMPTY_TREE_ADDRESS;
|
|
260
|
+
|
|
261
|
+
device.queue.writeBuffer(this.#buffer_data, BLAS_EMPTY_TREE_ADDRESS * BVH2_NODE_STRUCT.size, empty_tree);
|
|
262
|
+
|
|
263
|
+
// Capacity is what the buffer actually holds, not what was asked for — `createBuffer`
|
|
264
|
+
// rounds up, and an allocator sized below its arena just refuses allocations that fit.
|
|
265
|
+
this.#node_capacity = Math.floor(this.#buffer_data.size / BVH2_NODE_STRUCT.size);
|
|
266
|
+
|
|
267
|
+
this.#allocator = new OffsetAllocator(this.#node_capacity, NODE_ALLOCATION_LIMIT);
|
|
268
|
+
|
|
269
|
+
// Take node 0 out of circulation permanently: it holds the empty tree written above, and
|
|
270
|
+
// a real tree handed that region would overwrite what every unresident geometry traces.
|
|
271
|
+
const empty_tree_region = this.#allocator.allocate(1);
|
|
272
|
+
|
|
273
|
+
assert.equal(
|
|
274
|
+
empty_tree_region.offset, BLAS_EMPTY_TREE_ADDRESS,
|
|
275
|
+
'the first region out of a fresh arena is the one the empty tree already occupies'
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Arena node index of the tree a geometry traces while it has none of its own.
|
|
281
|
+
*
|
|
282
|
+
* @returns {number}
|
|
283
|
+
*/
|
|
284
|
+
get empty_tree_address() {
|
|
285
|
+
return BLAS_EMPTY_TREE_ADDRESS;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
get gpu_memory_usage() {
|
|
289
|
+
let r = 0;
|
|
290
|
+
|
|
291
|
+
if (this.#buffer_metadata) {
|
|
292
|
+
r += this.#buffer_metadata.size;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (this.#buffer_data) {
|
|
296
|
+
r += this.#buffer_data.size;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (this.#buffer_parents) {
|
|
300
|
+
r += this.#buffer_parents.size;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (this.#buffer_leaf_nodes) {
|
|
304
|
+
r += this.#buffer_leaf_nodes.size;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return r;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* The tree belonging to `owner`, registered against `source`'s shape if it does not exist yet.
|
|
312
|
+
*
|
|
313
|
+
* Two arguments rather than one because those are two different questions. `owner` is the
|
|
314
|
+
* identity the tree is looked up by; `source` is the geometry whose nodes it starts as. They
|
|
315
|
+
* coincide for anything that owns its geometry outright, and they do not for a per-instance
|
|
316
|
+
* copy — a skinned clone deforms its own vertices, so its bounds are its own and cannot be
|
|
317
|
+
* written into the tree everyone else is tracing, but its *shape* is by definition the
|
|
318
|
+
* source's. Keeping the two separate is what lets one derivation serve every copy.
|
|
319
|
+
*
|
|
320
|
+
* @param {object} owner identity to look this tree up by — a geometry, a clone's metadata row
|
|
321
|
+
* @param {MeshletGeometry} source geometry whose nodes this tree takes its shape and rest-pose
|
|
322
|
+
* bounds from
|
|
323
|
+
* @returns {GPUGeometryMeshletsBVH}
|
|
324
|
+
*/
|
|
325
|
+
obtain(owner, source) {
|
|
326
|
+
assert.defined(owner, 'owner');
|
|
327
|
+
assert.equal(source.isMeshletGeometry, true, 'source.isMeshletGeometry !== true');
|
|
328
|
+
|
|
329
|
+
let bvh = this.#map.get(owner);
|
|
330
|
+
|
|
331
|
+
if (bvh === undefined) {
|
|
332
|
+
bvh = this.#register(owner, source);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return bvh;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Register a tree, keyed by whatever owns it.
|
|
340
|
+
*
|
|
341
|
+
* `owner` is the identity the tree is looked up by and `source` is the geometry whose nodes it
|
|
342
|
+
* holds. For an ordinary geometry those are the same object. For a per-instance copy they are
|
|
343
|
+
* not: the copy needs a node region of its own, because its bounds move independently, while
|
|
344
|
+
* the *shape* it is a copy of belongs to the source.
|
|
345
|
+
*
|
|
346
|
+
* @param {object} owner
|
|
347
|
+
* @param {MeshletGeometry} source
|
|
348
|
+
* @returns {GPUGeometryMeshletsBVH}
|
|
349
|
+
*/
|
|
350
|
+
#register(owner, source) {
|
|
351
|
+
|
|
352
|
+
const bvh = new GPUGeometryMeshletsBVH();
|
|
353
|
+
|
|
354
|
+
// No bytes are taken now. The upload reads the geometry's tree as it is *then*, so a tree
|
|
355
|
+
// that arrives after registration is uploaded once it exists rather than never — and a
|
|
356
|
+
// geometry with none is not resident over nothing.
|
|
357
|
+
bvh.topology = this.#obtain_topology(source);
|
|
358
|
+
|
|
359
|
+
this.#map.set(owner, bvh);
|
|
360
|
+
|
|
361
|
+
this.#update_queue.add(bvh);
|
|
362
|
+
|
|
363
|
+
return bvh;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* The parents/leaf-nodes derivation for a geometry, made once and shared by every tree that is
|
|
368
|
+
* a copy of it.
|
|
369
|
+
*
|
|
370
|
+
* @param {MeshletGeometry} source
|
|
371
|
+
* @returns {object} the topology record
|
|
372
|
+
*/
|
|
373
|
+
#obtain_topology(source) {
|
|
374
|
+
let topology = this.#topology.get(source);
|
|
375
|
+
|
|
376
|
+
if (topology !== undefined) {
|
|
377
|
+
return topology;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Derivation waits for {@link #upload_pending_topology}, beside the residency decision it
|
|
381
|
+
// feeds. A geometry can be registered before its BLAS bytes exist — nothing has to have
|
|
382
|
+
// built one yet — and a record that says "not resident" is the honest answer for that,
|
|
383
|
+
// rather than an empty derivation cached forever.
|
|
384
|
+
topology = {
|
|
385
|
+
source,
|
|
386
|
+
leaf_count: 0,
|
|
387
|
+
node_count: 0,
|
|
388
|
+
parents_offset: 0,
|
|
389
|
+
leaf_nodes_offset: 0,
|
|
390
|
+
resident: false,
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
this.#topology.set(source, topology);
|
|
394
|
+
|
|
395
|
+
return topology;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Everything a refit needs to find a resident tree, or `undefined` while it is not resident.
|
|
400
|
+
*
|
|
401
|
+
* "Resident" means the owner's nodes and its shape are both in their arenas, and that they are
|
|
402
|
+
* the same tree. A record snapshots its geometry's bytes when it is registered, and a geometry
|
|
403
|
+
* registered before its tree exists is uploaded as nothing — resident over zero bytes, at the
|
|
404
|
+
* arena's end. Its shape is derived later, from the bytes as they are then. Answering with that
|
|
405
|
+
* shape's counts at that record's address would hand a refit whichever tree was appended next,
|
|
406
|
+
* so the answer is withheld until the nodes actually uploaded are the tree the shape describes.
|
|
407
|
+
*
|
|
408
|
+
* @param {object} owner the identity passed to {@link obtain}
|
|
409
|
+
* @returns {{node_word_offset: number, node_count: number, leaf_count: number, parents_offset: number, leaf_nodes_offset: number}|undefined}
|
|
410
|
+
*/
|
|
411
|
+
get_refit_target(owner) {
|
|
412
|
+
const bvh = this.#map.get(owner);
|
|
413
|
+
|
|
414
|
+
if (bvh === undefined || bvh.released || !bvh.resident || !bvh.topology.resident) {
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (bvh.uploaded_byte_length !== bvh.topology.node_count * BVH2_NODE_STRUCT.size) {
|
|
419
|
+
// the nodes in the arena are not the tree the shape was derived from
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
node_word_offset: bvh.address * (BVH2_NODE_STRUCT.size / Uint32Array.BYTES_PER_ELEMENT),
|
|
425
|
+
node_count: bvh.topology.node_count,
|
|
426
|
+
leaf_count: bvh.topology.leaf_count,
|
|
427
|
+
parents_offset: bvh.topology.parents_offset,
|
|
428
|
+
leaf_nodes_offset: bvh.topology.leaf_nodes_offset,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Where a registered tree's root sits in the arena, or `undefined` while nothing is resident
|
|
434
|
+
* for this owner — either it was never registered, or {@link update} has not run since.
|
|
435
|
+
*
|
|
436
|
+
* @param {object} owner the identity passed to {@link obtain}
|
|
437
|
+
* @returns {number|undefined} arena node index
|
|
438
|
+
*/
|
|
439
|
+
get_address(owner) {
|
|
440
|
+
const bvh = this.#map.get(owner);
|
|
441
|
+
|
|
442
|
+
if (bvh === undefined || bvh.released || !bvh.resident) {
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return bvh.address;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Publish the lookup table the traversal shaders read.
|
|
451
|
+
*
|
|
452
|
+
* `addresses[i]` is the arena node index that index `i` resolves to. What `i` means is the
|
|
453
|
+
* caller's business — today it is a geometry index, and the shaders agree because
|
|
454
|
+
* {@link GPUGeometryManager} builds the table from the same ids it stamps into the geometry
|
|
455
|
+
* metadata buffer.
|
|
456
|
+
*
|
|
457
|
+
* @param {Uint32Array} addresses
|
|
458
|
+
*/
|
|
459
|
+
publish_lookup(addresses) {
|
|
460
|
+
assert.defined(addresses, 'addresses');
|
|
461
|
+
|
|
462
|
+
this.#ensure_metadata_capacity(addresses.length);
|
|
463
|
+
|
|
464
|
+
this.#device.queue.writeBuffer(this.#buffer_metadata, 0, addresses, 0, addresses.length);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Release an owner's BLAS: the map entry is dropped, its arena region goes back to
|
|
469
|
+
* {@link #allocator} for the next tree of that size, and the next {@link publish_lookup}
|
|
470
|
+
* stops naming its address.
|
|
471
|
+
*
|
|
472
|
+
* The bytes are left as they are. Nothing reads a region between the free and the tree that
|
|
473
|
+
* takes it next, because the lookup is republished — {@link #residency_version} moves below,
|
|
474
|
+
* which is what tells {@link GPUGeometryManager} to do it — before anything traces again.
|
|
475
|
+
*
|
|
476
|
+
* @param {object} owner the identity passed to {@link obtain}
|
|
477
|
+
* @returns {boolean} true if a BLAS was registered for this owner and released
|
|
478
|
+
*/
|
|
479
|
+
remove(owner) {
|
|
480
|
+
const bvh = this.#map.get(owner);
|
|
481
|
+
|
|
482
|
+
if (bvh === undefined) {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// may still be sitting in #update_queue if removed the same frame it was registered;
|
|
487
|
+
// the update pass skips released records
|
|
488
|
+
bvh.released = true;
|
|
489
|
+
|
|
490
|
+
// A record registered but never uploaded holds no region — it was skipped for want of a
|
|
491
|
+
// tree, and there is nothing to give back.
|
|
492
|
+
if (bvh.allocation !== null) {
|
|
493
|
+
this.#allocator.free(bvh.allocation);
|
|
494
|
+
|
|
495
|
+
bvh.allocation = null;
|
|
496
|
+
bvh.resident = false;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
this.#map.delete(owner);
|
|
500
|
+
|
|
501
|
+
this.#release_topology_if_orphaned(bvh.topology);
|
|
502
|
+
|
|
503
|
+
// a refit target that answered a moment ago answers `undefined` now
|
|
504
|
+
this.#residency_version += 1;
|
|
505
|
+
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Drop a shape once the last tree that had it is gone.
|
|
511
|
+
*
|
|
512
|
+
* The arena bytes are not reclaimed — same append-only story as the nodes — but the map entry
|
|
513
|
+
* holds a strong reference to the geometry it is keyed by, so leaving it would keep every
|
|
514
|
+
* geometry a scene ever loaded alive for as long as the manager is.
|
|
515
|
+
*
|
|
516
|
+
* @param {object} topology
|
|
517
|
+
*/
|
|
518
|
+
#release_topology_if_orphaned(topology) {
|
|
519
|
+
for (const bvh of this.#map.values()) {
|
|
520
|
+
if (bvh.topology === topology) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
for (const [source, candidate] of this.#topology) {
|
|
526
|
+
if (candidate === topology) {
|
|
527
|
+
this.#topology.delete(source);
|
|
528
|
+
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Make the node arena hold at least `size` nodes, keeping {@link #allocator} the same size as
|
|
536
|
+
* the buffer it describes.
|
|
537
|
+
*
|
|
538
|
+
* Growth never moves a tree: `ensure_buffer_capacity` copies the old buffer in at offset 0,
|
|
539
|
+
* and {@link OffsetAllocator#grow} appends the new tail as free space rather than re-packing
|
|
540
|
+
* what is already out. So every address handed out before this call still names the same
|
|
541
|
+
* nodes afterwards, and nothing needs republishing on account of the growth itself.
|
|
542
|
+
*
|
|
543
|
+
* @param {number} size in nodes
|
|
544
|
+
*/
|
|
545
|
+
#ensure_data_capacity(size) {
|
|
546
|
+
if (this.#node_capacity >= size) {
|
|
547
|
+
// big enough already
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
this.#buffer_data = ensure_buffer_capacity(
|
|
552
|
+
this.#device,
|
|
553
|
+
this.#buffer_data,
|
|
554
|
+
size * BVH2_NODE_STRUCT.size,
|
|
555
|
+
DATA_SIZE_ALIGNMENT,
|
|
556
|
+
DATA_GROWTH_STEP,
|
|
557
|
+
DATA_GROWTH_MULTIPLIER
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
// What the buffer actually came back as, not what was asked for: the helper grows by at
|
|
561
|
+
// least a step and rounds up, and arena space the allocator does not know about is space
|
|
562
|
+
// nothing can ever be put in.
|
|
563
|
+
this.#node_capacity = Math.floor(this.#buffer_data.size / BVH2_NODE_STRUCT.size);
|
|
564
|
+
|
|
565
|
+
this.#allocator.grow(this.#node_capacity);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Reserve `node_count` consecutive nodes, growing the arena if the allocator cannot place
|
|
570
|
+
* them where it stands.
|
|
571
|
+
*
|
|
572
|
+
* **Must not be called between a command encoder being opened and submitted.** Growing
|
|
573
|
+
* replaces {@link #buffer_data} and destroys the old one, and an encoder holding copies into
|
|
574
|
+
* the destroyed buffer fails validation at submit — which is why every region for an upload
|
|
575
|
+
* batch is reserved before the batch's encoder exists.
|
|
576
|
+
*
|
|
577
|
+
* @param {number} node_count
|
|
578
|
+
* @returns {Allocation}
|
|
579
|
+
*/
|
|
580
|
+
#allocate_nodes(node_count) {
|
|
581
|
+
const first_attempt = this.#allocator.allocate(node_count);
|
|
582
|
+
|
|
583
|
+
if (first_attempt.offset !== ALLOCATOR_NO_SPACE) {
|
|
584
|
+
return first_attempt;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// The allocator sorts free space into bins by size and rounds a bin down, so it can refuse
|
|
588
|
+
// a request the arena has room for. Asking for the shortfall exactly can therefore come
|
|
589
|
+
// back refused a second time; the growth helper's own step and multiplier are the
|
|
590
|
+
// headroom that makes the retry land.
|
|
591
|
+
this.#ensure_data_capacity(this.#node_capacity + node_count);
|
|
592
|
+
|
|
593
|
+
const second_attempt = this.#allocator.allocate(node_count);
|
|
594
|
+
|
|
595
|
+
assert.notEqual(
|
|
596
|
+
second_attempt.offset, ALLOCATOR_NO_SPACE,
|
|
597
|
+
`BLAS node arena cannot place ${node_count} nodes in ${this.#node_capacity}; ` +
|
|
598
|
+
`${NODE_ALLOCATION_LIMIT} regions is the ceiling and grow() does not raise it`
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
return second_attempt;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
*
|
|
606
|
+
* @param {number} size
|
|
607
|
+
*/
|
|
608
|
+
#ensure_metadata_capacity(row_count) {
|
|
609
|
+
this.#buffer_metadata = ensure_buffer_capacity(
|
|
610
|
+
this.#device,
|
|
611
|
+
this.#buffer_metadata,
|
|
612
|
+
Math.max(1, row_count) * BLAS_METADATA_STRUCT.size,
|
|
613
|
+
BLAS_METADATA_STRUCT.align,
|
|
614
|
+
16,
|
|
615
|
+
1.2
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Append every topology derived but not yet uploaded to its arena.
|
|
621
|
+
*
|
|
622
|
+
* Queue-free: the map is small (one entry per distinct geometry) and a `resident` flag says what
|
|
623
|
+
* is left to do, which is one fewer thing to keep in step than a second queue would be.
|
|
624
|
+
*
|
|
625
|
+
* @returns {boolean} whether any shape became resident
|
|
626
|
+
*/
|
|
627
|
+
#upload_pending_topology() {
|
|
628
|
+
const queue = this.#device.queue;
|
|
629
|
+
|
|
630
|
+
let uploaded = false;
|
|
631
|
+
|
|
632
|
+
for (const topology of this.#topology.values()) {
|
|
633
|
+
if (topology.resident) {
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const bytes = topology.source.bvh;
|
|
638
|
+
|
|
639
|
+
if (bytes === undefined || bytes.byteLength === 0) {
|
|
640
|
+
// no tree to take the shape of yet; try again next time
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const { parents, leaf_nodes } = bvh2_derive_topology(bytes);
|
|
645
|
+
|
|
646
|
+
this.#buffer_parents = ensure_buffer_capacity(
|
|
647
|
+
this.#device,
|
|
648
|
+
this.#buffer_parents,
|
|
649
|
+
(this.#buffer_parents_end + parents.length) * Uint32Array.BYTES_PER_ELEMENT,
|
|
650
|
+
DATA_SIZE_ALIGNMENT,
|
|
651
|
+
DATA_GROWTH_STEP,
|
|
652
|
+
DATA_GROWTH_MULTIPLIER
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
this.#buffer_leaf_nodes = ensure_buffer_capacity(
|
|
656
|
+
this.#device,
|
|
657
|
+
this.#buffer_leaf_nodes,
|
|
658
|
+
(this.#buffer_leaf_nodes_end + leaf_nodes.length) * Uint32Array.BYTES_PER_ELEMENT,
|
|
659
|
+
DATA_SIZE_ALIGNMENT,
|
|
660
|
+
DATA_GROWTH_STEP,
|
|
661
|
+
DATA_GROWTH_MULTIPLIER
|
|
662
|
+
);
|
|
663
|
+
|
|
664
|
+
topology.parents_offset = this.#buffer_parents_end;
|
|
665
|
+
topology.leaf_nodes_offset = this.#buffer_leaf_nodes_end;
|
|
666
|
+
|
|
667
|
+
queue.writeBuffer(
|
|
668
|
+
this.#buffer_parents,
|
|
669
|
+
topology.parents_offset * Uint32Array.BYTES_PER_ELEMENT,
|
|
670
|
+
parents, 0, parents.length
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
queue.writeBuffer(
|
|
674
|
+
this.#buffer_leaf_nodes,
|
|
675
|
+
topology.leaf_nodes_offset * Uint32Array.BYTES_PER_ELEMENT,
|
|
676
|
+
leaf_nodes, 0, leaf_nodes.length
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
this.#buffer_parents_end += parents.length;
|
|
680
|
+
this.#buffer_leaf_nodes_end += leaf_nodes.length;
|
|
681
|
+
|
|
682
|
+
topology.leaf_count = leaf_nodes.length;
|
|
683
|
+
// slots, not tree nodes — the arena numbers a tree's nodes with the free list's holes
|
|
684
|
+
// in, and a refit's arrival counters are indexed by that numbering
|
|
685
|
+
topology.node_count = parents.length;
|
|
686
|
+
topology.resident = true;
|
|
687
|
+
|
|
688
|
+
uploaded = true;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return uploaded;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Bring the arenas up to date with everything registered since the last call, and move the
|
|
696
|
+
* residency version if anything a refit reads has changed.
|
|
697
|
+
*
|
|
698
|
+
* Shapes first, and as their own question: they are not behind the node queue, so a frame in
|
|
699
|
+
* which a shape arrives for an already-uploaded record still uploads it — and still moves the
|
|
700
|
+
* version, because that record's refit answer has changed all the same.
|
|
701
|
+
*/
|
|
702
|
+
update() {
|
|
703
|
+
const topology_uploaded = this.#upload_pending_topology();
|
|
704
|
+
const nodes_uploaded = this.#upload_pending_nodes();
|
|
705
|
+
|
|
706
|
+
if (topology_uploaded || nodes_uploaded) {
|
|
707
|
+
this.#residency_version += 1;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Append the nodes of every queued record whose geometry has a tree to the node arena.
|
|
713
|
+
*
|
|
714
|
+
* A record whose geometry has no tree yet stays queued: there is nothing to upload, and a record
|
|
715
|
+
* resident over zero bytes would answer with an address the next tree then occupies. It uploads
|
|
716
|
+
* on the first update after its bytes exist, from the bytes as they are then. A record removed
|
|
717
|
+
* before it was uploaded leaves the queue without uploading.
|
|
718
|
+
*
|
|
719
|
+
* @returns {boolean} whether anything became resident
|
|
720
|
+
*/
|
|
721
|
+
#upload_pending_nodes() {
|
|
722
|
+
const queue = this.#update_queue;
|
|
723
|
+
|
|
724
|
+
const queue_size = queue.size();
|
|
725
|
+
|
|
726
|
+
if (queue_size === 0) {
|
|
727
|
+
// nothing to do
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// `offset` is into the staging buffer the batch is assembled in; `allocation` is the arena
|
|
732
|
+
// region the tree ends up at, filled in once every record's size is known.
|
|
733
|
+
/** @type {{record: GPUGeometryMeshletsBVH, bytes: ArrayBuffer, offset: number, allocation: Allocation}[]} */
|
|
734
|
+
const uploads = [];
|
|
735
|
+
|
|
736
|
+
/** @type {GPUGeometryMeshletsBVH[]} */
|
|
737
|
+
const waiting = [];
|
|
738
|
+
|
|
739
|
+
let total_byte_size = 0;
|
|
740
|
+
let used_node_count = 0;
|
|
741
|
+
|
|
742
|
+
for (let i = 0; i < queue_size; i++) {
|
|
743
|
+
const record = queue.getElementByIndex(i);
|
|
744
|
+
|
|
745
|
+
if (record.released) { continue; }
|
|
746
|
+
|
|
747
|
+
const bytes = record.topology.source.bvh;
|
|
748
|
+
|
|
749
|
+
if (bytes === undefined || bytes.byteLength === 0) {
|
|
750
|
+
// no tree yet; try again next time
|
|
751
|
+
waiting.push(record);
|
|
752
|
+
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const node_count = bytes.byteLength / BVH2_NODE_STRUCT.size;
|
|
757
|
+
|
|
758
|
+
assert.isNonNegativeInteger(node_count, "node_count");
|
|
759
|
+
|
|
760
|
+
uploads.push({ record, bytes, offset: total_byte_size });
|
|
761
|
+
|
|
762
|
+
total_byte_size += bytes.byteLength;
|
|
763
|
+
used_node_count += node_count;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Drained: what waits goes back in, what was released is gone. Leaving uploaded entries in
|
|
767
|
+
// the queue re-uploads every BLAS ever registered on each update and appends fresh copies
|
|
768
|
+
// at #buffer_data_end — quadratic memory growth until createBuffer fails.
|
|
769
|
+
queue.clear();
|
|
770
|
+
|
|
771
|
+
for (const record of waiting) {
|
|
772
|
+
queue.add(record);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (uploads.length === 0) {
|
|
776
|
+
// nothing became resident, so nothing a refit reads has changed, and no consumer needs
|
|
777
|
+
// to ask again
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
console.log(`GPUGeometryBVHManager#update, uploading ${uploads.length} of ${queue_size} queued`);
|
|
782
|
+
|
|
783
|
+
// Every region first, while growing is still allowed to replace the buffer — see
|
|
784
|
+
// #allocate_nodes. `used_node_count` is only the total; the regions need not be adjacent.
|
|
785
|
+
for (const upload of uploads) {
|
|
786
|
+
upload.allocation = this.#allocate_nodes(upload.bytes.byteLength / BVH2_NODE_STRUCT.size);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const device = this.#device;
|
|
790
|
+
const encoder = device.createCommandEncoder({
|
|
791
|
+
label: "GPUGeometryBVHManager#update"
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
const temp_data_buffer = device.createBuffer({
|
|
795
|
+
label: "temp_data_buffer",
|
|
796
|
+
usage: GPUBufferUsage.COPY_SRC,
|
|
797
|
+
size: total_byte_size,
|
|
798
|
+
mappedAtCreation: true
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
const mapped_temp_data = temp_data_buffer.getMappedRange();
|
|
802
|
+
|
|
803
|
+
// write to temp buffer
|
|
804
|
+
for (const { bytes, offset } of uploads) {
|
|
805
|
+
array_buffer_copy(bytes, 0, mapped_temp_data, offset, bytes.byteLength);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
temp_data_buffer.unmap();
|
|
809
|
+
|
|
810
|
+
// copy data from temp buffer to where it needs to go
|
|
811
|
+
for (const { record, bytes, offset, allocation } of uploads) {
|
|
812
|
+
// A record uploaded twice would strand its first region; the queue is drained on every
|
|
813
|
+
// pass and a released record is skipped, so reaching here twice means a bug upstream.
|
|
814
|
+
assert.equal(record.allocation, null, 'a record already holding a region is being uploaded again');
|
|
815
|
+
|
|
816
|
+
record.allocation = allocation;
|
|
817
|
+
record.address = allocation.offset;
|
|
818
|
+
record.resident = true;
|
|
819
|
+
record.bvh = bytes;
|
|
820
|
+
// what actually went into the arena, for `get_refit_target` to hold against the shape
|
|
821
|
+
record.uploaded_byte_length = bytes.byteLength;
|
|
822
|
+
|
|
823
|
+
encoder.copyBufferToBuffer(
|
|
824
|
+
temp_data_buffer, offset,
|
|
825
|
+
this.#buffer_data, allocation.offset * BVH2_NODE_STRUCT.size,
|
|
826
|
+
bytes.byteLength
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const free_nodes = this.#allocator.storageReport().freeStorage;
|
|
831
|
+
|
|
832
|
+
console.warn(
|
|
833
|
+
`BLAS size: ${number_format_by_thousands((this.#node_capacity - free_nodes) * BVH2_NODE_STRUCT.size)} bytes` +
|
|
834
|
+
` of ${number_format_by_thousands(this.#node_capacity * BVH2_NODE_STRUCT.size)}`
|
|
835
|
+
);
|
|
836
|
+
|
|
837
|
+
device.queue.submit([encoder.finish()])
|
|
838
|
+
|
|
839
|
+
// free up the temp buffer
|
|
840
|
+
temp_data_buffer.destroy();
|
|
841
|
+
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
735
844
|
}
|