@woosh/meep-engine 3.26.0 → 3.28.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/build/bundle-worker-terrain.js +1 -1
- package/package.json +1 -1
- package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +542 -0
- package/src/core/binary/lz4/LZ4_LEVEL.d.ts +33 -0
- package/src/core/binary/lz4/LZ4_LEVEL.d.ts.map +1 -0
- package/src/core/binary/lz4/LZ4_LEVEL.js +32 -0
- package/src/core/binary/lz4/lz4_compress_block_at_level.d.ts +33 -0
- package/src/core/binary/lz4/lz4_compress_block_at_level.d.ts.map +1 -0
- package/src/core/binary/lz4/lz4_compress_block_at_level.js +47 -0
- package/src/core/binary/lz4/lz4_compress_block_hc.d.ts +40 -0
- package/src/core/binary/lz4/lz4_compress_block_hc.d.ts.map +1 -0
- package/src/core/binary/lz4/lz4_compress_block_hc.js +1382 -0
- package/src/core/binary/lz4/lz4_decompress_block.d.ts +2 -1
- package/src/core/binary/lz4/lz4_decompress_block.d.ts.map +1 -1
- package/src/core/binary/lz4/lz4_decompress_block.js +31 -6
- package/src/core/geom/3d/mat4/m4_linear_determinant.d.ts +15 -0
- package/src/core/geom/3d/mat4/m4_linear_determinant.d.ts.map +1 -0
- package/src/core/geom/3d/mat4/m4_linear_determinant.js +24 -0
- package/src/core/geom/3d/mat4/m4_normal_matrix3.d.ts +4 -2
- package/src/core/geom/3d/mat4/m4_normal_matrix3.d.ts.map +1 -1
- package/src/core/geom/3d/mat4/m4_normal_matrix3.js +21 -11
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +14 -7
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/HeightMapShape3D.js +87 -78
- package/src/engine/Engine.d.ts.map +1 -1
- package/src/engine/Engine.js +38 -1
- package/src/engine/graphics3/GraphicsEngine.d.ts +5 -1
- package/src/engine/graphics3/GraphicsEngine.d.ts.map +1 -1
- package/src/engine/graphics3/GraphicsEngine.js +834 -830
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/RENDERER_CONTRACT.md +19 -10
- package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +1 -1
- package/src/shade/playground/vgeo_runtime/README.md +186 -27
- package/src/shade/playground/vgeo_runtime/index.html +112 -62
- package/src/shade/playground/vgeo_runtime/main.js +678 -88
- package/src/shade/playground/vgeo_runtime/sample_asset.d.ts +18 -0
- package/src/shade/playground/vgeo_runtime/sample_asset.d.ts.map +1 -0
- package/src/shade/playground/{vgeo_viewer → vgeo_runtime}/sample_asset.js +23 -5
- package/src/shade/playground/vgeo_scene/README.md +408 -0
- package/src/shade/playground/vgeo_scene/index.html +144 -0
- package/src/shade/playground/vgeo_scene/main.d.ts +2 -0
- package/src/shade/playground/vgeo_scene/main.d.ts.map +1 -0
- package/src/shade/playground/vgeo_scene/main.js +1716 -0
- package/src/shade/playground/vgeo_viewer/sample_asset.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.d.ts +4 -1
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +5 -2
- package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +1 -2
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts +11 -0
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +16 -8
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +16 -11
- package/src/shade/renderer/geometry/meshlet/MeshletBatch.d.ts.map +1 -1
- package/src/shade/renderer/geometry/meshlet/MeshletBatch.js +3 -29
- package/src/shade/renderer/geometry/meshlet/encoding/ENCODED_ATTRIBUTE_DEFAULT_VALUES.d.ts +22 -0
- package/src/shade/renderer/geometry/meshlet/encoding/ENCODED_ATTRIBUTE_DEFAULT_VALUES.d.ts.map +1 -0
- package/src/shade/renderer/geometry/meshlet/encoding/ENCODED_ATTRIBUTE_DEFAULT_VALUES.js +49 -0
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +77 -7
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +36 -23
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +17 -14
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +19 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +21 -1
- package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.js +5 -9
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevel.d.ts +32 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevel.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelOptions.d.ts +59 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelOptions.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelSummary.d.ts +44 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelSummary.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_levels.d.ts +36 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_levels.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_build_levels.js → level/vgeo_build_levels.js} +61 -95
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_root_group.d.ts +19 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_root_group.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_build_root_group.js → level/vgeo_build_root_group.js} +1 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/VirtualGeometryMesh.d.ts +73 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/VirtualGeometryMesh.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{VirtualGeometryMesh.js → mesh/VirtualGeometryMesh.js} +7 -7
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts +29 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_compute_vertex_normals.js → mesh/vgeo_compute_vertex_normals.js} +3 -3
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_tangents.d.ts +31 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_tangents.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_compute_vertex_tangents.js → mesh/vgeo_compute_vertex_tangents.js} +5 -5
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts +46 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_normalize_source.js → mesh/vgeo_normalize_source.js} +9 -9
- package/src/shade/renderer/geometry/virtual/build/page/vgeo_assemble_pages.d.ts +13 -0
- package/src/shade/renderer/geometry/virtual/build/page/vgeo_assemble_pages.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_assemble_pages.js → page/vgeo_assemble_pages.js} +63 -16
- package/src/shade/renderer/geometry/virtual/build/page/vgeo_build_page_nodes.d.ts +13 -0
- package/src/shade/renderer/geometry/virtual/build/page/vgeo_build_page_nodes.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_build_page_nodes.js → page/vgeo_build_page_nodes.js} +4 -4
- package/src/shade/renderer/geometry/virtual/build/page/vgeo_page_node_count.d.ts +22 -0
- package/src/shade/renderer/geometry/virtual/build/page/vgeo_page_node_count.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_page_node_count.js → page/vgeo_page_node_count.js} +2 -2
- package/src/shade/renderer/geometry/virtual/build/partition/VGeoFaceGraph.d.ts +52 -0
- package/src/shade/renderer/geometry/virtual/build/partition/VGeoFaceGraph.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{VGeoFaceGraph.js → partition/VGeoFaceGraph.js} +1 -1
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_graph_components.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_graph_components.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_partition_graph.d.ts +29 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_partition_graph.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_partition_graph.js → partition/vgeo_partition_graph.js} +2 -2
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_partition_subgraph.d.ts +41 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_partition_subgraph.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_partition_subgraph.js +92 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set.d.ts +10 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_split_face_set.js → partition/vgeo_split_face_set.js} +12 -55
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set_packed.d.ts +15 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set_packed.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_split_face_set_packed.js → partition/vgeo_split_face_set_packed.js} +3 -3
- package/src/shade/renderer/geometry/virtual/build/sphere/vgeo_bounding_sphere.d.ts +12 -0
- package/src/shade/renderer/geometry/virtual/build/sphere/vgeo_bounding_sphere.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_bounding_sphere.js → sphere/vgeo_bounding_sphere.js} +3 -3
- package/src/shade/renderer/geometry/virtual/build/sphere/vgeo_containing_sphere.d.ts +18 -0
- package/src/shade/renderer/geometry/virtual/build/sphere/vgeo_containing_sphere.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_containing_sphere.js → sphere/vgeo_containing_sphere.js} +3 -3
- package/src/shade/renderer/geometry/virtual/build/sphere/vgeo_sphere_inflate_to_contain.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/build/sphere/vgeo_sphere_inflate_to_contain.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_sphere_inflate_to_contain.js → sphere/vgeo_sphere_inflate_to_contain.js} +1 -1
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoGatheredFaces.d.ts +24 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoGatheredFaces.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoSubmesh.d.ts +20 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoSubmesh.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_extract_submesh.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_extract_submesh.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_extract_submesh.js → submesh/vgeo_extract_submesh.js} +5 -5
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_gather_faces.d.ts +12 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_gather_faces.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_gather_faces.js → submesh/vgeo_gather_faces.js} +2 -2
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_narrow_indices.d.ts +11 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_narrow_indices.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_narrow_indices.js → submesh/vgeo_narrow_indices.js} +3 -3
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +10 -11
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts +22 -0
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +24 -0
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.d.ts +8 -2
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.js +8 -2
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts +12 -0
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +13 -0
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts +8 -1
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +11 -3
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +10 -1
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_OFFSET.d.ts +15 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_OFFSET.js +15 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.d.ts +18 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.js +17 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.d.ts +8 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.js +7 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.js +13 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.d.ts +11 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.js +10 -0
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.d.ts +28 -0
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.js +7 -3
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts +20 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.js +20 -2
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts +4 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.js +4 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts +29 -3
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.js +81 -122
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts +40 -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 +44 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts +69 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.js +236 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.js +15 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.d.ts +31 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.js +32 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts +10 -0
- 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 +21 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.d.ts +15 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.js +84 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.d.ts +22 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.js +96 -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 +63 -3
- 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 +219 -17
- 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 +156 -11
- package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +656 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts +33 -36
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.js +109 -193
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +109 -29
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +173 -105
- package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +37 -0
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +109 -26
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +548 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts +29 -50
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +55 -487
- package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.d.ts +84 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.js +393 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts +234 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +1388 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.d.ts +81 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.js +89 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts +53 -34
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.js +407 -124
- package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.d.ts +100 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.js +219 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.js +47 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.d.ts +20 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.js +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.js +30 -12
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts +8 -8
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +11 -73
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_arena.d.ts +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_arena.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_arena.js +98 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_claim.d.ts +31 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_claim.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_claim.js +99 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_queue.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_queue.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_queue.js +39 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.js +18 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.js +5 -15
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.js +77 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.js +130 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.js +32 -37
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +576 -230
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts +15 -18
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.js +19 -28
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +45 -25
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js +11 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.js +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts +0 -1
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.js +12 -10
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts +7 -2
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.js +8 -3
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts +2 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.js +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.js +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.d.ts +5 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.js +22 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.js +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.js +16 -0
- package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.d.ts +15 -0
- package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.js +16 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.d.ts +23 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.js +28 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.d.ts +2 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.js +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts +10 -5
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.js +15 -6
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts +6 -1
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.js +38 -1
- package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.js +4 -3
- package/src/shade/renderer/loader/gltf/load_gltf.d.ts.map +1 -1
- package/src/shade/renderer/loader/gltf/load_gltf.js +0 -19
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/material/chunk_material_draw_side.d.ts +14 -0
- package/src/shade/renderer/material/chunk_material_draw_side.d.ts.map +1 -0
- package/src/shade/renderer/material/chunk_material_draw_side.js +34 -0
- package/src/shade/renderer/material/standard/fragment_gbuffer.d.ts.map +1 -1
- package/src/shade/renderer/material/standard/fragment_gbuffer.js +8 -1
- package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_emit.js +6 -3
- package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +6 -3
- package/src/shade/renderer/path_tracer/shader/chunk_sample_material_data.d.ts.map +1 -1
- package/src/shade/renderer/path_tracer/shader/chunk_sample_material_data.js +13 -1
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/rasterize/bucket/construct_primitive_state.d.ts +19 -5
- package/src/shade/renderer/rasterize/bucket/construct_primitive_state.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +23 -5
- package/src/shade/renderer/rasterize/bucket/filter_materials_by_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/filter_materials_by_bucket.js +12 -5
- 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 +12 -4
- package/src/shade/renderer/rasterize/bucket/rasterization_opaque_buckets.d.ts +22 -0
- package/src/shade/renderer/rasterize/bucket/rasterization_opaque_buckets.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/bucket/rasterization_opaque_buckets.js +36 -0
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts +2 -0
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +58 -57
- package/src/shade/renderer/rasterize/chunk_triangle_winding_is_reversed.d.ts +15 -0
- package/src/shade/renderer/rasterize/chunk_triangle_winding_is_reversed.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/chunk_triangle_winding_is_reversed.js +35 -0
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.js +3 -5
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.js +3 -4
- package/src/shade/renderer/rasterize/expand/bucket/RASTERIZATION_BUCKET_LIMIT.d.ts +10 -1
- package/src/shade/renderer/rasterize/expand/bucket/RASTERIZATION_BUCKET_LIMIT.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/RASTERIZATION_BUCKET_LIMIT.js +38 -29
- package/src/shade/renderer/rasterize/expand/bucket/chunk_rasterization_material_bucket.d.ts +5 -0
- package/src/shade/renderer/rasterize/expand/bucket/chunk_rasterization_material_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/chunk_rasterization_material_bucket.js +63 -55
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.js +3 -4
- package/src/shade/renderer/rasterize/expand/graph_meshlets_rasterize.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/graph_meshlets_rasterize.js +1 -0
- package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.js +7 -7
- package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.js +83 -78
- 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 +44 -53
- package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_brick4.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_brick4.js +6 -2
- package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_ibl.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_ibl.js +6 -2
- package/src/shade/renderer/rasterize/forward/vertex_shader_chunk.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/forward/vertex_shader_chunk.js +19 -2
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +7 -4
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +3 -2
- package/src/shade/renderer/rasterize/native/chunk_rasterize_triangle.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/chunk_rasterize_triangle.js +15 -4
- package/src/shade/renderer/rasterize/native/depth/shadow_rasterization_alpha_tested_pass_descriptor.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/depth/shadow_rasterization_alpha_tested_pass_descriptor.js +16 -5
- package/src/shade/renderer/rasterize/native/depth/shadow_rasterization_pass_descriptor.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/depth/shadow_rasterization_pass_descriptor.js +16 -5
- package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.js +2 -1
- package/src/shade/renderer/rasterize/native/rasterization_shader_resources.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/rasterization_shader_resources.js +4 -0
- package/src/shade/renderer/rasterize/native/viz/viz_rasterization_alpha_tested_pass_descriptor.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/viz/viz_rasterization_alpha_tested_pass_descriptor.js +16 -7
- 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 +85 -89
- 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 +28 -32
- package/src/shade/renderer/scene/GPUSceneContext.d.ts +22 -2
- package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
- package/src/shade/renderer/scene/GPUSceneContext.js +61 -4
- package/src/shade/renderer/scene/NODE3D_STRUCT.d.ts +19 -0
- package/src/shade/renderer/scene/NODE3D_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/scene/NODE3D_STRUCT.js +34 -0
- package/src/shade/renderer/scene/chunk_node3d_flags.d.ts +9 -0
- package/src/shade/renderer/scene/chunk_node3d_flags.d.ts.map +1 -0
- package/src/shade/renderer/scene/chunk_node3d_flags.js +23 -0
- package/src/shade/renderer/scene/chunk_scene_database_access.d.ts.map +1 -1
- package/src/shade/renderer/scene/chunk_scene_database_access.js +15 -1
- package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts.map +1 -1
- package/src/shade/renderer/scene/rows/GPUSceneRows.js +9 -1
- package/src/shade/renderer/shader/chunk/geometry/mat4/chunk_compute_normal_matrix_from_m4.d.ts +7 -0
- package/src/shade/renderer/shader/chunk/geometry/mat4/chunk_compute_normal_matrix_from_m4.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/mat4/chunk_compute_normal_matrix_from_m4.js +16 -4
- package/src/shade/renderer/shader/chunk/geometry/mat4/chunk_mat4_linear_determinant.d.ts +12 -0
- package/src/shade/renderer/shader/chunk/geometry/mat4/chunk_mat4_linear_determinant.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/mat4/chunk_mat4_linear_determinant.js +18 -0
- package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_triangle_list_reverse_winding.d.ts +24 -0
- package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_triangle_list_reverse_winding.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_triangle_list_reverse_winding.js +33 -0
- package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts.map +1 -1
- package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.js +92 -16
- package/src/shade/renderer/view/GPUViewContext.d.ts +5 -0
- package/src/shade/renderer/view/GPUViewContext.d.ts.map +1 -1
- package/src/shade/renderer/view/GPUViewContext.js +21 -0
- package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/WGSLLanguageCore.js +74 -3
- package/src/shade/playground/vgeo_viewer/README.md +0 -150
- package/src/shade/playground/vgeo_viewer/cut_geometry.js +0 -460
- package/src/shade/playground/vgeo_viewer/index.html +0 -77
- package/src/shade/playground/vgeo_viewer/main.js +0 -1343
- package/src/shade/playground/vgeo_viewer/select_cut.js +0 -481
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts +0 -258
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.js +0 -723
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts +0 -49
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.js +0 -50
- package/src/shade/renderer/loader/gltf/fix_up_material_sides.js +0 -109
- package/src/shade/renderer/rasterize/expand/graph_triangles_filter_by_material_side.js +0 -78
- package/src/shade/renderer/rasterize/expand/shader_filter_triangle_by_material_side.js +0 -100
- package/src/shade/renderer/rasterize/forward/chunk_material_side_should_draw.js +0 -26
- package/src/shade/renderer/shader/chunk/jobs/NOTES.md +0 -47
- package/src/shade/renderer/shader/chunk/jobs/chunk_job_system.d.ts +0 -3
- package/src/shade/renderer/shader/chunk/jobs/chunk_job_system.d.ts.map +0 -1
- package/src/shade/renderer/shader/chunk/jobs/chunk_job_system.js +0 -102
- /package/src/shade/renderer/geometry/virtual/build/{VGeoLevel.js → level/VGeoLevel.js} +0 -0
- /package/src/shade/renderer/geometry/virtual/build/{VGeoLevelOptions.js → level/VGeoLevelOptions.js} +0 -0
- /package/src/shade/renderer/geometry/virtual/build/{VGeoLevelSummary.js → level/VGeoLevelSummary.js} +0 -0
- /package/src/shade/renderer/geometry/virtual/build/{vgeo_graph_components.js → partition/vgeo_graph_components.js} +0 -0
- /package/src/shade/renderer/geometry/virtual/build/{VGeoGatheredFaces.js → submesh/VGeoGatheredFaces.js} +0 -0
- /package/src/shade/renderer/geometry/virtual/build/{VGeoSubmesh.js → submesh/VGeoSubmesh.js} +0 -0
|
@@ -3,6 +3,7 @@ import { WebGPUArray } from "../../../../shader/type/WebGPUArray.js";
|
|
|
3
3
|
import { CodeChunk } from "../../../../shader/compiler/CodeChunk.js";
|
|
4
4
|
import { ComputeShader } from "../../../../shader/ComputeShader.js";
|
|
5
5
|
import { ShaderResourceSetDescriptor } from "../../../../shader/resource/ShaderResourceSetDescriptor.js";
|
|
6
|
+
import { WebGPUExtensionType } from "../../../../../descriptor/WebGPUExtensionType.js";
|
|
6
7
|
import { COLLECTION_U32_STRUCT_READ } from "../../../../gpu_primitive/COLLECTION_U32_STRUCT_READ.js";
|
|
7
8
|
import { COLLECTION_U32_STRUCT_WRITE } from "../../../../gpu_primitive/COLLECTION_U32_STRUCT_WRITE.js";
|
|
8
9
|
import { MESHLET_COLLECTION_WRITE_STRUCT } from "../../../../rasterize/MESHLET_COLLECTION_WRITE_STRUCT.js";
|
|
@@ -17,16 +18,20 @@ import { VGEO_NODE_ROW_OFFSET } from "../layout/VGEO_NODE_ROW_OFFSET.js";
|
|
|
17
18
|
import { VGEO_NO_CHILD_PAGE } from "../layout/VGEO_NO_CHILD_PAGE.js";
|
|
18
19
|
import { VGEO_PAGE_ROW_NONE } from "../layout/VGEO_PAGE_ROW_NONE.js";
|
|
19
20
|
import { VGEO_PAGE_ROW_OFFSET } from "../layout/VGEO_PAGE_ROW_OFFSET.js";
|
|
21
|
+
import { VGEO_PARENT_ROW_OFFSET } from "../layout/VGEO_PARENT_ROW_OFFSET.js";
|
|
22
|
+
import { VGEO_QUEUE_TARGET_PARENT_BIT } from "../layout/VGEO_QUEUE_TARGET_PARENT_BIT.js";
|
|
20
23
|
import { VGEO_QUEUE_TARGET_GROUP_BIT } from "../layout/VGEO_QUEUE_TARGET_GROUP_BIT.js";
|
|
21
24
|
import { VGEO_WANT_WORDS } from "../layout/VGEO_WANT_WORDS.js";
|
|
22
25
|
import { chunk_vgeo_cut_access } from "./chunk_vgeo_cut_access.js";
|
|
26
|
+
import { chunk_vgeo_cut_claim } from "./chunk_vgeo_cut_claim.js";
|
|
27
|
+
import { VGEO_CUT_EXPAND } from "./VGEO_CUT_EXPAND.js";
|
|
28
|
+
import { VGEO_CUT_FLAG_COARSEN } from "./VGEO_CUT_FLAG_COARSEN.js";
|
|
23
29
|
import { VGEO_CUT_FLAG_CULL } from "./VGEO_CUT_FLAG_CULL.js";
|
|
24
30
|
import { VGEO_CUT_PARAMS_STRUCT } from "./VGEO_CUT_PARAMS_STRUCT.js";
|
|
25
31
|
import { VGEO_CUT_WORKGROUP_SIZE } from "./VGEO_CUT_WORKGROUP_SIZE.js";
|
|
26
32
|
|
|
27
33
|
/**
|
|
28
|
-
*
|
|
29
|
-
* structure under it exists.
|
|
34
|
+
* The virtual-geometry cut — design §7's pass, as a descent that lives inside one dispatch.
|
|
30
35
|
*
|
|
31
36
|
* ## The rule
|
|
32
37
|
*
|
|
@@ -35,29 +40,67 @@ import { VGEO_CUT_WORKGROUP_SIZE } from "./VGEO_CUT_WORKGROUP_SIZE.js";
|
|
|
35
40
|
* > `own group selectable AND project(parent) > tau AND (project(self) <= tau OR child group not
|
|
36
41
|
* > selectable)`
|
|
37
42
|
*
|
|
38
|
-
* which is design §4's per-cluster form
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* missing page, which is what makes it hard to attribute and worth stating first.
|
|
43
|
+
* which is design §4's per-cluster form together with its first term. That term is load-bearing
|
|
44
|
+
* and §4 states the rule without it: a group that is resident but not yet selectable has clusters
|
|
45
|
+
* satisfying the rest of the rule while their coarse stand-in is drawn over the same surface, a few
|
|
46
|
+
* microns apart, with the depth buffer choosing between them. That reads as corrupt geometry rather
|
|
47
|
+
* than as a missing page, which is what makes it hard to attribute and worth stating first.
|
|
44
48
|
*
|
|
45
49
|
* ## The shape
|
|
46
50
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
51
|
+
* The loop over depth is *inside* this kernel: a dispatch is a spill generation rather than a level
|
|
52
|
+
* of the descent. Depth is what a cut costs. Measured on a 28M-face container at tau = 1 on a 4090:
|
|
53
|
+
* the descent converges at **39 levels**, carrying 15,027 items — an average frontier of 385 items,
|
|
54
|
+
* or **six workgroups of 64 on a 128-SM part** — and an empty prepare/traverse pair costs 3.9 us
|
|
55
|
+
* whatever it contains. Page depth grows like log2(triangles), so a dispatch per level gets *worse*
|
|
56
|
+
* with better assets and no better with a faster GPU.
|
|
50
57
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* clusters each (format §11.5) — legal input, and 16,384 iterations in the lane that popped it if
|
|
55
|
-
* the leaf walked them itself.
|
|
58
|
+
* What a lane cannot do is wait for another workgroup: WebGPU guarantees no forward progress across
|
|
59
|
+
* workgroups, so persistent threads over an MPMC queue — Nanite's answer — is unbounded waiting.
|
|
60
|
+
* What a lane *can* do is cooperate with the other lanes of its own subgroup, and that is enough:
|
|
56
61
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
62
|
+
* - Every lane executes its item and says how many children it produced. No lane walks its own
|
|
63
|
+
* subtree while its neighbours idle; production is a **count**, at constant cost.
|
|
64
|
+
* - `subgroupExclusiveAdd` over those counts numbers the wave's whole production densely. The first
|
|
65
|
+
* `subgroup_size` of them are taken back into the wave's own lanes — perfectly packed, one child
|
|
66
|
+
* per lane, whoever produced them — and the descent takes another step without touching memory.
|
|
67
|
+
* - Everything past a full wave **spills** to the global queue under one `atomicAdd`. Breadth the
|
|
68
|
+
* wave cannot hold becomes work for other waves rather than a serial loop inside this one.
|
|
69
|
+
*
|
|
70
|
+
* So the number of dispatches is the number of spill generations, and the narrow top of the DAG —
|
|
71
|
+
* twenty levels that move between 1 and 120 items and draw nothing — is one wave taking twenty
|
|
72
|
+
* steps in registers.
|
|
73
|
+
*
|
|
74
|
+
* This is Karis' "Variable sized work" (graphicrants, 2026-03) with its groupshared scatter
|
|
75
|
+
* removed. That scatter needs HLSL's `GroupMemoryBarrier()` — memory ordering with no execution
|
|
76
|
+
* sync, sound because a wave is in lockstep — and WGSL has no memory-only workgroup barrier.
|
|
77
|
+
* `workgroupBarrier` synchronizes execution as well, which pins every subgroup of the workgroup to
|
|
78
|
+
* the same number of steps and is a deadlock the moment they disagree; `workgroupUniformLoad` is
|
|
79
|
+
* that same control barrier plus a load, and what it returns is one value uniform across the
|
|
80
|
+
* workgroup, which is the opposite of the per-lane read a scatter is made of.
|
|
81
|
+
* {@link vg_child_owner} finds each item's producer by binary search over the same prefix sums
|
|
82
|
+
* instead: `log2(subgroup_size)` shuffles, no workgroup memory and no barrier anywhere in this
|
|
83
|
+
* file, so nothing here needs the workgroup to agree on anything.
|
|
84
|
+
*
|
|
85
|
+
* ## What WGSL will not let this do
|
|
86
|
+
*
|
|
87
|
+
* Every loop around a subgroup operation needs a **uniform trip count**, and nothing launders one —
|
|
88
|
+
* not `subgroupAdd`, not `subgroupMax`, not even `subgroupBroadcastFirst`, which launders a value
|
|
89
|
+
* for a use but not for control flow. Nor may such a loop carry a non-uniform `break`, `continue`
|
|
90
|
+
* or `return`. So the descent runs `params.descent_steps` times and masks; a wave that drains its
|
|
91
|
+
* subtree early pays the remaining steps at about ten instructions each rather than leaving. That
|
|
92
|
+
* makes `descent_steps` a tuning knob rather than a safety bound, and
|
|
93
|
+
* `VGEO_CUT_STAT.QUEUE_REMAINING` is what says when it is set too low.
|
|
94
|
+
*
|
|
95
|
+
* Divergence *inside* the loop is free: Tint models reconvergence, so a non-uniform `if` that opens
|
|
96
|
+
* and closes taints nothing after it, and the spill's per-lane loop is fine because it contains no
|
|
97
|
+
* subgroup operation of its own.
|
|
98
|
+
*
|
|
99
|
+
* ## One lane per cluster
|
|
100
|
+
*
|
|
101
|
+
* A leaf produces one item per cluster of its groups and the wave packs them, so what bounds a
|
|
102
|
+
* lane's work is the packing rather than a chunk size, and consecutive lanes read consecutive
|
|
103
|
+
* cluster rows instead of striding over runs of them.
|
|
61
104
|
*
|
|
62
105
|
* ## Why page-granular dedup is enough, and why it is necessary
|
|
63
106
|
*
|
|
@@ -83,6 +126,11 @@ import { VGEO_CUT_WORKGROUP_SIZE } from "./VGEO_CUT_WORKGROUP_SIZE.js";
|
|
|
83
126
|
* `max_parent_error` is at least `P.parent_error`, so the LOD cull cannot fire, and the frustum
|
|
84
127
|
* cull fires only where `C` is off-screen anyway.
|
|
85
128
|
*
|
|
129
|
+
* Descent crosses a page boundary by producing the child page's **node 0**: uniform items, one
|
|
130
|
+
* kernel, no special case for "entering a page". `SAME_PAGE` needs no special case either, because
|
|
131
|
+
* the CPU resolved it to this page's own index at install, so it looks up a page that is trivially
|
|
132
|
+
* resident and falls through to the only question it was really asking — is that group selectable.
|
|
133
|
+
*
|
|
86
134
|
* ## Bounds
|
|
87
135
|
*
|
|
88
136
|
* Every append is checked and every overflow is counted, and they all degrade in one direction —
|
|
@@ -92,10 +140,11 @@ import { VGEO_CUT_WORKGROUP_SIZE } from "./VGEO_CUT_WORKGROUP_SIZE.js";
|
|
|
92
140
|
*
|
|
93
141
|
* ## Bindings
|
|
94
142
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* is
|
|
98
|
-
*
|
|
143
|
+
* Nine storage buffers against the engine's floor of ten (RUNTIME_PLAN §4.9) — the tightest kernel
|
|
144
|
+
* of the cut, and `vgeo_cut_shaders.compile.spec.js` holds the exact count rather than the bound.
|
|
145
|
+
* The page table is not one of them: a page's header is the first words of its own arena slot, so
|
|
146
|
+
* `vg_page_field` is an offset into a buffer this kernel already binds rather than a second buffer
|
|
147
|
+
* to bind and keep coherent.
|
|
99
148
|
*
|
|
100
149
|
* @author Alex Goldring
|
|
101
150
|
* @copyright Company Named Limited (c) 2026
|
|
@@ -122,9 +171,20 @@ resources.createGroup()
|
|
|
122
171
|
// one bit per (instance, page ordinal); cleared per invocation because each expansion site
|
|
123
172
|
// sees a different instance set
|
|
124
173
|
.addStorageBuffer("claim", WebGPUArray.from(atomic_u32), true)
|
|
125
|
-
|
|
126
|
-
|
|
174
|
+
/*
|
|
175
|
+
The tenth, and the last this kernel can have: `Renderer.js` requires ten storage
|
|
176
|
+
buffers per stage of an adapter and refuses one that offers fewer. It is spent on the
|
|
177
|
+
frontier because the alternative — deriving the page set afterwards from the draw
|
|
178
|
+
records — costs a pass over every drawn cluster to recover a fact the claim already
|
|
179
|
+
knew at the moment it was established.
|
|
180
|
+
*/
|
|
181
|
+
.addStorageBuffer("frontier", WebGPUArray.from(atomic_u32), true)
|
|
182
|
+
// header, stats, one touch bit per page row, then the want records — one per frame, shared by
|
|
183
|
+
// every view and bucket, which is what dedups a touch across them
|
|
127
184
|
.addStorageBuffer("feedback", WebGPUArray.from(atomic_u32), true)
|
|
185
|
+
// one stamp per lookup word, persistent across frames and tagged with the frame that wrote it,
|
|
186
|
+
// so a want is deduped without a lookup-sized clear every frame
|
|
187
|
+
.addStorageBuffer("vg_want_stamps", WebGPUArray.from(atomic_u32), true)
|
|
128
188
|
// the same collection the static mesh expansion appends to, read downstream by the HZB meshlet
|
|
129
189
|
// filter and the indirect draw with no knowledge that virtual geometry exists
|
|
130
190
|
.addStorageBuffer("out_meshlets", MESHLET_COLLECTION_WRITE_STRUCT, true)
|
|
@@ -134,97 +194,55 @@ const body = CodeChunk.from(
|
|
|
134
194
|
//language=WGSL
|
|
135
195
|
`
|
|
136
196
|
const VG_QUEUE_GROUP_BIT = ${VGEO_QUEUE_TARGET_GROUP_BIT}u;
|
|
197
|
+
const VG_QUEUE_PARENT_BIT = ${VGEO_QUEUE_TARGET_PARENT_BIT}u;
|
|
137
198
|
const VG_PAGE_ROW_NONE = ${VGEO_PAGE_ROW_NONE}u;
|
|
138
199
|
const VG_NO_CHILD_PAGE = ${VGEO_NO_CHILD_PAGE}u;
|
|
139
200
|
const VG_GROUP_SELECTABLE = ${VGEO_GROUP_FLAG_SELECTABLE}u;
|
|
140
201
|
const VG_WANT_WORDS = ${VGEO_WANT_WORDS}u;
|
|
141
202
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
203
|
+
const VG_EXPAND_NONE = ${VGEO_CUT_EXPAND.NONE}u;
|
|
204
|
+
const VG_EXPAND_NODES = ${VGEO_CUT_EXPAND.NODES}u;
|
|
205
|
+
const VG_EXPAND_CLUSTERS = ${VGEO_CUT_EXPAND.CLUSTERS}u;
|
|
206
|
+
const VG_EXPAND_PAGE = ${VGEO_CUT_EXPAND.PAGE}u;
|
|
207
|
+
const VG_EXPAND_PARENTS = ${VGEO_CUT_EXPAND.PARENTS}u;
|
|
145
208
|
|
|
146
209
|
/**
|
|
147
|
-
*
|
|
210
|
+
* One unit of work: a node visit, or a single cluster of one group.
|
|
148
211
|
*
|
|
149
|
-
* The
|
|
150
|
-
*
|
|
212
|
+
* The same four words the queue holds, and bit 31 of \`visit\` tells the two apart — a node
|
|
213
|
+
* index below it, a group index above it with \`aux\` naming the cluster inside that group.
|
|
151
214
|
*/
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (slot >= params.queue_capacity) {
|
|
158
|
-
vg_stat(${VGEO_CUT_STAT.QUEUE_DROPPED}u, 1u);
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
let base = slot * 4u;
|
|
163
|
-
|
|
164
|
-
queue_out.elements[base] = instance_slot;
|
|
165
|
-
queue_out.elements[base + 1u] = page_row;
|
|
166
|
-
queue_out.elements[base + 2u] = visit;
|
|
167
|
-
queue_out.elements[base + 3u] = first_cluster;
|
|
215
|
+
struct VGItem {
|
|
216
|
+
instance_slot: u32,
|
|
217
|
+
page_row: u32,
|
|
218
|
+
visit: u32,
|
|
219
|
+
aux: u32,
|
|
168
220
|
}
|
|
169
221
|
|
|
170
222
|
/**
|
|
171
|
-
*
|
|
223
|
+
* What an executed item wants done next, as a count and a rule for the k-th child rather than as
|
|
224
|
+
* children written somewhere.
|
|
172
225
|
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
226
|
+
* This is the whole reason the descent can stay in registers: a producer states its production at
|
|
227
|
+
* constant cost and any lane of the wave can then build child \`k\` of it from five numbers.
|
|
175
228
|
*/
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
let bit = instances[instance_base + ${VGEO_INSTANCE_OFFSET.CLAIM_BASE}u] + ordinal;
|
|
184
|
-
|
|
185
|
-
let word = bit >> 5u;
|
|
186
|
-
|
|
187
|
-
if (word >= params.claim_word_count) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
let mask = 1u << (bit & 31u);
|
|
192
|
-
|
|
193
|
-
if ((atomicOr(&claim[word], mask) & mask) != 0u) {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
vg_stat(${VGEO_CUT_STAT.PAGES}u, 1u);
|
|
198
|
-
|
|
199
|
-
vg_touch_page(page_row);
|
|
200
|
-
|
|
201
|
-
return true;
|
|
229
|
+
struct VGExpand {
|
|
230
|
+
kind: u32,
|
|
231
|
+
count: u32,
|
|
232
|
+
page_row: u32,
|
|
233
|
+
first: u32,
|
|
234
|
+
span: u32,
|
|
202
235
|
}
|
|
203
236
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
*
|
|
207
|
-
* LRU on last-*touched*, never on last-wanted: stamping at fetch time freezes the stamp of exactly
|
|
208
|
-
* the pages that are on screen and evicts them, which is the classic fallback-chain bug the virtual
|
|
209
|
-
* texture cache already keys on touch to avoid.
|
|
210
|
-
*
|
|
211
|
-
* Deduped across every view and bucket of the frame by one stamp word per page row, because the
|
|
212
|
-
* feedback buffer is per frame rather than per invocation.
|
|
213
|
-
*/
|
|
214
|
-
fn vg_touch_page(page_row: u32) {
|
|
215
|
-
if (atomicExchange(&feedback[params.touch_stamp_base + page_row], 1u) != 0u) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
let slot = atomicAdd(&feedback[${VGEO_FEEDBACK_OFFSET.TOUCH_COUNT}u], 1u);
|
|
220
|
-
|
|
221
|
-
if (slot >= params.touch_capacity) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
atomicStore(&feedback[params.touch_base + slot], page_row);
|
|
237
|
+
fn vg_expand_none() -> VGExpand {
|
|
238
|
+
return VGExpand(VG_EXPAND_NONE, 0u, 0u, 0u, 0u);
|
|
226
239
|
}
|
|
227
240
|
|
|
241
|
+
/*
|
|
242
|
+
vg_stat, vg_claim_page and vg_touch_page are chunk_vgeo_cut_claim's — the seed and reseed
|
|
243
|
+
passes take the same three, which is why they are no longer written here.
|
|
244
|
+
*/
|
|
245
|
+
|
|
228
246
|
/**
|
|
229
247
|
* Ask for the page a clamped cluster could not reach.
|
|
230
248
|
*
|
|
@@ -241,9 +259,10 @@ fn vg_touch_page(page_row: u32) {
|
|
|
241
259
|
* and VGeoResidencyManager maxes across frames.
|
|
242
260
|
*/
|
|
243
261
|
fn vg_want(source_page_row: u32, child_ref: u32, child_page_index: u32, lookup_base: u32, projected: f32) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (atomicExchange(&
|
|
262
|
+
// persistent and frame-tagged rather than cleared: a stamp already holding this frame's tag
|
|
263
|
+
// was written by this frame, and anything else is a frame that has gone
|
|
264
|
+
if (atomicExchange(&vg_want_stamps[lookup_base + child_page_index], params.frame_tag)
|
|
265
|
+
== params.frame_tag) {
|
|
247
266
|
return;
|
|
248
267
|
}
|
|
249
268
|
|
|
@@ -282,18 +301,41 @@ fn vg_draw(page_row: u32, cluster: u32, mesh_index: u32) {
|
|
|
282
301
|
out_meshlets.elements[slot] = ${MESHLET_DEFINITION_STRUCT.wgsl_ref}(meshlet, mesh_index);
|
|
283
302
|
|
|
284
303
|
vg_stat(${VGEO_CUT_STAT.DRAWN}u, 1u);
|
|
304
|
+
|
|
305
|
+
/*
|
|
306
|
+
And remember the page for next frame. The frontier is where this cut *ended*, not
|
|
307
|
+
everywhere it went: recording at the claim instead would name every page the descent
|
|
308
|
+
passed through on its way down, and reseeding from that set is more work than the root
|
|
309
|
+
descent it replaces rather than less. A page that drew something is a page the cut is
|
|
310
|
+
standing on.
|
|
311
|
+
|
|
312
|
+
A bitmask, so the several clusters of a page that draw cost one bit and no dedup. Per view,
|
|
313
|
+
so two views keep their own answer.
|
|
314
|
+
*/
|
|
315
|
+
let frontier_word = page_row >> 5u;
|
|
316
|
+
|
|
317
|
+
if (frontier_word < params.frontier_word_count) {
|
|
318
|
+
atomicOr(&frontier[frontier_word], 1u << (page_row & 31u));
|
|
319
|
+
}
|
|
285
320
|
}
|
|
286
321
|
|
|
287
|
-
fn
|
|
288
|
-
|
|
322
|
+
fn vg_group_row(page_row: u32, group: u32) -> u32 {
|
|
323
|
+
return vg_group_base(page_row) + group * VG_GROUP_ROW_WORDS;
|
|
324
|
+
}
|
|
289
325
|
|
|
290
|
-
|
|
326
|
+
fn vg_group_selectable(page_row: u32, group: u32) -> bool {
|
|
327
|
+
return (vg_arena[vg_group_row(page_row, group) + ${VGEO_GROUP_ROW_OFFSET.FLAGS}u]
|
|
328
|
+
& VG_GROUP_SELECTABLE) != 0u;
|
|
291
329
|
}
|
|
292
330
|
|
|
293
331
|
/**
|
|
294
|
-
* A node: cull, then
|
|
332
|
+
* A node: cull, then say what it expands into.
|
|
333
|
+
*
|
|
334
|
+
* Nothing is enqueued here. An interior node expands into its child nodes and a leaf into every
|
|
335
|
+
* cluster of its groups, and either is a base and a count — which is what lets the wave take the
|
|
336
|
+
* first \`subgroup_size\` of them into its own lanes and spill the rest.
|
|
295
337
|
*/
|
|
296
|
-
fn vg_visit_node(
|
|
338
|
+
fn vg_visit_node(instance_base: u32, page_row: u32, node: u32) -> VGExpand {
|
|
297
339
|
let row = vg_node_base(page_row) + node * VG_NODE_ROW_WORDS;
|
|
298
340
|
|
|
299
341
|
let sphere = vg_sphere_at(row + ${VGEO_NODE_ROW_OFFSET.SPHERE}u);
|
|
@@ -301,7 +343,7 @@ fn vg_visit_node(instance_slot: u32, instance_base: u32, page_row: u32, node: u3
|
|
|
301
343
|
if ((params.flags & ${VGEO_CUT_FLAG_CULL}u) != 0u
|
|
302
344
|
&& vg_sphere_outside_frustum(instance_base, sphere)) {
|
|
303
345
|
vg_stat(${VGEO_CUT_STAT.CULLED_FRUSTUM}u, 1u);
|
|
304
|
-
return;
|
|
346
|
+
return vg_expand_none();
|
|
305
347
|
}
|
|
306
348
|
|
|
307
349
|
let eye = vg_instance_eye(instance_base);
|
|
@@ -312,11 +354,26 @@ fn vg_visit_node(instance_slot: u32, instance_base: u32, page_row: u32, node: u3
|
|
|
312
354
|
*largest* parent error under this node still projects to no more than tau, nothing under it can
|
|
313
355
|
be drawn and the whole subtree goes. The node's sphere is the union of the covered clusters'
|
|
314
356
|
parent spheres, so the projection is conservative in both terms.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
357
|
+
|
|
358
|
+
**It is a descent-only accelerator**, and the coarsen flag turns it off. Its premise is that
|
|
359
|
+
a subtree nothing can be drawn in is a subtree worth skipping, which is true of a traversal
|
|
360
|
+
that only ever looks for what to draw. A walk up is looking for something else: the groups
|
|
361
|
+
under this node are below the cut, and being below the cut is precisely what sends them to
|
|
362
|
+
their parents. Skipping them here is what made a cut seeded at the leaves draw nothing at all
|
|
363
|
+
-- the subtree carrying the whole walk was pruned at its first node, silently and by a rule
|
|
364
|
+
that is correct for the direction it was written for.
|
|
365
|
+
|
|
366
|
+
A node has no parent list of its own -- the upward edge is per group (format §6.3) -- so
|
|
367
|
+
there is nothing to coarsen *from* here, and entering the subtree is the only way to reach
|
|
368
|
+
the groups that can. That is the cost of walking up through a page: the pages on the way
|
|
369
|
+
are evaluated whole. It is why CUT_PLAN §1 keeps a frontier at all rather than re-deriving
|
|
370
|
+
one, and why the frontier is what makes this affordable rather than this pass.
|
|
371
|
+
*/
|
|
372
|
+
if ((params.flags & ${VGEO_CUT_FLAG_COARSEN}u) == 0u
|
|
373
|
+
&& vg_project(vg_arena_f32(row + ${VGEO_NODE_ROW_OFFSET.MAX_PARENT_ERROR}u), sphere, eye, k)
|
|
374
|
+
<= params.tau) {
|
|
318
375
|
vg_stat(${VGEO_CUT_STAT.CULLED_LOD}u, 1u);
|
|
319
|
-
return;
|
|
376
|
+
return vg_expand_none();
|
|
320
377
|
}
|
|
321
378
|
|
|
322
379
|
let child_first = vg_arena[row + ${VGEO_NODE_ROW_OFFSET.CHILD_FIRST}u];
|
|
@@ -325,204 +382,493 @@ fn vg_visit_node(instance_slot: u32, instance_base: u32, page_row: u32, node: u3
|
|
|
325
382
|
let child_count = child_word & VG_NODE_CHILD_COUNT_MASK;
|
|
326
383
|
|
|
327
384
|
if ((child_word & VG_NODE_LEAF_BIT) == 0u) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
for (var i = 0u; i < child_count; i = i + 1u) {
|
|
331
|
-
vg_queue_write(slot + i, instance_slot, page_row, child_first + i, 0u);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return;
|
|
385
|
+
return VGExpand(VG_EXPAND_NODES, child_count, page_row, child_first, 0u);
|
|
335
386
|
}
|
|
336
387
|
|
|
337
|
-
//
|
|
338
|
-
// four groups of four thousand clusters is legal input and would be one lane's whole life.
|
|
339
|
-
let group_base = vg_group_base(page_row);
|
|
340
|
-
|
|
388
|
+
// a leaf's children are groups, and its production is every cluster they hold
|
|
341
389
|
var total = 0u;
|
|
342
390
|
|
|
343
391
|
for (var i = 0u; i < child_count; i = i + 1u) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
+ ${VGEO_GROUP_ROW_OFFSET.CLUSTER_COUNT}u
|
|
392
|
+
total = total + vg_arena[
|
|
393
|
+
vg_group_row(page_row, child_first + i) + ${VGEO_GROUP_ROW_OFFSET.CLUSTER_COUNT}u
|
|
347
394
|
];
|
|
348
|
-
|
|
349
|
-
total = total + (members + params.cluster_chunk - 1u) / params.cluster_chunk;
|
|
350
395
|
}
|
|
351
396
|
|
|
352
|
-
|
|
397
|
+
return VGExpand(VG_EXPAND_CLUSTERS, total, page_row, child_first, child_count);
|
|
398
|
+
}
|
|
353
399
|
|
|
354
|
-
|
|
355
|
-
|
|
400
|
+
/**
|
|
401
|
+
* The k-th cluster under a leaf, as a group and an index inside it.
|
|
402
|
+
*
|
|
403
|
+
* Bounded by the leaf's own group count and branchless, which are both about the same hazard. Every
|
|
404
|
+
* lane of the wave runs this, including the lanes past the end of the wave's production, and those
|
|
405
|
+
* hold a \`k\` with no cluster behind it. A walk that stopped when it found the group would run off
|
|
406
|
+
* the end of the group rows for them, and a run of zero-member rows is an infinite loop rather than
|
|
407
|
+
* a wrong answer — a hung GPU, on data the builder is free to produce.
|
|
408
|
+
*
|
|
409
|
+
* Their result is nonsense and is never executed, because the lane is inactive. It only has to be
|
|
410
|
+
* in-range and finite.
|
|
411
|
+
*
|
|
412
|
+
* It is a loop with a non-uniform trip count and no subgroup operation inside, which WGSL permits
|
|
413
|
+
* and which taints nothing after it.
|
|
414
|
+
*/
|
|
415
|
+
fn vg_leaf_cluster(page_row: u32, group_first: u32, group_count: u32, k: u32) -> vec2<u32> {
|
|
416
|
+
var remaining = k;
|
|
417
|
+
var group = 0u;
|
|
418
|
+
var found = false;
|
|
356
419
|
|
|
420
|
+
for (var i = 0u; i < group_count; i = i + 1u) {
|
|
357
421
|
let members = vg_arena[
|
|
358
|
-
|
|
422
|
+
vg_group_row(page_row, group_first + i) + ${VGEO_GROUP_ROW_OFFSET.CLUSTER_COUNT}u
|
|
359
423
|
];
|
|
360
424
|
|
|
361
|
-
|
|
425
|
+
/*
|
|
426
|
+
The latch is the whole of it. Walking on without one is not a slower answer but a wrong
|
|
427
|
+
one: the walk keeps testing \`remaining\` against later groups, and any later group smaller
|
|
428
|
+
than the remainder it already settled on takes the item off the group that owns it. Two
|
|
429
|
+
values of k then name one cluster, and one cluster drawn twice is the exact failure the
|
|
430
|
+
claim bitmask and the own-group term exist to prevent.
|
|
431
|
+
*/
|
|
432
|
+
let here = !found && remaining < members;
|
|
362
433
|
|
|
363
|
-
|
|
364
|
-
|
|
434
|
+
group = select(group, i, here);
|
|
435
|
+
found = found || here;
|
|
365
436
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
437
|
+
// consumed only while still searching, so this never underflows
|
|
438
|
+
remaining = select(remaining - members, remaining, found);
|
|
369
439
|
}
|
|
440
|
+
|
|
441
|
+
return vec2<u32>(group_first + group, remaining);
|
|
370
442
|
}
|
|
371
443
|
|
|
372
444
|
/**
|
|
373
|
-
*
|
|
445
|
+
* One cluster: the rule, once.
|
|
374
446
|
*/
|
|
375
|
-
fn
|
|
376
|
-
instance_slot: u32,
|
|
447
|
+
fn vg_visit_cluster(
|
|
377
448
|
instance_base: u32,
|
|
378
449
|
page_row: u32,
|
|
379
450
|
group: u32,
|
|
380
|
-
|
|
381
|
-
) {
|
|
451
|
+
member: u32
|
|
452
|
+
) -> VGExpand {
|
|
382
453
|
/*
|
|
383
454
|
The own-group term. A group that is resident but not selectable draws nothing at all — its
|
|
384
455
|
coarser selectable ancestors are already covering that surface, and drawing both is the
|
|
385
456
|
double-draw this whole runtime is arranged to avoid.
|
|
386
457
|
*/
|
|
387
458
|
if (!vg_group_selectable(page_row, group)) {
|
|
388
|
-
return;
|
|
459
|
+
return vg_expand_none();
|
|
389
460
|
}
|
|
390
461
|
|
|
391
|
-
let group_row =
|
|
392
|
-
|
|
393
|
-
let members = vg_arena[group_row + ${VGEO_GROUP_ROW_OFFSET.CLUSTER_COUNT}u];
|
|
394
|
-
let cluster_first = vg_arena[group_row + ${VGEO_GROUP_ROW_OFFSET.CLUSTER_FIRST}u];
|
|
462
|
+
let group_row = vg_group_row(page_row, group);
|
|
395
463
|
|
|
396
|
-
let
|
|
464
|
+
let cluster = vg_arena[group_row + ${VGEO_GROUP_ROW_OFFSET.CLUSTER_FIRST}u] + member;
|
|
397
465
|
|
|
398
|
-
let
|
|
466
|
+
let row = vg_cluster_base(page_row) + cluster * VG_CLUSTER_ROW_WORDS;
|
|
399
467
|
|
|
400
468
|
let eye = vg_instance_eye(instance_base);
|
|
401
469
|
let k = vg_instance_f32(instance_base, ${VGEO_INSTANCE_OFFSET.PROJECTION_K}u);
|
|
402
470
|
let mesh_index = instances[instance_base + ${VGEO_INSTANCE_OFFSET.MESH_INDEX}u];
|
|
403
471
|
let lookup_base = instances[instance_base + ${VGEO_INSTANCE_OFFSET.LOOKUP_WORD_OFFSET}u];
|
|
404
472
|
|
|
405
|
-
|
|
406
|
-
let cluster = cluster_first + i;
|
|
473
|
+
vg_stat(${VGEO_CUT_STAT.CLUSTERS_TESTED}u, 1u);
|
|
407
474
|
|
|
408
|
-
|
|
475
|
+
/*
|
|
476
|
+
Before the cluster can refine, and the reason is residency rather than the draw: refining
|
|
477
|
+
claims the child page, and a claim is a touch — the LRU's whole input — so an off-screen
|
|
478
|
+
cluster that refined anyway would keep pages nobody can see hot and the tier full of them.
|
|
479
|
+
self_sphere contains the cluster's own vertices (FORMAT §7), so this rejects nothing on
|
|
480
|
+
screen; and every parent of a group shares one self_sphere, so a group's parents decide this
|
|
481
|
+
uniformly and the cut's proofs stand (RUNTIME_PLAN §4.4).
|
|
482
|
+
*/
|
|
483
|
+
if ((params.flags & ${VGEO_CUT_FLAG_CULL}u) != 0u
|
|
484
|
+
&& vg_sphere_outside_frustum(
|
|
485
|
+
instance_base, vg_sphere_at(row + ${VGEO_CLUSTER_ROW_OFFSET.SELF_SPHERE}u)
|
|
486
|
+
)) {
|
|
487
|
+
vg_stat(${VGEO_CUT_STAT.CULLED_CLUSTER}u, 1u);
|
|
409
488
|
|
|
410
|
-
|
|
489
|
+
return vg_expand_none();
|
|
490
|
+
}
|
|
411
491
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
492
|
+
let parent_projected = vg_project(
|
|
493
|
+
vg_arena_f32(row + ${VGEO_CLUSTER_ROW_OFFSET.PARENT_ERROR}u),
|
|
494
|
+
vg_sphere_at(row + ${VGEO_CLUSTER_ROW_OFFSET.PARENT_SPHERE}u),
|
|
495
|
+
eye, k
|
|
496
|
+
);
|
|
417
497
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
498
|
+
if (parent_projected <= params.tau) {
|
|
499
|
+
/*
|
|
500
|
+
Below the cut: something coarser covers this surface. A descent from the root never
|
|
501
|
+
arrives here -- it only descends through a step that projected above tau -- so this
|
|
502
|
+
is the branch a cut that was *kept* between frames lands in when the camera pulls
|
|
503
|
+
back, and stopping is what makes such a cut unable to coarsen.
|
|
504
|
+
|
|
505
|
+
parent_error and parent_sphere are the consuming step's, and every cluster of a group
|
|
506
|
+
shares that one step (FORMAT section 11.16), so this decision is group-uniform and
|
|
507
|
+
the group's first member can carry it alone. That is an election with nothing to
|
|
508
|
+
communicate: the other members reach the same answer and defer to the same lane.
|
|
509
|
+
*/
|
|
510
|
+
let group_row_here = vg_group_row(page_row, group);
|
|
511
|
+
|
|
512
|
+
let parent_count = vg_arena[group_row_here + ${VGEO_GROUP_ROW_OFFSET.PARENT_COUNT}u];
|
|
513
|
+
|
|
514
|
+
if ((params.flags & ${VGEO_CUT_FLAG_COARSEN}u) == 0u || member != 0u
|
|
515
|
+
|| parent_count == 0u) {
|
|
516
|
+
return vg_expand_none();
|
|
421
517
|
}
|
|
422
518
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
519
|
+
return VGExpand(
|
|
520
|
+
VG_EXPAND_PARENTS,
|
|
521
|
+
parent_count,
|
|
522
|
+
page_row,
|
|
523
|
+
vg_arena[group_row_here + ${VGEO_GROUP_ROW_OFFSET.PARENT_FIRST}u],
|
|
524
|
+
0u
|
|
427
525
|
);
|
|
526
|
+
}
|
|
428
527
|
|
|
429
|
-
|
|
528
|
+
let self_projected = vg_project(
|
|
529
|
+
vg_arena_f32(row + ${VGEO_CLUSTER_ROW_OFFSET.SELF_ERROR}u),
|
|
530
|
+
vg_sphere_at(row + ${VGEO_CLUSTER_ROW_OFFSET.SELF_SPHERE}u),
|
|
531
|
+
eye, k
|
|
532
|
+
);
|
|
430
533
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
534
|
+
let child_page_index = vg_arena[row + ${VGEO_CLUSTER_ROW_OFFSET.CHILD_PAGE_INDEX}u];
|
|
535
|
+
|
|
536
|
+
if (self_projected <= params.tau || child_page_index == VG_NO_CHILD_PAGE) {
|
|
537
|
+
vg_draw(page_row, cluster, mesh_index);
|
|
538
|
+
return vg_expand_none();
|
|
539
|
+
}
|
|
435
540
|
|
|
436
|
-
|
|
541
|
+
let child_page_row = vg_lookup[lookup_base + child_page_index];
|
|
437
542
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
543
|
+
if (child_page_row == VG_PAGE_ROW_NONE) {
|
|
544
|
+
/*
|
|
545
|
+
The clamp. Drawing this cluster is not a fallback for a missing one — it is the correct
|
|
546
|
+
coarser surface over exactly the region the missing page would have refined, which is
|
|
547
|
+
what makes an arriving cut watertight at every moment rather than only at the end.
|
|
548
|
+
*/
|
|
549
|
+
vg_draw(page_row, cluster, mesh_index);
|
|
445
550
|
|
|
446
|
-
|
|
551
|
+
vg_stat(${VGEO_CUT_STAT.CLAMPED}u, 1u);
|
|
447
552
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
553
|
+
vg_want(
|
|
554
|
+
page_row,
|
|
555
|
+
vg_arena[row + ${VGEO_CLUSTER_ROW_OFFSET.CHILD_REF}u],
|
|
556
|
+
child_page_index,
|
|
557
|
+
lookup_base,
|
|
558
|
+
self_projected
|
|
559
|
+
);
|
|
455
560
|
|
|
456
|
-
|
|
457
|
-
|
|
561
|
+
return vg_expand_none();
|
|
562
|
+
}
|
|
458
563
|
|
|
459
|
-
|
|
564
|
+
let child_group = vg_arena[row + ${VGEO_CLUSTER_ROW_OFFSET.CHILD_GROUP}u];
|
|
460
565
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
566
|
+
if (!vg_group_selectable(child_page_row, child_group)) {
|
|
567
|
+
/*
|
|
568
|
+
The bytes are here and the group is still waiting on a page holding one of its *other*
|
|
569
|
+
parents, so there is nothing to fetch from this cluster. Another cluster in this same
|
|
570
|
+
cut is clamped directly against that page and wants it — monotonicity puts every
|
|
571
|
+
ancestor of a cluster projecting above tau above tau as well.
|
|
572
|
+
*/
|
|
573
|
+
vg_draw(page_row, cluster, mesh_index);
|
|
469
574
|
|
|
470
|
-
|
|
471
|
-
|
|
575
|
+
vg_stat(${VGEO_CUT_STAT.CLAMPED}u, 1u);
|
|
576
|
+
vg_stat(${VGEO_CUT_STAT.CLAMPED_ON_ACTIVATION}u, 1u);
|
|
472
577
|
|
|
473
|
-
|
|
474
|
-
|
|
578
|
+
return vg_expand_none();
|
|
579
|
+
}
|
|
475
580
|
|
|
476
|
-
|
|
477
|
-
|
|
581
|
+
if (!vg_claim_page(instance_base, child_page_row)) {
|
|
582
|
+
// another lane, or another parent of this page, got there first
|
|
583
|
+
return vg_expand_none();
|
|
584
|
+
}
|
|
478
585
|
|
|
479
|
-
|
|
480
|
-
|
|
586
|
+
return VGExpand(VG_EXPAND_PAGE, 1u, child_page_row, 0u, 0u);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* One entry of a page's parent rows: the page holding that parent, claimed and queued.
|
|
591
|
+
*
|
|
592
|
+
* The mirror of the descent's page crossing, and deliberately page-granular like it. The
|
|
593
|
+
* entry names a cluster as well, and this ignores it: a page reached from any direction is
|
|
594
|
+
* evaluated whole, the rule is a pure function of each row, and visiting a cluster the walk
|
|
595
|
+
* did not need costs only the test. What the cluster half of the entry is for is the
|
|
596
|
+
* refine-side election of CUT_PLAN_2026_09_14 section 3, which is section 7.6's work.
|
|
597
|
+
*
|
|
598
|
+
* A parent that is not resident stops the walk rather than clamping. There is nothing to
|
|
599
|
+
* clamp *to* -- the surface this lane is standing on is below the cut and is not drawn -- so
|
|
600
|
+
* the honest answer is that this instance cannot be served incrementally this frame, which
|
|
601
|
+
* is the fallback section 4 already defines and section 7.6 wires up.
|
|
602
|
+
*/
|
|
603
|
+
fn vg_visit_parent(instance_base: u32, page_row: u32, entry: u32) -> VGExpand {
|
|
604
|
+
if (entry >= vg_page_field(page_row, ${VGEO_PAGE_ROW_OFFSET.PARENT_ENTRY_COUNT}u)) {
|
|
605
|
+
return vg_expand_none();
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
let row = vg_parent_base(page_row) + entry * VG_PARENT_ROW_WORDS;
|
|
609
|
+
|
|
610
|
+
let parent_page_index = vg_arena[row + ${VGEO_PARENT_ROW_OFFSET.PAGE_INDEX}u];
|
|
611
|
+
|
|
612
|
+
if (parent_page_index == VG_NO_CHILD_PAGE) {
|
|
613
|
+
return vg_expand_none();
|
|
481
614
|
}
|
|
615
|
+
|
|
616
|
+
let lookup_base = instances[instance_base + ${VGEO_INSTANCE_OFFSET.LOOKUP_WORD_OFFSET}u];
|
|
617
|
+
|
|
618
|
+
let parent_page_row = vg_lookup[lookup_base + parent_page_index];
|
|
619
|
+
|
|
620
|
+
if (parent_page_row == VG_PAGE_ROW_NONE) {
|
|
621
|
+
return vg_expand_none();
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (!vg_claim_page(instance_base, parent_page_row)) {
|
|
625
|
+
// this page is already in this instance's cut, from above or from another parent
|
|
626
|
+
return vg_expand_none();
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return VGExpand(VG_EXPAND_PAGE, 1u, parent_page_row, 0u, 0u);
|
|
482
630
|
}
|
|
483
631
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
632
|
+
/**
|
|
633
|
+
* Execute whatever this lane is holding.
|
|
634
|
+
*/
|
|
635
|
+
fn vg_execute(item: VGItem, holding: bool) -> VGExpand {
|
|
636
|
+
if (!holding) {
|
|
637
|
+
return vg_expand_none();
|
|
638
|
+
}
|
|
487
639
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
640
|
+
vg_stat(${VGEO_CUT_STAT.ITEMS}u, 1u);
|
|
641
|
+
|
|
642
|
+
let instance_base = item.instance_slot * VG_INSTANCE_WORDS;
|
|
643
|
+
|
|
644
|
+
if ((item.visit & VG_QUEUE_GROUP_BIT) != 0u) {
|
|
645
|
+
return vg_visit_cluster(
|
|
646
|
+
instance_base, item.page_row, item.visit & ~VG_QUEUE_GROUP_BIT, item.aux
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if ((item.visit & VG_QUEUE_PARENT_BIT) != 0u) {
|
|
651
|
+
return vg_visit_parent(
|
|
652
|
+
instance_base, item.page_row, item.visit & ~VG_QUEUE_PARENT_BIT
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return vg_visit_node(instance_base, item.page_row, item.visit);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Child \`k\` of an expansion, as an item.
|
|
661
|
+
*
|
|
662
|
+
* Pure, and that is what lets the same rule serve both consumers: the lane that keeps a child calls
|
|
663
|
+
* it with values shuffled out of the producer's registers, and the lane that spills one calls it
|
|
664
|
+
* with its own.
|
|
665
|
+
*/
|
|
666
|
+
fn vg_child(instance_slot: u32, expand: VGExpand, k: u32) -> VGItem {
|
|
667
|
+
if (expand.kind == VG_EXPAND_NODES) {
|
|
668
|
+
return VGItem(instance_slot, expand.page_row, expand.first + k, 0u);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (expand.kind == VG_EXPAND_CLUSTERS) {
|
|
672
|
+
let at = vg_leaf_cluster(expand.page_row, expand.first, expand.span, k);
|
|
673
|
+
|
|
674
|
+
return VGItem(instance_slot, expand.page_row, at.x | VG_QUEUE_GROUP_BIT, at.y);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (expand.kind == VG_EXPAND_PARENTS) {
|
|
678
|
+
// entry first + k of the *producing* page's parent rows; the page it names is
|
|
679
|
+
// resolved and claimed when this item is executed, not here
|
|
680
|
+
return VGItem(
|
|
681
|
+
instance_slot, expand.page_row, (expand.first + k) | VG_QUEUE_PARENT_BIT, 0u
|
|
682
|
+
);
|
|
496
683
|
}
|
|
497
684
|
|
|
498
|
-
|
|
685
|
+
// VG_EXPAND_PAGE: the child page's node 0, which is how a descent crosses a page boundary
|
|
686
|
+
return VGItem(instance_slot, expand.page_row, 0u, 0u);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Which lane of the wave produced the item at \`index\` of its densely numbered production.
|
|
691
|
+
*
|
|
692
|
+
* The owner is the **largest** lane whose base is at or below the index — largest, because a lane
|
|
693
|
+
* that produced nothing repeats its neighbour's base, and a repeat sits at the start of the next
|
|
694
|
+
* producer's run rather than the end of the previous one. The bases are non-decreasing, so this is
|
|
695
|
+
* a binary search, and \`span\` starts at a builtin so the loop's trip count is uniform.
|
|
696
|
+
*/
|
|
697
|
+
fn vg_child_owner(first: u32, index: u32, lane_count: u32) -> u32 {
|
|
698
|
+
var owner = 0u;
|
|
699
|
+
var span = lane_count;
|
|
700
|
+
|
|
701
|
+
while (span > 1u) {
|
|
702
|
+
let step_half = span >> 1u;
|
|
703
|
+
let mid = owner + step_half;
|
|
704
|
+
|
|
705
|
+
// branchless: a non-uniform if around the next iteration's shuffle would not compile
|
|
706
|
+
owner = select(owner, mid, subgroupShuffle(first, mid) <= index);
|
|
707
|
+
|
|
708
|
+
span = span - step_half;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return owner;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
fn vg_queue_write(slot: u32, item: VGItem) {
|
|
715
|
+
if (slot >= params.queue_capacity) {
|
|
716
|
+
vg_stat(${VGEO_CUT_STAT.QUEUE_DROPPED}u, 1u);
|
|
499
717
|
return;
|
|
500
718
|
}
|
|
501
719
|
|
|
502
|
-
let base =
|
|
720
|
+
let base = slot * 4u;
|
|
503
721
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
722
|
+
queue_out.elements[base] = item.instance_slot;
|
|
723
|
+
queue_out.elements[base + 1u] = item.page_row;
|
|
724
|
+
queue_out.elements[base + 2u] = item.visit;
|
|
725
|
+
queue_out.elements[base + 3u] = item.aux;
|
|
726
|
+
}
|
|
508
727
|
|
|
509
|
-
|
|
728
|
+
@compute @workgroup_size(${VGEO_CUT_WORKGROUP_SIZE})
|
|
729
|
+
fn main(
|
|
730
|
+
@builtin(global_invocation_id) global_id: vec3<u32>,
|
|
731
|
+
@builtin(subgroup_size) lane_count: u32,
|
|
732
|
+
@builtin(subgroup_invocation_id) lane_index: u32,
|
|
733
|
+
) {
|
|
734
|
+
let slot = global_id.x;
|
|
735
|
+
|
|
736
|
+
/*
|
|
737
|
+
The queue's count against what the queue holds. \`shader_vgeo_cut_queue_prepare\` runs
|
|
738
|
+
immediately before every dispatch of this kernel and stores exactly this clamp back, so the
|
|
739
|
+
bound is already there — but it was the *read* below that carried it here and not the predicate
|
|
740
|
+
that decides whether a lane executes, and those two have to agree. A lane held past capacity
|
|
741
|
+
reads the clamped index, which is the last item of the queue: it would execute a duplicate of
|
|
742
|
+
an item another lane is already holding, and a cluster visited twice from two lanes is two draw
|
|
743
|
+
records for one surface. That is the one failure the claim bitmask and the own-group term are
|
|
744
|
+
both arranged to prevent, so the predicate does not get to be the looser of the two.
|
|
745
|
+
*/
|
|
746
|
+
let queue_count = min(queue_in.count, params.queue_capacity);
|
|
747
|
+
|
|
748
|
+
/*
|
|
749
|
+
One per dispatch that had work, which is one per spill generation rather than one per level of
|
|
750
|
+
the descent: the depth is inside this kernel. Reaching params.descent_steps with items still
|
|
751
|
+
held is what QUEUE_REMAINING reports, and nothing else can.
|
|
752
|
+
*/
|
|
753
|
+
if (slot == 0u && queue_count > 0u) {
|
|
754
|
+
vg_stat(${VGEO_CUT_STAT.WAVEFRONTS}u, 1u);
|
|
755
|
+
}
|
|
510
756
|
|
|
511
|
-
|
|
757
|
+
var holding = slot < queue_count;
|
|
512
758
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
759
|
+
let base = slot * 4u;
|
|
760
|
+
|
|
761
|
+
/*
|
|
762
|
+
Read unconditionally and mask the result. A branch here would be a non-uniform if, which is
|
|
763
|
+
harmless on its own — but every lane must reach every subgroup operation below, and a lane that
|
|
764
|
+
returned early is a lane the shuffles are not allowed to read.
|
|
765
|
+
*/
|
|
766
|
+
let in_bounds = min(base, params.queue_capacity * 4u - 4u);
|
|
767
|
+
|
|
768
|
+
var item = VGItem(
|
|
769
|
+
queue_in.elements[in_bounds],
|
|
770
|
+
queue_in.elements[in_bounds + 1u],
|
|
771
|
+
queue_in.elements[in_bounds + 2u],
|
|
772
|
+
queue_in.elements[in_bounds + 3u]
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
for (var step = 0u; step < params.descent_steps; step = step + 1u) {
|
|
776
|
+
let expand = vg_execute(item, holding);
|
|
777
|
+
|
|
778
|
+
let first = subgroupExclusiveAdd(expand.count);
|
|
779
|
+
let total = subgroupAdd(expand.count);
|
|
780
|
+
|
|
781
|
+
// ----- what this wave keeps: one child per lane, densely packed, whoever produced it -----
|
|
782
|
+
|
|
783
|
+
let owner = vg_child_owner(first, lane_index, lane_count);
|
|
784
|
+
|
|
785
|
+
let kept = vg_child(
|
|
786
|
+
subgroupShuffle(item.instance_slot, owner),
|
|
787
|
+
VGExpand(
|
|
788
|
+
subgroupShuffle(expand.kind, owner),
|
|
789
|
+
0u,
|
|
790
|
+
subgroupShuffle(expand.page_row, owner),
|
|
791
|
+
subgroupShuffle(expand.first, owner),
|
|
792
|
+
subgroupShuffle(expand.span, owner)
|
|
793
|
+
),
|
|
794
|
+
lane_index - subgroupShuffle(first, owner)
|
|
516
795
|
);
|
|
517
|
-
|
|
518
|
-
|
|
796
|
+
|
|
797
|
+
// ----- what it cannot hold: spilled, packed, under one reservation for the whole wave -----
|
|
798
|
+
|
|
799
|
+
let mine_first = max(first, lane_count);
|
|
800
|
+
let mine_last = max(first + expand.count, lane_count);
|
|
801
|
+
|
|
802
|
+
let spill_count = mine_last - mine_first;
|
|
803
|
+
|
|
804
|
+
let spill_offset = subgroupExclusiveAdd(spill_count);
|
|
805
|
+
let spill_total = subgroupAdd(spill_count);
|
|
806
|
+
|
|
807
|
+
/*
|
|
808
|
+
Elected *before* the branch, so the branch carries no subgroup operation of its own and is
|
|
809
|
+
free to be non-uniform — which lets a wave with nothing to spill skip the reservation
|
|
810
|
+
outright. An atomic on the queue head is a global round trip; a descent step that keeps
|
|
811
|
+
everything it produced is otherwise register work, and would spend more reserving zero
|
|
812
|
+
slots than on the step.
|
|
813
|
+
*/
|
|
814
|
+
let spill_elected = subgroupElect();
|
|
815
|
+
|
|
816
|
+
var reserved = 0u;
|
|
817
|
+
|
|
818
|
+
if (spill_elected && spill_total > 0u) {
|
|
819
|
+
reserved = atomicAdd(&queue_out.count, spill_total);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// after the branch closes, which Tint permits: it models reconvergence
|
|
823
|
+
let spill_base = subgroupBroadcastFirst(reserved) + spill_offset;
|
|
824
|
+
|
|
825
|
+
for (var i = 0u; i < spill_count; i = i + 1u) {
|
|
826
|
+
vg_queue_write(spill_base + i, vg_child(
|
|
827
|
+
item.instance_slot, expand, mine_first - first + i
|
|
828
|
+
));
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
item = kept;
|
|
832
|
+
holding = lane_index < total;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/*
|
|
836
|
+
Whatever is still held has not been executed, so it goes back to the queue rather than being
|
|
837
|
+
dropped. This is the descent handing its unfinished frontier to the next dispatch, and at
|
|
838
|
+
lane_count items per wave it is the common case rather than an overflow path.
|
|
839
|
+
*/
|
|
840
|
+
let tail_count = select(0u, 1u, holding);
|
|
841
|
+
|
|
842
|
+
let tail_offset = subgroupExclusiveAdd(tail_count);
|
|
843
|
+
let tail_total = subgroupAdd(tail_count);
|
|
844
|
+
|
|
845
|
+
let tail_elected = subgroupElect();
|
|
846
|
+
|
|
847
|
+
var tail_reserved = 0u;
|
|
848
|
+
|
|
849
|
+
// as above: a wave holding nothing does not reserve
|
|
850
|
+
if (tail_elected && tail_total > 0u) {
|
|
851
|
+
tail_reserved = atomicAdd(&queue_out.count, tail_total);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
let tail_base = subgroupBroadcastFirst(tail_reserved) + tail_offset;
|
|
855
|
+
|
|
856
|
+
if (holding) {
|
|
857
|
+
vg_queue_write(tail_base, item);
|
|
519
858
|
}
|
|
520
859
|
}
|
|
521
860
|
`, [
|
|
522
861
|
chunk_vgeo_cut_access,
|
|
862
|
+
chunk_vgeo_cut_claim,
|
|
523
863
|
MESHLET_DEFINITION_STRUCT.declaration_chunk,
|
|
524
864
|
]);
|
|
525
865
|
|
|
866
|
+
/*
|
|
867
|
+
The descent is a subgroup cooperating with itself — every lane's item packed into every lane's
|
|
868
|
+
register at each step — so the device has to have that capability and the source has to say so.
|
|
869
|
+
*/
|
|
870
|
+
body.addExtension(WebGPUExtensionType.Subgroups);
|
|
871
|
+
|
|
526
872
|
export const shader_vgeo_cut_traverse = ComputeShader.from({
|
|
527
873
|
label: "VGeo cut traverse",
|
|
528
874
|
body,
|