@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.
Files changed (236) hide show
  1. package/package.json +1 -1
  2. package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +542 -0
  3. package/src/core/binary/lz4/LZ4_LEVEL.d.ts +33 -0
  4. package/src/core/binary/lz4/LZ4_LEVEL.d.ts.map +1 -0
  5. package/src/core/binary/lz4/LZ4_LEVEL.js +32 -0
  6. package/src/core/binary/lz4/lz4_compress_block_at_level.d.ts +33 -0
  7. package/src/core/binary/lz4/lz4_compress_block_at_level.d.ts.map +1 -0
  8. package/src/core/binary/lz4/lz4_compress_block_at_level.js +47 -0
  9. package/src/core/binary/lz4/lz4_compress_block_hc.d.ts +40 -0
  10. package/src/core/binary/lz4/lz4_compress_block_hc.d.ts.map +1 -0
  11. package/src/core/binary/lz4/lz4_compress_block_hc.js +1382 -0
  12. package/src/core/binary/lz4/lz4_decompress_block.d.ts +2 -1
  13. package/src/core/binary/lz4/lz4_decompress_block.d.ts.map +1 -1
  14. package/src/core/binary/lz4/lz4_decompress_block.js +31 -6
  15. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +14 -7
  16. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
  17. package/src/core/geom/3d/shape/HeightMapShape3D.js +87 -78
  18. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  19. package/src/shade/playground/vgeo_runtime/README.md +167 -22
  20. package/src/shade/playground/vgeo_runtime/index.html +112 -62
  21. package/src/shade/playground/vgeo_runtime/main.js +660 -83
  22. package/src/shade/playground/vgeo_viewer/sample_asset.d.ts.map +1 -1
  23. package/src/shade/playground/vgeo_viewer/sample_asset.js +19 -2
  24. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts +11 -0
  25. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
  26. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +16 -8
  27. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
  28. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +16 -11
  29. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +73 -5
  30. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +26 -15
  31. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +5 -3
  32. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +18 -0
  33. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
  34. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +20 -0
  35. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +1 -1
  36. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +56 -9
  37. package/src/shade/renderer/geometry/virtual/build/vgeo_build.d.ts.map +1 -1
  38. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +1 -0
  39. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts +22 -0
  40. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts.map +1 -1
  41. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +24 -0
  42. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts +12 -0
  43. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts.map +1 -1
  44. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +13 -0
  45. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts +8 -1
  46. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts.map +1 -1
  47. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +11 -3
  48. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts +9 -0
  49. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts.map +1 -1
  50. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +10 -1
  51. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.d.ts +18 -0
  52. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.d.ts.map +1 -0
  53. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.js +17 -0
  54. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.d.ts +8 -0
  55. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.d.ts.map +1 -0
  56. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.js +7 -0
  57. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.d.ts +3 -0
  58. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -0
  59. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.d.ts +14 -0
  60. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.d.ts.map +1 -0
  61. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.js +13 -0
  62. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.d.ts +11 -0
  63. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.d.ts.map +1 -0
  64. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.js +10 -0
  65. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts +20 -0
  66. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts.map +1 -1
  67. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.js +20 -2
  68. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts +4 -0
  69. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts.map +1 -1
  70. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.js +4 -0
  71. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts +29 -3
  72. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts.map +1 -1
  73. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.js +81 -122
  74. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts +40 -0
  75. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts.map +1 -1
  76. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.js +44 -0
  77. package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts +69 -0
  78. package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts.map +1 -0
  79. package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.js +236 -0
  80. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts +14 -0
  81. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts.map +1 -1
  82. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.js +15 -0
  83. package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.d.ts +31 -0
  84. package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.d.ts.map +1 -0
  85. package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.js +32 -0
  86. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts +10 -0
  87. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts.map +1 -1
  88. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.js +21 -1
  89. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.d.ts +15 -0
  90. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.d.ts.map +1 -0
  91. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.js +84 -0
  92. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.d.ts +22 -0
  93. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.d.ts.map +1 -0
  94. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.js +96 -0
  95. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts.map +1 -1
  96. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.js +55 -0
  97. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts.map +1 -1
  98. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +202 -13
  99. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
  100. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +147 -10
  101. package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +637 -0
  102. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts +33 -36
  103. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts.map +1 -1
  104. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.js +109 -193
  105. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +58 -28
  106. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -1
  107. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +77 -105
  108. package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +37 -0
  109. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +105 -22
  110. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +548 -0
  111. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts +29 -50
  112. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -1
  113. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +47 -489
  114. package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.d.ts +84 -0
  115. package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.d.ts.map +1 -0
  116. package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.js +393 -0
  117. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts +234 -0
  118. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -0
  119. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +1363 -0
  120. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.d.ts +81 -0
  121. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.d.ts.map +1 -0
  122. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.js +89 -0
  123. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts +53 -34
  124. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts.map +1 -1
  125. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.js +407 -124
  126. package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.d.ts +100 -0
  127. package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.d.ts.map +1 -0
  128. package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.js +219 -0
  129. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.d.ts +17 -0
  130. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.d.ts.map +1 -0
  131. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.js +47 -0
  132. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.d.ts +20 -0
  133. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.d.ts.map +1 -0
  134. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.js +19 -0
  135. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts.map +1 -1
  136. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.js +30 -12
  137. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts.map +1 -1
  138. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +15 -0
  139. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts.map +1 -1
  140. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.js +18 -1
  141. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts.map +1 -1
  142. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.js +5 -15
  143. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.d.ts +3 -0
  144. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.d.ts.map +1 -0
  145. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.js +77 -0
  146. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.d.ts +3 -0
  147. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.d.ts.map +1 -0
  148. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.js +186 -0
  149. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts.map +1 -1
  150. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.js +20 -8
  151. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -1
  152. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +580 -188
  153. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts +15 -18
  154. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts.map +1 -1
  155. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.js +19 -28
  156. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -1
  157. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +45 -25
  158. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts +1 -0
  159. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js +11 -0
  160. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.js +1 -1
  161. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts +0 -1
  162. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.js +12 -10
  163. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts +1 -0
  164. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.js +8 -0
  165. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts +7 -2
  166. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts.map +1 -1
  167. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.js +8 -3
  168. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts +2 -0
  169. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.js +19 -0
  170. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts.map +1 -1
  171. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.js +1 -1
  172. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.d.ts +5 -0
  173. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.d.ts.map +1 -0
  174. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.js +22 -0
  175. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.d.ts +14 -0
  176. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.d.ts.map +1 -0
  177. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.js +13 -0
  178. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.d.ts +17 -0
  179. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.d.ts.map +1 -0
  180. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.js +16 -0
  181. package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.d.ts +15 -0
  182. package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.d.ts.map +1 -0
  183. package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.js +16 -0
  184. package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.d.ts +23 -0
  185. package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.d.ts.map +1 -0
  186. package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.js +28 -0
  187. package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.d.ts +2 -0
  188. package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.d.ts.map +1 -0
  189. package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.js +13 -0
  190. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts +10 -5
  191. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts.map +1 -1
  192. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.js +15 -6
  193. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts +6 -1
  194. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts.map +1 -1
  195. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.js +38 -1
  196. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.d.ts.map +1 -1
  197. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.js +4 -3
  198. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  199. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  200. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +6 -3
  201. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  202. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +6 -3
  203. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  204. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  205. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +12 -4
  206. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts +2 -0
  207. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
  208. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +58 -57
  209. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.d.ts.map +1 -1
  210. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.js +3 -5
  211. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.d.ts.map +1 -1
  212. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.js +3 -4
  213. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.d.ts.map +1 -1
  214. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.js +3 -4
  215. package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.d.ts.map +1 -1
  216. package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.js +7 -7
  217. package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.d.ts.map +1 -1
  218. package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.js +83 -78
  219. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
  220. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +7 -1
  221. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
  222. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +7 -1
  223. package/src/shade/renderer/scene/GPUSceneContext.d.ts +22 -2
  224. package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
  225. package/src/shade/renderer/scene/GPUSceneContext.js +59 -4
  226. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts.map +1 -1
  227. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.js +92 -16
  228. package/src/shade/renderer/view/GPUViewContext.d.ts +5 -0
  229. package/src/shade/renderer/view/GPUViewContext.d.ts.map +1 -1
  230. package/src/shade/renderer/view/GPUViewContext.js +21 -0
  231. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  232. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +74 -3
  233. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.js +0 -723
  234. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.js +0 -50
  235. package/src/shade/renderer/shader/chunk/jobs/NOTES.md +0 -47
  236. 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 {VGeoContainerReader}
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 {VGeoResidencyManager}
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
- canvas_height = 600;
242
+ screen_width = 1;
134
243
 
