@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
|
@@ -0,0 +1,1363 @@
|
|
|
1
|
+
import { assert } from "../../../../../core/assert.js";
|
|
2
|
+
import { ConcurrencyGate } from "../../../../../core/process/ConcurrencyGate.js";
|
|
3
|
+
import { VGEO_FEEDBACK_OFFSET } from "./layout/VGEO_FEEDBACK_OFFSET.js";
|
|
4
|
+
import { VGEO_PAGE_ROW_OFFSET } from "./layout/VGEO_PAGE_ROW_OFFSET.js";
|
|
5
|
+
import { VGEO_WANT_WORDS } from "./layout/VGEO_WANT_WORDS.js";
|
|
6
|
+
import { VGeoPageFetcher } from "./VGeoPageFetcher.js";
|
|
7
|
+
import { VGeoResidencyOptions } from "./VGeoResidencyOptions.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {import("../format/read/VGeoContainerReader.js").VGeoContainerReader} VGeoContainerReader
|
|
11
|
+
* @typedef {import("../format/read/VGeoPage.js").VGeoPage} VGeoPage
|
|
12
|
+
* @typedef {import("./GPUVirtualGeometryPool.js").GPUVirtualGeometryPool} GPUVirtualGeometryPool
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Where a page of a registered container is, as one word per page beside the lookup: the top two
|
|
17
|
+
* bits are the tag, the rest is the RAM slot or the VRAM row.
|
|
18
|
+
*/
|
|
19
|
+
const TAG_SHIFT = 30;
|
|
20
|
+
const INDEX_MASK = (1 << TAG_SHIFT) - 1;
|
|
21
|
+
|
|
22
|
+
const TAG_ABSENT = 0;
|
|
23
|
+
const TAG_PENDING = 1;
|
|
24
|
+
const TAG_RAM = 2;
|
|
25
|
+
const TAG_VRAM = 3;
|
|
26
|
+
|
|
27
|
+
const STATE_ABSENT = 0;
|
|
28
|
+
const STATE_FETCHING = (TAG_PENDING << TAG_SHIFT) | 1;
|
|
29
|
+
const STATE_QUARANTINED = (TAG_PENDING << TAG_SHIFT) | 2;
|
|
30
|
+
|
|
31
|
+
const NO_SLOT = 0xFFFFFFFF;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {number} state
|
|
35
|
+
* @returns {number}
|
|
36
|
+
*/
|
|
37
|
+
function tag_of(state) {
|
|
38
|
+
return state >>> TAG_SHIFT;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {number} state
|
|
43
|
+
* @returns {number}
|
|
44
|
+
*/
|
|
45
|
+
function index_of(state) {
|
|
46
|
+
return state & INDEX_MASK;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* One registered container: its reader, its geometry row in the pool, and one word per page.
|
|
51
|
+
*/
|
|
52
|
+
class VGeoResidencyGeometry {
|
|
53
|
+
/**
|
|
54
|
+
* @type {VGeoContainerReader}
|
|
55
|
+
*/
|
|
56
|
+
reader;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @type {number}
|
|
60
|
+
*/
|
|
61
|
+
row;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Per container page index, where the page is — see the tags above.
|
|
65
|
+
* @type {Uint32Array}
|
|
66
|
+
*/
|
|
67
|
+
state;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Per container page index, the RAM slot the page holds while it is in RAM or VRAM (VRAM is a
|
|
71
|
+
* subset of RAM), or {@link NO_SLOT}.
|
|
72
|
+
* @type {Uint32Array}
|
|
73
|
+
*/
|
|
74
|
+
slot;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Fetches this reading dispatched for this container and how they ended, so that a container
|
|
78
|
+
* whose every fetch of a reading was quarantined is reported once.
|
|
79
|
+
* @type {{size: number, settled: number, quarantined: number}|undefined}
|
|
80
|
+
*/
|
|
81
|
+
batch;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {VGeoContainerReader} reader
|
|
85
|
+
* @param {number} row
|
|
86
|
+
* @param {number} page_count
|
|
87
|
+
*/
|
|
88
|
+
constructor(reader, row, page_count) {
|
|
89
|
+
this.reader = reader;
|
|
90
|
+
this.row = row;
|
|
91
|
+
this.state = new Uint32Array(page_count);
|
|
92
|
+
this.slot = new Uint32Array(page_count).fill(NO_SLOT);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Both tiers of residency for every registered container, flat, and nothing per frame that scales
|
|
98
|
+
* with what is resident.
|
|
99
|
+
*
|
|
100
|
+
* `VIRTUAL_GEOMETRY_DESIGN.md` §6 names the tiers and the rules — RAM ⊇ VRAM, a byte budget each,
|
|
101
|
+
* LRU on last-touched under a protect window, never a page whose child is resident in the same
|
|
102
|
+
* tier, the root pinned, admission before the fetch. What this class changes is not a rule but the
|
|
103
|
+
* shape everything is kept in (RUNTIME_PLAN_2026_09_14 §4). Two owners kept the rules before it:
|
|
104
|
+
* `GPUVirtualGeometryPool` for VRAM, with a `Map` of touch stamps it walked once per install to
|
|
105
|
+
* find the coldest row, and `VGeoResidencyManager` for RAM, which built and sorted an array of
|
|
106
|
+
* every resident page of every reader whenever its budget bound. On the Zorah bdf6f961 container at
|
|
107
|
+
* 4,096 slots the first was 0.55 ms per refused install and the second the same defect in the tier
|
|
108
|
+
* above, and neither scaled with anything the host had chosen.
|
|
109
|
+
*
|
|
110
|
+
* So:
|
|
111
|
+
*
|
|
112
|
+
* - **One word per page**, beside the lookup and the same length, says where a page is — absent,
|
|
113
|
+
* fetching, quarantined, in RAM at a slot, in VRAM at a row. A want is dropped, promoted or
|
|
114
|
+
* fetched by one read of it, which is the whole of the cross-frame dedup.
|
|
115
|
+
* - **Struct-of-arrays per tier**, sized to a capacity the host stated: last use, bytes, the page,
|
|
116
|
+
* the key. No `Map` is walked per frame.
|
|
117
|
+
* - **Touches arrive as bits** and are folded into `last_used` per set bit; a page in RAM and not in
|
|
118
|
+
* VRAM cannot be touched, so its last use is the frame it left VRAM, written once.
|
|
119
|
+
* - **Eviction is CLOCK under a test budget.** A hand over each tier's array tests candidates in
|
|
120
|
+
* order and evicts the first that may go; a reading may run at most
|
|
121
|
+
* `max_eviction_tests_per_reading` tests across both tiers, after which it refuses what is left
|
|
122
|
+
* and the hand carries on next reading. A full tier therefore turns over at a rate the options
|
|
123
|
+
* chose, never at one the resident count chose. Not exact LRU, deliberately: the protect window
|
|
124
|
+
* already coarsens the order to "not recently", and an exact order costs an update per touch.
|
|
125
|
+
* - **Admission before the fetch**, against the RAM budget and a slot, out of the `decoded_size`
|
|
126
|
+
* the parent's child table carries (FORMAT §10) — a page that will not fit is never fetched.
|
|
127
|
+
* - **VRAM ⊆ RAM is enforced**: the RAM hand skips a page that is in VRAM, so the page a want was
|
|
128
|
+
* discovered through is in RAM whenever the want is valid, and its child table resolves the
|
|
129
|
+
* range. Before this nothing enforced it, and a want whose source the RAM tier had dropped was
|
|
130
|
+
* silently skipped.
|
|
131
|
+
*
|
|
132
|
+
* A registered reader is this class's: its page map is driven through `install` and `evict` from
|
|
133
|
+
* here, and a tool or a viewer driving the same reader alongside it is unsupported rather than
|
|
134
|
+
* reconciled per frame.
|
|
135
|
+
*
|
|
136
|
+
* @author Alex Goldring
|
|
137
|
+
* @copyright Company Named Limited (c) 2026
|
|
138
|
+
*/
|
|
139
|
+
export class VGeoResidency {
|
|
140
|
+
/**
|
|
141
|
+
* @type {GPUVirtualGeometryPool}
|
|
142
|
+
*/
|
|
143
|
+
#pool;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @type {VGeoResidencyOptions}
|
|
147
|
+
*/
|
|
148
|
+
#options;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @type {ConcurrencyGate}
|
|
152
|
+
*/
|
|
153
|
+
#gate;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @type {VGeoPageFetcher}
|
|
157
|
+
*/
|
|
158
|
+
#fetcher;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Whether {@link destroy} terminates the fetcher's workers — it does when this made it.
|
|
162
|
+
* @type {boolean}
|
|
163
|
+
*/
|
|
164
|
+
#owns_fetcher;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @type {Map<VGeoContainerReader, VGeoResidencyGeometry>}
|
|
168
|
+
*/
|
|
169
|
+
#geometries = new Map();
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Geometry row to its record, which is how a page row found through the pool reaches its
|
|
173
|
+
* container.
|
|
174
|
+
* @type {Map<number, VGeoResidencyGeometry>}
|
|
175
|
+
*/
|
|
176
|
+
#by_row = new Map();
|
|
177
|
+
|
|
178
|
+
// ------------------------------------------------------------------ the VRAM tier
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Per page row, the frame it was last drawn from — or installed at, so a page promoted in this
|
|
182
|
+
* reading is inside the protect window and not this reading's next victim.
|
|
183
|
+
* @type {Uint32Array}
|
|
184
|
+
*/
|
|
185
|
+
#vram_last_used;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @type {Uint8Array}
|
|
189
|
+
*/
|
|
190
|
+
#vram_pinned;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @type {number}
|
|
194
|
+
*/
|
|
195
|
+
#vram_hand = 0;
|
|
196
|
+
|
|
197
|
+
// ------------------------------------------------------------------ the RAM tier
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @type {(VGeoPage|undefined)[]}
|
|
201
|
+
*/
|
|
202
|
+
#ram_page;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Geometry row per slot, or -1 for a free one.
|
|
206
|
+
* @type {Int32Array}
|
|
207
|
+
*/
|
|
208
|
+
#ram_geometry;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @type {Uint32Array}
|
|
212
|
+
*/
|
|
213
|
+
#ram_page_index;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Per slot, the frame the page last left VRAM, or entered RAM.
|
|
217
|
+
* @type {Uint32Array}
|
|
218
|
+
*/
|
|
219
|
+
#ram_last;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Decoded bytes per slot; what the tier is charged and refunded.
|
|
223
|
+
* @type {Uint32Array}
|
|
224
|
+
*/
|
|
225
|
+
#ram_bytes;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @type {number[]}
|
|
229
|
+
*/
|
|
230
|
+
#ram_free = [];
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @type {number}
|
|
234
|
+
*/
|
|
235
|
+
#ram_hand = 0;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* @type {number}
|
|
239
|
+
*/
|
|
240
|
+
#ram_resident_bytes = 0;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Bytes admitted for fetches that have not landed, charged against the budget from the moment
|
|
244
|
+
* of admission so that a reading cannot admit more than the tier will have room for.
|
|
245
|
+
* @type {number}
|
|
246
|
+
*/
|
|
247
|
+
#ram_reserved_bytes = 0;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* @type {number}
|
|
251
|
+
*/
|
|
252
|
+
#ram_count = 0;
|
|
253
|
+
|
|
254
|
+
// ------------------------------------------------------------------ per reading
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @type {number}
|
|
258
|
+
*/
|
|
259
|
+
#tests_left = 0;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @type {number}
|
|
263
|
+
*/
|
|
264
|
+
#last_touch_count = 0;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* @type {number}
|
|
268
|
+
*/
|
|
269
|
+
#last_want_count = 0;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Scratch for ranking a reading's wants: record index by priority.
|
|
273
|
+
* @type {number[]}
|
|
274
|
+
*/
|
|
275
|
+
#order = [];
|
|
276
|
+
|
|
277
|
+
// ------------------------------------------------------------------ counters
|
|
278
|
+
|
|
279
|
+
#evicted_vram_count = 0;
|
|
280
|
+
#evicted_ram_count = 0;
|
|
281
|
+
#refused_install_count = 0;
|
|
282
|
+
#refused_fetch_count = 0;
|
|
283
|
+
#deferred_want_count = 0;
|
|
284
|
+
#deduped_want_count = 0;
|
|
285
|
+
#stale_record_count = 0;
|
|
286
|
+
#lost_source_count = 0;
|
|
287
|
+
#moot_record_count = 0;
|
|
288
|
+
#failed_batch_count = 0;
|
|
289
|
+
#fetches_in_flight = 0;
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* @param {Object} options
|
|
293
|
+
* @param {GPUVirtualGeometryPool} options.pool
|
|
294
|
+
* @param {VGeoResidencyOptions} [options.options]
|
|
295
|
+
* @param {ConcurrencyGate} [options.gate] a fetch gate shared with other residencies
|
|
296
|
+
* @param {VGeoPageFetcher} [options.fetcher] where pages are fetched and decoded; by default
|
|
297
|
+
* an in-process one, because only a host that can spawn a worker knows how
|
|
298
|
+
*/
|
|
299
|
+
constructor({ pool, options = new VGeoResidencyOptions(), gate = undefined, fetcher = undefined }) {
|
|
300
|
+
assert.defined(pool, 'pool');
|
|
301
|
+
|
|
302
|
+
this.#pool = pool;
|
|
303
|
+
this.#options = options;
|
|
304
|
+
this.#gate = gate ?? new ConcurrencyGate(options.max_concurrent_fetches);
|
|
305
|
+
this.#fetcher = fetcher ?? new VGeoPageFetcher();
|
|
306
|
+
this.#owns_fetcher = fetcher === undefined;
|
|
307
|
+
|
|
308
|
+
const rows = pool.tables.page_capacity;
|
|
309
|
+
|
|
310
|
+
this.#vram_last_used = new Uint32Array(rows);
|
|
311
|
+
this.#vram_pinned = new Uint8Array(rows);
|
|
312
|
+
|
|
313
|
+
const slots = options.ram_slot_capacity;
|
|
314
|
+
|
|
315
|
+
assert.isPositiveInteger(slots, 'ram_slot_capacity');
|
|
316
|
+
|
|
317
|
+
this.#ram_page = new Array(slots).fill(undefined);
|
|
318
|
+
this.#ram_geometry = new Int32Array(slots).fill(-1);
|
|
319
|
+
this.#ram_page_index = new Uint32Array(slots);
|
|
320
|
+
this.#ram_last = new Uint32Array(slots);
|
|
321
|
+
this.#ram_bytes = new Uint32Array(slots);
|
|
322
|
+
|
|
323
|
+
for (let slot = slots - 1; slot >= 0; slot--) {
|
|
324
|
+
this.#ram_free.push(slot);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ------------------------------------------------------------------ readings
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* @returns {VGeoResidencyOptions}
|
|
332
|
+
*/
|
|
333
|
+
get options() {
|
|
334
|
+
return this.#options;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @returns {ConcurrencyGate}
|
|
339
|
+
*/
|
|
340
|
+
get gate() {
|
|
341
|
+
return this.#gate;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* @returns {VGeoPageFetcher}
|
|
346
|
+
*/
|
|
347
|
+
get fetcher() {
|
|
348
|
+
return this.#fetcher;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* @returns {GPUVirtualGeometryPool}
|
|
353
|
+
*/
|
|
354
|
+
get pool() {
|
|
355
|
+
return this.#pool;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* The clock both tiers read: the pool's frame, which the runtime advances at `begin_frame`
|
|
360
|
+
* whether or not a reading is scheduled that frame.
|
|
361
|
+
* @returns {number}
|
|
362
|
+
*/
|
|
363
|
+
get frame() {
|
|
364
|
+
return this.#pool.frame;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Decoded bytes the VRAM tier may hold: the option, or what the pool's slots hold at its page
|
|
369
|
+
* size. Read each time, so a host may move it.
|
|
370
|
+
* @returns {number}
|
|
371
|
+
*/
|
|
372
|
+
get vram_budget_bytes() {
|
|
373
|
+
return this.#options.vram_budget_bytes
|
|
374
|
+
?? this.#pool.tables.page_capacity * this.#pool.page_size;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
get ram_budget_bytes() {
|
|
378
|
+
return this.#options.ram_budget_bytes;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
get ram_resident_bytes() {
|
|
382
|
+
return this.#ram_resident_bytes;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
get ram_page_count() {
|
|
386
|
+
return this.#ram_count;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
get reader_count() {
|
|
390
|
+
return this.#geometries.size;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
get evicted_vram_count() {
|
|
394
|
+
return this.#evicted_vram_count;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
get evicted_ram_count() {
|
|
398
|
+
return this.#evicted_ram_count;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Promotions the VRAM tier had no room for — no slot could be freed under the reading's test
|
|
403
|
+
* budget, or the page did not fit one.
|
|
404
|
+
* @returns {number}
|
|
405
|
+
*/
|
|
406
|
+
get refused_install_count() {
|
|
407
|
+
return this.#refused_install_count;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Fetches the RAM tier had no room for.
|
|
412
|
+
* @returns {number}
|
|
413
|
+
*/
|
|
414
|
+
get refused_fetch_count() {
|
|
415
|
+
return this.#refused_fetch_count;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Wants past a reading's promotion or fetch budget. Not a failure: the next cut asks again.
|
|
420
|
+
* @returns {number}
|
|
421
|
+
*/
|
|
422
|
+
get deferred_want_count() {
|
|
423
|
+
return this.#deferred_want_count;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Wants for a page already in flight, or already in VRAM by the time the reading landed.
|
|
428
|
+
* @returns {number}
|
|
429
|
+
*/
|
|
430
|
+
get deduped_want_count() {
|
|
431
|
+
return this.#deduped_want_count;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Records naming a page row that was refilled between the cut and the reading.
|
|
436
|
+
* @returns {number}
|
|
437
|
+
*/
|
|
438
|
+
get stale_record_count() {
|
|
439
|
+
return this.#stale_record_count;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Wants whose source page was not in RAM, which the RAM tier's hand is meant to make impossible.
|
|
444
|
+
* @returns {number}
|
|
445
|
+
*/
|
|
446
|
+
get lost_source_count() {
|
|
447
|
+
return this.#lost_source_count;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Records that named an empty row, an unregistered container, or a child reference the source
|
|
452
|
+
* page's table does not carry. The first is ordinary — a page evicted between the cut and the
|
|
453
|
+
* reading; the others a cut over a validated container does not produce.
|
|
454
|
+
* @returns {number}
|
|
455
|
+
*/
|
|
456
|
+
get moot_record_count() {
|
|
457
|
+
return this.#moot_record_count;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Readings in which every fetch dispatched for one container was quarantined.
|
|
462
|
+
* @returns {number}
|
|
463
|
+
*/
|
|
464
|
+
get failed_batch_count() {
|
|
465
|
+
return this.#failed_batch_count;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
get fetches_in_flight() {
|
|
469
|
+
return this.#fetches_in_flight;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
get last_touch_count() {
|
|
473
|
+
return this.#last_touch_count;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
get last_want_count() {
|
|
477
|
+
return this.#last_want_count;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* @returns {number}
|
|
482
|
+
*/
|
|
483
|
+
get quarantined_page_count() {
|
|
484
|
+
let count = 0;
|
|
485
|
+
|
|
486
|
+
for (const geometry of this.#geometries.values()) {
|
|
487
|
+
count += geometry.reader.quarantined_page_count;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return count;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ------------------------------------------------------------------ containers
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Take a container under both tiers: a geometry row and a lookup in the pool, its root page in
|
|
497
|
+
* RAM and VRAM, pinned in both.
|
|
498
|
+
*
|
|
499
|
+
* A refusal leaves nothing behind. Both ways this fails — a container built at another page
|
|
500
|
+
* size, and a tier with no room for another root — throw, and the pool's row is given back
|
|
501
|
+
* before they do; the message says what the tier holds.
|
|
502
|
+
*
|
|
503
|
+
* @param {VGeoContainerReader} reader open, with its root page resident
|
|
504
|
+
* @returns {number} the geometry row
|
|
505
|
+
*/
|
|
506
|
+
register(reader) {
|
|
507
|
+
assert.defined(reader, 'reader');
|
|
508
|
+
|
|
509
|
+
const existing = this.#geometries.get(reader);
|
|
510
|
+
|
|
511
|
+
if (existing !== undefined) {
|
|
512
|
+
return existing.row;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
assert.defined(reader.root, 'reader.root — the container must be open');
|
|
516
|
+
|
|
517
|
+
const row = this.#pool.register(reader);
|
|
518
|
+
|
|
519
|
+
const geometry = new VGeoResidencyGeometry(reader, row, reader.header.total_pages);
|
|
520
|
+
|
|
521
|
+
this.#geometries.set(reader, geometry);
|
|
522
|
+
this.#by_row.set(row, geometry);
|
|
523
|
+
|
|
524
|
+
const root = reader.root;
|
|
525
|
+
|
|
526
|
+
// a registration is a reading of its own as far as the eviction budget is concerned
|
|
527
|
+
this.#tests_left = this.#options.max_eviction_tests_per_reading;
|
|
528
|
+
|
|
529
|
+
const page_row = this.#make_ram_room(root.decoded_size)
|
|
530
|
+
? (this.#take_ram_slot(geometry, root), this.#promote(geometry, root))
|
|
531
|
+
: -1;
|
|
532
|
+
|
|
533
|
+
if (page_row === -1) {
|
|
534
|
+
this.#drop_geometry(geometry);
|
|
535
|
+
|
|
536
|
+
throw new Error(
|
|
537
|
+
`virtual geometry: the root page of this container could not be made resident, so`
|
|
538
|
+
+ ` it would draw nothing and every cut seeded from it would read an absent row.`
|
|
539
|
+
+ ` The VRAM tier holds ${this.#pool.tables.page_capacity} slots and`
|
|
540
|
+
+ ` ${this.#pool.resident_page_count} are taken, of a ${this.vram_budget_bytes}-byte`
|
|
541
|
+
+ ` budget with ${this.#pool.resident_bytes} spent; the RAM tier holds`
|
|
542
|
+
+ ` ${this.#ram_count} pages of a ${this.#options.ram_budget_bytes}-byte budget`
|
|
543
|
+
+ ` with ${this.#ram_resident_bytes} spent.`
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
this.#vram_pinned[page_row] = 1;
|
|
548
|
+
|
|
549
|
+
return row;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Give a container back: its rows, its lookup, its RAM slots, and the fetches it had on the
|
|
554
|
+
* wire. The decoded pages stay in the reader's map for the host to drop or keep.
|
|
555
|
+
*
|
|
556
|
+
* @param {VGeoContainerReader} reader
|
|
557
|
+
* @returns {boolean} whether it was registered
|
|
558
|
+
*/
|
|
559
|
+
unregister(reader) {
|
|
560
|
+
const geometry = this.#geometries.get(reader);
|
|
561
|
+
|
|
562
|
+
if (geometry === undefined) {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// a reply that lands after this finds no geometry and is dropped
|
|
567
|
+
this.#fetcher.cancel(reader);
|
|
568
|
+
reader.cancel_all();
|
|
569
|
+
|
|
570
|
+
this.#drop_geometry(geometry);
|
|
571
|
+
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* @param {VGeoContainerReader} reader
|
|
577
|
+
* @returns {boolean}
|
|
578
|
+
*/
|
|
579
|
+
is_registered(reader) {
|
|
580
|
+
return this.#geometries.has(reader);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Where one page of a registered container is, by name — for a panel or a spec.
|
|
585
|
+
*
|
|
586
|
+
* @param {VGeoContainerReader} reader
|
|
587
|
+
* @param {number} page_index
|
|
588
|
+
* @returns {'absent'|'fetching'|'quarantined'|'ram'|'vram'|'unregistered'}
|
|
589
|
+
*/
|
|
590
|
+
where(reader, page_index) {
|
|
591
|
+
const geometry = this.#geometries.get(reader);
|
|
592
|
+
|
|
593
|
+
if (geometry === undefined || page_index >= geometry.state.length) {
|
|
594
|
+
return 'unregistered';
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const state = geometry.state[page_index];
|
|
598
|
+
|
|
599
|
+
switch (tag_of(state)) {
|
|
600
|
+
case TAG_RAM:
|
|
601
|
+
return 'ram';
|
|
602
|
+
case TAG_VRAM:
|
|
603
|
+
return 'vram';
|
|
604
|
+
case TAG_PENDING:
|
|
605
|
+
return state === STATE_FETCHING ? 'fetching' : 'quarantined';
|
|
606
|
+
default:
|
|
607
|
+
return 'absent';
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* @param {VGeoResidencyGeometry} geometry
|
|
613
|
+
*/
|
|
614
|
+
#drop_geometry(geometry) {
|
|
615
|
+
const state = geometry.state;
|
|
616
|
+
|
|
617
|
+
for (let page_index = 0; page_index < state.length; page_index++) {
|
|
618
|
+
const tag = tag_of(state[page_index]);
|
|
619
|
+
|
|
620
|
+
if (tag === TAG_VRAM) {
|
|
621
|
+
const row = index_of(state[page_index]);
|
|
622
|
+
|
|
623
|
+
this.#vram_pinned[row] = 0;
|
|
624
|
+
this.#vram_last_used[row] = 0;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (tag === TAG_VRAM || tag === TAG_RAM) {
|
|
628
|
+
this.#free_ram_slot(geometry.slot[page_index]);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
state[page_index] = STATE_ABSENT;
|
|
632
|
+
geometry.slot[page_index] = NO_SLOT;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
this.#pool.unregister(geometry.reader);
|
|
636
|
+
|
|
637
|
+
this.#geometries.delete(geometry.reader);
|
|
638
|
+
this.#by_row.delete(geometry.row);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Stop every fetch every registered container has on the wire.
|
|
643
|
+
*
|
|
644
|
+
* @returns {void}
|
|
645
|
+
*/
|
|
646
|
+
abort() {
|
|
647
|
+
for (const geometry of this.#geometries.values()) {
|
|
648
|
+
this.#fetcher.cancel(geometry.reader);
|
|
649
|
+
geometry.reader.cancel_all();
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Give up on everything: every fetch, every container.
|
|
655
|
+
*
|
|
656
|
+
* @returns {void}
|
|
657
|
+
*/
|
|
658
|
+
destroy() {
|
|
659
|
+
for (const reader of Array.from(this.#geometries.keys())) {
|
|
660
|
+
this.unregister(reader);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (this.#owns_fetcher) {
|
|
664
|
+
this.#fetcher.destroy();
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Let every quarantined page be asked for again.
|
|
670
|
+
*
|
|
671
|
+
* @returns {number} pages released
|
|
672
|
+
*/
|
|
673
|
+
clear_quarantines() {
|
|
674
|
+
let released = 0;
|
|
675
|
+
|
|
676
|
+
for (const geometry of this.#geometries.values()) {
|
|
677
|
+
released += geometry.reader.clear_quarantine();
|
|
678
|
+
|
|
679
|
+
const state = geometry.state;
|
|
680
|
+
|
|
681
|
+
for (let page_index = 0; page_index < state.length; page_index++) {
|
|
682
|
+
if (state[page_index] === STATE_QUARANTINED) {
|
|
683
|
+
state[page_index] = STATE_ABSENT;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return released;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// ------------------------------------------------------------------ a reading
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Apply one frame's feedback: the touches to both tiers' clocks, and the wants to promotions and
|
|
695
|
+
* fetches, under the reading's budgets.
|
|
696
|
+
*
|
|
697
|
+
* @param {Uint32Array} words the feedback buffer, decoded with its own frame's layout
|
|
698
|
+
* @param {{touch_bits_base: number, touch_word_count: number, want_base: number,
|
|
699
|
+
* word_count: number}} layout
|
|
700
|
+
* @param {number} reading_frame the frame whose cut wrote these words
|
|
701
|
+
* @returns {void}
|
|
702
|
+
*/
|
|
703
|
+
apply(words, layout, reading_frame) {
|
|
704
|
+
const options = this.#options;
|
|
705
|
+
|
|
706
|
+
this.#tests_left = options.max_eviction_tests_per_reading;
|
|
707
|
+
|
|
708
|
+
for (const geometry of this.#geometries.values()) {
|
|
709
|
+
geometry.batch = undefined;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
this.#fold_touches(words, layout, reading_frame);
|
|
713
|
+
|
|
714
|
+
const count = this.#rank_wants(words, layout);
|
|
715
|
+
|
|
716
|
+
let promotions = 0;
|
|
717
|
+
let fetches = 0;
|
|
718
|
+
|
|
719
|
+
for (let i = 0; i < count; i++) {
|
|
720
|
+
const base = layout.want_base + this.#order[i] * VGEO_WANT_WORDS;
|
|
721
|
+
|
|
722
|
+
const source_row = words[base];
|
|
723
|
+
const child_ref = words[base + 1];
|
|
724
|
+
const child_page_index = words[base + 2];
|
|
725
|
+
const priority = decode_float(words[base + 3]);
|
|
726
|
+
|
|
727
|
+
const entry = this.#pool.entry_of(source_row);
|
|
728
|
+
|
|
729
|
+
if (entry === undefined) {
|
|
730
|
+
// evicted between the cut and this reading, and the row is empty: moot
|
|
731
|
+
this.#moot_record_count++;
|
|
732
|
+
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (!this.#still_the_same_page(source_row, reading_frame)) {
|
|
737
|
+
// the row was refilled between the cut and this reading, so the page index and the
|
|
738
|
+
// byte range the child table would give come from two different pages
|
|
739
|
+
this.#stale_record_count++;
|
|
740
|
+
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const geometry = this.#geometries.get(entry.reader);
|
|
745
|
+
|
|
746
|
+
if (geometry === undefined || child_page_index >= geometry.state.length) {
|
|
747
|
+
this.#moot_record_count++;
|
|
748
|
+
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const state = geometry.state[child_page_index];
|
|
753
|
+
const tag = tag_of(state);
|
|
754
|
+
|
|
755
|
+
if (tag === TAG_VRAM || tag === TAG_PENDING) {
|
|
756
|
+
// promoted a reading or two ago and the cut had not seen it yet; or on the wire, or
|
|
757
|
+
// not coming back until somebody clears the quarantine
|
|
758
|
+
this.#deduped_want_count++;
|
|
759
|
+
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (tag === TAG_RAM) {
|
|
764
|
+
if (promotions >= options.max_promotions_per_reading) {
|
|
765
|
+
this.#deferred_want_count++;
|
|
766
|
+
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const page = this.#ram_page[geometry.slot[child_page_index]];
|
|
771
|
+
|
|
772
|
+
if (this.#promote(geometry, page) !== -1) {
|
|
773
|
+
promotions++;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// absent: a fetch, through the page the want was discovered through
|
|
780
|
+
if (fetches >= options.max_fetches_per_reading
|
|
781
|
+
|| this.#fetches_in_flight >= options.max_concurrent_fetches * 2) {
|
|
782
|
+
this.#deferred_want_count++;
|
|
783
|
+
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const source_index = this.#pool.page_index_of(source_row);
|
|
788
|
+
|
|
789
|
+
const source = entry.reader.page(source_index);
|
|
790
|
+
|
|
791
|
+
if (source === undefined) {
|
|
792
|
+
this.#lost_source_count++;
|
|
793
|
+
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
if (child_ref >= source.child_page_index.length
|
|
798
|
+
|| source.child_page_index[child_ref] !== child_page_index) {
|
|
799
|
+
// a record the child table cannot resolve, which a cut over a validated container
|
|
800
|
+
// does not produce
|
|
801
|
+
this.#moot_record_count++;
|
|
802
|
+
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (this.#fetch(
|
|
807
|
+
geometry,
|
|
808
|
+
child_page_index,
|
|
809
|
+
source.child_frame_offset[child_ref],
|
|
810
|
+
source.child_fetch_size[child_ref],
|
|
811
|
+
source.child_decoded_size[child_ref],
|
|
812
|
+
priority
|
|
813
|
+
)) {
|
|
814
|
+
fetches++;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/*
|
|
819
|
+
And the budgets themselves, once a reading, under whatever is left of the test budget. A
|
|
820
|
+
host that lowers a budget while the tiers are full gets them back under it over the
|
|
821
|
+
following readings rather than only when the next admission happens to ask; within budget
|
|
822
|
+
this is two comparisons.
|
|
823
|
+
*/
|
|
824
|
+
this.#make_vram_room(0, false);
|
|
825
|
+
this.#make_ram_room(0, false);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Whether a page row still holds the page it held when the cut of `reading_frame` ran.
|
|
830
|
+
*
|
|
831
|
+
* @param {number} page_row
|
|
832
|
+
* @param {number} reading_frame
|
|
833
|
+
* @returns {boolean}
|
|
834
|
+
*/
|
|
835
|
+
#still_the_same_page(page_row, reading_frame) {
|
|
836
|
+
return this.#pool.visible_from(page_row) <= reading_frame;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* @param {Uint32Array} words
|
|
841
|
+
* @param {object} layout
|
|
842
|
+
* @param {number} reading_frame
|
|
843
|
+
*/
|
|
844
|
+
#fold_touches(words, layout, reading_frame) {
|
|
845
|
+
const frame = this.frame;
|
|
846
|
+
|
|
847
|
+
let count = 0;
|
|
848
|
+
|
|
849
|
+
for (let word = 0; word < layout.touch_word_count; word++) {
|
|
850
|
+
let bits = words[layout.touch_bits_base + word];
|
|
851
|
+
|
|
852
|
+
while (bits !== 0) {
|
|
853
|
+
const bit = 31 - Math.clz32(bits & -bits);
|
|
854
|
+
|
|
855
|
+
bits &= bits - 1;
|
|
856
|
+
|
|
857
|
+
const page_row = word * 32 + bit;
|
|
858
|
+
|
|
859
|
+
count++;
|
|
860
|
+
|
|
861
|
+
if (this.#pool.entry_of(page_row) === undefined) {
|
|
862
|
+
// evicted between the cut and this reading; the stamp it would take is moot
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
if (!this.#still_the_same_page(page_row, reading_frame)) {
|
|
867
|
+
// evicted and refilled in between; stamping now would make a page nobody has
|
|
868
|
+
// drawn the hottest thing in the tier
|
|
869
|
+
this.#stale_record_count++;
|
|
870
|
+
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
this.#vram_last_used[page_row] = frame;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
this.#last_touch_count = count;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Order this reading's want records by priority, highest first.
|
|
883
|
+
*
|
|
884
|
+
* A sort of at most `want_capacity` indices. It is not on the GPU, deliberately: the list is
|
|
885
|
+
* capped, a sort of two thousand numbers is microseconds, and a device sort is a dozen dispatches
|
|
886
|
+
* to shave a few kilobytes off a readback (RUNTIME_PLAN_2026_09_14 §4.5).
|
|
887
|
+
*
|
|
888
|
+
* @param {Uint32Array} words
|
|
889
|
+
* @param {object} layout
|
|
890
|
+
* @returns {number} records
|
|
891
|
+
*/
|
|
892
|
+
#rank_wants(words, layout) {
|
|
893
|
+
const capacity = Math.floor((layout.word_count - layout.want_base) / VGEO_WANT_WORDS);
|
|
894
|
+
|
|
895
|
+
// clamped: the count is an atomicAdd that ran past the capacity the kernel refused to write at
|
|
896
|
+
const count = Math.min(words[VGEO_FEEDBACK_OFFSET.WANT_COUNT], capacity);
|
|
897
|
+
|
|
898
|
+
this.#last_want_count = count;
|
|
899
|
+
|
|
900
|
+
const order = this.#order;
|
|
901
|
+
|
|
902
|
+
order.length = count;
|
|
903
|
+
|
|
904
|
+
for (let i = 0; i < count; i++) {
|
|
905
|
+
order[i] = i;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const priority = i => decode_float(words[layout.want_base + i * VGEO_WANT_WORDS + 3]);
|
|
909
|
+
|
|
910
|
+
order.sort((a, b) => priority(b) - priority(a));
|
|
911
|
+
|
|
912
|
+
return count;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// ------------------------------------------------------------------ VRAM
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Whether a page row may be evicted from VRAM right now: resident, not pinned, outside the
|
|
919
|
+
* protect window, and with no child page resident in VRAM.
|
|
920
|
+
*
|
|
921
|
+
* @param {number} page_row
|
|
922
|
+
* @returns {boolean}
|
|
923
|
+
*/
|
|
924
|
+
evictable_vram(page_row) {
|
|
925
|
+
if (this.#pool.entry_of(page_row) === undefined) {
|
|
926
|
+
return false;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (this.#vram_pinned[page_row] !== 0) {
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (this.frame - this.#vram_last_used[page_row] < this.#options.protect_frames) {
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
return this.#pool.tables.resident_children(page_row) === 0;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Whether a RAM slot's page may be evicted from RAM right now: occupied, not the root, not in
|
|
942
|
+
* VRAM, outside the protect window, and with no child page resident in either tier.
|
|
943
|
+
*
|
|
944
|
+
* @param {number} slot
|
|
945
|
+
* @returns {boolean}
|
|
946
|
+
*/
|
|
947
|
+
evictable_ram(slot) {
|
|
948
|
+
const geometry_row = this.#ram_geometry[slot];
|
|
949
|
+
|
|
950
|
+
if (geometry_row === -1) {
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const page = this.#ram_page[slot];
|
|
955
|
+
|
|
956
|
+
if (page === undefined || page.index === 0) {
|
|
957
|
+
return false;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const geometry = this.#by_row.get(geometry_row);
|
|
961
|
+
|
|
962
|
+
if (tag_of(geometry.state[page.index]) === TAG_VRAM) {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (this.frame - this.#ram_last[slot] < this.#options.protect_frames) {
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const state = geometry.state;
|
|
971
|
+
|
|
972
|
+
for (const child of page.child_page_index) {
|
|
973
|
+
const tag = tag_of(state[child]);
|
|
974
|
+
|
|
975
|
+
if (tag === TAG_RAM || tag === TAG_VRAM) {
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
return true;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Make a page resident in VRAM out of RAM, making room under the reading's test budget.
|
|
985
|
+
*
|
|
986
|
+
* @param {VGeoResidencyGeometry} geometry
|
|
987
|
+
* @param {VGeoPage} page
|
|
988
|
+
* @returns {number} page row, or -1
|
|
989
|
+
*/
|
|
990
|
+
#promote(geometry, page) {
|
|
991
|
+
if (!this.#make_vram_room(page.decoded_size)) {
|
|
992
|
+
this.#refused_install_count++;
|
|
993
|
+
|
|
994
|
+
return -1;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const page_row = this.#pool.install(geometry.reader, page.index);
|
|
998
|
+
|
|
999
|
+
if (page_row === -1) {
|
|
1000
|
+
this.#refused_install_count++;
|
|
1001
|
+
|
|
1002
|
+
return -1;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
geometry.state[page.index] = (TAG_VRAM << TAG_SHIFT) | page_row;
|
|
1006
|
+
|
|
1007
|
+
this.#vram_last_used[page_row] = this.frame;
|
|
1008
|
+
this.#vram_pinned[page_row] = 0;
|
|
1009
|
+
|
|
1010
|
+
return page_row;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* @param {number} bytes
|
|
1015
|
+
* @param {boolean} [slot] whether a free page row is needed as well — it is for an install,
|
|
1016
|
+
* and not for bringing a tier back under a budget that moved
|
|
1017
|
+
* @returns {boolean}
|
|
1018
|
+
*/
|
|
1019
|
+
#make_vram_room(bytes, slot = true) {
|
|
1020
|
+
const pool = this.#pool;
|
|
1021
|
+
|
|
1022
|
+
for (;;) {
|
|
1023
|
+
if (pool.resident_bytes + bytes <= this.vram_budget_bytes
|
|
1024
|
+
&& (!slot || pool.tables.has_free_page_row)) {
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
const victim = this.#next_vram_victim();
|
|
1029
|
+
|
|
1030
|
+
if (victim === -1) {
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
this.#evict_vram(victim);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* The CLOCK hand over the page rows, under the reading's test budget.
|
|
1040
|
+
*
|
|
1041
|
+
* @returns {number} page row, or -1 when the budget is spent without finding one
|
|
1042
|
+
*/
|
|
1043
|
+
#next_vram_victim() {
|
|
1044
|
+
const capacity = this.#vram_last_used.length;
|
|
1045
|
+
|
|
1046
|
+
while (this.#tests_left > 0) {
|
|
1047
|
+
this.#tests_left--;
|
|
1048
|
+
|
|
1049
|
+
const page_row = this.#vram_hand;
|
|
1050
|
+
|
|
1051
|
+
this.#vram_hand = (page_row + 1) % capacity;
|
|
1052
|
+
|
|
1053
|
+
if (this.evictable_vram(page_row)) {
|
|
1054
|
+
return page_row;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return -1;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* @param {number} page_row
|
|
1063
|
+
*/
|
|
1064
|
+
#evict_vram(page_row) {
|
|
1065
|
+
const entry = this.#pool.entry_of(page_row);
|
|
1066
|
+
|
|
1067
|
+
const geometry = this.#geometries.get(entry.reader);
|
|
1068
|
+
|
|
1069
|
+
const page_index = this.#pool.page_index_of(page_row);
|
|
1070
|
+
|
|
1071
|
+
this.#pool.evict(page_row);
|
|
1072
|
+
|
|
1073
|
+
const slot = geometry.slot[page_index];
|
|
1074
|
+
|
|
1075
|
+
geometry.state[page_index] = (TAG_RAM << TAG_SHIFT) | slot;
|
|
1076
|
+
|
|
1077
|
+
// the last time it was drawn from is the last time it was in VRAM
|
|
1078
|
+
this.#ram_last[slot] = this.frame;
|
|
1079
|
+
|
|
1080
|
+
this.#vram_pinned[page_row] = 0;
|
|
1081
|
+
this.#vram_last_used[page_row] = 0;
|
|
1082
|
+
|
|
1083
|
+
this.#evicted_vram_count++;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// ------------------------------------------------------------------ RAM
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* @param {number} bytes
|
|
1090
|
+
* @param {boolean} [slot] whether a free slot is needed as well
|
|
1091
|
+
* @returns {boolean}
|
|
1092
|
+
*/
|
|
1093
|
+
#make_ram_room(bytes, slot = true) {
|
|
1094
|
+
const budget = this.#options.ram_budget_bytes;
|
|
1095
|
+
|
|
1096
|
+
for (;;) {
|
|
1097
|
+
if (this.#ram_resident_bytes + this.#ram_reserved_bytes + bytes <= budget
|
|
1098
|
+
&& (!slot || this.#ram_free.length > 0)) {
|
|
1099
|
+
return true;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
const victim = this.#next_ram_victim();
|
|
1103
|
+
|
|
1104
|
+
if (victim === -1) {
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
this.#evict_ram(victim);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* @returns {number} slot, or -1
|
|
1114
|
+
*/
|
|
1115
|
+
#next_ram_victim() {
|
|
1116
|
+
const capacity = this.#ram_geometry.length;
|
|
1117
|
+
|
|
1118
|
+
while (this.#tests_left > 0) {
|
|
1119
|
+
this.#tests_left--;
|
|
1120
|
+
|
|
1121
|
+
const slot = this.#ram_hand;
|
|
1122
|
+
|
|
1123
|
+
this.#ram_hand = (slot + 1) % capacity;
|
|
1124
|
+
|
|
1125
|
+
if (this.evictable_ram(slot)) {
|
|
1126
|
+
return slot;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
return -1;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* @param {number} slot
|
|
1135
|
+
*/
|
|
1136
|
+
#evict_ram(slot) {
|
|
1137
|
+
const geometry = this.#by_row.get(this.#ram_geometry[slot]);
|
|
1138
|
+
|
|
1139
|
+
const page_index = this.#ram_page_index[slot];
|
|
1140
|
+
|
|
1141
|
+
geometry.reader.evict(page_index);
|
|
1142
|
+
|
|
1143
|
+
geometry.state[page_index] = STATE_ABSENT;
|
|
1144
|
+
geometry.slot[page_index] = NO_SLOT;
|
|
1145
|
+
|
|
1146
|
+
this.#free_ram_slot(slot);
|
|
1147
|
+
|
|
1148
|
+
this.#evicted_ram_count++;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* Put a page the reader already holds into a slot. Room is the caller's business.
|
|
1153
|
+
*
|
|
1154
|
+
* @param {VGeoResidencyGeometry} geometry
|
|
1155
|
+
* @param {VGeoPage} page
|
|
1156
|
+
* @returns {number} slot
|
|
1157
|
+
*/
|
|
1158
|
+
#take_ram_slot(geometry, page) {
|
|
1159
|
+
const slot = this.#reserve_ram_slot(geometry, page.index, page.decoded_size);
|
|
1160
|
+
|
|
1161
|
+
this.#land(slot, page);
|
|
1162
|
+
|
|
1163
|
+
return slot;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Take a slot and charge the bytes, for a page that is here or on its way.
|
|
1168
|
+
*
|
|
1169
|
+
* @param {VGeoResidencyGeometry} geometry
|
|
1170
|
+
* @param {number} page_index
|
|
1171
|
+
* @param {number} bytes
|
|
1172
|
+
* @returns {number} slot
|
|
1173
|
+
*/
|
|
1174
|
+
#reserve_ram_slot(geometry, page_index, bytes) {
|
|
1175
|
+
const slot = this.#ram_free.pop();
|
|
1176
|
+
|
|
1177
|
+
this.#ram_geometry[slot] = geometry.row;
|
|
1178
|
+
this.#ram_page_index[slot] = page_index;
|
|
1179
|
+
this.#ram_page[slot] = undefined;
|
|
1180
|
+
this.#ram_bytes[slot] = bytes;
|
|
1181
|
+
this.#ram_last[slot] = this.frame;
|
|
1182
|
+
|
|
1183
|
+
this.#ram_reserved_bytes += bytes;
|
|
1184
|
+
|
|
1185
|
+
geometry.slot[page_index] = slot;
|
|
1186
|
+
|
|
1187
|
+
return slot;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* A reserved slot's page has arrived.
|
|
1192
|
+
*
|
|
1193
|
+
* @param {number} slot
|
|
1194
|
+
* @param {VGeoPage} page
|
|
1195
|
+
*/
|
|
1196
|
+
#land(slot, page) {
|
|
1197
|
+
const geometry = this.#by_row.get(this.#ram_geometry[slot]);
|
|
1198
|
+
|
|
1199
|
+
this.#ram_reserved_bytes -= this.#ram_bytes[slot];
|
|
1200
|
+
|
|
1201
|
+
// what it actually decoded to, which is what the tier holds and refunds
|
|
1202
|
+
this.#ram_bytes[slot] = page.decoded_size;
|
|
1203
|
+
this.#ram_page[slot] = page;
|
|
1204
|
+
this.#ram_last[slot] = this.frame;
|
|
1205
|
+
|
|
1206
|
+
this.#ram_resident_bytes += page.decoded_size;
|
|
1207
|
+
this.#ram_count++;
|
|
1208
|
+
|
|
1209
|
+
geometry.state[page.index] = (TAG_RAM << TAG_SHIFT) | slot;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* @param {number} slot
|
|
1214
|
+
*/
|
|
1215
|
+
#free_ram_slot(slot) {
|
|
1216
|
+
if (slot === NO_SLOT || this.#ram_geometry[slot] === -1) {
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
if (this.#ram_page[slot] !== undefined) {
|
|
1221
|
+
this.#ram_resident_bytes -= this.#ram_bytes[slot];
|
|
1222
|
+
this.#ram_count--;
|
|
1223
|
+
} else {
|
|
1224
|
+
this.#ram_reserved_bytes -= this.#ram_bytes[slot];
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
this.#ram_page[slot] = undefined;
|
|
1228
|
+
this.#ram_geometry[slot] = -1;
|
|
1229
|
+
this.#ram_bytes[slot] = 0;
|
|
1230
|
+
|
|
1231
|
+
this.#ram_free.push(slot);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// ------------------------------------------------------------------ fetching
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* Admit a page against the RAM tier and start its fetch.
|
|
1238
|
+
*
|
|
1239
|
+
* @param {VGeoResidencyGeometry} geometry
|
|
1240
|
+
* @param {number} page_index
|
|
1241
|
+
* @param {number} frame_offset
|
|
1242
|
+
* @param {number} fetch_size
|
|
1243
|
+
* @param {number} decoded_size
|
|
1244
|
+
* @param {number} priority
|
|
1245
|
+
* @returns {boolean} whether a fetch was started
|
|
1246
|
+
*/
|
|
1247
|
+
#fetch(geometry, page_index, frame_offset, fetch_size, decoded_size, priority) {
|
|
1248
|
+
if (!this.#make_ram_room(decoded_size)) {
|
|
1249
|
+
this.#refused_fetch_count++;
|
|
1250
|
+
|
|
1251
|
+
return false;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
const reader = geometry.reader;
|
|
1255
|
+
|
|
1256
|
+
const slot = this.#reserve_ram_slot(geometry, page_index, decoded_size);
|
|
1257
|
+
|
|
1258
|
+
geometry.state[page_index] = STATE_FETCHING;
|
|
1259
|
+
|
|
1260
|
+
if (geometry.batch === undefined) {
|
|
1261
|
+
geometry.batch = { size: 0, settled: 0, quarantined: 0 };
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
const batch = geometry.batch;
|
|
1265
|
+
|
|
1266
|
+
batch.size++;
|
|
1267
|
+
|
|
1268
|
+
this.#fetches_in_flight++;
|
|
1269
|
+
|
|
1270
|
+
this.#gate.submit(
|
|
1271
|
+
priority,
|
|
1272
|
+
() => this.#fetcher.fetch(reader, page_index, frame_offset, fetch_size)
|
|
1273
|
+
)
|
|
1274
|
+
.then(
|
|
1275
|
+
page => {
|
|
1276
|
+
this.#fetches_in_flight--;
|
|
1277
|
+
|
|
1278
|
+
if (this.#geometries.get(reader) !== geometry) {
|
|
1279
|
+
// unregistered while on the wire; the slot went with the geometry
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
if (geometry.state[page_index] !== STATE_FETCHING) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// the reader's page map is the RAM tier's store, and this is its one door
|
|
1288
|
+
this.#land(slot, reader.adopt(page));
|
|
1289
|
+
|
|
1290
|
+
this.#settle(reader, geometry, batch, false);
|
|
1291
|
+
},
|
|
1292
|
+
error => {
|
|
1293
|
+
this.#fetches_in_flight--;
|
|
1294
|
+
|
|
1295
|
+
if (this.#geometries.get(reader) !== geometry) {
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
if (geometry.state[page_index] !== STATE_FETCHING) {
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// a quarantine is the fetch's verdict and the reader's fact; a cancellation is
|
|
1304
|
+
// nobody's fault, and the next cut asks again
|
|
1305
|
+
const quarantined = error?.quarantine === true;
|
|
1306
|
+
|
|
1307
|
+
if (quarantined) {
|
|
1308
|
+
reader.quarantine_page(page_index, error.reason ?? error.message);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
geometry.state[page_index] = quarantined ? STATE_QUARANTINED : STATE_ABSENT;
|
|
1312
|
+
geometry.slot[page_index] = NO_SLOT;
|
|
1313
|
+
|
|
1314
|
+
this.#free_ram_slot(slot);
|
|
1315
|
+
|
|
1316
|
+
this.#settle(reader, geometry, batch, quarantined);
|
|
1317
|
+
}
|
|
1318
|
+
);
|
|
1319
|
+
|
|
1320
|
+
return true;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
/**
|
|
1324
|
+
* One fetch of a reading's batch has ended; a batch every page of which was quarantined is a
|
|
1325
|
+
* container that cannot be streamed, which is worth one line and a counter.
|
|
1326
|
+
*
|
|
1327
|
+
* @param {VGeoContainerReader} reader
|
|
1328
|
+
* @param {VGeoResidencyGeometry} geometry
|
|
1329
|
+
* @param {{size: number, settled: number, quarantined: number}} batch
|
|
1330
|
+
* @param {boolean} quarantined
|
|
1331
|
+
*/
|
|
1332
|
+
#settle(reader, geometry, batch, quarantined) {
|
|
1333
|
+
batch.settled++;
|
|
1334
|
+
|
|
1335
|
+
if (quarantined) {
|
|
1336
|
+
batch.quarantined++;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
if (batch.settled < batch.size || batch.quarantined < batch.size) {
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
this.#failed_batch_count++;
|
|
1344
|
+
|
|
1345
|
+
console.warn(
|
|
1346
|
+
`vgeo: every one of ${batch.size} page fetches was quarantined`
|
|
1347
|
+
+ ` by ${reader.name ?? 'a reader'}`
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
const FLOAT_SCRATCH = new Float32Array(1);
|
|
1353
|
+
const WORD_SCRATCH = new Uint32Array(FLOAT_SCRATCH.buffer);
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* @param {number} word
|
|
1357
|
+
* @returns {number}
|
|
1358
|
+
*/
|
|
1359
|
+
function decode_float(word) {
|
|
1360
|
+
WORD_SCRATCH[0] = word;
|
|
1361
|
+
|
|
1362
|
+
return FLOAT_SCRATCH[0];
|
|
1363
|
+
}
|