@woosh/meep-engine 3.26.0 → 3.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/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/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/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/playground/vgeo_runtime/README.md +167 -22
- package/src/shade/playground/vgeo_runtime/index.html +112 -62
- package/src/shade/playground/vgeo_runtime/main.js +660 -83
- package/src/shade/playground/vgeo_viewer/sample_asset.d.ts.map +1 -1
- package/src/shade/playground/vgeo_viewer/sample_asset.js +19 -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/virtual/VGEO_FORMAT.md +73 -5
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +26 -15
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +5 -3
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +18 -0
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +20 -0
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +56 -9
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +1 -0
- 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/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_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/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 +55 -0
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +202 -13
- 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 +147 -10
- package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +637 -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 +58 -28
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +77 -105
- package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +37 -0
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +105 -22
- 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 +47 -489
- 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 +1363 -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.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +15 -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 +186 -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 +20 -8
- 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 +580 -188
- 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/tiny-gltf.d.ts +1 -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/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +12 -4
- 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/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/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/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/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +7 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +7 -1
- 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 +59 -4
- 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/renderer/geometry/virtual/residency/VGeoResidencyManager.js +0 -723
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.js +0 -50
- package/src/shade/renderer/shader/chunk/jobs/NOTES.md +0 -47
- package/src/shade/renderer/shader/chunk/jobs/chunk_job_system.js +0 -102
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Color } from "../../../core/color/Color.js";
|
|
2
2
|
import Vector3 from "../../../core/geom/Vector3.js";
|
|
3
|
-
import { DEG_TO_RAD } from "../../../core/math/DEG_TO_RAD.js";
|
|
4
3
|
import { EntityComponentDataset } from "../../../engine/ecs/EntityComponentDataset.js";
|
|
5
4
|
import { Transform64 } from "../../../engine/ecs/transform/Transform64.js";
|
|
6
5
|
import { OrbitalCameraController } from "../../renderer/camera/OrbitalCameraController.js";
|
|
@@ -8,23 +7,27 @@ import { PerspectiveCamera } from "../../renderer/camera/PerspectiveCamera.js";
|
|
|
8
7
|
import { FramePhase } from "../../renderer/extension/FramePhase.js";
|
|
9
8
|
import { RenderExtension } from "../../renderer/extension/RenderExtension.js";
|
|
10
9
|
import { VGEO_CUT_STAT } from "../../renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js";
|
|
11
|
-
import { VGeoFeedbackLoop } from "../../renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js";
|
|
12
10
|
import { VirtualGeometry } from "../../renderer/geometry/virtual/runtime/VirtualGeometry.js";
|
|
11
|
+
import {
|
|
12
|
+
VGeoByteSource
|
|
13
|
+
} from "../../renderer/geometry/virtual/format/read/VGeoByteSource.js";
|
|
13
14
|
import {
|
|
14
15
|
VGeoContainerReader
|
|
15
16
|
} from "../../renderer/geometry/virtual/format/read/VGeoContainerReader.js";
|
|
17
|
+
import {
|
|
18
|
+
vgeo_fetch_byte_source
|
|
19
|
+
} from "../../renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.js";
|
|
16
20
|
import {
|
|
17
21
|
VGeoReadOptions
|
|
18
22
|
} from "../../renderer/geometry/virtual/format/read/VGeoReadOptions.js";
|
|
19
|
-
import {
|
|
20
|
-
VGeoResidencyManager
|
|
21
|
-
} from "../../renderer/geometry/virtual/residency/VGeoResidencyManager.js";
|
|
22
23
|
import { make_sunlight } from "../../renderer/light/make_sunlight.js";
|
|
23
24
|
import { place_light } from "../../renderer/light/place_light.js";
|
|
24
25
|
import { StandardShadeMaterial } from "../../renderer/material/StandardShadeMaterial.js";
|
|
25
26
|
import { Renderer } from "../../renderer/Renderer.js";
|
|
26
27
|
import { place_primitive } from "../../renderer/scene/rows/place_primitive.js";
|
|
27
28
|
import { Scene } from "../../renderer/scene/Scene.js";
|
|
29
|
+
import { load_default_environment } from "../load_default_environment.js";
|
|
30
|
+
import { install_profile_hotkey } from "../profile_hotkey.js";
|
|
28
31
|
import { vgeo_sample_source } from "../vgeo_viewer/sample_asset.js";
|
|
29
32
|
|
|
30
33
|
/**
|
|
@@ -48,6 +51,12 @@ import { vgeo_sample_source } from "../vgeo_viewer/sample_asset.js";
|
|
|
48
51
|
* are what fetch the rest.
|
|
49
52
|
* - **nothing is drawn twice.** `drawn` against the triangle count the rasterizer reports.
|
|
50
53
|
*
|
|
54
|
+
* The container is built in the page so there is something to look at on load, but the page is not
|
|
55
|
+
* about that container: **drop a `.vgeo` file on it**, or open it at `?file=`, and the asset is
|
|
56
|
+
* swapped for a real one. That is the reading worth having, because a sample built here is a torus
|
|
57
|
+
* of thirteen pages and an asset off a converter is thousands — and the claim being made is about
|
|
58
|
+
* the second.
|
|
59
|
+
*
|
|
51
60
|
* `__state()` is what a headless session asserts on; everything the panel shows comes from it.
|
|
52
61
|
*
|
|
53
62
|
* @author Alex Goldring
|
|
@@ -56,6 +65,39 @@ import { vgeo_sample_source } from "../vgeo_viewer/sample_asset.js";
|
|
|
56
65
|
|
|
57
66
|
const element = id => document.getElementById(id);
|
|
58
67
|
|
|
68
|
+
/**
|
|
69
|
+
* The page size every container shown here is expected to have been built at.
|
|
70
|
+
*
|
|
71
|
+
* One number for the asset and for the tier that holds it, and it is fixed for the life of the
|
|
72
|
+
* page: a page size is a bound the container states (FORMAT §11.14), the pool lays its arena out in
|
|
73
|
+
* slots of exactly that stride, and there is no slot-size class table. So a dropped container built
|
|
74
|
+
* at a different size is refused at `register` — loudly, by an error naming both numbers — rather
|
|
75
|
+
* than reconciled. The sample is built at this size for the same reason.
|
|
76
|
+
*
|
|
77
|
+
* @type {number}
|
|
78
|
+
*/
|
|
79
|
+
const PAGE_SIZE = 64 * 1024;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Page slots the VRAM tier is built with, over the engine's default of 1,024.
|
|
83
|
+
*
|
|
84
|
+
* The default is sized for a scene, and this page is a single-asset viewer for containers off a
|
|
85
|
+
* converter — where a ninth of a measured 2,975-asset set has more than a thousand pages, and the
|
|
86
|
+
* largest has seventeen thousand. At 1,024 slots the tier fills, `install` starts refusing, and the
|
|
87
|
+
* cut clamps against a *budget* rather than against τ: the surface stays watertight and goes coarse,
|
|
88
|
+
* `clamped` stops falling to zero, and the one claim this page exists to show — that the cut is a
|
|
89
|
+
* function of the threshold and the resolution, not of the source — is no longer what you are
|
|
90
|
+
* looking at.
|
|
91
|
+
*
|
|
92
|
+
* It is cheap in the place it sounds expensive. A slot holds a page's *runtime rows* rather than its
|
|
93
|
+
* payload — an eighth of the page size, see `vgeo_runtime_page_slot_words` — so this is a 32 MB
|
|
94
|
+
* arena, and the payload goes where a static mesh's meshlets go. What it does spend is the decoded
|
|
95
|
+
* budget the pool derives from it, which is 256 MB.
|
|
96
|
+
*
|
|
97
|
+
* @type {number}
|
|
98
|
+
*/
|
|
99
|
+
const PAGE_CAPACITY = 4096;
|
|
100
|
+
|
|
59
101
|
/**
|
|
60
102
|
* Opens the VG frame before anything records a rasterization pass, because the seam inside those
|
|
61
103
|
* passes writes into the feedback buffer this creates.
|
|
@@ -103,10 +145,70 @@ class VGeoFrameEnd extends RenderExtension {
|
|
|
103
145
|
|
|
104
146
|
class VGeoPlayground {
|
|
105
147
|
/**
|
|
106
|
-
* @type {
|
|
148
|
+
* @type {Renderer}
|
|
149
|
+
*/
|
|
150
|
+
renderer;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Held for a session driving the page rather than for anything here: `fit` measures the
|
|
154
|
+
* document, and the renderer is what writes this element's two sizes.
|
|
155
|
+
* @type {HTMLCanvasElement}
|
|
156
|
+
*/
|
|
157
|
+
canvas;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @type {Scene}
|
|
161
|
+
*/
|
|
162
|
+
scene;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @type {GPUSceneContext}
|
|
166
|
+
*/
|
|
167
|
+
scene_context;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @type {PerspectiveCamera}
|
|
171
|
+
*/
|
|
172
|
+
camera;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @type {OrbitalCameraController}
|
|
176
|
+
*/
|
|
177
|
+
controller;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The one material every asset is drawn with — a `Front`-sided standard material, which is what
|
|
181
|
+
* makes this page able to report a container whose triangles face the wrong way. The viewer
|
|
182
|
+
* beside it draws with `cullMode: 'none'` and cannot.
|
|
183
|
+
*
|
|
184
|
+
* @type {StandardShadeMaterial}
|
|
185
|
+
*/
|
|
186
|
+
material;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The open container, or `undefined` before the first one arrives.
|
|
190
|
+
* @type {VGeoContainerReader|undefined}
|
|
107
191
|
*/
|
|
108
192
|
reader;
|
|
109
193
|
|
|
194
|
+
/**
|
|
195
|
+
* @type {VGeoByteSource|undefined}
|
|
196
|
+
*/
|
|
197
|
+
source;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* What the scene draws the container as, held so that {@link retire} can give its record back
|
|
201
|
+
* to the geometry manager — nothing else names it once `place_primitive` has returned.
|
|
202
|
+
* @type {VirtualGeometry|undefined}
|
|
203
|
+
*/
|
|
204
|
+
geometry;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The entity the container is drawn at, or `-1` when nothing is placed.
|
|
208
|
+
* @type {number}
|
|
209
|
+
*/
|
|
210
|
+
entity = -1;
|
|
211
|
+
|
|
110
212
|
/**
|
|
111
213
|
* @type {GPUVirtualGeometryRuntime}
|
|
112
214
|
*/
|
|
@@ -118,7 +220,7 @@ class VGeoPlayground {
|
|
|
118
220
|
feedback;
|
|
119
221
|
|
|
120
222
|
/**
|
|
121
|
-
* @type {
|
|
223
|
+
* @type {VGeoResidency}
|
|
122
224
|
*/
|
|
123
225
|
residency;
|
|
124
226
|
|
|
@@ -127,32 +229,110 @@ class VGeoPlayground {
|
|
|
127
229
|
*/
|
|
128
230
|
frames = 0;
|
|
129
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Height of the render target in pixels, which is what τ is measured against — so it follows
|
|
234
|
+
* the window, and moving the page to a display of a different density changes it.
|
|
235
|
+
* @type {number}
|
|
236
|
+
*/
|
|
237
|
+
screen_height = 1;
|
|
238
|
+
|
|
130
239
|
/**
|
|
131
240
|
* @type {number}
|
|
132
241
|
*/
|
|
133
|
-
|
|
242
|
+
screen_width = 1;
|
|
134
243
|
|
|
135
244
|
/**
|
|
136
245
|
* @type {number}
|
|
137
246
|
*/
|
|
138
|
-
fov_y =
|
|
247
|
+
fov_y = 1;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* One open at a time. Opening is asynchronous and the frame loop keeps running underneath it,
|
|
251
|
+
* so two drops in flight would race on the swap below.
|
|
252
|
+
* @type {boolean}
|
|
253
|
+
*/
|
|
254
|
+
loading = false;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @type {string}
|
|
258
|
+
*/
|
|
259
|
+
status_text = '';
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @param {string} text
|
|
263
|
+
* @param {boolean} [error]
|
|
264
|
+
*/
|
|
265
|
+
status(text, error = false) {
|
|
266
|
+
this.status_text = text;
|
|
267
|
+
|
|
268
|
+
const node = element('status');
|
|
269
|
+
|
|
270
|
+
node.textContent = text;
|
|
271
|
+
node.classList.toggle('error', error);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Bring the canvas, the renderer, the camera and the cut's own notion of the screen up to the
|
|
276
|
+
* size the window actually is.
|
|
277
|
+
*
|
|
278
|
+
* **Measured from the document, never from the canvas.** `Renderer` takes CSS pixels here and
|
|
279
|
+
* writes back both of the canvas's sizes when it configures the context — the backing store in
|
|
280
|
+
* device pixels, and an *inline* `style.width`/`style.height` in CSS pixels. An inline style
|
|
281
|
+
* wins over a stylesheet, so a canvas laid out at `width: 100%` stops following the window the
|
|
282
|
+
* moment the first frame is drawn, and a `fit` that then read `clientWidth` back would be
|
|
283
|
+
* reading the renderer its own last answer: the canvas would never change size again, which is
|
|
284
|
+
* exactly how this page shipped with a fixed viewport.
|
|
285
|
+
*
|
|
286
|
+
* Three things downstream have to follow, and all three are why a fixed-size canvas was wrong
|
|
287
|
+
* rather than merely small:
|
|
288
|
+
*
|
|
289
|
+
* - the camera's **aspect**, which comes off the renderer rather than the window, because the
|
|
290
|
+
* internal resolution is what the projection has to match;
|
|
291
|
+
* - the cut's **screen height**, because `err_px = r_e * screen_height / (2 tan(fov/2) d)` is a
|
|
292
|
+
* count of *render-target* pixels, and a τ of one pixel against a screen height the window
|
|
293
|
+
* has outgrown selects a cut for a screen nobody is looking at;
|
|
294
|
+
* - the cut's **fov**, for the same reason, so that changing the camera's is enough.
|
|
295
|
+
*/
|
|
296
|
+
fit() {
|
|
297
|
+
const renderer = this.renderer;
|
|
298
|
+
|
|
299
|
+
const viewport = document.documentElement;
|
|
300
|
+
|
|
301
|
+
const width = Math.max(1, Math.floor(viewport.clientWidth));
|
|
302
|
+
const height = Math.max(1, Math.floor(viewport.clientHeight));
|
|
303
|
+
|
|
304
|
+
// a window dragged onto a display of another density changes this and nothing else
|
|
305
|
+
renderer.pixel_ratio = globalThis.devicePixelRatio || 1;
|
|
306
|
+
|
|
307
|
+
renderer.resize(width, height);
|
|
308
|
+
|
|
309
|
+
this.camera.aspect = renderer.aspect_ratio;
|
|
310
|
+
|
|
311
|
+
/*
|
|
312
|
+
The internal resolution rather than the output one: the rasterizer draws at
|
|
313
|
+
`output × internal_resolution_scale` and upscales, and the cut is selected for what is
|
|
314
|
+
rasterized. At the scale of 1 this page runs at, the two are the same number.
|
|
315
|
+
*/
|
|
316
|
+
const resolution = renderer.output_resolution;
|
|
317
|
+
const scale = renderer.internal_resolution_scale;
|
|
318
|
+
|
|
319
|
+
this.screen_width = Math.max(1, Math.floor(resolution.x * scale));
|
|
320
|
+
this.screen_height = Math.max(1, Math.floor(resolution.y * scale));
|
|
321
|
+
|
|
322
|
+
this.fov_y = this.camera.fov;
|
|
323
|
+
}
|
|
139
324
|
|
|
140
325
|
/**
|
|
141
326
|
* @param {FrameContext} frame
|
|
142
327
|
*/
|
|
143
328
|
begin(frame) {
|
|
144
|
-
this.runtime.begin_frame(frame.graph
|
|
145
|
-
screen_height: this.canvas_height,
|
|
146
|
-
fov_y: this.fov_y,
|
|
147
|
-
});
|
|
329
|
+
this.runtime.begin_frame({ graph: frame.graph });
|
|
148
330
|
}
|
|
149
331
|
|
|
150
332
|
/**
|
|
151
333
|
* @param {FrameContext} frame
|
|
152
334
|
*/
|
|
153
335
|
finish(frame) {
|
|
154
|
-
// the RAM tier's clock moves inside this: a host that drives the loop gets a bounded
|
|
155
|
-
// second tier without having to know it had one to wind
|
|
156
336
|
this.feedback.read(frame.graph);
|
|
157
337
|
|
|
158
338
|
this.runtime.end_frame();
|
|
@@ -160,6 +340,226 @@ class VGeoPlayground {
|
|
|
160
340
|
this.frames++;
|
|
161
341
|
}
|
|
162
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Open a container over a byte source and put it on screen in place of whatever is there.
|
|
345
|
+
*
|
|
346
|
+
* A refusal leaves the page exactly as it was, which is the reason the new container is
|
|
347
|
+
* registered *before* the old one is taken down: the two ways this fails — a container built at
|
|
348
|
+
* another page size, and a tier with no room for another root — both fail inside `register`,
|
|
349
|
+
* and a page that had already dropped its asset to find that out would answer a bad file with
|
|
350
|
+
* an empty screen and an error, rather than with the error alone.
|
|
351
|
+
*
|
|
352
|
+
* @param {VGeoByteSource} source
|
|
353
|
+
* @returns {Promise<boolean>} whether the asset on screen changed
|
|
354
|
+
*/
|
|
355
|
+
async load(source) {
|
|
356
|
+
if (this.loading) {
|
|
357
|
+
this.status(`a container is still opening — ${source.name} was ignored`, true);
|
|
358
|
+
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
this.loading = true;
|
|
363
|
+
|
|
364
|
+
this.status(`opening ${source.name}…`);
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
/*
|
|
368
|
+
Frame verification on, because this reader is a runtime's rather than a tool's —
|
|
369
|
+
format §10 leaves it off by default precisely because a tool reading a file it just
|
|
370
|
+
built is not defending against a network, and a runtime is. A dropped file is the case
|
|
371
|
+
that was always meant: bytes this page did not produce.
|
|
372
|
+
*/
|
|
373
|
+
const options = new VGeoReadOptions();
|
|
374
|
+
|
|
375
|
+
options.verify_frames = true;
|
|
376
|
+
|
|
377
|
+
// the runtime answers activation in the arena; the reader's own counters would be
|
|
378
|
+
// tens of megabytes at fifty thousand pages, read by nothing
|
|
379
|
+
options.track_selectability = false;
|
|
380
|
+
|
|
381
|
+
source.reset_counters();
|
|
382
|
+
|
|
383
|
+
const started = performance.now();
|
|
384
|
+
|
|
385
|
+
const reader = await VGeoContainerReader.open(source, options);
|
|
386
|
+
|
|
387
|
+
const open_ms = performance.now() - started;
|
|
388
|
+
|
|
389
|
+
this.adopt(reader, source);
|
|
390
|
+
|
|
391
|
+
this.status(
|
|
392
|
+
`${source.name} — ${reader.header.total_pages} pages,`
|
|
393
|
+
+ ` opened in ${open_ms.toFixed(0)} ms`
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
return true;
|
|
397
|
+
} catch (error) {
|
|
398
|
+
this.status(error.message ?? String(error), true);
|
|
399
|
+
|
|
400
|
+
console.error(error);
|
|
401
|
+
|
|
402
|
+
return false;
|
|
403
|
+
} finally {
|
|
404
|
+
this.loading = false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* The same door the drop target opens, for a session that has no pointer to drop with — and for
|
|
410
|
+
* a link to an asset. A `.vgeo` is read by ranges, so this is one `HEAD` and one request per
|
|
411
|
+
* page the cut asks for, against a server that honours `Range`.
|
|
412
|
+
*
|
|
413
|
+
* @param {string} url
|
|
414
|
+
* @returns {Promise<boolean>} whether the asset on screen changed
|
|
415
|
+
*/
|
|
416
|
+
async load_url(url) {
|
|
417
|
+
this.status(`probing ${url}…`);
|
|
418
|
+
|
|
419
|
+
let source;
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
source = await vgeo_fetch_byte_source(url);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
this.status(`${url} — ${error.message ?? error}`, true);
|
|
425
|
+
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return this.load(source);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Register an open container with both tiers, retire the one it replaces, and place it.
|
|
434
|
+
*
|
|
435
|
+
* @param {VGeoContainerReader} reader
|
|
436
|
+
* @param {VGeoByteSource} source
|
|
437
|
+
*/
|
|
438
|
+
adopt(reader, source) {
|
|
439
|
+
/*
|
|
440
|
+
Only the root page is resident. Everything else arrives because the cut asked for it,
|
|
441
|
+
which is the loop this page exists to show — and it means the first frames draw a
|
|
442
|
+
coarse, watertight surface rather than nothing at all. A refusal throws here and
|
|
443
|
+
leaves nothing registered.
|
|
444
|
+
*/
|
|
445
|
+
const geometry_row = this.residency.register(reader);
|
|
446
|
+
|
|
447
|
+
this.retire();
|
|
448
|
+
|
|
449
|
+
this.reader = reader;
|
|
450
|
+
this.source = source;
|
|
451
|
+
this.geometry = new VirtualGeometry(reader);
|
|
452
|
+
|
|
453
|
+
this.geometry.vg_row = geometry_row;
|
|
454
|
+
|
|
455
|
+
this.entity = place_primitive(
|
|
456
|
+
this.scene_context, this.geometry, this.material, new Transform64(),
|
|
457
|
+
{ name: source.name }
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
this.frame_camera(reader.header.object_sphere);
|
|
461
|
+
|
|
462
|
+
/*
|
|
463
|
+
The scene it is drawing has just been replaced wholesale, which is what this exists to be
|
|
464
|
+
told. TAA reprojects through motion vectors, and the pixels the new container covers have
|
|
465
|
+
no correspondence in a history written from the old one — so without this the first frames
|
|
466
|
+
after a drop blend one asset's colour into the other's silhouette.
|
|
467
|
+
*/
|
|
468
|
+
this.renderer.indicate_view_change();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Give back everything the container on screen holds: its mesh row, its entity, its geometry
|
|
473
|
+
* record, its VRAM pages and the decoded bytes it was holding in RAM.
|
|
474
|
+
*
|
|
475
|
+
* **Detaching the mesh row is only the first of them.** `detach_mesh` gives back the `meshes`
|
|
476
|
+
* row and releases the material, and it does *not* give the geometry back to
|
|
477
|
+
* `GPUGeometryManager` — `#write_mesh` calls `add` and `#free_mesh` has no matching `remove`.
|
|
478
|
+
* So without the line below, every container ever dropped stayed named by the manager, and the
|
|
479
|
+
* record it was named by holds the `VirtualGeometry`, which holds the **reader**, which holds
|
|
480
|
+
* every page that reader had decoded. The RAM tier's bytes were not merely uncollected, they
|
|
481
|
+
* were unreachable. Five drops, measured: with `remove`, the manager names none of the five
|
|
482
|
+
* retired geometries and each retired reader is down to its one pinned root page; without it,
|
|
483
|
+
* all five and everything they had streamed.
|
|
484
|
+
*
|
|
485
|
+
* `evict_all` is then about *when* rather than whether — it releases the decoded pages at the
|
|
486
|
+
* swap instead of at whatever garbage collection comes next, which is worth having when the
|
|
487
|
+
* container leaving held a hundred megabytes and the one arriving is about to ask for its own.
|
|
488
|
+
* `VGeoResidencyManager.unregister` says in as many words that this is the caller's business:
|
|
489
|
+
* it stops counting a reader against the budget and drops nothing.
|
|
490
|
+
*
|
|
491
|
+
* What this does *not* give back is the geometry **id**. `__last_geometry_index` only ever
|
|
492
|
+
* counts up — `remove` frees the record, the BLAS slot and the meshlet batch, but not the
|
|
493
|
+
* index — so the metadata table and the BLAS address lookup are still one row longer per drop.
|
|
494
|
+
* Measured at 64 bytes a drop, which is a residual rather than a leak worth a playground
|
|
495
|
+
* reaching into the geometry manager over.
|
|
496
|
+
*
|
|
497
|
+
* `abort()` first, and that is not tidiness either. The wants in flight name page indices *of
|
|
498
|
+
* the container being retired*, and a fetch that lands after its reader is gone resolves a byte
|
|
499
|
+
* range out of a child table nothing owns any more.
|
|
500
|
+
*/
|
|
501
|
+
retire() {
|
|
502
|
+
if (this.entity !== -1) {
|
|
503
|
+
this.scene_context.rows.detach_mesh(this.entity);
|
|
504
|
+
this.scene.dataset.removeEntity(this.entity);
|
|
505
|
+
|
|
506
|
+
this.entity = -1;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (this.geometry !== undefined) {
|
|
510
|
+
this.scene_context.geometries.remove(this.geometry);
|
|
511
|
+
|
|
512
|
+
this.geometry = undefined;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const reader = this.reader;
|
|
516
|
+
|
|
517
|
+
if (reader === undefined) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// gives back its rows, its lookup, its RAM slots, and cancels what it had on the wire
|
|
522
|
+
this.residency.unregister(reader);
|
|
523
|
+
|
|
524
|
+
reader.evict_all();
|
|
525
|
+
|
|
526
|
+
this.reader = undefined;
|
|
527
|
+
this.source = undefined;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Stand the camera off the container's own bounding sphere.
|
|
532
|
+
*
|
|
533
|
+
* A sample torus is a unit across and sits at the origin; a converted asset can be a centimetre
|
|
534
|
+
* or a hundred metres, and can stand a long way from it. Everything that has a length in it —
|
|
535
|
+
* the near plane, the orbit limits, the speed the controller moves at — is taken from the
|
|
536
|
+
* radius for the same reason: a near plane of 2 cm is right for the torus and puts a whole
|
|
537
|
+
* building inside itself.
|
|
538
|
+
*
|
|
539
|
+
* @param {Float32Array} sphere centre xyz then radius, object space — and object space is world
|
|
540
|
+
* space here, because the primitive is placed at the identity
|
|
541
|
+
*/
|
|
542
|
+
frame_camera(sphere) {
|
|
543
|
+
const radius = sphere[3] > 0 ? sphere[3] : 1;
|
|
544
|
+
|
|
545
|
+
const center = new Vector3(sphere[0], sphere[1], sphere[2]);
|
|
546
|
+
|
|
547
|
+
const eye = new Vector3(0.35, 0.45, 1);
|
|
548
|
+
|
|
549
|
+
eye.normalize();
|
|
550
|
+
eye.multiplyScalar(radius * 2.8);
|
|
551
|
+
eye.add(center);
|
|
552
|
+
|
|
553
|
+
this.camera.near = radius * 0.005;
|
|
554
|
+
this.camera.far = radius * 200;
|
|
555
|
+
|
|
556
|
+
this.controller.distanceLimits.min = radius * 0.02;
|
|
557
|
+
this.controller.distanceLimits.max = radius * 200;
|
|
558
|
+
this.controller.movement_speed_scale = radius;
|
|
559
|
+
|
|
560
|
+
this.controller.look(eye, center);
|
|
561
|
+
}
|
|
562
|
+
|
|
163
563
|
/**
|
|
164
564
|
* Everything a headless session needs, and everything the panel shows.
|
|
165
565
|
*
|
|
@@ -168,12 +568,25 @@ class VGeoPlayground {
|
|
|
168
568
|
state() {
|
|
169
569
|
const stats = this.feedback.stats;
|
|
170
570
|
const pool = this.runtime.pool;
|
|
571
|
+
const reader = this.reader;
|
|
171
572
|
|
|
172
573
|
return {
|
|
173
574
|
frames: this.frames,
|
|
174
575
|
tau: this.runtime.tau,
|
|
576
|
+
status: this.status_text,
|
|
577
|
+
screen_width: this.screen_width,
|
|
578
|
+
screen_height: this.screen_height,
|
|
175
579
|
feedback_reads: this.feedback.read_count,
|
|
176
580
|
feedback_skipped: this.feedback.skipped_frames,
|
|
581
|
+
asset: {
|
|
582
|
+
name: this.source?.name ?? '',
|
|
583
|
+
bytes: this.source?.byte_length ?? 0,
|
|
584
|
+
// this thread's reads and the workers', which the source never sees
|
|
585
|
+
bytes_read: (this.source?.bytes_read ?? 0) + this.residency.fetcher.worker_bytes_read,
|
|
586
|
+
faces: reader === undefined ? 0 : Number(reader.header.source_face_count),
|
|
587
|
+
radius: reader?.header.object_sphere[3] ?? 0,
|
|
588
|
+
page_size: reader?.header.nominal_page_size ?? 0,
|
|
589
|
+
},
|
|
177
590
|
cut: {
|
|
178
591
|
drawn: stats[VGEO_CUT_STAT.DRAWN],
|
|
179
592
|
clamped: stats[VGEO_CUT_STAT.CLAMPED],
|
|
@@ -182,6 +595,7 @@ class VGeoPlayground {
|
|
|
182
595
|
items: stats[VGEO_CUT_STAT.ITEMS],
|
|
183
596
|
clusters_tested: stats[VGEO_CUT_STAT.CLUSTERS_TESTED],
|
|
184
597
|
culled_frustum: stats[VGEO_CUT_STAT.CULLED_FRUSTUM],
|
|
598
|
+
culled_cluster: stats[VGEO_CUT_STAT.CULLED_CLUSTER],
|
|
185
599
|
culled_lod: stats[VGEO_CUT_STAT.CULLED_LOD],
|
|
186
600
|
meshlets_dropped: stats[VGEO_CUT_STAT.MESHLETS_DROPPED],
|
|
187
601
|
wants_dropped: stats[VGEO_CUT_STAT.WANTS_DROPPED],
|
|
@@ -195,103 +609,176 @@ class VGeoPlayground {
|
|
|
195
609
|
vram: {
|
|
196
610
|
pages: pool.resident_page_count,
|
|
197
611
|
bytes: pool.resident_bytes,
|
|
198
|
-
evicted:
|
|
199
|
-
refused:
|
|
612
|
+
evicted: this.residency.evicted_vram_count,
|
|
613
|
+
refused: this.residency.refused_install_count,
|
|
200
614
|
},
|
|
201
615
|
ram: {
|
|
202
|
-
pages: this.
|
|
203
|
-
bytes: this.
|
|
204
|
-
|
|
616
|
+
pages: this.residency.ram_page_count,
|
|
617
|
+
bytes: this.residency.ram_resident_bytes,
|
|
618
|
+
evicted: this.residency.evicted_ram_count,
|
|
619
|
+
refused: this.residency.refused_fetch_count,
|
|
620
|
+
total_pages: reader?.header.total_pages ?? 0,
|
|
205
621
|
},
|
|
622
|
+
fetches_in_flight: this.residency.fetches_in_flight,
|
|
623
|
+
worker_fetches: this.residency.fetcher.worker_fetch_count,
|
|
624
|
+
in_process_fetches: this.residency.fetcher.in_process_fetch_count,
|
|
625
|
+
deferred: this.residency.deferred_want_count,
|
|
206
626
|
};
|
|
207
627
|
}
|
|
208
628
|
}
|
|
209
629
|
|
|
210
|
-
|
|
211
|
-
|
|
630
|
+
/**
|
|
631
|
+
* @param {number} bytes
|
|
632
|
+
* @returns {string}
|
|
633
|
+
*/
|
|
634
|
+
function human_bytes(bytes) {
|
|
635
|
+
if (bytes < 1024) {
|
|
636
|
+
return `${bytes} B`;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (bytes < 1024 * 1024) {
|
|
640
|
+
return `${(bytes / 1024).toFixed(0)} KB`;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Take a file the moment it is dropped anywhere on the page.
|
|
648
|
+
*
|
|
649
|
+
* Counted rather than latched, because `dragleave` fires for every element the pointer crosses on
|
|
650
|
+
* its way in — the overlay's own, the panel's — and a boolean flag put the hint away while the file
|
|
651
|
+
* was still over the window.
|
|
652
|
+
*
|
|
653
|
+
* @param {VGeoPlayground} page
|
|
654
|
+
*/
|
|
655
|
+
function wire_drop_target(page) {
|
|
656
|
+
const overlay = element('drop');
|
|
212
657
|
|
|
213
|
-
|
|
214
|
-
|
|
658
|
+
let depth = 0;
|
|
659
|
+
|
|
660
|
+
const hide = () => {
|
|
661
|
+
depth = 0;
|
|
662
|
+
|
|
663
|
+
overlay.classList.remove('active');
|
|
215
664
|
};
|
|
216
665
|
|
|
217
|
-
|
|
666
|
+
globalThis.addEventListener('dragenter', event => {
|
|
667
|
+
event.preventDefault();
|
|
218
668
|
|
|
219
|
-
|
|
669
|
+
depth++;
|
|
220
670
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
renderer.feature_bloom_enabled = false;
|
|
671
|
+
overlay.classList.add('active');
|
|
672
|
+
});
|
|
224
673
|
|
|
225
|
-
|
|
674
|
+
globalThis.addEventListener('dragover', event => event.preventDefault());
|
|
226
675
|
|
|
227
|
-
|
|
676
|
+
globalThis.addEventListener('dragleave', () => {
|
|
677
|
+
depth = Math.max(0, depth - 1);
|
|
228
678
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
*/
|
|
234
|
-
const page_size = 64 * 1024;
|
|
679
|
+
if (depth === 0) {
|
|
680
|
+
overlay.classList.remove('active');
|
|
681
|
+
}
|
|
682
|
+
});
|
|
235
683
|
|
|
236
|
-
|
|
684
|
+
globalThis.addEventListener('drop', async event => {
|
|
685
|
+
event.preventDefault();
|
|
237
686
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
defending against a network, and a runtime is.
|
|
242
|
-
*/
|
|
243
|
-
const options = new VGeoReadOptions();
|
|
687
|
+
hide();
|
|
688
|
+
|
|
689
|
+
const file = event.dataTransfer?.files[0];
|
|
244
690
|
|
|
245
|
-
|
|
691
|
+
if (file === undefined) {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
246
694
|
|
|
247
|
-
|
|
695
|
+
await page.load(VGeoByteSource.from_blob(file));
|
|
696
|
+
|
|
697
|
+
// the controller's keyboard device listens on the body, and a drop leaves focus elsewhere
|
|
698
|
+
document.body.focus();
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
async function start() {
|
|
703
|
+
const canvas = element('view');
|
|
248
704
|
|
|
249
705
|
const page = new VGeoPlayground();
|
|
250
706
|
|
|
251
|
-
page.
|
|
252
|
-
|
|
707
|
+
page.canvas = canvas;
|
|
708
|
+
|
|
709
|
+
const renderer = new Renderer();
|
|
710
|
+
|
|
711
|
+
page.renderer = renderer;
|
|
712
|
+
|
|
713
|
+
await renderer.initialize({
|
|
714
|
+
context: canvas.getContext('webgpu'),
|
|
715
|
+
pixelRatio: globalThis.devicePixelRatio || 1,
|
|
716
|
+
});
|
|
253
717
|
|
|
254
|
-
|
|
255
|
-
|
|
718
|
+
/*
|
|
719
|
+
TAA on, which for a page about a *cut* is not a cosmetic choice. A cut is selected per frame
|
|
720
|
+
against a threshold in pixels, so the clusters it draws change under the camera and the seams
|
|
721
|
+
between two levels of detail land on different pixels from one frame to the next. Without a
|
|
722
|
+
temporal filter every one of those is an aliased edge that crawls, and the crawl reads as the
|
|
723
|
+
cut being unstable when it is the rasterizer being point-sampled. It also costs the readings
|
|
724
|
+
nothing: TAA is downstream of the G-buffer, and the cut has already been selected and drawn
|
|
725
|
+
by the time it runs.
|
|
726
|
+
|
|
727
|
+
SSAO and bloom stay off. They are off because this page is a measurement harness and they are
|
|
728
|
+
two more passes between a change and its effect, not because they would break anything.
|
|
729
|
+
*/
|
|
730
|
+
renderer.feature_taa_enabled = true;
|
|
731
|
+
renderer.feature_ssao_enabled = false;
|
|
732
|
+
renderer.feature_bloom_enabled = false;
|
|
256
733
|
|
|
257
734
|
const scene = new Scene();
|
|
258
735
|
|
|
259
|
-
// the sun stands at an entity, and so does the
|
|
736
|
+
// the sun stands at an entity, and so does the asset, so the scene names a dataset
|
|
260
737
|
scene.dataset = new EntityComponentDataset();
|
|
261
738
|
|
|
739
|
+
page.scene = scene;
|
|
740
|
+
|
|
262
741
|
// a light stands at an entity of the dataset now, like everything else the scene holds
|
|
263
742
|
place_light(scene, make_sunlight());
|
|
264
743
|
|
|
744
|
+
/*
|
|
745
|
+
Image-based lighting, and the reason is the geometry rather than the picture. A sun alone puts
|
|
746
|
+
every surface facing away from it at ambient zero, and the whole point of a cut is what happens
|
|
747
|
+
at the silhouette and in the creases — the places a single directional light renders as flat
|
|
748
|
+
black. A converted asset arrives with normals and no material authoring, so the environment is
|
|
749
|
+
most of what there is to shade it with.
|
|
750
|
+
|
|
751
|
+
Not awaited, and the page does not depend on it: `test_assets/` is gitignored, so a fresh
|
|
752
|
+
checkout has no sky and gets the sun alone, which still draws every triangle the cut selected.
|
|
753
|
+
The environment lands a frame or two later when it is there.
|
|
754
|
+
*/
|
|
755
|
+
load_default_environment(scene);
|
|
756
|
+
|
|
265
757
|
const camera = new PerspectiveCamera();
|
|
266
758
|
|
|
267
|
-
camera.
|
|
268
|
-
camera.far = 200;
|
|
269
|
-
camera.fov = page.fov_y;
|
|
270
|
-
camera.aspect = canvas.width / canvas.height;
|
|
759
|
+
camera.fov_degrees = 50;
|
|
271
760
|
|
|
272
|
-
|
|
761
|
+
page.camera = camera;
|
|
762
|
+
page.controller = new OrbitalCameraController(camera, document.body);
|
|
273
763
|
|
|
274
|
-
|
|
764
|
+
page.fit();
|
|
765
|
+
|
|
766
|
+
globalThis.addEventListener('resize', () => page.fit());
|
|
275
767
|
|
|
276
768
|
// one frame to let the renderer build its scene context, which is what owns the geometry manager
|
|
277
769
|
renderer.render(camera, scene, 1 / 60);
|
|
278
770
|
|
|
279
771
|
const scene_context = renderer.scenes.obtain(scene);
|
|
280
772
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
Only the root page is resident. Everything else arrives because the cut asked for it, which is
|
|
287
|
-
the loop this page exists to show — and it means the first frames draw a coarse, watertight
|
|
288
|
-
surface rather than nothing at all.
|
|
289
|
-
*/
|
|
290
|
-
const geometry_row = runtime.pool.register(reader);
|
|
291
|
-
|
|
292
|
-
const geometry = new VirtualGeometry(reader);
|
|
773
|
+
page.scene_context = scene_context;
|
|
774
|
+
page.runtime = scene_context.enable_virtual_geometry({
|
|
775
|
+
page_size: PAGE_SIZE,
|
|
776
|
+
table_options: { page_capacity: PAGE_CAPACITY },
|
|
777
|
+
});
|
|
293
778
|
|
|
294
|
-
|
|
779
|
+
// the scene context built both tiers and the loop with the runtime; this page only reads them
|
|
780
|
+
page.residency = scene_context.virtual_geometry_residency;
|
|
781
|
+
page.feedback = scene_context.virtual_geometry_feedback;
|
|
295
782
|
|
|
296
783
|
const material = new StandardShadeMaterial();
|
|
297
784
|
|
|
@@ -299,11 +786,7 @@ async function start() {
|
|
|
299
786
|
material.roughness_factor = 0.55;
|
|
300
787
|
material.metallic_factor = 0;
|
|
301
788
|
|
|
302
|
-
|
|
303
|
-
scene_context, geometry, material, new Transform64(), { name: 'virtual torus' }
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
page.feedback = new VGeoFeedbackLoop({ runtime, residency: page.residency });
|
|
789
|
+
page.material = material;
|
|
307
790
|
|
|
308
791
|
/*
|
|
309
792
|
The loop's only teardown caller anywhere, and worth having for the same reason M12's clock was:
|
|
@@ -318,15 +801,71 @@ async function start() {
|
|
|
318
801
|
renderer.add_extension(new VGeoFrameEnd(page));
|
|
319
802
|
|
|
320
803
|
element('tau').addEventListener('input', event => {
|
|
321
|
-
runtime.tau = Number(event.target.value);
|
|
804
|
+
page.runtime.tau = Number(event.target.value);
|
|
322
805
|
|
|
323
|
-
element('tau-value').textContent = runtime.tau.toFixed(2);
|
|
806
|
+
element('tau-value').textContent = page.runtime.tau.toFixed(2);
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// a drag on the τ slider is a drag on the body, and the camera controller listens there
|
|
810
|
+
element('panel').addEventListener('pointerdown', event => event.stopPropagation());
|
|
811
|
+
|
|
812
|
+
wire_drop_target(page);
|
|
813
|
+
|
|
814
|
+
/*
|
|
815
|
+
T records a GPU capture, T again saves it — the same key the other playgrounds bind, out of the
|
|
816
|
+
same module (`playground/profile_hotkey.js`), so there is one implementation of the drain and
|
|
817
|
+
the `.sgpt` naming rather than one per page.
|
|
818
|
+
|
|
819
|
+
What it is for here is the question this page cannot otherwise answer. Every reading in the
|
|
820
|
+
panel is a *count* — clusters drawn, pages visited, wavefronts run — and none of them is a
|
|
821
|
+
cost. The cut is four compute passes ahead of a G-buffer that does not know virtual geometry
|
|
822
|
+
exists, so what is worth knowing is how the seed, the traverse loop, the collect and the
|
|
823
|
+
expansion compare against the rasterization they feed, and whether the descent's wavefront
|
|
824
|
+
count shows up as time or disappears into occupancy. That is a capture, not a counter.
|
|
825
|
+
|
|
826
|
+
The settings line is what makes one readable. A pass at 0.4 ms says nothing without τ, the
|
|
827
|
+
asset and the cut it selected — the same 0.4 ms is a triumph at 10,000 clusters and a scandal
|
|
828
|
+
at nine — so all three go into the note beside the adapter and the resolution.
|
|
829
|
+
*/
|
|
830
|
+
install_profile_hotkey({
|
|
831
|
+
renderer,
|
|
832
|
+
name: "vgeo_runtime",
|
|
833
|
+
status: element('profile-status'),
|
|
834
|
+
settings: () => {
|
|
835
|
+
const state = page.state();
|
|
836
|
+
|
|
837
|
+
return [
|
|
838
|
+
`τ=${state.tau}px`,
|
|
839
|
+
state.asset.name,
|
|
840
|
+
`${state.cut.drawn} clusters`,
|
|
841
|
+
`${state.vram.pages} resident of ${state.ram.total_pages}`,
|
|
842
|
+
`${state.screen_width}×${state.screen_height}`,
|
|
843
|
+
];
|
|
844
|
+
},
|
|
324
845
|
});
|
|
325
846
|
|
|
326
847
|
globalThis.__state = () => page.state();
|
|
327
848
|
globalThis.__page = page;
|
|
849
|
+
globalThis.__load_url = url => page.load_url(url);
|
|
850
|
+
|
|
851
|
+
const requested = new URL(globalThis.location.href).searchParams.get('file');
|
|
328
852
|
|
|
329
|
-
|
|
853
|
+
if (requested !== null) {
|
|
854
|
+
await page.load_url(requested);
|
|
855
|
+
} else {
|
|
856
|
+
page.status('building a container in the page…');
|
|
857
|
+
|
|
858
|
+
// yielded so the status paints before the builder takes the thread for half a second
|
|
859
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
860
|
+
|
|
861
|
+
const { source, build_ms } = vgeo_sample_source(128, 48, PAGE_SIZE);
|
|
862
|
+
|
|
863
|
+
await page.load(source);
|
|
864
|
+
|
|
865
|
+
page.status(
|
|
866
|
+
`${page.reader.header.total_pages} pages, built in the page in ${build_ms.toFixed(0)} ms`
|
|
867
|
+
);
|
|
868
|
+
}
|
|
330
869
|
|
|
331
870
|
let stopped = false;
|
|
332
871
|
let last_time;
|
|
@@ -334,12 +873,24 @@ async function start() {
|
|
|
334
873
|
function paint() {
|
|
335
874
|
const state = page.state();
|
|
336
875
|
|
|
876
|
+
const name = escape_html(state.asset.name);
|
|
877
|
+
|
|
878
|
+
element('asset').innerHTML = [
|
|
879
|
+
['source', `<span title="${name}">${name}</span>`],
|
|
880
|
+
['triangles', state.asset.faces.toLocaleString()],
|
|
881
|
+
['file', human_bytes(state.asset.bytes)],
|
|
882
|
+
['read', human_bytes(state.asset.bytes_read)],
|
|
883
|
+
['view', `${state.screen_width}×${state.screen_height}`],
|
|
884
|
+
].map(([name, value]) => `<div class="row"><span>${name}</span><span>${value}</span></div>`)
|
|
885
|
+
.join('');
|
|
886
|
+
|
|
337
887
|
element('cut').innerHTML = [
|
|
338
888
|
['drawn clusters', state.cut.drawn],
|
|
339
889
|
['clamped', state.cut.clamped],
|
|
340
890
|
['pages visited', state.cut.pages],
|
|
341
891
|
['clusters tested', state.cut.clusters_tested],
|
|
342
892
|
['subtrees culled', state.cut.culled_frustum + state.cut.culled_lod],
|
|
893
|
+
['clusters culled', state.cut.culled_cluster],
|
|
343
894
|
['wants', state.wants],
|
|
344
895
|
].map(([name, value]) => `<div class="row"><span>${name}</span><span>${value}</span></div>`)
|
|
345
896
|
.join('');
|
|
@@ -347,11 +898,25 @@ async function start() {
|
|
|
347
898
|
const dropped = state.cut.meshlets_dropped + state.cut.queue_dropped
|
|
348
899
|
+ state.cut.instances_dropped;
|
|
349
900
|
|
|
901
|
+
/*
|
|
902
|
+
`refused` is the tier saying it had nowhere to put a page the cut asked for, and it is the
|
|
903
|
+
one reading that explains a picture going coarse for a reason that is not τ. It belongs
|
|
904
|
+
beside `clamped` above: a clamped count that will not fall to zero while this climbs is a
|
|
905
|
+
budget, not a threshold.
|
|
906
|
+
*/
|
|
350
907
|
element('residency').innerHTML = [
|
|
351
|
-
['VRAM pages', `${state.vram.pages}`],
|
|
352
|
-
['VRAM bytes',
|
|
908
|
+
['VRAM pages', `${state.vram.pages} / ${PAGE_CAPACITY}`],
|
|
909
|
+
['VRAM bytes', human_bytes(state.vram.bytes)],
|
|
353
910
|
['RAM pages', `${state.ram.pages} / ${state.ram.total_pages}`],
|
|
911
|
+
['RAM evicted', state.ram.evicted],
|
|
912
|
+
['fetching', state.fetches_in_flight],
|
|
913
|
+
['worker fetches', `${state.worker_fetches} / ${state.worker_fetches + state.in_process_fetches}`],
|
|
354
914
|
['evicted', state.vram.evicted],
|
|
915
|
+
[
|
|
916
|
+
'refused',
|
|
917
|
+
`<span class="${state.vram.refused > 0 ? 'bad' : 'good'}">`
|
|
918
|
+
+ `${state.vram.refused.toLocaleString()}</span>`,
|
|
919
|
+
],
|
|
355
920
|
['dropped', `<span class="${dropped > 0 ? 'bad' : 'good'}">${dropped}</span>`],
|
|
356
921
|
].map(([name, value]) => `<div class="row"><span>${name}</span><span>${value}</span></div>`)
|
|
357
922
|
.join('');
|
|
@@ -367,14 +932,13 @@ async function start() {
|
|
|
367
932
|
last_time = time;
|
|
368
933
|
|
|
369
934
|
try {
|
|
370
|
-
controller.update(dt);
|
|
935
|
+
page.controller.update(dt);
|
|
371
936
|
|
|
372
937
|
renderer.render(camera, scene, dt);
|
|
373
938
|
} catch (error) {
|
|
374
939
|
stopped = true;
|
|
375
940
|
|
|
376
|
-
status(error.message ?? String(error));
|
|
377
|
-
element('status').classList.add('error');
|
|
941
|
+
page.status(error.message ?? String(error), true);
|
|
378
942
|
|
|
379
943
|
console.error(error);
|
|
380
944
|
|
|
@@ -389,6 +953,19 @@ async function start() {
|
|
|
389
953
|
requestAnimationFrame(tick);
|
|
390
954
|
}
|
|
391
955
|
|
|
956
|
+
/**
|
|
957
|
+
* A file name arrives from a drop and lands in the panel's markup, so it is a string from outside
|
|
958
|
+
* the page in the one place a string from outside the page can do something.
|
|
959
|
+
*
|
|
960
|
+
* @param {string} text
|
|
961
|
+
* @returns {string}
|
|
962
|
+
*/
|
|
963
|
+
function escape_html(text) {
|
|
964
|
+
return text.replace(/[&<>"]/g, character => ({
|
|
965
|
+
'&': '&', '<': '<', '>': '>', '"': '"',
|
|
966
|
+
})[character]);
|
|
967
|
+
}
|
|
968
|
+
|
|
392
969
|
start().catch(error => {
|
|
393
970
|
element('status').textContent = error.message ?? String(error);
|
|
394
971
|
element('status').classList.add('error');
|