135
244
  /**
136
245
  * @type {number}
137
246
  */
138
- fov_y = 50 * DEG_TO_RAD;
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: pool.evicted_page_count,
199
- refused: pool.refused_install_count,
612
+ evicted: this.residency.evicted_vram_count,
613
+ refused: this.residency.refused_install_count,
200
614
  },
201
615
  ram: {
202
- pages: this.reader.resident_page_count,
203
- bytes: this.reader.resident_bytes,
204
- total_pages: this.reader.header.total_pages,
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
- async function start() {
211
- const canvas = element('view');
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
- const status = text => {
214
- element('status').textContent = text;
658
+ let depth = 0;
659
+
660
+ const hide = () => {
661
+ depth = 0;
662
+
663
+ overlay.classList.remove('active');
215
664
  };
216
665
 
217
- const renderer = new Renderer();
666
+ globalThis.addEventListener('dragenter', event => {
667
+ event.preventDefault();
218
668
 
219
- await renderer.initialize({ context: canvas.getContext('webgpu'), pixelRatio: 1 });
669
+ depth++;
220
670
 
221
- renderer.feature_taa_enabled = false;
222
- renderer.feature_ssao_enabled = false;
223
- renderer.feature_bloom_enabled = false;
671
+ overlay.classList.add('active');
672
+ });
224
673
 
225
- renderer.resize(canvas.width, canvas.height);
674
+ globalThis.addEventListener('dragover', event => event.preventDefault());
226
675
 
227
- status('building a container in the page…');
676
+ globalThis.addEventListener('dragleave', () => {
677
+ depth = Math.max(0, depth - 1);
228
678
 
229
- /*
230
- One number for the asset and for the tier that holds it. A page size is a bound the container
231
- states (FORMAT §11.14) and the pool holds pages at exactly one, so a mismatch here is a refusal
232
- at `register` rather than something that limps.
233
- */
234
- const page_size = 64 * 1024;
679
+ if (depth === 0) {
680
+ overlay.classList.remove('active');
681
+ }
682
+ });
235
683
 
236
- const { source, build_ms } = vgeo_sample_source(128, 48, page_size);
684
+ globalThis.addEventListener('drop', async event => {
685
+ event.preventDefault();
237
686
 
238
- /*
239
- Frame verification on, because this reader is a runtime's rather than a tool's — format §10
240
- leaves it off by default precisely because a tool reading a file it just built is not
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
- options.verify_frames = true;
691
+ if (file === undefined) {
692
+ return;
693
+ }
246
694
 
247
- const reader = await VGeoContainerReader.open(source, options);
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.reader = reader;
252
- page.canvas_height = canvas.height;
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
- page.residency = new VGeoResidencyManager();
255
- page.residency.register(reader);
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 torus, so the scene names a dataset
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.near = 0.02;
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
- const controller = new OrbitalCameraController(camera, document.body);
761
+ page.camera = camera;
762
+ page.controller = new OrbitalCameraController(camera, document.body);
273
763
 
274
- controller.look(new Vector3(0, 1.6, 3.6), new Vector3(0, 0, 0));
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
- const runtime = scene_context.enable_virtual_geometry({ page_size });
282
-
283
- page.runtime = runtime;
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
- geometry.vg_row = geometry_row;
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
- place_primitive(
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
- status(`container built in ${build_ms.toFixed(0)} ms · ${reader.header.total_pages} pages`);
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', `${(state.vram.bytes / 1024).toFixed(0)} KB`],
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
+ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;',
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');