@woosh/meep-engine 3.27.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/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/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/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 +27 -13
- package/src/shade/playground/vgeo_runtime/main.js +18 -5
- 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 +9 -8
- 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/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/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 +4 -2
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +10 -8
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +12 -11
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +1 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +1 -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} +9 -9
- 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_build.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +9 -11
- 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/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/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/vgeo_read_page.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.js +9 -4
- 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 +18 -5
- 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 +9 -1
- package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +21 -2
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +52 -2
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +97 -1
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +4 -4
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +10 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +59 -34
- 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 -88
- 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_reseed.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.js +7 -63
- 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 +13 -30
- 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 +6 -52
- 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/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/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/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/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/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/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/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/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 +78 -88
- 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 +21 -31
- package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
- package/src/shade/renderer/scene/GPUSceneContext.js +2 -0
- 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/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/VGeoResidencyOptions.d.ts +0 -49
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts.map +0 -1
- 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/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/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
|
@@ -277,16 +277,25 @@ change every frame, you are on the wrong path — see §8.
|
|
|
277
277
|
|
|
278
278
|
`ShadeMaterial` is the base: `transparency_mode`, `draw_mode` and `draw_side`.
|
|
279
279
|
|
|
280
|
-
**`draw_side`
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
280
|
+
**`draw_side` changes what is drawn.** `Front` (the default) draws the front face, `Back` the back
|
|
281
|
+
face, `Double` both. A face seen from behind is shaded with its normal flipped, so `Double` is a
|
|
282
|
+
working double-sided material and not a hint. glTF `doubleSided: true` arrives as `Double` and
|
|
283
|
+
stays that way; glTF cannot ask for `Back`, which comes from authoring.
|
|
284
|
+
|
|
285
|
+
**Only `Double` costs a pipeline of its own.** It culls nothing, and no amount of winding expresses
|
|
286
|
+
that. `Front` and `Back` differ by winding alone, and the vertex shader reverses winding per
|
|
287
|
+
instance, so the two share a rasterization bucket and draw in one pass — which matters because a
|
|
288
|
+
bucket is not a pipeline here but a whole chain: extract, expand to meshlets, select a
|
|
289
|
+
virtual-geometry cut, one render pass.
|
|
290
|
+
|
|
291
|
+
**That is also why a mirrored instance works.** Place a mesh under a negative scale, or under a
|
|
292
|
+
parent that reflects, and it draws right way out, with the handedness of its tangent frame flipped
|
|
293
|
+
to match — the reflection and the material's side are the same one bit, combined before a corner is
|
|
294
|
+
chosen. glTF makes this the rule rather than an extra: the determinant of a node's world transform
|
|
295
|
+
decides its winding, and a conformant renderer has to read it.
|
|
296
|
+
|
|
297
|
+
Shadow passes cull the other face — so a one-sided surface casts from its back face and does not
|
|
298
|
+
shadow-acne against its own lit side. `Double` has no opposite and stays unculled in both.
|
|
290
299
|
|
|
291
300
|
`StandardShadeMaterial` is the PBR one everything uses:
|
|
292
301
|
|
|
@@ -693,7 +693,7 @@ what they are looking at is the observer.
|
|
|
693
693
|
## 7. The inspector
|
|
694
694
|
|
|
695
695
|
`packages/gpu-inspector-tool/`. A static site: drop a `.sgpt` on it, or pass `?file=` for a
|
|
696
|
-
bookmarkable view — the affordance the `
|
|
696
|
+
bookmarkable view — the affordance the `.vgeo` playgrounds already establish in this codebase.
|
|
697
697
|
|
|
698
698
|
### 7.1 Technology
|
|
699
699
|
|
|
@@ -6,19 +6,26 @@ This page draws nothing itself: every triangle on screen is there because the co
|
|
|
6
6
|
the HZB meshlet filter, the material bucket, the indirect draw and the G-buffer took it from there
|
|
7
7
|
without knowing that virtual geometry exists.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
This page proves the engine seam and nothing about the DAG, because the DAG has its own tests and
|
|
10
|
+
its own oracle: `renderer/geometry/virtual/runtime/cut/__fixtures__/vgeo_select_cut.js`, a **CPU**
|
|
11
|
+
traversal of the same rule that every cut spec is held to. A `vgeo_viewer` page used to draw that
|
|
12
|
+
traversal on screen through Shade's direct geometry path, carrying its own renderer, its own camera
|
|
13
|
+
and its own streaming loop; it was retired rather than kept as a second runtime, and everything it
|
|
14
|
+
showed is shown here or on the scene page below.
|
|
15
|
+
|
|
16
|
+
It is also not [`../vgeo_scene/`](../vgeo_scene/README.md), which opens a whole converted set —
|
|
17
|
+
thousands of containers, tens of thousands of instances, one cut — and is where the claim about a
|
|
18
|
+
*scene* is measured. This page is one container framed on its own bounding sphere, which is what
|
|
19
|
+
makes it a viewer and what makes its readings about the seam rather than about breadth.
|
|
20
|
+
|
|
21
|
+
And it proves one thing no spec can, which is what the surface **looks like**. The retired viewer
|
|
22
|
+
drew with `cullMode: 'none'` and coloured its cut per cluster; this page draws through a
|
|
23
|
+
`Front`-sided standard material with a light on it. That is the difference that caught the sample
|
|
24
|
+
torus being wound inside-out — the near surface culled away, the far inner wall showing through the
|
|
25
|
+
hole where it should have been, and normals pointing into the tube so what survived was lit from
|
|
26
|
+
inside. Eighteen thousand specs and a second playground had drawn that container without a single
|
|
27
|
+
one of them being able to notice, which is the argument for this page and part of the argument
|
|
28
|
+
against the other one.
|
|
22
29
|
|
|
23
30
|
## How it is lit, and why that is not decoration
|
|
24
31
|
|
|
@@ -138,6 +145,13 @@ The panel's `RAM evicted`, `fetching` and `worker fetches` rows are new with it,
|
|
|
138
145
|
the reading to keep watching: it climbs only while the tier is full of pages the cut still draws
|
|
139
146
|
from, which is a budget saying no and not a tier scanning.
|
|
140
147
|
|
|
148
|
+
**`quarantined`** came here from the retired viewer, and it is the row that tells two coarse
|
|
149
|
+
surfaces apart. A page that fails its retries is quarantined with its reason and is never asked for
|
|
150
|
+
again; the clamped cut then keeps that region correct with a coarser ancestor, indefinitely. On
|
|
151
|
+
screen that is indistinguishable from a container that is merely still arriving, and `clamped`
|
|
152
|
+
alone cannot separate them — a count that will not fall while this is non-zero is a page that is
|
|
153
|
+
never coming.
|
|
154
|
+
|
|
141
155
|
Two things the tour still shows. The four long tasks left are `GPUMeshletManager` compactions —
|
|
142
156
|
a refused allocation with free space behind it rebuilds every batch, which is the one O(resident)
|
|
143
157
|
event the rebuild deferred. And under a vite dev server the first seconds after a container opens
|
|
@@ -28,15 +28,15 @@ import { place_primitive } from "../../renderer/scene/rows/place_primitive.js";
|
|
|
28
28
|
import { Scene } from "../../renderer/scene/Scene.js";
|
|
29
29
|
import { load_default_environment } from "../load_default_environment.js";
|
|
30
30
|
import { install_profile_hotkey } from "../profile_hotkey.js";
|
|
31
|
-
import { vgeo_sample_source } from "
|
|
31
|
+
import { vgeo_sample_source } from "./sample_asset.js";
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* A `.vgeo` container drawn **through the meshlet pipeline**, by a cut selected on the GPU.
|
|
35
35
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* nothing itself. It puts an ordinary mesh row in an ordinary `Scene` with an ordinary material,
|
|
36
|
+
* What proves the DAG, the format and the clamp is not a page:
|
|
37
|
+
* `renderer/geometry/virtual/runtime/cut/__fixtures__/vgeo_select_cut.js` is a CPU traversal of the
|
|
38
|
+
* same rule, and every cut spec is held to it. This page proves the other half — the engine seam —
|
|
39
|
+
* and it draws nothing itself. It puts an ordinary mesh row in an ordinary `Scene` with an ordinary material,
|
|
40
40
|
* and every triangle on screen arrives because the compute cut appended `MESHLET_DEFINITION_STRUCT`
|
|
41
41
|
* records into the same collection a static mesh's meshlets go into — through the HZB meshlet
|
|
42
42
|
* filter, the material bucket, the indirect draw and the G-buffer, none of which know that virtual
|
|
@@ -617,6 +617,7 @@ class VGeoPlayground {
|
|
|
617
617
|
bytes: this.residency.ram_resident_bytes,
|
|
618
618
|
evicted: this.residency.evicted_ram_count,
|
|
619
619
|
refused: this.residency.refused_fetch_count,
|
|
620
|
+
quarantined: this.residency.quarantined_page_count,
|
|
620
621
|
total_pages: reader?.header.total_pages ?? 0,
|
|
621
622
|
},
|
|
622
623
|
fetches_in_flight: this.residency.fetches_in_flight,
|
|
@@ -917,6 +918,18 @@ async function start() {
|
|
|
917
918
|
`<span class="${state.vram.refused > 0 ? 'bad' : 'good'}">`
|
|
918
919
|
+ `${state.vram.refused.toLocaleString()}</span>`,
|
|
919
920
|
],
|
|
921
|
+
/*
|
|
922
|
+
A page that failed its retries is quarantined with its reason and is never asked for
|
|
923
|
+
again, and the clamped cut keeps its surface correct with a coarser ancestor
|
|
924
|
+
indefinitely. That is the difference between a container that is coarse because the
|
|
925
|
+
camera just moved and one that is coarse because a page will never arrive — which look
|
|
926
|
+
identical on screen, and which `clamped` alone cannot tell apart.
|
|
927
|
+
*/
|
|
928
|
+
[
|
|
929
|
+
'quarantined',
|
|
930
|
+
`<span class="${state.ram.quarantined > 0 ? 'bad' : 'good'}">`
|
|
931
|
+
+ `${state.ram.quarantined}</span>`,
|
|
932
|
+
],
|
|
920
933
|
['dropped', `<span class="${dropped > 0 ? 'bad' : 'good'}">${dropped}</span>`],
|
|
921
934
|
].map(([name, value]) => `<div class="row"><span>${name}</span><span>${value}</span></div>`)
|
|
922
935
|
.join('');
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a sample container and hand back a source over it.
|
|
3
|
+
*
|
|
4
|
+
* The page size is deliberately far below the 256 KB default: a sample that came out as one page
|
|
5
|
+
* would be a container with nothing to stream, and the point of opening this one is to watch it
|
|
6
|
+
* arrive.
|
|
7
|
+
*
|
|
8
|
+
* @param {number} [major]
|
|
9
|
+
* @param {number} [minor]
|
|
10
|
+
* @param {number} [page_size] target decoded bytes per page
|
|
11
|
+
* @returns {{source: VGeoByteSource, build_ms: number}}
|
|
12
|
+
*/
|
|
13
|
+
export function vgeo_sample_source(major?: number, minor?: number, page_size?: number): {
|
|
14
|
+
source: VGeoByteSource;
|
|
15
|
+
build_ms: number;
|
|
16
|
+
};
|
|
17
|
+
import { VGeoByteSource } from "../../renderer/geometry/virtual/format/read/VGeoByteSource.js";
|
|
18
|
+
//# sourceMappingURL=sample_asset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sample_asset.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/vgeo_runtime/sample_asset.js"],"names":[],"mappings":"AA2GA;;;;;;;;;;;GAWG;AACH,2CALW,MAAM,UACN,MAAM,cACN,MAAM,GACJ;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,CAiBtD;+BA3HM,+DAA+D"}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "../../renderer/geometry/virtual/format/read/VGeoByteSource.js";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* A `.vgeo` built in the page, so
|
|
15
|
+
* A `.vgeo` built in the page, so there is something to open without a file.
|
|
16
16
|
*
|
|
17
17
|
* The builder is environment-agnostic by design (`VIRTUAL_GEOMETRY_DESIGN.md` §8) — typed arrays and
|
|
18
18
|
* topology, no file or process API anywhere under it — so the same call the node CLI makes runs here.
|
|
@@ -31,8 +31,8 @@ import {
|
|
|
31
31
|
*
|
|
32
32
|
* Curvature is what makes it a useful sample rather than a shape: a flat face simplifies at zero
|
|
33
33
|
* quadric cost, so a box would produce a DAG whose every step records no error and whose every
|
|
34
|
-
* threshold selects the same cut. A torus has an error at every level, which is the thing the
|
|
35
|
-
* is
|
|
34
|
+
* threshold selects the same cut. A torus has an error at every level, which is the thing the cut
|
|
35
|
+
* is selecting against.
|
|
36
36
|
*
|
|
37
37
|
* @param {number} major segments the long way round
|
|
38
38
|
* @param {number} minor segments around the tube
|
|
@@ -61,12 +61,13 @@ function torus(major, minor) {
|
|
|
61
61
|
`CullPolicy.Standard` means by a front face and what `vgeo_compute_vertex_normals` takes the
|
|
62
62
|
sign of its normals from.
|
|
63
63
|
|
|
64
|
-
It was the other way round, and
|
|
65
|
-
|
|
66
|
-
the shading could report it. The moment the same sample went through the engine's
|
|
67
|
-
path —
|
|
64
|
+
It was the other way round, and the CPU viewer this sample was written for could not see it:
|
|
65
|
+
that page drew its cut with `cullMode: 'none'` and coloured it per cluster, so neither the
|
|
66
|
+
culling nor the shading could report it. The moment the same sample went through the engine's
|
|
67
|
+
material path — this page, where a `Front`-sided material culls back faces — the torus
|
|
68
68
|
rendered as its own far inner wall with the near surface missing, and the normals pointed into
|
|
69
|
-
the tube so what survived was lit from inside.
|
|
69
|
+
the tube so what survived was lit from inside. The viewer has since been retired; this is the
|
|
70
|
+
only page that builds it now, and it is the one that could have caught it.
|
|
70
71
|
|
|
71
72
|
The winding of a sample is not a detail of the sample. Everything downstream of the builder
|
|
72
73
|
carries it: the cluster's triangles are stored in this order, the DAG's simplification
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# `vgeo_scene` — a whole converted scene, one cut
|
|
2
|
+
|
|
3
|
+
Thousands of `.vgeo` containers, tens of thousands of instances, **one** compute cut, drawn through
|
|
4
|
+
the ordinary meshlet pipeline.
|
|
5
|
+
|
|
6
|
+
[`../vgeo_runtime/`](../vgeo_runtime/README.md) is the page that proves the engine seam, and it does
|
|
7
|
+
it with one container framed on its own bounding sphere. That makes it a *viewer*, and by
|
|
8
|
+
construction it cannot show the other half of what
|
|
9
|
+
[`VIRTUAL_GEOMETRY_DESIGN.md`](../../renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md) §1
|
|
10
|
+
claims — that rendering cost is a function of the threshold and the resolution rather than of the
|
|
11
|
+
source, **across every asset in view at once**, with instances sharing pages and one residency
|
|
12
|
+
budget arbitrating between all of them. With one reader there is nothing to arbitrate.
|
|
13
|
+
|
|
14
|
+
So everything here that the runtime page does not do is about breadth:
|
|
15
|
+
|
|
16
|
+
- **One residency, N containers.** Every asset registers with the same `VGeoResidency`, whose two
|
|
17
|
+
budgets are scene-wide by construction: the RAM tier's bytes and the VRAM tier's slots are counted
|
|
18
|
+
across every reader, and a page evicted from one asset is room for another's.
|
|
19
|
+
- **One cut, N instances.** `shader_vgeo_cut_collect` runs over the visible mesh rows and emits one
|
|
20
|
+
instance record per virtual one; the descent is seeded with one item per instance and every cull
|
|
21
|
+
and error test downstream runs in that instance's own object space. Seventeen thousand instances
|
|
22
|
+
are seventeen thousand seeds of **one** wavefront, not seventeen thousand cuts.
|
|
23
|
+
- **Instances share the asset.** A geometry placed by 381 nodes is one reader, one geometry row, one
|
|
24
|
+
page lookup and one set of resident pages — which is `VirtualGeometry.equals`, and most of why
|
|
25
|
+
this is affordable at all.
|
|
26
|
+
|
|
27
|
+
## Running it
|
|
28
|
+
|
|
29
|
+
Three things: the dev server in front of the page, an asset server in front of the containers, and
|
|
30
|
+
a scene manifest telling the page which is which.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
npm run dev --workspace @woosh/meep-engine
|
|
34
|
+
|
|
35
|
+
node tools/geometry/virtual/vgeo_scene.js <manifest.vgeo.json> --camera "..." --sun "..."
|
|
36
|
+
node tools/geometry/virtual/vgeo_serve.js <container directory>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then open `/src/shade/playground/vgeo_scene/index.html`. With no parameters it asks the asset server
|
|
40
|
+
what it is holding and opens the one scene that is there; `?scene=<url>` overrides.
|
|
41
|
+
|
|
42
|
+
**The asset server is not optional and vite cannot replace it.** A `.vgeo` is opened out of its head
|
|
43
|
+
and streamed by `Range`, and `vgeo_fetch_byte_source` refuses anything but a `206` naming the range
|
|
44
|
+
it asked for — a `200` is a server ignoring the request and answering the head of the file where the
|
|
45
|
+
reader asked for the middle of a frame, silently and with plausible numbers. Vite also refuses
|
|
46
|
+
`/@fs/` outside the workspace, and a converted scene is tens of gigabytes that has no business
|
|
47
|
+
inside one. `vgeo_serve.js` is ranges, CORS and nothing else; the header that is easy to miss is
|
|
48
|
+
`Access-Control-Expose-Headers`, because a cross-origin response's `Content-Range` is invisible to
|
|
49
|
+
the page without it.
|
|
50
|
+
|
|
51
|
+
## What a scene manifest is, and what it is not
|
|
52
|
+
|
|
53
|
+
`gltf_to_vgeo.js` already writes everything the page needs — a glTF primitive is one container and
|
|
54
|
+
the nodes that placed it are its instances — but its manifest is a *build report*: every level of
|
|
55
|
+
every DAG, every cluster and group count, the weld distance each geometry settled on. At this scale
|
|
56
|
+
that is twenty-odd megabytes of numbers a renderer has no use for. `vgeo_scene.js` is the projection
|
|
57
|
+
to four fields and a list of matrices: **2.2 MiB for Zorah's 2,966 containers and 17,424 instances**,
|
|
58
|
+
and nothing in it is discovered — it is all in the build manifest or in the containers' own headers.
|
|
59
|
+
|
|
60
|
+
The headers are **read rather than trusted**. A manifest is written by the builder that produced the
|
|
61
|
+
files and a directory is whatever is on disk now, so the probe is what catches a half-finished
|
|
62
|
+
conversion and a container rebuilt at another page size. It costs one head read each and 1.8 s for
|
|
63
|
+
all 2,966.
|
|
64
|
+
|
|
65
|
+
Two fields are neither: `--camera` and `--sun`.
|
|
66
|
+
|
|
67
|
+
- **A bounding box is a bad place to put a camera.** Zorah's instances span 107 km, almost all of it
|
|
68
|
+
terrain, and the town the scene is *about* is forty metres across at the origin — a viewer framed
|
|
69
|
+
on the box opens a hundred kilometres up looking at a speck. The pose belongs to the source, and
|
|
70
|
+
Zorah's own `.cfg` carries one, so it is passed in rather than guessed at either end.
|
|
71
|
+
- **A converted container carries geometry and nothing else.** Design §7: VG is geometry only, so a
|
|
72
|
+
converted set arrives with normals, no material authoring and no lights. The sun is most of what
|
|
73
|
+
there is to shade it with, and the engine's default direction over a scene authored around a
|
|
74
|
+
different one puts every façade a viewer stands in front of into ambient.
|
|
75
|
+
|
|
76
|
+
## What it measures
|
|
77
|
+
|
|
78
|
+
Zorah, 2,966 containers and 17,424 instances of them, **46.71 GiB** on the server across 947,664
|
|
79
|
+
pages, 1.36 billion source triangles and **10.75 billion instanced** ones. Measured on an
|
|
80
|
+
nvidia/lovelace adapter at a render target of 1578×746, from the scene's authored viewpoint, with a
|
|
81
|
+
**fresh page load per row** — residency is a high-water mark, so a page swept downwards through the
|
|
82
|
+
thresholds reports at every step what the busiest setting it has already been at asked for, and a
|
|
83
|
+
row of this table is only about τ if the page has been at nothing else.
|
|
84
|
+
|
|
85
|
+
| τ (px) | drawn clusters | pages visited | clusters tested | resident pages | bytes read | frame |
|
|
86
|
+
|---:|---:|---:|---:|---:|---:|---:|
|
|
87
|
+
| 0.25 | 387,875 | 79,574 | 1,232,352 | 12,288 *(full)* | 452 MB | 7.3 ms |
|
|
88
|
+
| 0.5 | 182,887 | 51,035 | 617,540 | 9,365 | 242 MB | 5.0 ms |
|
|
89
|
+
| 1 | 84,481 | 31,842 | 279,088 | 6,387 | 128 MB | 4.7 ms |
|
|
90
|
+
| 2 | 45,302 | 22,356 | 139,290 | 4,939 | 76 MB | 4.8 ms |
|
|
91
|
+
| 4 | 28,874 | 17,483 | 79,651 | 4,121 | 52 MB | 5.2 ms |
|
|
92
|
+
| 8 | 21,334 | 15,235 | 56,561 | 3,704 | 40 MB | 4.8 ms |
|
|
93
|
+
| 16 | 18,137 | 14,289 | 45,717 | 3,489 | 35 MB | 6.0 ms |
|
|
94
|
+
|
|
95
|
+
Monotone in τ, and a *fraction* of the asset at every setting: at τ=1 the scene is on screen out of
|
|
96
|
+
**0.67% of its pages** and 128 MB of 46.71 GiB, and at τ=16 out of 0.37% and 35 MB. `clamped` is
|
|
97
|
+
zero at every row but the first, which is the streaming having finished rather than a budget having
|
|
98
|
+
given up — the distinction the `refused` counter beside it exists to make.
|
|
99
|
+
|
|
100
|
+
**τ=0.25 is the budget wall, and it is in the table on purpose.** At the slider's minimum the cut
|
|
101
|
+
wants more than 12,288 page slots, `refused` climbs to 1.1 million, `clamped` stops falling to zero,
|
|
102
|
+
and the cut is then being selected by a **budget** rather than by a threshold — which is the one
|
|
103
|
+
thing this page is not meant to be showing you. It is one slider notch away at all times, and
|
|
104
|
+
`refused` going red is how you know that is what you are looking at. `?pages=` raises the tier.
|
|
105
|
+
|
|
106
|
+
A tour of five viewpoints at τ=1 — a hundred metres up, three hundred out, a nose against one
|
|
107
|
+
façade, the whole 107 km box, and back to the authored pose — held 4.5 to 5.8 ms a frame with
|
|
108
|
+
`clamped`, `refused` and every drop counter at zero throughout, and residency settling at 7,948 of
|
|
109
|
+
12,288 pages without a single eviction.
|
|
110
|
+
|
|
111
|
+
## Opening it, and what the first five seconds are
|
|
112
|
+
|
|
113
|
+
Measured cold, three times: **2,966 containers open in about four seconds**, and the whole scene is
|
|
114
|
+
drawing about a second and a half later.
|
|
115
|
+
|
|
116
|
+
The opens are one range request each. §10's always-resident set — header, level table, root frame —
|
|
117
|
+
is 2,560 bytes at the median across this set and 6,528 at the very worst, so the page asks for an
|
|
118
|
+
**8 KB head prefix** rather than the reader's default of 64 KB: every container in the set opens in
|
|
119
|
+
exactly one round trip and the cold read is 7.4 MB of heads instead of 190 MB of over-fetch. The
|
|
120
|
+
byte length comes from the manifest rather than from a `HEAD`, which is 2,966 round trips not spent
|
|
121
|
+
learning something the page was already holding.
|
|
122
|
+
|
|
123
|
+
**About four and a half seconds of that is not this page.** The first frame that draws geometry pays
|
|
124
|
+
the engine's pipeline warm-up, and `vgeo_runtime` — driven the same way, with a thirteen-page torus
|
|
125
|
+
built in-page — sits on exactly the same stall before *its* first frame. It is a fixed cost of a
|
|
126
|
+
cold device under a dev server, it is not a function of the scene, and a bundled build does not pay
|
|
127
|
+
all of it. Containers keep opening underneath it.
|
|
128
|
+
|
|
129
|
+
Containers are **placed as they land** rather than at the end, which costs nothing: a geometry that
|
|
130
|
+
is not registered yet is one the cut does not collect, and both residency tiers recompute their
|
|
131
|
+
budgets per reading rather than at registration, so a scene that grows under the camera is the case
|
|
132
|
+
they were designed for. A container that refuses leaves every other asset exactly as it was and is
|
|
133
|
+
counted — 2,965 of 2,966 is a scene, and the count is what says there is a hole in it.
|
|
134
|
+
|
|
135
|
+
## The four capacities a scene reaches first
|
|
136
|
+
|
|
137
|
+
These are the page's real finding, because every one of them is an engine default that a
|
|
138
|
+
single-asset page never touches, and two of them degrade *silently*.
|
|
139
|
+
|
|
140
|
+
- **`queue_capacity`, and this is the one that hurt.** At τ=0.5 with 17,424 instances the descent
|
|
141
|
+
pushed past the engine's 65,536 items in flight and `VGEO_CUT_STAT.QUEUE_DROPPED` came back at
|
|
142
|
+
**32,676** — a third of the frontier thrown away. It does not surface as an error or as a visible
|
|
143
|
+
hole; it surfaces as `drawn` wandering over ten thousand clusters between one frame and the next,
|
|
144
|
+
as different subtrees lose the race. At `1 << 18` the same view drops nothing and `drawn` is
|
|
145
|
+
stable, at a cost of 4 MiB a site against 1 MiB.
|
|
146
|
+
- **`lookup_words`** is one word per page of every registered container out of one arena, and the
|
|
147
|
+
default of 65,536 holds a fourteenth of Zorah's 947,664. It is allocated out of an
|
|
148
|
+
`OffsetAllocator` whose bins round *down* — it can refuse an allocation that fits — so this is
|
|
149
|
+
sized to the next power of two above 1.5× the total rather than to the total.
|
|
150
|
+
- **`geometry_capacity`** is one row per container against a default of 256.
|
|
151
|
+
- **`claim_word_capacity`** is one bit per (collected instance, page ordinal of its geometry), and
|
|
152
|
+
the engine derives it from a hint of a thousand instances times the whole tier. At this scale that
|
|
153
|
+
is both too small and too large, and the real bound is knowable exactly: ordinals come out of the
|
|
154
|
+
tier, so `Σ instances × ordinals` is maximised by giving every slot to the most-instanced asset,
|
|
155
|
+
which makes it `max instances of any one geometry × page capacity`. For Zorah that is 381 × the
|
|
156
|
+
tier — a fifth of what the default derivation asks for — doubled here, because an ordinal capacity
|
|
157
|
+
is a high-water mark and a scene that has churned can hold more of them than the tier holds pages.
|
|
158
|
+
|
|
159
|
+
`RasterizationCollectionType.Meshes` is a fifth, and it self-corrects: it grows from what the frame
|
|
160
|
+
reports, so priming it to the instance count only buys the first seconds. But the first seconds are
|
|
161
|
+
when the scene is being placed, and against a default of 1e4 the other 7,424 instances would be
|
|
162
|
+
counted as `instances_dropped` while the collection caught up.
|
|
163
|
+
|
|
164
|
+
## Two things it approximates, and says so
|
|
165
|
+
|
|
166
|
+
- **Shear.** A transform row carries a TRS and the GPU composes the world from it, so an instance
|
|
167
|
+
whose matrix has shear in it cannot be represented exactly and is placed at the closest
|
|
168
|
+
rigid-plus-scale fit. 391 of Zorah's 17,424 have any and the worst is about eleven degrees off
|
|
169
|
+
orthogonal; the other 17,033 are exact, including the 2,722 reflected ones — `m4_decompose_array`
|
|
170
|
+
folds a negative determinant into a scale axis, so a reflection survives. This is a property of
|
|
171
|
+
the engine's transform model rather than of this page. The reflections surviving is what the next
|
|
172
|
+
section is about.
|
|
173
|
+
- **One material for the whole scene.** Not a simplification being made here: a `.vgeo` carries
|
|
174
|
+
geometry only, so the source's 2,714 materials did not survive a conversion that never asked them
|
|
175
|
+
to. Tinting each container would read better, would be a lie about where the colour came from, and
|
|
176
|
+
would put 2,714 material buckets in front of the draw path to say it with.
|
|
177
|
+
|
|
178
|
+
## Reflected instances — the bug this page was built to show, and where it went
|
|
179
|
+
|
|
180
|
+
2,722 of Zorah's 17,424 instances have a negative-determinant transform: the courtyard is
|
|
181
|
+
bilaterally symmetric and the left half is the mirror of the right. Until 2026-09-15 every one of
|
|
182
|
+
them rendered with its near surface culled and its far surface showing through, because:
|
|
183
|
+
|
|
184
|
+
1. A reflection reverses the orientation of every triangle it transforms, so a reflected instance
|
|
185
|
+
presents the **opposite screen-space winding** from the same geometry under a rotation.
|
|
186
|
+
2. Front-face determination is fixed-function and reads that winding and nothing else.
|
|
187
|
+
3. `cullMode` is **pipeline state chosen per material bucket**, and the bucket key was three fields
|
|
188
|
+
of a *material* — transparency, topology and side — with `construct_primitive_state` turning the
|
|
189
|
+
third into a cull mode. Nothing from there down to the rasterizer saw a transform.
|
|
190
|
+
|
|
191
|
+
The sign was never lost — `m4_decompose_array` preserves it and the row the GPU composes reflects
|
|
192
|
+
exactly as the source did — it was simply never consulted.
|
|
193
|
+
|
|
194
|
+
**It is consulted now, and not through the bucket key.** A node's row carries
|
|
195
|
+
`NODE3D_GLOBAL_FLAG_MIRRORED`, derived from its composed world matrix by whoever publishes it, and
|
|
196
|
+
every vertex shader over a meshlet emits that instance's triangle corners in the opposite order —
|
|
197
|
+
`triangle_list_reverse_winding`. The draw is non-indexed and the vertex shader resolves its own
|
|
198
|
+
index, so which corner lands where is already the shader's choice; reversing it costs a `select`,
|
|
199
|
+
no second pipeline, and no fourth field in the bucket key. It is what Nanite does for the same
|
|
200
|
+
reason (`FInstanceSceneData::DeterminantSign`, read in `HWRasterizeVS`), and what the glTF spec
|
|
201
|
+
requires of a conformant renderer. The tangent frame's handedness flips with it.
|
|
202
|
+
|
|
203
|
+
The parameters remain, and now read as diagnostics rather than as fixes:
|
|
204
|
+
|
|
205
|
+
- `?mirrored=tint` colours reflected instances red — in this view the **whole left arcade** comes up
|
|
206
|
+
red while the right stays grey. Nothing about which of them is *drawn* changes.
|
|
207
|
+
- `?mirrored=flip` draws them `Back`-sided. This used to be the page's own fix; `Back` is now drawn
|
|
208
|
+
by reversing winding too, so on a reflected instance the two cancel and it reproduces the old bug
|
|
209
|
+
on demand.
|
|
210
|
+
- `?mirrored=double` draws both sides of them — the fix people reach for first, and still the wrong
|
|
211
|
+
one: it doubles the work and gets the shading wrong.
|
|
212
|
+
- `?side=front|back|double` sets the whole scene's side, for the cruder question of whether a
|
|
213
|
+
surface is missing or merely culled.
|
|
214
|
+
|
|
215
|
+
The bug was hard to recognise because `fragment_gbuffer` flips normal, tangent and face normal when
|
|
216
|
+
`dot(world_face_normal, view_direction) < 0` — a test about the *view*, not about the winding — so
|
|
217
|
+
the far wall you were left looking at was correctly lit, and it read as bad geometry rather than as
|
|
218
|
+
a culling problem. Nothing about any of it was virtual-geometry-specific: it reached every mesh
|
|
219
|
+
placed under a mirroring transform, by any loader or prefab.
|
|
220
|
+
|
|
221
|
+
## Two different things are called flicker here, and they have different causes
|
|
222
|
+
|
|
223
|
+
**A dropout is not a shimmer and they do not measure alike.** A shimmer moves a tenth of a per cent
|
|
224
|
+
of the pixels and comes from the TAA jitter under sub-pixel geometry; a frame missing a large part
|
|
225
|
+
of what the frames either side of it drew moves tens of per cent and comes from the descent running
|
|
226
|
+
out of wavefronts. One section each, below. Measuring the first when asked about the second cost a
|
|
227
|
+
whole pass of this investigation, and the tell that separates them is the amplified frame
|
|
228
|
+
difference: an **edge map** is the shimmer, **solid blobs** are the dropout.
|
|
229
|
+
|
|
230
|
+
## The shimmer is the TAA jitter, and the geometry is finer than a pixel
|
|
231
|
+
|
|
232
|
+
There is a visible shimmer over the ornament, and it is **exactly 16 frames long**.
|
|
233
|
+
|
|
234
|
+
Measured over 600 consecutive frame pairs at 780×368 samples (~2×2 screen pixels each), camera
|
|
235
|
+
static, everything settled:
|
|
236
|
+
|
|
237
|
+
| | pixels changing >8/255 per frame | >24/255 | worst single step | period |
|
|
238
|
+
|---|---:|---:|---:|---:|
|
|
239
|
+
| as shipped | 350 (0.12%) | 17 | 57/255 | 16 (r=0.97) |
|
|
240
|
+
| τ=16 | 182 (0.06%) | 13 | 69/255 | 16 (r=0.95) |
|
|
241
|
+
| `?mirrored=flip` | 314 (0.11%) | 21 | 56/255 | 16 (r=0.96) |
|
|
242
|
+
| TAA off | **79,391 (27.66%)** | 33,841 | 161/255 | 16 (r=0.97) |
|
|
243
|
+
| jitter pinned | 89 (0.03%) | 3 | 88/255 | 16 (r=0.96) |
|
|
244
|
+
|
|
245
|
+
Sixteen is `TAA.#jitter_sequence_size`, and the attribution follows from the last two rows. **Pin the
|
|
246
|
+
jitter and fifteen frames out of every sixteen go pixel-static** — 18 to 123 changed samples out of
|
|
247
|
+
287,040, which is the noise floor — against 240 to 470 *every* frame as shipped. The flicker is the
|
|
248
|
+
Halton jitter cycling the projection under geometry whose detail is finer than a pixel, with TAA
|
|
249
|
+
resolving most but not all of it.
|
|
250
|
+
|
|
251
|
+
An amplified `|frame(t) − frame(t−1)|` over eighteen consecutive pairs says the same thing in one
|
|
252
|
+
picture: it is an **edge map**. Every silhouette and every piece of filigree sparkles; the flat
|
|
253
|
+
courtyard floor is dead black. LOD popping would light up in cluster-shaped patches and does not.
|
|
254
|
+
|
|
255
|
+
Three things fall out of it that are worth writing down:
|
|
256
|
+
|
|
257
|
+
- **`feature_taa_enabled = false` does not turn the jitter off.** `Renderer` writes
|
|
258
|
+
`view_ctx.camera.setViewportOffset` from the jitter sequence unconditionally, and only the
|
|
259
|
+
*resolve* is behind the flag. So TAA off is not "no temporal AA", it is "jitter with nothing to
|
|
260
|
+
resolve it": 27.66% of pixels move every frame, which is forty times worse than shipping it on and
|
|
261
|
+
worse than never jittering at all. Anyone A/B-ing TAA on this content is not measuring what they
|
|
262
|
+
think.
|
|
263
|
+
- **The jitter does move the cut, slightly, and it is not where the flicker comes from.** `drawn`
|
|
264
|
+
and `pages visited` are weakly 16-periodic (r=0.34 and r=0.40) because the cut's frustum planes
|
|
265
|
+
come out of the *jittered* view-projection while `projection_k` — the error metric — does not. So
|
|
266
|
+
what moves is which clusters are inside the frustum, by up to 117 of 84,455 (0.14%), confined to
|
|
267
|
+
the screen border. `vgeo_runtime`'s README says the jitter does not move the cut and its τ table is
|
|
268
|
+
identical either way; at this scale that is very nearly true rather than exactly true, and the
|
|
269
|
+
difference is not visible in the diff image.
|
|
270
|
+
- **`GPUViewContext.setJitter` and `setJitterDelta` write the same two fields.** Only the first is
|
|
271
|
+
called, so nothing is broken today; a caller that used the second would silently overwrite the
|
|
272
|
+
jitter with its delta.
|
|
273
|
+
|
|
274
|
+
None of this is the scene's fault and τ=16 halving the count is the proof: less sub-pixel detail,
|
|
275
|
+
less shimmer, same period. It is what a temporal filter does on content authored at a density no
|
|
276
|
+
rasterizer resolves, which is the content virtual geometry exists to put on screen — so it is worth
|
|
277
|
+
knowing that the page's picture has a floor set by TAA rather than by the cut.
|
|
278
|
+
|
|
279
|
+
## The dropout: the descent's wavefront bound, and how the engine finds it
|
|
280
|
+
|
|
281
|
+
**`max_iterations` starts at 8, and at a large viewport that is the binding bound rather than the
|
|
282
|
+
safety one.** The descent then stops with items still queued, and a queued item is a **subtree that
|
|
283
|
+
was never expanded** — so what it would have drawn is not on screen that frame. It is the one
|
|
284
|
+
degradation of this cut that costs a *hole* rather than a coarser surface, which is why it reads as
|
|
285
|
+
geometry disappearing rather than as anything softening.
|
|
286
|
+
|
|
287
|
+
Reported from a live session at **3441×1440** with `dropped: queue 82` in the panel and `wavefronts`
|
|
288
|
+
jittering between 8 and 13. Reproduced at that pose and viewport, 900 frames, camera static, with
|
|
289
|
+
the bound pinned:
|
|
290
|
+
|
|
291
|
+
| pinned `max_iterations` | `queue_remaining` non-zero | worst | `drawn` swing |
|
|
292
|
+
|---|---:|---:|---:|
|
|
293
|
+
| **8** | **750 of 900 frames** | 13,467 items | **11,857 clusters** |
|
|
294
|
+
| 16 / 32 / 64 | 0 | 0 | 81 |
|
|
295
|
+
|
|
296
|
+
`drawn` going from a swing of 11,857 clusters to **81** is the flicker, in one number. An amplified
|
|
297
|
+
`|frame(t) − frame(t−1)|` says the same thing in a picture: at 8 it is **solid white blobs** — whole
|
|
298
|
+
pieces of carved ornament blinking on and off — and above it those are gone, leaving only the thin
|
|
299
|
+
edge shimmer of the section above. The two phenomena look nothing alike once they are separated.
|
|
300
|
+
|
|
301
|
+
**It is a resolution story, and that is why it took so long to find.** The cut is a function of the
|
|
302
|
+
render target, so the same viewpoint at 1578×746 — a quarter of the pixels — drains inside 8
|
|
303
|
+
wavefronts and never drops an item. Every negative result in this file's earlier drafts was measured
|
|
304
|
+
at that size. A quarter-sized window is not a smaller version of this test.
|
|
305
|
+
|
|
306
|
+
### The bound raises itself
|
|
307
|
+
|
|
308
|
+
Nothing here pins it. `GPUVirtualGeometryRuntime.note_incomplete_descent` takes the one reading that
|
|
309
|
+
says the descent was truncated and **doubles the bound, permanently**, which is the same shape the
|
|
310
|
+
rasterizer already uses for meshlet counts: a quantity that cannot be known before the work runs,
|
|
311
|
+
learned from what the work reported, kept as a high-water mark.
|
|
312
|
+
|
|
313
|
+
It has to be learned, because how many wavefronts a cut needs is a function of the frontier's
|
|
314
|
+
*breadth* — the scene, the viewpoint and the render target together — and neither the kernel before
|
|
315
|
+
it runs nor the host at any time can compute it. Doubling rather than solving for it, because
|
|
316
|
+
`QUEUE_REMAINING` is a count of *items* and there is no ratio converting those into wavefronts. It
|
|
317
|
+
is never lowered: a frame that drained is evidence about that frame, not that the scene got smaller,
|
|
318
|
+
and a bound that fell back would re-learn the same lesson at a hole a time every time the camera
|
|
319
|
+
turned away and back. An unused wavefront is an empty indirect dispatch, which is the cheap side of
|
|
320
|
+
that trade.
|
|
321
|
+
|
|
322
|
+
**What it costs when it is wrong is one frame.** Measured cold at 3419×1286 on the reported
|
|
323
|
+
viewpoint: `queue_remaining` non-zero on **1 frame of 600**, and zero for the rest of the session.
|
|
324
|
+
The ceiling is 64, so the worst case is three such frames, once, ever.
|
|
325
|
+
|
|
326
|
+
The panel shows `wavefronts` as *used / bound*, with an arrow and a count when the bound has moved,
|
|
327
|
+
because eight wavefronts is a drained descent under a bound of 32 and a truncated one under a bound
|
|
328
|
+
of 8 — and the reading could not tell them apart while it showed only the count.
|
|
329
|
+
|
|
330
|
+
`?iterations=N` pins both the start and the ceiling, which turns the escalation off and reproduces
|
|
331
|
+
the hole on demand: at 8 the same view gives `queue_remaining` on 395 frames of 500 and a `drawn`
|
|
332
|
+
swing of 15,874.
|
|
333
|
+
|
|
334
|
+
**`descent_steps` is the wrong knob for this.** It sets how many levels one dispatch advances, so it
|
|
335
|
+
answers depth; what runs out here is *breadth* — a wave keeps one child per lane and spills the
|
|
336
|
+
rest, and the spill generations are what consume the bound. Measured at the small viewport, 24
|
|
337
|
+
against 48 left `drawn` bit-identical.
|
|
338
|
+
|
|
339
|
+
### Catching it where it happens
|
|
340
|
+
|
|
341
|
+
`?catch=0.15` arms {@link FlickerCatcher} — a per-frame detector in the page itself. It judges each
|
|
342
|
+
frame against the pair before it **and** the pair after it, taking the smaller of the two
|
|
343
|
+
departures and only when both point the same way. Judging against the median of all four is the
|
|
344
|
+
obvious thing and is wrong: when the picture takes a persistent *step* — the scene streaming in, a
|
|
345
|
+
coarse level replaced by a fine one — two of the four sit on the far side, the median lands between
|
|
346
|
+
the levels, and the frame *before* the step is convicted for being where it always was. Fourteen
|
|
347
|
+
poses each "caught" one that way. On a step the smaller departure is ~0 by construction, which is
|
|
348
|
+
what makes the corrected test score the reported event at 62% and every one of those artifacts at
|
|
349
|
+
or under 1%.
|
|
350
|
+
|
|
351
|
+
It works on **pixels rather than counters**:
|
|
352
|
+
the cut's statistics come from a readback capped at two in flight and skipped on more than half the
|
|
353
|
+
frames, so `drawn` on any given frame is a reading from one to three frames ago and a one-frame
|
|
354
|
+
disappearance need never appear in it at all. That is why the panel's numbers look calm during one.
|
|
355
|
+
|
|
356
|
+
```
|
|
357
|
+
?catch=0.15 count them, panel shows a running tally — fly around looking for one
|
|
358
|
+
?catch=0.15&freeze=1 stop on the first one and leave that frame on screen
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**"copy camera link" is how a viewpoint gets out of the browser.** A viewpoint is the one piece of
|
|
362
|
+
state a person has and a session does not: everything else this page can be told is a URL parameter
|
|
363
|
+
already, and a phenomenon that happens "in many places" is not chaseable without one of them. The
|
|
364
|
+
button writes a link that reopens the page where the camera is standing — `?eye=`/`?at=` from the
|
|
365
|
+
live pose, `?tau=` from the slider as it stands rather than as the page was opened, and every other
|
|
366
|
+
parameter left exactly as it was, so a link captured with `?catch=0.15&mirrored=tint` on comes back
|
|
367
|
+
with both still armed. It goes to the clipboard, and to a selected box under the button when a
|
|
368
|
+
browser declines that. `__camera_url()` is the same string for a session with no pointer.
|
|
369
|
+
|
|
370
|
+
**An event carries its own camera link**, captured at the instant it fires, so `__caught()` is
|
|
371
|
+
self-contained: where it happened is the one thing a report of a one-frame dropout cannot be
|
|
372
|
+
reconstructed without, and by the time anybody reads the report the camera may have moved. The held
|
|
373
|
+
frame goes on *under* the panel, so freezing does not hide the readings that explain it.
|
|
374
|
+
|
|
375
|
+
`__caught()` reports what it has seen and the counters for the five frames around the last one;
|
|
376
|
+
`__catch_rows()` is the per-frame log. Freezing also holds the five frames around it, and
|
|
377
|
+
`__show_frame(n)` pages between them — the frame before and the frame itself, side by side, is what
|
|
378
|
+
says whether the geometry went missing or merely moved.
|
|
379
|
+
|
|
380
|
+
It costs a whole-canvas `drawImage` and a small `getImageData` per frame: 4.2 ms a frame becomes
|
|
381
|
+
10.3 ms here, which is slack on a 60 Hz display and is why the full-resolution ring is only kept
|
|
382
|
+
when `freeze=1` is asked for.
|
|
383
|
+
|
|
384
|
+
## Driving it
|
|
385
|
+
|
|
386
|
+
`__state()` is the whole surface a headless session needs and the panel shows nothing it does not:
|
|
387
|
+
the scene's counts, the cut's counters, both residency tiers, the render target's size and the
|
|
388
|
+
camera. `__page.runtime.tau` is the threshold, `__look(eye, target)` stands the camera somewhere,
|
|
389
|
+
and `__page.viewpoints` is what the buttons in the panel are.
|
|
390
|
+
|
|
391
|
+
URL parameters, all of them for a session rather than for a person: `?scene=` the manifest,
|
|
392
|
+
`?tau=` the threshold **before the first container** (see the table above for why a load per row),
|
|
393
|
+
`?pages=` the VRAM tier's slots, `?iterations=` the descent's wavefront ceiling,
|
|
394
|
+
`?eye=x,y,z&at=x,y,z` a viewpoint, `?side=front|back|double` the whole scene's draw side,
|
|
395
|
+
`?mirrored=flip|tint|double` the reflected instances' — and `?catch=<fraction>[&freeze=1]` the
|
|
396
|
+
dropout catcher. `__state().scene.mirrored` is the reflected count the panel shows, and `__caught()`
|
|
397
|
+
the catcher's report.
|
|
398
|
+
|
|
399
|
+
**`T` records a GPU capture and `T` again downloads an `.sgpt`** — the same key and the same module
|
|
400
|
+
(`playground/profile_hotkey.js`) every other playground binds. It answers the question the panel
|
|
401
|
+
cannot: every reading above is a *count* and not one of them is a cost. The capture's note carries
|
|
402
|
+
τ, the container and instance counts, the cut it selected and the resolution, because the same
|
|
403
|
+
0.4 ms is a triumph at eighty thousand clusters and a scandal at eighteen thousand.
|
|
404
|
+
|
|
405
|
+
The headless recipe is `vgeo_runtime`'s and has not changed — `--enable-unsafe-webgpu` and nothing
|
|
406
|
+
else, pick the CDP target by URL, resize the window rather than the viewport, and poll for `__state`
|
|
407
|
+
rather than trusting `load`. Its README is where those four are written down and why each one costs
|
|
408
|
+
an hour.
|