@woosh/meep-engine 3.24.0 → 3.25.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/engine/graphics3/PickingSystem.d.ts +3 -3
- package/src/engine/graphics3/PickingSystem.d.ts.map +1 -1
- package/src/engine/graphics3/PickingSystem.js +52 -55
- package/src/engine/graphics3/shader_picking_gather.d.ts +40 -0
- package/src/engine/graphics3/shader_picking_gather.d.ts.map +1 -0
- package/src/engine/graphics3/shader_picking_gather.js +120 -0
- package/src/engine/physics/cloth/MEASUREMENTS.md +1050 -0
- package/src/engine/physics/cloth/PLAN.md +411 -49
- package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.d.ts +52 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.js +70 -0
- package/src/engine/physics/cloth/ecs/ClothDynamics.d.ts +12 -3
- package/src/engine/physics/cloth/ecs/ClothDynamics.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothDynamics.js +340 -331
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +36 -22
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothInstance.js +38 -23
- package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.d.ts +84 -0
- package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.js +141 -0
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +51 -82
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothSystem.js +81 -869
- package/src/engine/physics/cloth/ecs/ClothWorkerCore.d.ts +48 -0
- package/src/engine/physics/cloth/ecs/ClothWorkerCore.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothWorkerCore.js +107 -0
- package/src/engine/physics/cloth/ecs/ClothWorld.d.ts +286 -0
- package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothWorld.js +1104 -0
- package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts +182 -0
- package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/WorkerClothSystem.js +562 -0
- package/src/engine/physics/cloth/ecs/cloth.worker.d.ts +2 -0
- package/src/engine/physics/cloth/ecs/cloth.worker.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth.worker.js +12 -0
- package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_build_rows.js +2 -1
- package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts +58 -0
- package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_dynamics_map.js +341 -271
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +20 -2
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +39 -15
- package/src/engine/physics/cloth/ecs/cloth_worker_protocol.d.ts +29 -0
- package/src/engine/physics/cloth/ecs/cloth_worker_protocol.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_worker_protocol.js +78 -0
- package/src/engine/physics/cloth/ecs/makeClothWorker.d.ts +17 -0
- package/src/engine/physics/cloth/ecs/makeClothWorker.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/makeClothWorker.js +18 -0
- package/src/engine/physics/cloth/playground/README.md +73 -2
- package/src/engine/physics/cloth/playground/wind.html +166 -0
- package/src/engine/physics/cloth/playground/wind_build.d.ts +81 -0
- package/src/engine/physics/cloth/playground/wind_build.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/wind_build.js +141 -0
- package/src/engine/physics/cloth/playground/wind_main.d.ts +2 -0
- package/src/engine/physics/cloth/playground/wind_main.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/wind_main.js +681 -0
- package/src/engine/physics/cloth/solver/ClothState.d.ts +134 -79
- package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/ClothState.js +273 -12
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +33 -0
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_solver_constants.js +34 -0
- package/src/engine/physics/cloth/solver/cloth_step.d.ts +21 -2
- package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_step.js +173 -4
- package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts +40 -0
- package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/cloth_step_instance.js +284 -0
- package/src/engine/physics/cloth/solver/cloth_wind_accelerate.d.ts +107 -0
- package/src/engine/physics/cloth/solver/cloth_wind_accelerate.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/cloth_wind_accelerate.js +263 -0
- package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts +88 -0
- package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts.map +1 -0
- package/src/engine/physics/cloth/wind/AbstractClothWind.js +100 -0
- package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts +85 -0
- package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts.map +1 -0
- package/src/engine/physics/cloth/wind/ClothAmbientWind.js +182 -0
- package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts +79 -0
- package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts.map +1 -0
- package/src/engine/physics/cloth/wind/ClothFluidWind.js +210 -0
- package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts +101 -0
- package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts.map +1 -0
- package/src/engine/physics/cloth/wind/cloth_sample_air.js +290 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/device/mock/texture_copy.d.ts.map +1 -1
- package/src/shade/device/mock/texture_copy.js +28 -0
- package/src/shade/playground/vgeo_runtime/README.md +94 -0
- package/src/shade/playground/vgeo_runtime/index.html +62 -0
- package/src/shade/playground/vgeo_runtime/main.d.ts +2 -0
- package/src/shade/playground/vgeo_runtime/main.d.ts.map +1 -0
- package/src/shade/playground/vgeo_runtime/main.js +399 -0
- package/src/shade/playground/vgeo_viewer/sample_asset.js +1 -1
- package/src/shade/renderer/camera/Camera.d.ts +14 -0
- package/src/shade/renderer/camera/Camera.d.ts.map +1 -1
- package/src/shade/renderer/camera/Camera.js +7 -1
- package/src/shade/renderer/geometry/GEOMETRY_METADATA_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/geometry/GEOMETRY_METADATA_STRUCT.js +16 -1
- package/src/shade/renderer/geometry/GPUGeometryManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/GPUGeometryManager.js +29 -9
- package/src/shade/renderer/geometry/GPUGeometryMetadata.d.ts +10 -0
- package/src/shade/renderer/geometry/GPUGeometryMetadata.d.ts.map +1 -1
- package/src/shade/renderer/geometry/GPUGeometryMetadata.js +13 -0
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +46 -18
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +70 -22
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +97 -19
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +12 -2
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +14 -3
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts +2 -2
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +124 -6
- package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts +22 -0
- package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.js +53 -0
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts +8 -1
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +8 -1
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.d.ts +16 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.js +15 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.d.ts +54 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.js +53 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts +13 -2
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.js +14 -2
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts +100 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.js +113 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.js +5 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.js +108 -0
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +23 -0
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +40 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts +228 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.js +932 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +176 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +425 -0
- package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +241 -0
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +574 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts +120 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +674 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts +287 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.js +976 -0
- package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts +42 -0
- package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.js +96 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.d.ts +16 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.js +15 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.js +83 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +160 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.js +255 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.js +80 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.js +144 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +530 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts +39 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.js +54 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.d.ts +41 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.js +56 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts +49 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +328 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts +10 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.js +51 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.js +12 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js +99 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts +7 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.js +27 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.js +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.d.ts +7 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.js +35 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.d.ts +15 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.js +14 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts +7 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.js +36 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts +10 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.js +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.d.ts +11 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.js +54 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.d.ts +11 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.js +10 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.d.ts +7 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.js +30 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.d.ts +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.js +12 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.js +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts +11 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.js +61 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.d.ts +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.js +12 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.d.ts +20 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.js +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts +23 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +70 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts +18 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.js +25 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts +35 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.js +41 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts +32 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.js +138 -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/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +9 -0
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +12 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +12 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +10 -0
- package/src/shade/renderer/scene/GPUSceneContext.d.ts +18 -0
- package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
- package/src/shade/renderer/scene/GPUSceneContext.js +647 -594
- package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts.map +1 -1
- package/src/shade/renderer/scene/rows/GPUSceneRows.js +22 -0
- package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.d.ts +35 -0
- package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.js +91 -0
- package/src/engine/graphics3/instantiate_scene_bundle.d.ts +0 -54
- package/src/engine/graphics3/instantiate_scene_bundle.d.ts.map +0 -1
- package/src/engine/graphics3/shade_node_to_entity_composition.d.ts +0 -39
- package/src/engine/graphics3/shade_node_to_entity_composition.d.ts.map +0 -1
- package/src/shade/renderer/animation/compute_skin_world_bounds.d.ts +0 -35
- package/src/shade/renderer/animation/compute_skin_world_bounds.d.ts.map +0 -1
- package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.d.ts +0 -96
- package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.d.ts.map +0 -1
- package/src/shade/renderer/animation/pose/pose_evaluate_world.d.ts +0 -20
- package/src/shade/renderer/animation/pose/pose_evaluate_world.d.ts.map +0 -1
- package/src/shade/renderer/animation/skin_bind_transform.d.ts +0 -25
- package/src/shade/renderer/animation/skin_bind_transform.d.ts.map +0 -1
- package/src/shade/renderer/animation/skin_frame_defect.d.ts +0 -34
- package/src/shade/renderer/animation/skin_frame_defect.d.ts.map +0 -1
- package/src/shade/renderer/animation/skin_joint_matrix.d.ts +0 -24
- package/src/shade/renderer/animation/skin_joint_matrix.d.ts.map +0 -1
- package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts +0 -70
- package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts.map +0 -1
- package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts +0 -96
- package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts.map +0 -1
- package/src/shade/renderer/scene/InstanceBatch.d.ts +0 -92
- package/src/shade/renderer/scene/InstanceBatch.d.ts.map +0 -1
- package/src/shade/renderer/scene/Mesh.d.ts +0 -55
- package/src/shade/renderer/scene/Mesh.d.ts.map +0 -1
- package/src/shade/renderer/scene/Node3D.d.ts +0 -159
- package/src/shade/renderer/scene/Node3D.d.ts.map +0 -1
- package/src/shade/renderer/scene/Node3DDirtyList.d.ts +0 -59
- package/src/shade/renderer/scene/Node3DDirtyList.d.ts.map +0 -1
- package/src/shade/renderer/scene/SceneNodeBinding.d.ts +0 -52
- package/src/shade/renderer/scene/SceneNodeBinding.d.ts.map +0 -1
- package/src/shade/renderer/scene/SkinnedMesh.d.ts +0 -27
- package/src/shade/renderer/scene/SkinnedMesh.d.ts.map +0 -1
- package/src/shade/renderer/scene/TransformAuthority.d.ts +0 -19
- package/src/shade/renderer/scene/TransformAuthority.d.ts.map +0 -1
|
@@ -0,0 +1,976 @@
|
|
|
1
|
+
import { assert } from "../../../../../core/assert.js";
|
|
2
|
+
import { ALLOCATOR_NO_SPACE, OffsetAllocator } from "../../../../../core/binary/allocator/OffsetAllocator.js";
|
|
3
|
+
import { VGEO_DEFAULT_PAGE_SIZE } from "../format/page/VGEO_DEFAULT_PAGE_SIZE.js";
|
|
4
|
+
import { VGEO_CLUSTER_ROW_OFFSET } from "./layout/VGEO_CLUSTER_ROW_OFFSET.js";
|
|
5
|
+
import { VGEO_CLUSTER_ROW_WORDS } from "./layout/VGEO_CLUSTER_ROW_WORDS.js";
|
|
6
|
+
import { VGEO_GEOMETRY_ROW_OFFSET } from "./layout/VGEO_GEOMETRY_ROW_OFFSET.js";
|
|
7
|
+
import { VGEO_GEOMETRY_ROW_WORDS } from "./layout/VGEO_GEOMETRY_ROW_WORDS.js";
|
|
8
|
+
import { VGEO_GROUP_FLAG_SELECTABLE } from "./layout/VGEO_GROUP_FLAG_SELECTABLE.js";
|
|
9
|
+
import { VGEO_GROUP_ROW_OFFSET } from "./layout/VGEO_GROUP_ROW_OFFSET.js";
|
|
10
|
+
import { VGEO_GROUP_ROW_WORDS } from "./layout/VGEO_GROUP_ROW_WORDS.js";
|
|
11
|
+
import { VGEO_NODE_ROW_WORDS } from "./layout/VGEO_NODE_ROW_WORDS.js";
|
|
12
|
+
import { VGEO_NO_CHILD_PAGE } from "./layout/VGEO_NO_CHILD_PAGE.js";
|
|
13
|
+
import { VGEO_PAGE_ROW_NONE } from "./layout/VGEO_PAGE_ROW_NONE.js";
|
|
14
|
+
import { VGEO_PAGE_ROW_OFFSET } from "./layout/VGEO_PAGE_ROW_OFFSET.js";
|
|
15
|
+
import { VGEO_PAGE_ROW_WORDS } from "./layout/VGEO_PAGE_ROW_WORDS.js";
|
|
16
|
+
import { vgeo_runtime_page_block_words } from "./vgeo_runtime_page_block_words.js";
|
|
17
|
+
import { vgeo_runtime_page_slot_words } from "./vgeo_runtime_page_slot_words.js";
|
|
18
|
+
import { vgeo_runtime_write_page_block } from "./vgeo_runtime_write_page_block.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* What a VRAM-resident page **is**, with no device anywhere near it.
|
|
22
|
+
*
|
|
23
|
+
* Two word arrays: the arena of fixed-stride page slots, and the per-geometry page lookup.
|
|
24
|
+
* {@link GPUVirtualGeometryPool} owns the buffers, the budget and the meshlet-pool allocation; this
|
|
25
|
+
* owns the layout, and the split is the same one `format/read/` and `residency/` already make —
|
|
26
|
+
* mechanics below, policy and devices above.
|
|
27
|
+
*
|
|
28
|
+
* It is worth having as its own object for a reason beyond tidiness: the cut kernel's agreement
|
|
29
|
+
* against `vgeo_select_cut` is the single most important test in this campaign, and it can run in
|
|
30
|
+
* node against these arrays. A mirror written for the spec would prove the mirror.
|
|
31
|
+
*
|
|
32
|
+
* ## Slots, not allocations
|
|
33
|
+
*
|
|
34
|
+
* A page's rows live at `page_row × slot_words`, and everything that used to make that address a
|
|
35
|
+
* question is gone with the question: no `OffsetAllocator`, no growth, no ceiling, no shadow that
|
|
36
|
+
* can grow before the buffer it mirrors. `VIRTUAL_GEOMETRY_DESIGN.md` §6 asked for this from the
|
|
37
|
+
* start and format §11.14 is what finally permits it — `nominal_page_size` is a bound every page is
|
|
38
|
+
* within, `GPUVirtualGeometryPool.register` refuses a container that does not promise it, and
|
|
39
|
+
* {@link vgeo_runtime_page_slot_words} turns that promise into a stride. A page whose rows do not
|
|
40
|
+
* fit the stride is refused; see that function for why the stride is a choice and what it was
|
|
41
|
+
* measured against.
|
|
42
|
+
*
|
|
43
|
+
* The page's own header is the first {@link VGEO_PAGE_ROW_WORDS} words of its slot rather than a
|
|
44
|
+
* row of a second table, which saves the traversal a storage binding it does not have to spare
|
|
45
|
+
* (RUNTIME_PLAN §4.9) and makes an install one contiguous range.
|
|
46
|
+
*
|
|
47
|
+
* ## This tier answers its own activation question
|
|
48
|
+
*
|
|
49
|
+
* {@link refresh_selectable} computes selectability from the arena and the lookup — this tier's
|
|
50
|
+
* residency — and consults no reader. It used to publish `VGeoContainerReader.is_selectable`, which
|
|
51
|
+
* is a fact about the **RAM** tier, into the flag the kernel reads while the descent was gated on
|
|
52
|
+
* the **VRAM** lookup. VRAM is a strict subset, so a group could be flagged selectable while a page
|
|
53
|
+
* holding one of its parents had no row here, and the parent's own ancestor then drew a coarse
|
|
54
|
+
* stand-in over the surface the group was drawing. That is REVIEW_LEDGER C1, and what closes it is
|
|
55
|
+
* the group row carrying its parent count (VGEO_GROUP_ROW_OFFSET.PARENT_COUNT) so that the rule
|
|
56
|
+
* can be evaluated where the residency it is about lives.
|
|
57
|
+
*
|
|
58
|
+
* **Nothing here decides anything.** What to install, what to evict — all of that is the pool's.
|
|
59
|
+
* What this refuses is only what it cannot represent.
|
|
60
|
+
*
|
|
61
|
+
* @author Alex Goldring
|
|
62
|
+
* @copyright Company Named Limited (c) 2026
|
|
63
|
+
*/
|
|
64
|
+
export class VGeoRuntimeTables {
|
|
65
|
+
/**
|
|
66
|
+
* Fixed-stride page slots: `[page header][node rows][group rows][cluster rows]`, one per row.
|
|
67
|
+
* @type {Uint32Array}
|
|
68
|
+
*/
|
|
69
|
+
#arena;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The same memory as {@link #arena}, for the halves of a row that are distances rather than
|
|
73
|
+
* indices.
|
|
74
|
+
* @type {Float32Array}
|
|
75
|
+
*/
|
|
76
|
+
#arena_floats;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Container page index to page row, or {@link VGEO_PAGE_ROW_NONE}, one block per geometry.
|
|
80
|
+
*
|
|
81
|
+
* Its own buffer and its own allocator, so a block's offset is a small dense number. That is
|
|
82
|
+
* what lets the cut index its per-want dedup stamps by `lookup offset + child page index`
|
|
83
|
+
* without a second per-geometry field or a stamp array the size of the arena.
|
|
84
|
+
*
|
|
85
|
+
* The allocator here is not the one the review took out: it hands out one block per *geometry*,
|
|
86
|
+
* at registration, and a scene registers a handful of assets where it installs and evicts
|
|
87
|
+
* thousands of pages.
|
|
88
|
+
*
|
|
89
|
+
* @type {Uint32Array}
|
|
90
|
+
*/
|
|
91
|
+
#lookup;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* `geometry row * VGEO_GEOMETRY_ROW_WORDS + field`. Read by the instance-collect pass and
|
|
95
|
+
* folded into instance records, never bound by the traversal.
|
|
96
|
+
* @type {Uint32Array}
|
|
97
|
+
*/
|
|
98
|
+
#geometries;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @type {OffsetAllocator}
|
|
102
|
+
*/
|
|
103
|
+
#lookup_allocator;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @type {number}
|
|
107
|
+
*/
|
|
108
|
+
#slot_words;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @type {number}
|
|
112
|
+
*/
|
|
113
|
+
#page_capacity;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Page rows in use, so an eviction can tell a resident row from a free one without a second
|
|
117
|
+
* structure and {@link resident_page_count} is a size rather than a scan.
|
|
118
|
+
* @type {Set<number>}
|
|
119
|
+
*/
|
|
120
|
+
#resident_rows = new Set();
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Resident page rows per geometry row — what {@link refresh_selectable} runs over, and what an
|
|
124
|
+
* eviction has to look at to find a page's VRAM children.
|
|
125
|
+
* @type {Map<number, Set<number>>}
|
|
126
|
+
*/
|
|
127
|
+
#geometry_rows = new Map();
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Lookup allocation per geometry row, so an unregistered geometry gives its lookup back.
|
|
131
|
+
* @type {Map<number, Allocation>}
|
|
132
|
+
*/
|
|
133
|
+
#geometry_allocations = new Map();
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Page rows that are free, newest first. A row is a slot and carries no identity, so re-using
|
|
137
|
+
* the most recently freed one keeps the live rows dense and the table's high-water mark — which
|
|
138
|
+
* is what sizes the touch-stamp region every frame — from creeping.
|
|
139
|
+
* @type {number[]}
|
|
140
|
+
*/
|
|
141
|
+
#free_page_rows = [];
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Per page row, how many times it has been freed.
|
|
145
|
+
*
|
|
146
|
+
* A row is reused the moment it is freed, and feedback is at least a frame behind the cut that
|
|
147
|
+
* produced it, so a want or a touch naming row 7 may be naming the page that was in row 7 when
|
|
148
|
+
* the cut ran rather than the one there now. The host snapshots a row's generation while it
|
|
149
|
+
* builds the graph and compares when the readback lands; a mismatch is a record about a page
|
|
150
|
+
* that no longer exists, and dropping it is the whole of the fix (REVIEW_LEDGER M10).
|
|
151
|
+
*
|
|
152
|
+
* CPU-side deliberately. Nothing on the device ever needs this — the kernel reads rows that are
|
|
153
|
+
* resident *now*, which is by construction the state it was dispatched against.
|
|
154
|
+
*
|
|
155
|
+
* @type {Uint32Array}
|
|
156
|
+
*/
|
|
157
|
+
#generations;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @type {number}
|
|
161
|
+
*/
|
|
162
|
+
#page_row_high_water = 0;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @type {number}
|
|
166
|
+
*/
|
|
167
|
+
#geometry_row_high_water = 0;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Geometry rows an unregister gave back. A scene that swaps assets would otherwise walk the
|
|
171
|
+
* geometry table's high-water mark up until registration refused, with the table mostly empty.
|
|
172
|
+
* @type {number[]}
|
|
173
|
+
*/
|
|
174
|
+
#free_geometry_rows = [];
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Per geometry row: ordinals that are free, and the high-water mark of ordinals handed out.
|
|
178
|
+
*
|
|
179
|
+
* An ordinal is a page's dense index *within its geometry*, and it exists only to index the
|
|
180
|
+
* per-instance claim bitmask. Its capacity is therefore what one instance's slice of that
|
|
181
|
+
* bitmask costs, which is why the mark matters and why ordinals are recycled rather than
|
|
182
|
+
* counted upward.
|
|
183
|
+
*
|
|
184
|
+
* @type {Map<number, {free: number[], high_water: number}>}
|
|
185
|
+
*/
|
|
186
|
+
#ordinals = new Map();
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Clusters installed here whose `child_ref` named an entry outside their page's child table.
|
|
190
|
+
*
|
|
191
|
+
* Impossible in a container the validator accepts, reachable because validation is off by
|
|
192
|
+
* default, and worth counting rather than asserting on: the cluster is written as a leaf and
|
|
193
|
+
* drawn, which is what `vgeo_select_cut` does with the same input.
|
|
194
|
+
*
|
|
195
|
+
* @type {number}
|
|
196
|
+
*/
|
|
197
|
+
#broken_child_refs = 0;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Scratch for {@link refresh_selectable}: live parent references per resident group, indexed by
|
|
201
|
+
* the group's word offset in the arena. One map reused across calls rather than one allocated
|
|
202
|
+
* per call, because activation runs on every install and every eviction.
|
|
203
|
+
* @type {Map<number, number>}
|
|
204
|
+
*/
|
|
205
|
+
#live_refs = new Map();
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @param {Object} [options]
|
|
209
|
+
* @param {number} [options.page_capacity] slots
|
|
210
|
+
* @param {number} [options.page_size] bytes a page of every container held here is bounded by;
|
|
211
|
+
* what the slot stride is derived from
|
|
212
|
+
* @param {number} [options.slot_words] the stride itself, overriding the derivation
|
|
213
|
+
* @param {number} [options.lookup_words]
|
|
214
|
+
* @param {number} [options.geometry_capacity] rows
|
|
215
|
+
*/
|
|
216
|
+
constructor({
|
|
217
|
+
page_capacity = 1024,
|
|
218
|
+
page_size = VGEO_DEFAULT_PAGE_SIZE,
|
|
219
|
+
slot_words = vgeo_runtime_page_slot_words(page_size),
|
|
220
|
+
lookup_words = 1 << 16,
|
|
221
|
+
geometry_capacity = 256,
|
|
222
|
+
} = {}) {
|
|
223
|
+
assert.isPositiveInteger(page_capacity, 'page_capacity');
|
|
224
|
+
assert.isPositiveInteger(slot_words, 'slot_words');
|
|
225
|
+
assert.isNonNegativeInteger(lookup_words, 'lookup_words');
|
|
226
|
+
assert.isNonNegativeInteger(geometry_capacity, 'geometry_capacity');
|
|
227
|
+
assert.greaterThan(slot_words, VGEO_PAGE_ROW_WORDS, 'slot_words');
|
|
228
|
+
|
|
229
|
+
this.#slot_words = slot_words;
|
|
230
|
+
this.#page_capacity = page_capacity;
|
|
231
|
+
|
|
232
|
+
this.#lookup = new Uint32Array(lookup_words);
|
|
233
|
+
this.#geometries = new Uint32Array(geometry_capacity * VGEO_GEOMETRY_ROW_WORDS);
|
|
234
|
+
this.#generations = new Uint32Array(page_capacity);
|
|
235
|
+
|
|
236
|
+
const words = new Uint32Array(page_capacity * slot_words);
|
|
237
|
+
|
|
238
|
+
this.#arena = words;
|
|
239
|
+
this.#arena_floats = new Float32Array(words.buffer, words.byteOffset, words.length);
|
|
240
|
+
|
|
241
|
+
this.#lookup_allocator = new OffsetAllocator(lookup_words);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* @returns {Uint32Array}
|
|
246
|
+
*/
|
|
247
|
+
get lookup_words() {
|
|
248
|
+
return this.#lookup;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @returns {Uint32Array}
|
|
253
|
+
*/
|
|
254
|
+
get arena_words() {
|
|
255
|
+
return this.#arena;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* @returns {Uint32Array}
|
|
260
|
+
*/
|
|
261
|
+
get geometry_words() {
|
|
262
|
+
return this.#geometries;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* @returns {number}
|
|
267
|
+
*/
|
|
268
|
+
get broken_child_refs() {
|
|
269
|
+
return this.#broken_child_refs;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* @returns {number}
|
|
274
|
+
*/
|
|
275
|
+
get slot_words() {
|
|
276
|
+
return this.#slot_words;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* @returns {number}
|
|
281
|
+
*/
|
|
282
|
+
get page_capacity() {
|
|
283
|
+
return this.#page_capacity;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* One past the highest page row ever handed out — what sizes a per-page-row stamp region.
|
|
288
|
+
* @returns {number}
|
|
289
|
+
*/
|
|
290
|
+
get page_row_high_water() {
|
|
291
|
+
return this.#page_row_high_water;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @returns {number}
|
|
296
|
+
*/
|
|
297
|
+
get resident_page_count() {
|
|
298
|
+
return this.#resident_rows.size;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Whether a row could take a page without anything being evicted first.
|
|
303
|
+
* @returns {boolean}
|
|
304
|
+
*/
|
|
305
|
+
get has_free_page_row() {
|
|
306
|
+
return this.#free_page_rows.length > 0 || this.#page_row_high_water < this.#page_capacity;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* How many times this row has been freed — the token that tells a feedback record about the
|
|
311
|
+
* page that *was* here from one about the page that is.
|
|
312
|
+
*
|
|
313
|
+
* @param {number} page_row
|
|
314
|
+
* @returns {number}
|
|
315
|
+
*/
|
|
316
|
+
generation_of(page_row) {
|
|
317
|
+
return this.#generations[page_row];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Give a container a geometry row and a page lookup of its own.
|
|
322
|
+
*
|
|
323
|
+
* The lookup is `total_pages` words and starts entirely absent, which is the correct state for
|
|
324
|
+
* a container nothing has been installed from: every cluster that refers into it clamps, draws
|
|
325
|
+
* coarse, and wants.
|
|
326
|
+
*
|
|
327
|
+
* @param {number} page_count the container's `total_pages`
|
|
328
|
+
* @param {number} [ordinal_capacity] initial claim width; grown by installs
|
|
329
|
+
* @returns {number} geometry row
|
|
330
|
+
* @throws {Error} when the lookup does not fit
|
|
331
|
+
*/
|
|
332
|
+
register_geometry(page_count, ordinal_capacity = 0) {
|
|
333
|
+
assert.isNonNegativeInteger(page_count, 'page_count');
|
|
334
|
+
assert.greaterThan(page_count, 0, 'page_count');
|
|
335
|
+
|
|
336
|
+
const recycled = this.#free_geometry_rows.pop();
|
|
337
|
+
|
|
338
|
+
const row = recycled ?? this.#geometry_row_high_water;
|
|
339
|
+
|
|
340
|
+
if (recycled === undefined
|
|
341
|
+
&& (row + 1) * VGEO_GEOMETRY_ROW_WORDS > this.#geometries.length) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
`no room for a ${row + 1}th virtual geometry`
|
|
344
|
+
+ ` in a table of ${this.#geometries.length / VGEO_GEOMETRY_ROW_WORDS}`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const allocation = this.#lookup_allocator.allocate(page_count);
|
|
349
|
+
|
|
350
|
+
if (allocation.offset === ALLOCATOR_NO_SPACE) {
|
|
351
|
+
throw new Error(
|
|
352
|
+
`no room for a ${page_count}-page lookup in ${this.#lookup.length} words`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (recycled === undefined) {
|
|
357
|
+
this.#geometry_row_high_water = row + 1;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
this.#geometry_allocations.set(row, allocation);
|
|
361
|
+
|
|
362
|
+
this.#lookup.fill(
|
|
363
|
+
VGEO_PAGE_ROW_NONE, allocation.offset, allocation.offset + page_count
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
const base = row * VGEO_GEOMETRY_ROW_WORDS;
|
|
367
|
+
|
|
368
|
+
this.#geometries[base + VGEO_GEOMETRY_ROW_OFFSET.ROOT_PAGE_ROW] = VGEO_PAGE_ROW_NONE;
|
|
369
|
+
this.#geometries[base + VGEO_GEOMETRY_ROW_OFFSET.LOOKUP_WORD_OFFSET] = allocation.offset;
|
|
370
|
+
this.#geometries[base + VGEO_GEOMETRY_ROW_OFFSET.PAGE_COUNT] = page_count;
|
|
371
|
+
this.#geometries[base + VGEO_GEOMETRY_ROW_OFFSET.ORDINAL_CAPACITY] = ordinal_capacity;
|
|
372
|
+
|
|
373
|
+
this.#ordinals.set(row, { free: [], high_water: ordinal_capacity });
|
|
374
|
+
this.#geometry_rows.set(row, new Set());
|
|
375
|
+
|
|
376
|
+
return row;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Drop a geometry: every page it has here, then its lookup and its row.
|
|
381
|
+
*
|
|
382
|
+
* @param {number} geometry_row
|
|
383
|
+
* @returns {number[]} the page rows that were evicted, so a caller holding per-row state can
|
|
384
|
+
* drop it too
|
|
385
|
+
*/
|
|
386
|
+
unregister_geometry(geometry_row) {
|
|
387
|
+
const rows = this.#geometry_rows.get(geometry_row);
|
|
388
|
+
|
|
389
|
+
if (rows === undefined) {
|
|
390
|
+
return [];
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const evicted = Array.from(rows);
|
|
394
|
+
|
|
395
|
+
for (const page_row of evicted) {
|
|
396
|
+
this.evict(page_row);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const allocation = this.#geometry_allocations.get(geometry_row);
|
|
400
|
+
|
|
401
|
+
if (allocation !== undefined) {
|
|
402
|
+
this.#lookup.fill(
|
|
403
|
+
0,
|
|
404
|
+
allocation.offset,
|
|
405
|
+
allocation.offset + this.geometry_field(
|
|
406
|
+
geometry_row, VGEO_GEOMETRY_ROW_OFFSET.PAGE_COUNT
|
|
407
|
+
)
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
this.#lookup_allocator.free(allocation);
|
|
411
|
+
this.#geometry_allocations.delete(geometry_row);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
this.#geometries.fill(
|
|
415
|
+
0,
|
|
416
|
+
geometry_row * VGEO_GEOMETRY_ROW_WORDS,
|
|
417
|
+
(geometry_row + 1) * VGEO_GEOMETRY_ROW_WORDS
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
this.#geometries[
|
|
421
|
+
geometry_row * VGEO_GEOMETRY_ROW_WORDS + VGEO_GEOMETRY_ROW_OFFSET.ROOT_PAGE_ROW
|
|
422
|
+
] = VGEO_PAGE_ROW_NONE;
|
|
423
|
+
|
|
424
|
+
this.#geometry_rows.delete(geometry_row);
|
|
425
|
+
this.#ordinals.delete(geometry_row);
|
|
426
|
+
this.#free_geometry_rows.push(geometry_row);
|
|
427
|
+
|
|
428
|
+
return evicted;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @param {number} geometry_row
|
|
433
|
+
* @param {number} field one of {@link VGEO_GEOMETRY_ROW_OFFSET}
|
|
434
|
+
* @returns {number}
|
|
435
|
+
*/
|
|
436
|
+
geometry_field(geometry_row, field) {
|
|
437
|
+
return this.#geometries[geometry_row * VGEO_GEOMETRY_ROW_WORDS + field];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* @param {number} geometry_row
|
|
442
|
+
* @param {number} page_index container page index
|
|
443
|
+
* @returns {number} page row, or {@link VGEO_PAGE_ROW_NONE}
|
|
444
|
+
*/
|
|
445
|
+
page_row_of(geometry_row, page_index) {
|
|
446
|
+
const base = this.geometry_field(
|
|
447
|
+
geometry_row, VGEO_GEOMETRY_ROW_OFFSET.LOOKUP_WORD_OFFSET
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
return this.#lookup[base + page_index];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* @param {number} page_row
|
|
455
|
+
* @param {number} field one of {@link VGEO_PAGE_ROW_OFFSET}
|
|
456
|
+
* @returns {number}
|
|
457
|
+
*/
|
|
458
|
+
page_field(page_row, field) {
|
|
459
|
+
return this.#arena[page_row * this.#slot_words + field];
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Word offset of a page's node rows — the slot, past its header.
|
|
464
|
+
*
|
|
465
|
+
* @param {number} page_row
|
|
466
|
+
* @returns {number}
|
|
467
|
+
*/
|
|
468
|
+
node_base(page_row) {
|
|
469
|
+
return page_row * this.#slot_words + VGEO_PAGE_ROW_WORDS;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* @param {number} page_row
|
|
474
|
+
* @returns {number}
|
|
475
|
+
*/
|
|
476
|
+
group_base(page_row) {
|
|
477
|
+
return this.node_base(page_row)
|
|
478
|
+
+ this.page_field(page_row, VGEO_PAGE_ROW_OFFSET.NODE_COUNT) * VGEO_NODE_ROW_WORDS;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* @param {number} page_row
|
|
483
|
+
* @returns {number}
|
|
484
|
+
*/
|
|
485
|
+
cluster_base(page_row) {
|
|
486
|
+
return this.group_base(page_row)
|
|
487
|
+
+ this.page_field(page_row, VGEO_PAGE_ROW_OFFSET.GROUP_COUNT) * VGEO_GROUP_ROW_WORDS;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Make a decoded page resident: one slot, one lookup word.
|
|
492
|
+
*
|
|
493
|
+
* The page installs **not selectable** — every group's flag is clear — because a page that
|
|
494
|
+
* arrives drawing is a page that draws before the ancestors it hangs off have. Activation is
|
|
495
|
+
* {@link refresh_selectable}.
|
|
496
|
+
*
|
|
497
|
+
* @param {number} geometry_row
|
|
498
|
+
* @param {VGeoPage} page as `vgeo_read_page` decoded it
|
|
499
|
+
* @param {number} meshlet_base row of this page's cluster 0 in the meshlet metadata pool
|
|
500
|
+
* @returns {number} page row
|
|
501
|
+
* @throws {Error} when no slot is free — the caller's cue to evict, since what to drop is a
|
|
502
|
+
* budget question and this object holds no budget — or when the page's rows do not fit one
|
|
503
|
+
* @see broken_child_refs for what the last install reported about the page it wrote
|
|
504
|
+
*/
|
|
505
|
+
install(geometry_row, page, meshlet_base) {
|
|
506
|
+
assert.defined(page, 'page');
|
|
507
|
+
assert.isNonNegativeInteger(meshlet_base, 'meshlet_base');
|
|
508
|
+
|
|
509
|
+
const node_count = page.node_count;
|
|
510
|
+
const group_count = page.group_cluster_first.length;
|
|
511
|
+
const cluster_count = page.cluster_count;
|
|
512
|
+
|
|
513
|
+
const words = VGEO_PAGE_ROW_WORDS
|
|
514
|
+
+ vgeo_runtime_page_block_words(node_count, group_count, cluster_count);
|
|
515
|
+
|
|
516
|
+
if (words > this.#slot_words) {
|
|
517
|
+
throw new Error(
|
|
518
|
+
`virtual geometry page ${page.index} needs ${words} words and this tier's slots`
|
|
519
|
+
+ ` hold ${this.#slot_words}. Its ${node_count} nodes, ${group_count} groups and`
|
|
520
|
+
+ ` ${cluster_count} clusters are legal and unusually dense for their page size;`
|
|
521
|
+
+ ` construct the pool with slot_words of at least ${words}.`
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const page_row = this.#take_page_row();
|
|
526
|
+
|
|
527
|
+
if (page_row === -1) {
|
|
528
|
+
throw new Error(
|
|
529
|
+
`virtual geometry page table is full at ${this.#page_capacity} slots`
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const ordinal = this.#take_ordinal(geometry_row);
|
|
534
|
+
|
|
535
|
+
const base = page_row * this.#slot_words;
|
|
536
|
+
|
|
537
|
+
this.#broken_child_refs += vgeo_runtime_write_page_block(
|
|
538
|
+
this.#arena, this.#arena_floats, base + VGEO_PAGE_ROW_WORDS, page
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
this.#arena[base + VGEO_PAGE_ROW_OFFSET.NODE_COUNT] = node_count;
|
|
542
|
+
this.#arena[base + VGEO_PAGE_ROW_OFFSET.GROUP_COUNT] = group_count;
|
|
543
|
+
this.#arena[base + VGEO_PAGE_ROW_OFFSET.CLUSTER_COUNT] = cluster_count;
|
|
544
|
+
this.#arena[base + VGEO_PAGE_ROW_OFFSET.MESHLET_BASE] = meshlet_base;
|
|
545
|
+
this.#arena[base + VGEO_PAGE_ROW_OFFSET.PAGE_INDEX] = page.index;
|
|
546
|
+
this.#arena[base + VGEO_PAGE_ROW_OFFSET.ORDINAL] = ordinal;
|
|
547
|
+
this.#arena[base + VGEO_PAGE_ROW_OFFSET.GEOMETRY_ROW] = geometry_row;
|
|
548
|
+
this.#arena[base + VGEO_PAGE_ROW_OFFSET.DECODED_SIZE] = page.decoded_size;
|
|
549
|
+
|
|
550
|
+
this.#resident_rows.add(page_row);
|
|
551
|
+
this.#geometry_rows.get(geometry_row).add(page_row);
|
|
552
|
+
|
|
553
|
+
const lookup_base = this.geometry_field(
|
|
554
|
+
geometry_row, VGEO_GEOMETRY_ROW_OFFSET.LOOKUP_WORD_OFFSET
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
this.#lookup[lookup_base + page.index] = page_row;
|
|
558
|
+
|
|
559
|
+
if (page.index === 0) {
|
|
560
|
+
this.#geometries[
|
|
561
|
+
geometry_row * VGEO_GEOMETRY_ROW_WORDS + VGEO_GEOMETRY_ROW_OFFSET.ROOT_PAGE_ROW
|
|
562
|
+
] = page_row;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return page_row;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* How many words of a slot an install of this page would take, header included.
|
|
570
|
+
*
|
|
571
|
+
* @param {VGeoPage} page
|
|
572
|
+
* @returns {number}
|
|
573
|
+
*/
|
|
574
|
+
slot_words_for(page) {
|
|
575
|
+
return VGEO_PAGE_ROW_WORDS + vgeo_runtime_page_block_words(
|
|
576
|
+
page.node_count, page.group_cluster_first.length, page.cluster_count
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Drop a resident page.
|
|
582
|
+
*
|
|
583
|
+
* The lookup word goes back to {@link VGEO_PAGE_ROW_NONE} first, and that single word is the
|
|
584
|
+
* whole of what makes the eviction safe: every cluster anywhere that refines into this page
|
|
585
|
+
* reads it, finds nothing, and clamps — drawing the coarser surface it was already drawing
|
|
586
|
+
* before the page arrived. Nothing is patched and nothing is scanned.
|
|
587
|
+
*
|
|
588
|
+
* The slot's **header** is zeroed and its rows are not. A row nothing points at is unreachable
|
|
589
|
+
* — the lookup is the only way in and no queue item survives a frame — and leaving the rows
|
|
590
|
+
* costs nothing while zeroing them would dirty a slot's worth of words per eviction.
|
|
591
|
+
*
|
|
592
|
+
* @param {number} page_row
|
|
593
|
+
* @returns {boolean} whether the row was resident
|
|
594
|
+
*/
|
|
595
|
+
evict(page_row) {
|
|
596
|
+
if (!this.#resident_rows.has(page_row)) {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const geometry_row = this.page_field(page_row, VGEO_PAGE_ROW_OFFSET.GEOMETRY_ROW);
|
|
601
|
+
const page_index = this.page_field(page_row, VGEO_PAGE_ROW_OFFSET.PAGE_INDEX);
|
|
602
|
+
|
|
603
|
+
const lookup_base = this.geometry_field(
|
|
604
|
+
geometry_row, VGEO_GEOMETRY_ROW_OFFSET.LOOKUP_WORD_OFFSET
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
this.#lookup[lookup_base + page_index] = VGEO_PAGE_ROW_NONE;
|
|
608
|
+
|
|
609
|
+
if (page_index === 0) {
|
|
610
|
+
this.#geometries[
|
|
611
|
+
geometry_row * VGEO_GEOMETRY_ROW_WORDS + VGEO_GEOMETRY_ROW_OFFSET.ROOT_PAGE_ROW
|
|
612
|
+
] = VGEO_PAGE_ROW_NONE;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
this.#give_ordinal(geometry_row, this.page_field(page_row, VGEO_PAGE_ROW_OFFSET.ORDINAL));
|
|
616
|
+
|
|
617
|
+
const base = page_row * this.#slot_words;
|
|
618
|
+
|
|
619
|
+
this.#arena.fill(0, base, base + VGEO_PAGE_ROW_WORDS);
|
|
620
|
+
|
|
621
|
+
this.#resident_rows.delete(page_row);
|
|
622
|
+
this.#geometry_rows.get(geometry_row)?.delete(page_row);
|
|
623
|
+
|
|
624
|
+
this.#free_page_rows.push(page_row);
|
|
625
|
+
this.#generations[page_row]++;
|
|
626
|
+
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* The page rows this geometry holds, which is also what an eviction candidate's children are
|
|
632
|
+
* looked for among.
|
|
633
|
+
*
|
|
634
|
+
* @param {number} geometry_row
|
|
635
|
+
* @returns {Set<number>}
|
|
636
|
+
*/
|
|
637
|
+
pages_of(geometry_row) {
|
|
638
|
+
return this.#geometry_rows.get(geometry_row) ?? EMPTY_ROWS;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Whether any page resident **in this tier** refines out of this one.
|
|
643
|
+
*
|
|
644
|
+
* Design §6's eviction rule reads "no child page resident in this tier", and this answers it out
|
|
645
|
+
* of the arena rather than out of the RAM reader's child table — which returned "no children"
|
|
646
|
+
* the moment the RAM tier dropped the page, orphaning every VRAM-resident child under it
|
|
647
|
+
* (REVIEW_LEDGER M9).
|
|
648
|
+
*
|
|
649
|
+
* @param {number} page_row
|
|
650
|
+
* @returns {boolean}
|
|
651
|
+
*/
|
|
652
|
+
has_resident_child(page_row) {
|
|
653
|
+
const geometry_row = this.page_field(page_row, VGEO_PAGE_ROW_OFFSET.GEOMETRY_ROW);
|
|
654
|
+
|
|
655
|
+
const lookup_base = this.geometry_field(
|
|
656
|
+
geometry_row, VGEO_GEOMETRY_ROW_OFFSET.LOOKUP_WORD_OFFSET
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
const cluster_count = this.page_field(page_row, VGEO_PAGE_ROW_OFFSET.CLUSTER_COUNT);
|
|
660
|
+
const cluster_base = this.cluster_base(page_row);
|
|
661
|
+
|
|
662
|
+
for (let cluster = 0; cluster < cluster_count; cluster++) {
|
|
663
|
+
const child_page_index = this.#arena[
|
|
664
|
+
cluster_base + cluster * VGEO_CLUSTER_ROW_WORDS
|
|
665
|
+
+ VGEO_CLUSTER_ROW_OFFSET.CHILD_PAGE_INDEX
|
|
666
|
+
];
|
|
667
|
+
|
|
668
|
+
if (child_page_index === VGEO_NO_CHILD_PAGE) {
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const child_row = this.#lookup[lookup_base + child_page_index];
|
|
673
|
+
|
|
674
|
+
if (child_row !== VGEO_PAGE_ROW_NONE && child_row !== page_row) {
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Recompute one geometry's group selectability over **this tier's** residency.
|
|
684
|
+
*
|
|
685
|
+
* `VIRTUAL_GEOMETRY_DESIGN.md` §6: a group is selectable iff every one of its parent-cluster
|
|
686
|
+
* references is live — the referencing cluster's page resident here, and that cluster's own
|
|
687
|
+
* group selectable here. Groups with no parents at all are the top of the DAG and are selectable
|
|
688
|
+
* the moment their page is. That is a closure over the resident set, so it is computed as one:
|
|
689
|
+
* seed the parentless groups and cascade down, counting a reference only when the group making
|
|
690
|
+
* it is already selectable.
|
|
691
|
+
*
|
|
692
|
+
* Recomputed whole rather than propagated incrementally, because an install can turn groups on
|
|
693
|
+
* several pages away and an eviction can turn them off again, and because the cost is the same
|
|
694
|
+
* order as the install that provoked it — one pass over the geometry's resident clusters. The
|
|
695
|
+
* incremental form is a later optimization with a counter to keep correct; the one thing it
|
|
696
|
+
* must not be is a *second* implementation of the rule.
|
|
697
|
+
*
|
|
698
|
+
* @param {number} geometry_row
|
|
699
|
+
* @returns {number[]} page rows whose group flags changed, for the caller to upload
|
|
700
|
+
*/
|
|
701
|
+
refresh_selectable(geometry_row) {
|
|
702
|
+
const rows = this.#geometry_rows.get(geometry_row);
|
|
703
|
+
|
|
704
|
+
if (rows === undefined || rows.size === 0) {
|
|
705
|
+
return [];
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const arena = this.#arena;
|
|
709
|
+
const live = this.#live_refs;
|
|
710
|
+
|
|
711
|
+
live.clear();
|
|
712
|
+
|
|
713
|
+
const lookup_base = this.geometry_field(
|
|
714
|
+
geometry_row, VGEO_GEOMETRY_ROW_OFFSET.LOOKUP_WORD_OFFSET
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Word offsets of group rows to turn on, as a stack.
|
|
719
|
+
* @type {number[]}
|
|
720
|
+
*/
|
|
721
|
+
const queue = [];
|
|
722
|
+
|
|
723
|
+
for (const page_row of rows) {
|
|
724
|
+
const group_count = this.page_field(page_row, VGEO_PAGE_ROW_OFFSET.GROUP_COUNT);
|
|
725
|
+
const group_base = this.group_base(page_row);
|
|
726
|
+
|
|
727
|
+
for (let group = 0; group < group_count; group++) {
|
|
728
|
+
const row = group_base + group * VGEO_GROUP_ROW_WORDS;
|
|
729
|
+
|
|
730
|
+
live.set(row, 0);
|
|
731
|
+
|
|
732
|
+
if (arena[row + VGEO_GROUP_ROW_OFFSET.PARENT_COUNT] === 0) {
|
|
733
|
+
queue.push(row);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* @type {Set<number>}
|
|
740
|
+
*/
|
|
741
|
+
const selectable = new Set();
|
|
742
|
+
|
|
743
|
+
while (queue.length > 0) {
|
|
744
|
+
const group_row = queue.pop();
|
|
745
|
+
|
|
746
|
+
if (selectable.has(group_row)) {
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
selectable.add(group_row);
|
|
751
|
+
|
|
752
|
+
const page_row = Math.floor(group_row / this.#slot_words);
|
|
753
|
+
|
|
754
|
+
const cluster_base = this.cluster_base(page_row);
|
|
755
|
+
|
|
756
|
+
const first = arena[group_row + VGEO_GROUP_ROW_OFFSET.CLUSTER_FIRST];
|
|
757
|
+
const count = arena[group_row + VGEO_GROUP_ROW_OFFSET.CLUSTER_COUNT];
|
|
758
|
+
|
|
759
|
+
for (let i = 0; i < count; i++) {
|
|
760
|
+
const cluster = cluster_base + (first + i) * VGEO_CLUSTER_ROW_WORDS;
|
|
761
|
+
|
|
762
|
+
const child_page_index = arena[
|
|
763
|
+
cluster + VGEO_CLUSTER_ROW_OFFSET.CHILD_PAGE_INDEX
|
|
764
|
+
];
|
|
765
|
+
|
|
766
|
+
if (child_page_index === VGEO_NO_CHILD_PAGE) {
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const child_row = this.#lookup[lookup_base + child_page_index];
|
|
771
|
+
|
|
772
|
+
if (child_row === VGEO_PAGE_ROW_NONE) {
|
|
773
|
+
// the child page is not in this tier; its groups stay blocked, which is exactly
|
|
774
|
+
// what makes the cluster above clamp and draw
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const child_group = arena[cluster + VGEO_CLUSTER_ROW_OFFSET.CHILD_GROUP];
|
|
779
|
+
|
|
780
|
+
if (child_group >= this.page_field(child_row, VGEO_PAGE_ROW_OFFSET.GROUP_COUNT)) {
|
|
781
|
+
// a child_group outside the child page's table; counting it would corrupt a
|
|
782
|
+
// neighbour's counter, and the traversal draws the referring cluster instead
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const child = this.group_base(child_row) + child_group * VGEO_GROUP_ROW_WORDS;
|
|
787
|
+
|
|
788
|
+
const counted = live.get(child) + 1;
|
|
789
|
+
|
|
790
|
+
live.set(child, counted);
|
|
791
|
+
|
|
792
|
+
if (counted === arena[child + VGEO_GROUP_ROW_OFFSET.PARENT_COUNT]) {
|
|
793
|
+
queue.push(child);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* @type {number[]}
|
|
800
|
+
*/
|
|
801
|
+
const changed = [];
|
|
802
|
+
|
|
803
|
+
for (const page_row of rows) {
|
|
804
|
+
const group_count = this.page_field(page_row, VGEO_PAGE_ROW_OFFSET.GROUP_COUNT);
|
|
805
|
+
const group_base = this.group_base(page_row);
|
|
806
|
+
|
|
807
|
+
let dirty = false;
|
|
808
|
+
|
|
809
|
+
for (let group = 0; group < group_count; group++) {
|
|
810
|
+
const row = group_base + group * VGEO_GROUP_ROW_WORDS;
|
|
811
|
+
|
|
812
|
+
const flags = selectable.has(row) ? VGEO_GROUP_FLAG_SELECTABLE : 0;
|
|
813
|
+
|
|
814
|
+
if (arena[row + VGEO_GROUP_ROW_OFFSET.FLAGS] !== flags) {
|
|
815
|
+
arena[row + VGEO_GROUP_ROW_OFFSET.FLAGS] = flags;
|
|
816
|
+
|
|
817
|
+
dirty = true;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (dirty) {
|
|
822
|
+
changed.push(page_row);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return changed;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Publish one group's selectability directly.
|
|
831
|
+
*
|
|
832
|
+
* {@link refresh_selectable} is what a runtime calls; this is for a spec that needs a state the
|
|
833
|
+
* rule happens not to produce on the assets measured so far — the cut's own-group term is
|
|
834
|
+
* correctness for exactly such a state, and a guard nothing can construct is a guard nothing can
|
|
835
|
+
* test. The next {@link refresh_selectable} overwrites it.
|
|
836
|
+
*
|
|
837
|
+
* @param {number} page_row
|
|
838
|
+
* @param {number} group
|
|
839
|
+
* @param {boolean} selectable
|
|
840
|
+
* @returns {void}
|
|
841
|
+
*/
|
|
842
|
+
set_group_selectable(page_row, group, selectable) {
|
|
843
|
+
const word = this.group_base(page_row)
|
|
844
|
+
+ group * VGEO_GROUP_ROW_WORDS
|
|
845
|
+
+ VGEO_GROUP_ROW_OFFSET.FLAGS;
|
|
846
|
+
|
|
847
|
+
this.#arena[word] = selectable ? VGEO_GROUP_FLAG_SELECTABLE : 0;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* @param {number} page_row
|
|
852
|
+
* @param {number} group
|
|
853
|
+
* @returns {boolean}
|
|
854
|
+
*/
|
|
855
|
+
is_group_selectable(page_row, group) {
|
|
856
|
+
const word = this.group_base(page_row)
|
|
857
|
+
+ group * VGEO_GROUP_ROW_WORDS
|
|
858
|
+
+ VGEO_GROUP_ROW_OFFSET.FLAGS;
|
|
859
|
+
|
|
860
|
+
return (this.#arena[word] & VGEO_GROUP_FLAG_SELECTABLE) !== 0;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Word range of one page's slot, as `[start, count]` — what an install dirties.
|
|
865
|
+
*
|
|
866
|
+
* @param {number} page_row
|
|
867
|
+
* @returns {number[]}
|
|
868
|
+
*/
|
|
869
|
+
slot_range(page_row) {
|
|
870
|
+
const base = page_row * this.#slot_words;
|
|
871
|
+
|
|
872
|
+
const words = VGEO_PAGE_ROW_WORDS + vgeo_runtime_page_block_words(
|
|
873
|
+
this.page_field(page_row, VGEO_PAGE_ROW_OFFSET.NODE_COUNT),
|
|
874
|
+
this.page_field(page_row, VGEO_PAGE_ROW_OFFSET.GROUP_COUNT),
|
|
875
|
+
this.page_field(page_row, VGEO_PAGE_ROW_OFFSET.CLUSTER_COUNT)
|
|
876
|
+
);
|
|
877
|
+
|
|
878
|
+
return [base, words];
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Word range of one page's group rows — what an activation pass dirties.
|
|
883
|
+
*
|
|
884
|
+
* @param {number} page_row
|
|
885
|
+
* @returns {number[]}
|
|
886
|
+
*/
|
|
887
|
+
group_range(page_row) {
|
|
888
|
+
return [
|
|
889
|
+
this.group_base(page_row),
|
|
890
|
+
this.page_field(page_row, VGEO_PAGE_ROW_OFFSET.GROUP_COUNT) * VGEO_GROUP_ROW_WORDS,
|
|
891
|
+
];
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Republish one page's meshlet base, after the shared meshlet pool relocated its batch.
|
|
896
|
+
*
|
|
897
|
+
* @param {number} page_row
|
|
898
|
+
* @param {number} meshlet_base
|
|
899
|
+
* @returns {boolean} whether it moved
|
|
900
|
+
*/
|
|
901
|
+
set_meshlet_base(page_row, meshlet_base) {
|
|
902
|
+
const word = page_row * this.#slot_words + VGEO_PAGE_ROW_OFFSET.MESHLET_BASE;
|
|
903
|
+
|
|
904
|
+
if (this.#arena[word] === meshlet_base) {
|
|
905
|
+
return false;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
this.#arena[word] = meshlet_base;
|
|
909
|
+
|
|
910
|
+
return true;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* @returns {number} row, or -1 when every slot is taken
|
|
915
|
+
*/
|
|
916
|
+
#take_page_row() {
|
|
917
|
+
const recycled = this.#free_page_rows.pop();
|
|
918
|
+
|
|
919
|
+
if (recycled !== undefined) {
|
|
920
|
+
return recycled;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const row = this.#page_row_high_water;
|
|
924
|
+
|
|
925
|
+
if (row >= this.#page_capacity) {
|
|
926
|
+
return -1;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
this.#page_row_high_water = row + 1;
|
|
930
|
+
|
|
931
|
+
return row;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* @param {number} geometry_row
|
|
936
|
+
* @returns {number}
|
|
937
|
+
*/
|
|
938
|
+
#take_ordinal(geometry_row) {
|
|
939
|
+
const state = this.#ordinals.get(geometry_row);
|
|
940
|
+
|
|
941
|
+
const recycled = state.free.pop();
|
|
942
|
+
|
|
943
|
+
if (recycled !== undefined) {
|
|
944
|
+
return recycled;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const ordinal = state.high_water;
|
|
948
|
+
|
|
949
|
+
state.high_water = ordinal + 1;
|
|
950
|
+
|
|
951
|
+
this.#geometries[
|
|
952
|
+
geometry_row * VGEO_GEOMETRY_ROW_WORDS + VGEO_GEOMETRY_ROW_OFFSET.ORDINAL_CAPACITY
|
|
953
|
+
] = state.high_water;
|
|
954
|
+
|
|
955
|
+
return ordinal;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* An ordinal goes back to the free list and the capacity does **not** come down with it.
|
|
960
|
+
*
|
|
961
|
+
* The capacity is what one instance's slice of the claim bitmask costs, so lowering it would
|
|
962
|
+
* make the bitmask's layout a function of eviction order — and the layout is fixed for the
|
|
963
|
+
* frame by a prefix scan taken before any of this runs.
|
|
964
|
+
*
|
|
965
|
+
* @param {number} geometry_row
|
|
966
|
+
* @param {number} ordinal
|
|
967
|
+
*/
|
|
968
|
+
#give_ordinal(geometry_row, ordinal) {
|
|
969
|
+
this.#ordinals.get(geometry_row)?.free.push(ordinal);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* @type {Set<number>}
|
|
975
|
+
*/
|
|
976
|
+
const EMPTY_ROWS = new Set();
|