@woosh/meep-engine 3.39.0 → 3.40.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 (367) hide show
  1. package/build/bundle-worker-terrain.js +1 -1
  2. package/package.json +1 -1
  3. package/src/engine/EngineHarness.d.ts.map +1 -1
  4. package/src/engine/EngineHarness.js +17 -4
  5. package/src/engine/asset/loaders/image/avif/threaded_image_encoder.js +1 -1
  6. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
  7. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +4 -0
  8. package/src/engine/graphics/ecs/water/Water.d.ts +14 -17
  9. package/src/engine/graphics/ecs/water/Water.d.ts.map +1 -1
  10. package/src/engine/graphics/ecs/water/Water.js +100 -110
  11. package/src/engine/graphics/ecs/water/WaterSerializationAdapter.d.ts.map +1 -1
  12. package/src/engine/graphics/ecs/water/WaterSerializationAdapter.js +36 -38
  13. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_3_4.d.ts +6 -0
  14. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_3_4.d.ts.map +1 -0
  15. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_3_4.js +10 -0
  16. package/src/engine/graphics/ecs/water/WaterWaves.d.ts +3 -3
  17. package/src/engine/graphics/ecs/water/WaterWaves.js +46 -46
  18. package/src/engine/graphics/texture/sampler/sampler2d_to_avif.d.ts.map +1 -1
  19. package/src/engine/graphics/texture/sampler/sampler2d_to_avif.js +8 -5
  20. package/src/engine/graphics3/WaterSystem.d.ts +21 -74
  21. package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
  22. package/src/engine/graphics3/WaterSystem.js +174 -367
  23. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  24. package/src/format/image/avif/api/convert_to_rgba.d.ts +2 -2
  25. package/src/format/image/avif/api/convert_to_rgba.d.ts.map +1 -1
  26. package/src/format/image/avif/api/convert_to_rgba.js +10 -9
  27. package/src/format/image/avif/api/decode_avif.d.ts +2 -2
  28. package/src/format/image/avif/api/decode_avif.d.ts.map +1 -1
  29. package/src/format/image/avif/api/decode_avif.js +1 -1
  30. package/src/shade/playground/ssr_reprojection/README.md +111 -103
  31. package/src/shade/playground/ssr_reprojection/SpatialFilterProbe.js +68 -68
  32. package/src/shade/playground/ssr_reprojection/index.html +80 -79
  33. package/src/shade/playground/ssr_reprojection/main.js +256 -247
  34. package/src/shade/playground/ssr_variance/capture.js +96 -96
  35. package/src/shade/playground/ssr_variance/reference.js +78 -78
  36. package/src/shade/playground/vsm/README.md +96 -0
  37. package/src/shade/playground/vsm/VSMDebugOverlay.d.ts +49 -0
  38. package/src/shade/playground/vsm/VSMDebugOverlay.d.ts.map +1 -0
  39. package/src/shade/playground/vsm/VSMDebugOverlay.js +351 -0
  40. package/src/shade/playground/vsm/clipmap_panel.d.ts +8 -0
  41. package/src/shade/playground/vsm/clipmap_panel.d.ts.map +1 -0
  42. package/src/shade/playground/vsm/clipmap_panel.js +138 -0
  43. package/src/shade/playground/vsm/index.html +170 -0
  44. package/src/shade/playground/vsm/main.d.ts +2 -0
  45. package/src/shade/playground/vsm/main.d.ts.map +1 -0
  46. package/src/shade/playground/vsm/main.js +389 -0
  47. package/src/shade/playground/vsm/scenes.d.ts +52 -0
  48. package/src/shade/playground/vsm/scenes.d.ts.map +1 -0
  49. package/src/shade/playground/vsm/scenes.js +320 -0
  50. package/src/shade/playground/vsm/verify_vsm.d.ts +38 -0
  51. package/src/shade/playground/vsm/verify_vsm.d.ts.map +1 -0
  52. package/src/shade/playground/vsm/verify_vsm.js +305 -0
  53. package/src/shade/playground/water_refraction/README.md +57 -9
  54. package/src/shade/playground/water_refraction/main.js +12 -3
  55. package/src/shade/playground/water_refraction/world.d.ts +1 -0
  56. package/src/shade/playground/water_refraction/world.d.ts.map +1 -1
  57. package/src/shade/playground/water_refraction/world.js +15 -5
  58. package/src/shade/renderer/Renderer.d.ts +19 -0
  59. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  60. package/src/shade/renderer/Renderer.js +72 -1
  61. package/src/shade/renderer/animation/GPUAnimationManager.d.ts +8 -0
  62. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  63. package/src/shade/renderer/animation/GPUAnimationManager.js +18 -0
  64. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts +7 -0
  65. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts.map +1 -1
  66. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +11 -0
  67. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.d.ts +3 -1
  68. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.d.ts.map +1 -1
  69. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.js +10 -1
  70. package/src/shade/renderer/buffer/table/serde/table_wgsl_gen_read_code.d.ts.map +1 -1
  71. package/src/shade/renderer/buffer/table/serde/table_wgsl_gen_read_code.js +28 -0
  72. package/src/shade/renderer/buffer/table/serde/table_wgsl_gen_write_code.d.ts.map +1 -1
  73. package/src/shade/renderer/buffer/table/serde/table_wgsl_gen_write_code.js +31 -1
  74. package/src/shade/renderer/geometry/MeshletGeometry.d.ts +3 -0
  75. package/src/shade/renderer/geometry/MeshletGeometry.d.ts.map +1 -1
  76. package/src/shade/renderer/geometry/MeshletGeometry.js +1 -0
  77. package/src/shade/renderer/light/LIGHT_DATABASE_SPEC.d.ts.map +1 -1
  78. package/src/shade/renderer/light/LIGHT_DATABASE_SPEC.js +62 -4
  79. package/src/shade/renderer/light/environment/rgbe/parse_rgbe.d.ts +1 -1
  80. package/src/shade/renderer/light/environment/rgbe/parse_rgbe.js +2 -2
  81. package/src/shade/renderer/light/environment/rgbe/rgbe_byte_to_rgb_half.d.ts +2 -2
  82. package/src/shade/renderer/light/environment/rgbe/rgbe_byte_to_rgb_half.d.ts.map +1 -1
  83. package/src/shade/renderer/light/environment/rgbe/rgbe_byte_to_rgb_half.js +6 -14
  84. package/src/shade/renderer/light/environment/sh_exponential/chunk_sh_exp_workgroup_sum.d.ts.map +1 -1
  85. package/src/shade/renderer/light/environment/sh_exponential/chunk_sh_exp_workgroup_sum.js +5 -1
  86. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  87. package/src/shade/renderer/medium/ParticipatingMedium.d.ts +3 -0
  88. package/src/shade/renderer/medium/ParticipatingMedium.d.ts.map +1 -1
  89. package/src/shade/renderer/medium/ParticipatingMedium.js +7 -0
  90. package/src/shade/renderer/postprocess/eye/shader_autoexposure_build_histogram.d.ts.map +1 -1
  91. package/src/shade/renderer/postprocess/eye/shader_autoexposure_build_histogram.js +5 -35
  92. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  93. package/src/shade/renderer/postprocess/ssr/SSR.d.ts +12 -4
  94. package/src/shade/renderer/postprocess/ssr/SSR.d.ts.map +1 -1
  95. package/src/shade/renderer/postprocess/ssr/SSR.js +36 -8
  96. package/src/shade/renderer/postprocess/ssr/SSR_DENOISE_TILE_SIZE.d.ts +3 -0
  97. package/src/shade/renderer/postprocess/ssr/SSR_DENOISE_TILE_SIZE.d.ts.map +1 -0
  98. package/src/shade/renderer/postprocess/ssr/SSR_DENOISE_TILE_SIZE.js +2 -0
  99. package/src/shade/renderer/postprocess/ssr/chunk_ssr_denoise_tile_active.d.ts +8 -0
  100. package/src/shade/renderer/postprocess/ssr/chunk_ssr_denoise_tile_active.d.ts.map +1 -0
  101. package/src/shade/renderer/postprocess/ssr/chunk_ssr_denoise_tile_active.js +40 -0
  102. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.d.ts.map +1 -1
  103. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.js +150 -137
  104. package/src/shade/renderer/postprocess/ssr/shader_ssr_denoise_tile_classify.d.ts +3 -0
  105. package/src/shade/renderer/postprocess/ssr/shader_ssr_denoise_tile_classify.d.ts.map +1 -0
  106. package/src/shade/renderer/postprocess/ssr/shader_ssr_denoise_tile_classify.js +44 -0
  107. package/src/shade/renderer/postprocess/ssr/shader_ssr_denoise_tile_dilate.d.ts +5 -0
  108. package/src/shade/renderer/postprocess/ssr/shader_ssr_denoise_tile_dilate.d.ts.map +1 -0
  109. package/src/shade/renderer/postprocess/ssr/shader_ssr_denoise_tile_dilate.js +35 -0
  110. package/src/shade/renderer/postprocess/ssr/ssr_graph_denoise_tiles.d.ts +26 -0
  111. package/src/shade/renderer/postprocess/ssr/ssr_graph_denoise_tiles.d.ts.map +1 -0
  112. package/src/shade/renderer/postprocess/ssr/ssr_graph_denoise_tiles.js +42 -0
  113. package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.d.ts +1 -0
  114. package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.d.ts.map +1 -1
  115. package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.js +113 -105
  116. package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.d.ts +2 -0
  117. package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.d.ts.map +1 -1
  118. package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.js +177 -167
  119. package/src/shade/renderer/rasterize/DEPTH_STENCIL_STATE_SHADOW.d.ts.map +1 -1
  120. package/src/shade/renderer/rasterize/DEPTH_STENCIL_STATE_SHADOW.js +10 -0
  121. package/src/shade/renderer/rasterize/RasterizationJob.d.ts +1 -2
  122. package/src/shade/renderer/rasterize/RasterizationJob.d.ts.map +1 -1
  123. package/src/shade/renderer/rasterize/RasterizationJob.js +1 -2
  124. package/src/shade/renderer/rasterize/bucket/construct_primitive_state.d.ts +5 -6
  125. package/src/shade/renderer/rasterize/bucket/construct_primitive_state.d.ts.map +1 -1
  126. package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +5 -6
  127. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts +2 -2
  128. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts.map +1 -1
  129. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.js +1 -1
  130. package/src/shade/renderer/scene/SCENE_DATABASE_SPEC.d.ts.map +1 -1
  131. package/src/shade/renderer/scene/SCENE_DATABASE_SPEC.js +11 -0
  132. package/src/shade/renderer/shader/chunk/atmosphere/phase/jd/build_lut_fog_parameters_jendersie_deon.d.ts.map +1 -1
  133. package/src/shade/renderer/shader/chunk/atmosphere/phase/jd/build_lut_fog_parameters_jendersie_deon.js +4 -5
  134. package/src/shade/renderer/shader/chunk/brdf/dfg/generate_dfg_texture.d.ts.map +1 -1
  135. package/src/shade/renderer/shader/chunk/brdf/dfg/generate_dfg_texture.js +1 -5
  136. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_importance.d.ts +30 -0
  137. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_importance.d.ts.map +1 -0
  138. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_importance.js +54 -0
  139. package/src/shade/renderer/shader/type/serde/read_gpu_typed_buffer.d.ts.map +1 -1
  140. package/src/shade/renderer/shader/type/serde/read_gpu_typed_buffer.js +3 -2
  141. package/src/shade/renderer/shadow/map/DIRECTIONAL_SHADOWMAP_STRUCT.d.ts +19 -0
  142. package/src/shade/renderer/shadow/map/DIRECTIONAL_SHADOWMAP_STRUCT.d.ts.map +1 -0
  143. package/src/shade/renderer/shadow/map/DIRECTIONAL_SHADOWMAP_STRUCT.js +27 -0
  144. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.d.ts +1 -0
  145. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.d.ts.map +1 -1
  146. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.js +4 -0
  147. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts +54 -0
  148. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  149. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +502 -10
  150. package/src/shade/renderer/shadow/map/Shadowmap.d.ts +3 -2
  151. package/src/shade/renderer/shadow/map/Shadowmap.d.ts.map +1 -1
  152. package/src/shade/renderer/shadow/map/Shadowmap.js +3 -2
  153. package/src/shade/renderer/shadow/map/shader/chunk_sample_shadowmap_atlas_for_light.d.ts +8 -0
  154. package/src/shade/renderer/shadow/map/shader/chunk_sample_shadowmap_atlas_for_light.d.ts.map +1 -1
  155. package/src/shade/renderer/shadow/map/shader/chunk_sample_shadowmap_atlas_for_light.js +34 -3
  156. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_directional.d.ts +3 -2
  157. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_directional.d.ts.map +1 -1
  158. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_directional.js +17 -2
  159. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_point2.d.ts.map +1 -1
  160. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_point2.js +137 -133
  161. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_FIRST_TEXEL_SIZE.d.ts +15 -0
  162. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_FIRST_TEXEL_SIZE.d.ts.map +1 -0
  163. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_FIRST_TEXEL_SIZE.js +14 -0
  164. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_PAGE_BUDGET.d.ts +16 -0
  165. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_PAGE_BUDGET.d.ts.map +1 -0
  166. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_PAGE_BUDGET.js +15 -0
  167. package/src/shade/renderer/shadow/vsm/PLAN.md +440 -0
  168. package/src/shade/renderer/shadow/vsm/REVIEW_LEDGER.md +43 -0
  169. package/src/shade/renderer/shadow/vsm/VSMMoverTracker.d.ts +42 -0
  170. package/src/shade/renderer/shadow/vsm/VSMMoverTracker.d.ts.map +1 -0
  171. package/src/shade/renderer/shadow/vsm/VSMMoverTracker.js +153 -0
  172. package/src/shade/renderer/shadow/vsm/VSMPageManager.d.ts +327 -0
  173. package/src/shade/renderer/shadow/vsm/VSMPageManager.d.ts.map +1 -0
  174. package/src/shade/renderer/shadow/vsm/VSMPageManager.js +1408 -0
  175. package/src/shade/renderer/shadow/vsm/VSM_COVER_LEVEL_STEP.d.ts +14 -0
  176. package/src/shade/renderer/shadow/vsm/VSM_COVER_LEVEL_STEP.d.ts.map +1 -0
  177. package/src/shade/renderer/shadow/vsm/VSM_COVER_LEVEL_STEP.js +13 -0
  178. package/src/shade/renderer/shadow/vsm/VSM_FEEDBACK_LAYOUT.d.ts +63 -0
  179. package/src/shade/renderer/shadow/vsm/VSM_FEEDBACK_LAYOUT.d.ts.map +1 -0
  180. package/src/shade/renderer/shadow/vsm/VSM_FEEDBACK_LAYOUT.js +70 -0
  181. package/src/shade/renderer/shadow/vsm/VSM_LEVEL_LIMIT.d.ts +11 -0
  182. package/src/shade/renderer/shadow/vsm/VSM_LEVEL_LIMIT.d.ts.map +1 -0
  183. package/src/shade/renderer/shadow/vsm/VSM_LEVEL_LIMIT.js +10 -0
  184. package/src/shade/renderer/shadow/vsm/VSM_MOVER_LAYOUT.d.ts +54 -0
  185. package/src/shade/renderer/shadow/vsm/VSM_MOVER_LAYOUT.d.ts.map +1 -0
  186. package/src/shade/renderer/shadow/vsm/VSM_MOVER_LAYOUT.js +75 -0
  187. package/src/shade/renderer/shadow/vsm/VSM_RECORD_STRUCT.d.ts +23 -0
  188. package/src/shade/renderer/shadow/vsm/VSM_RECORD_STRUCT.d.ts.map +1 -0
  189. package/src/shade/renderer/shadow/vsm/VSM_RECORD_STRUCT.js +85 -0
  190. package/src/shade/renderer/shadow/vsm/VSM_SLOT_STRUCT.d.ts +16 -0
  191. package/src/shade/renderer/shadow/vsm/VSM_SLOT_STRUCT.d.ts.map +1 -0
  192. package/src/shade/renderer/shadow/vsm/VSM_SLOT_STRUCT.js +29 -0
  193. package/src/shade/renderer/shadow/vsm/VirtualShadowMap.d.ts +136 -25
  194. package/src/shade/renderer/shadow/vsm/VirtualShadowMap.d.ts.map +1 -1
  195. package/src/shade/renderer/shadow/vsm/VirtualShadowMap.js +282 -116
  196. package/src/shade/renderer/shadow/vsm/graph_vsm_feedback.d.ts +39 -0
  197. package/src/shade/renderer/shadow/vsm/graph_vsm_feedback.d.ts.map +1 -0
  198. package/src/shade/renderer/shadow/vsm/graph_vsm_feedback.js +289 -0
  199. package/src/shade/renderer/shadow/vsm/shader/chunk_shadowmap_sample_virtual.d.ts +18 -0
  200. package/src/shade/renderer/shadow/vsm/shader/chunk_shadowmap_sample_virtual.d.ts.map +1 -0
  201. package/src/shade/renderer/shadow/vsm/shader/chunk_shadowmap_sample_virtual.js +65 -0
  202. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_addressing.d.ts +17 -0
  203. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_addressing.d.ts.map +1 -0
  204. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_addressing.js +75 -0
  205. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_append_mover.d.ts +14 -0
  206. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_append_mover.d.ts.map +1 -0
  207. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_append_mover.js +45 -0
  208. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_find_page.d.ts +17 -0
  209. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_find_page.d.ts.map +1 -0
  210. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_find_page.js +100 -0
  211. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_page_view_projection.d.ts +15 -0
  212. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_page_view_projection.d.ts.map +1 -0
  213. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_page_view_projection.js +47 -0
  214. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_demand.d.ts +26 -0
  215. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_demand.d.ts.map +1 -0
  216. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_demand.js +156 -0
  217. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_gather_counts.d.ts +13 -0
  218. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_gather_counts.d.ts.map +1 -0
  219. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_gather_counts.js +65 -0
  220. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_movers.d.ts +30 -0
  221. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_movers.d.ts.map +1 -0
  222. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_movers.js +184 -0
  223. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_skinned_movers.d.ts +23 -0
  224. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_skinned_movers.d.ts.map +1 -0
  225. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_skinned_movers.js +109 -0
  226. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_stale.d.ts +56 -0
  227. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_stale.d.ts.map +1 -0
  228. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_stale.js +211 -0
  229. package/src/shade/renderer/texture/format/decode_typed_gpu_data.d.ts +1 -1
  230. package/src/shade/renderer/texture/format/decode_typed_gpu_data.d.ts.map +1 -1
  231. package/src/shade/renderer/texture/format/decode_typed_gpu_data.js +3 -13
  232. package/src/shade/renderer/texture/format/infer_format_from_parameters.d.ts.map +1 -1
  233. package/src/shade/renderer/texture/format/infer_format_from_parameters.js +2 -3
  234. package/src/shade/renderer/texture/texture_write_to_gpu.d.ts.map +1 -1
  235. package/src/shade/renderer/texture/texture_write_to_gpu.js +16 -16
  236. package/src/shade/renderer/texture/virtual/VTPhysicalCache.d.ts +4 -90
  237. package/src/shade/renderer/texture/virtual/VTPhysicalCache.d.ts.map +1 -1
  238. package/src/shade/renderer/texture/virtual/VTPhysicalCache.js +6 -210
  239. package/src/shade/renderer/texture/virtual/VTPhysicalSlots.d.ts +119 -0
  240. package/src/shade/renderer/texture/virtual/VTPhysicalSlots.d.ts.map +1 -0
  241. package/src/shade/renderer/texture/virtual/VTPhysicalSlots.js +245 -0
  242. package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.d.ts +1 -1
  243. package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.d.ts.map +1 -1
  244. package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.js +3 -19
  245. package/src/shade/renderer/texture/virtual/chunk_vt_page_table_entry.d.ts +13 -0
  246. package/src/shade/renderer/texture/virtual/chunk_vt_page_table_entry.d.ts.map +1 -0
  247. package/src/shade/renderer/texture/virtual/chunk_vt_page_table_entry.js +34 -0
  248. package/src/shade/renderer/water/GPUWaterRenderer.d.ts +7 -2
  249. package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
  250. package/src/shade/renderer/water/GPUWaterRenderer.js +49 -49
  251. package/src/shade/renderer/water/chunk_water_transport.js +3 -3
  252. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts +11 -25
  253. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts.map +1 -1
  254. package/src/shade/renderer/water/medium/GPUWaterBodies.js +64 -116
  255. package/src/shade/renderer/water/medium/WATER_BODIES_STRUCT.d.ts +1 -20
  256. package/src/shade/renderer/water/medium/WATER_BODIES_STRUCT.d.ts.map +1 -1
  257. package/src/shade/renderer/water/medium/WATER_BODIES_STRUCT.js +10 -42
  258. package/src/shade/renderer/water/medium/chunk_water_bodies.d.ts +3 -0
  259. package/src/shade/renderer/water/medium/chunk_water_bodies.d.ts.map +1 -0
  260. package/src/shade/renderer/water/medium/chunk_water_bodies.js +3 -0
  261. package/src/shade/renderer/water/medium/shader_water_bodies_derive.d.ts +1 -5
  262. package/src/shade/renderer/water/medium/shader_water_bodies_derive.d.ts.map +1 -1
  263. package/src/shade/renderer/water/medium/shader_water_bodies_derive.js +32 -30
  264. package/src/shade/renderer/water/medium/shader_water_meshlets.d.ts +4 -0
  265. package/src/shade/renderer/water/medium/shader_water_meshlets.d.ts.map +1 -0
  266. package/src/shade/renderer/water/medium/shader_water_meshlets.js +69 -0
  267. package/src/shade/renderer/water/optics/chunk_water_inscatter.d.ts.map +1 -1
  268. package/src/shade/renderer/water/optics/chunk_water_inscatter.js +124 -123
  269. package/src/shade/renderer/water/optics/chunk_water_receiver_downwelling.d.ts.map +1 -1
  270. package/src/shade/renderer/water/optics/chunk_water_receiver_downwelling.js +75 -74
  271. package/src/shade/renderer/water/shader_water.d.ts +1 -1
  272. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  273. package/src/shade/renderer/water/shader_water.js +490 -406
  274. package/src/shade/renderer/water/surface/GPUWaterWaveSimulation.d.ts +12 -85
  275. package/src/shade/renderer/water/surface/GPUWaterWaveSimulation.d.ts.map +1 -1
  276. package/src/shade/renderer/water/surface/GPUWaterWaveSimulation.js +128 -408
  277. package/src/shade/renderer/water/surface/WATER_WAVE_RECORD_STRUCT.d.ts +6 -0
  278. package/src/shade/renderer/water/surface/WATER_WAVE_RECORD_STRUCT.d.ts.map +1 -0
  279. package/src/shade/renderer/water/surface/WATER_WAVE_RECORD_STRUCT.js +23 -0
  280. package/src/shade/renderer/water/surface/chunk_water_tessellation.d.ts +9 -0
  281. package/src/shade/renderer/water/surface/chunk_water_tessellation.d.ts.map +1 -0
  282. package/src/shade/renderer/water/surface/chunk_water_tessellation.js +144 -0
  283. package/src/shade/renderer/water/surface/chunk_water_wave.d.ts +3 -5
  284. package/src/shade/renderer/water/surface/chunk_water_wave.d.ts.map +1 -1
  285. package/src/shade/renderer/water/surface/chunk_water_wave.js +40 -48
  286. package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts +14 -0
  287. package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts.map +1 -0
  288. package/src/shade/renderer/water/surface/graph_water_tessellation.js +34 -0
  289. package/src/shade/renderer/water/surface/shader_water_tiles.d.ts +4 -0
  290. package/src/shade/renderer/water/surface/shader_water_tiles.d.ts.map +1 -0
  291. package/src/shade/renderer/water/surface/shader_water_tiles.js +123 -0
  292. package/src/shade/renderer/water/surface/shader_water_wave_batch.d.ts +6 -0
  293. package/src/shade/renderer/water/surface/shader_water_wave_batch.d.ts.map +1 -0
  294. package/src/shade/renderer/water/surface/shader_water_wave_batch.js +85 -0
  295. package/src/shade/renderer/water/surface/shader_water_wave_domain.d.ts +6 -0
  296. package/src/shade/renderer/water/surface/shader_water_wave_domain.d.ts.map +1 -0
  297. package/src/shade/renderer/water/surface/shader_water_wave_domain.js +83 -0
  298. package/src/shade/renderer/water/water_height_texture_from_sampler.d.ts.map +1 -1
  299. package/src/shade/renderer/water/water_height_texture_from_sampler.js +4 -3
  300. package/src/engine/graphics/ecs/water/WATER_SIZE.d.ts +0 -2
  301. package/src/engine/graphics/ecs/water/WATER_SIZE.d.ts.map +0 -1
  302. package/src/engine/graphics/ecs/water/WATER_SIZE.js +0 -1
  303. package/src/shade/renderer/light/GPUViewLightContext.d.ts +0 -22
  304. package/src/shade/renderer/light/GPUViewLightContext.d.ts.map +0 -1
  305. package/src/shade/renderer/light/GPUViewLightContext.js +0 -62
  306. package/src/shade/renderer/shadow/map/csm/CSM_SHADOWMAP_STRUCT.d.ts +0 -14
  307. package/src/shade/renderer/shadow/map/csm/CSM_SHADOWMAP_STRUCT.d.ts.map +0 -1
  308. package/src/shade/renderer/shadow/map/csm/CSM_SHADOWMAP_STRUCT.js +0 -20
  309. package/src/shade/renderer/shadow/vsm/GPUViewShadowMaps.d.ts +0 -27
  310. package/src/shade/renderer/shadow/vsm/GPUViewShadowMaps.d.ts.map +0 -1
  311. package/src/shade/renderer/shadow/vsm/GPUViewShadowMaps.js +0 -147
  312. package/src/shade/renderer/shadow/vsm/GPUVirtualShadowTextureResidence.d.ts +0 -7
  313. package/src/shade/renderer/shadow/vsm/GPUVirtualShadowTextureResidence.d.ts.map +0 -1
  314. package/src/shade/renderer/shadow/vsm/GPUVirtualShadowTextureResidence.js +0 -10
  315. package/src/shade/renderer/shadow/vsm/VSM_RESIDENCE_WORKGROUP_TILE_RESOLUTION.d.ts +0 -2
  316. package/src/shade/renderer/shadow/vsm/VSM_RESIDENCE_WORKGROUP_TILE_RESOLUTION.d.ts.map +0 -1
  317. package/src/shade/renderer/shadow/vsm/VSM_RESIDENCE_WORKGROUP_TILE_RESOLUTION.js +0 -1
  318. package/src/shade/renderer/shadow/vsm/VSM_UNIFORMS_STRUCT.d.ts +0 -3
  319. package/src/shade/renderer/shadow/vsm/VSM_UNIFORMS_STRUCT.d.ts.map +0 -1
  320. package/src/shade/renderer/shadow/vsm/VSM_UNIFORMS_STRUCT.js +0 -8
  321. package/src/shade/renderer/shadow/vsm/VirtualShadowmapRenderer.d.ts +0 -27
  322. package/src/shade/renderer/shadow/vsm/VirtualShadowmapRenderer.d.ts.map +0 -1
  323. package/src/shade/renderer/shadow/vsm/VirtualShadowmapRenderer.js +0 -102
  324. package/src/shade/renderer/shadow/vsm/debug_inspect_coarse_bounds_buffer.d.ts +0 -8
  325. package/src/shade/renderer/shadow/vsm/debug_inspect_coarse_bounds_buffer.d.ts.map +0 -1
  326. package/src/shade/renderer/shadow/vsm/debug_inspect_coarse_bounds_buffer.js +0 -34
  327. package/src/shade/renderer/shadow/vsm/debug_inspect_residence_buffer.d.ts +0 -10
  328. package/src/shade/renderer/shadow/vsm/debug_inspect_residence_buffer.d.ts.map +0 -1
  329. package/src/shade/renderer/shadow/vsm/debug_inspect_residence_buffer.js +0 -64
  330. package/src/shade/renderer/shadow/vsm/decode_tile_address.d.ts +0 -11
  331. package/src/shade/renderer/shadow/vsm/decode_tile_address.d.ts.map +0 -1
  332. package/src/shade/renderer/shadow/vsm/decode_tile_address.js +0 -26
  333. package/src/shade/renderer/shadow/vsm/render/vms_render_tiles.d.ts +0 -2
  334. package/src/shade/renderer/shadow/vsm/render/vms_render_tiles.d.ts.map +0 -1
  335. package/src/shade/renderer/shadow/vsm/render/vms_render_tiles.js +0 -11
  336. package/src/shade/renderer/shadow/vsm/vms_pipeline_compute_coarse_bounds.d.ts +0 -20
  337. package/src/shade/renderer/shadow/vsm/vms_pipeline_compute_coarse_bounds.d.ts.map +0 -1
  338. package/src/shade/renderer/shadow/vsm/vms_pipeline_compute_coarse_bounds.js +0 -184
  339. package/src/shade/renderer/shadow/vsm/vsm_residence_pipeline.d.ts +0 -25
  340. package/src/shade/renderer/shadow/vsm/vsm_residence_pipeline.d.ts.map +0 -1
  341. package/src/shade/renderer/shadow/vsm/vsm_residence_pipeline.js +0 -230
  342. package/src/shade/renderer/shadow/vsm/vsm_tile_allocation_pipeline.d.ts +0 -19
  343. package/src/shade/renderer/shadow/vsm/vsm_tile_allocation_pipeline.d.ts.map +0 -1
  344. package/src/shade/renderer/shadow/vsm/vsm_tile_allocation_pipeline.js +0 -127
  345. package/src/shade/renderer/texture/encode/ENCODE_NOTES_2026_09_02.md +0 -462
  346. package/src/shade/renderer/water/optics/chunk_water_light_transmittance.d.ts +0 -23
  347. package/src/shade/renderer/water/optics/chunk_water_light_transmittance.d.ts.map +0 -1
  348. package/src/shade/renderer/water/optics/chunk_water_light_transmittance_dry.d.ts +0 -18
  349. package/src/shade/renderer/water/optics/chunk_water_light_transmittance_dry.d.ts.map +0 -1
  350. package/src/shade/renderer/water/surface/WATER_SURFACE_VERTEX_STRUCT.d.ts +0 -12
  351. package/src/shade/renderer/water/surface/WATER_SURFACE_VERTEX_STRUCT.d.ts.map +0 -1
  352. package/src/shade/renderer/water/surface/WATER_SURFACE_VERTEX_STRUCT.js +0 -27
  353. package/src/shade/renderer/water/surface/WATER_WAVE_PARAMETERS_STRUCT.d.ts +0 -14
  354. package/src/shade/renderer/water/surface/WATER_WAVE_PARAMETERS_STRUCT.d.ts.map +0 -1
  355. package/src/shade/renderer/water/surface/WATER_WAVE_PARAMETERS_STRUCT.js +0 -49
  356. package/src/shade/renderer/water/surface/chunk_water_wave_grid.d.ts +0 -9
  357. package/src/shade/renderer/water/surface/chunk_water_wave_grid.d.ts.map +0 -1
  358. package/src/shade/renderer/water/surface/chunk_water_wave_grid.js +0 -47
  359. package/src/shade/renderer/water/surface/shader_water_surface_write.d.ts +0 -10
  360. package/src/shade/renderer/water/surface/shader_water_surface_write.d.ts.map +0 -1
  361. package/src/shade/renderer/water/surface/shader_water_surface_write.js +0 -51
  362. package/src/shade/renderer/water/surface/shader_water_wave_step.d.ts +0 -12
  363. package/src/shade/renderer/water/surface/shader_water_wave_step.d.ts.map +0 -1
  364. package/src/shade/renderer/water/surface/shader_water_wave_step.js +0 -54
  365. package/src/shade/renderer/water/surface/water_surface_grid_indices.d.ts +0 -13
  366. package/src/shade/renderer/water/surface/water_surface_grid_indices.d.ts.map +0 -1
  367. package/src/shade/renderer/water/surface/water_surface_grid_indices.js +0 -39
@@ -0,0 +1,1408 @@
1
+ import { assert } from "../../../../core/assert.js";
2
+ import { IndexedFloatMaxHeap } from "../../../../core/collection/heap/IndexedFloatMaxHeap.js";
3
+ import { isPowerOfTwo } from "../../../../core/math/isPowerOfTwo.js";
4
+ import { VT_ENTRY_INVALID, VT_ENTRY_PHYS_MASK, vt_entry_pack } from "../../texture/virtual/VT_CONSTANTS.js";
5
+ import { VTPhysicalSlots } from "../../texture/virtual/VTPhysicalSlots.js";
6
+ import { VSM_COVER_LEVEL_STEP } from "./VSM_COVER_LEVEL_STEP.js";
7
+ import { VSM_FEEDBACK_LAYOUT, vsm_feedback_stale_offset } from "./VSM_FEEDBACK_LAYOUT.js";
8
+ import { VSM_LEVEL_LIMIT } from "./VSM_LEVEL_LIMIT.js";
9
+
10
+ /**
11
+ * Candidate kinds. A **cover** — a page several levels up that gives pixels with nothing resident
12
+ * above them a shadow at all — is served before anything else. Everything else is an **update**: a
13
+ * resident page that went stale under a mover while something looks at it, or a missing page that
14
+ * something coarser stands in for meanwhile.
15
+ */
16
+ const KIND_UPDATE = 1;
17
+ const KIND_COVER = 2;
18
+
19
+ /**
20
+ * What a cover scores above: an update scores `log2((1 + importance)(1 + frames waited))`, which
21
+ * stays under 64 for any screen's importance and any frame count a session reaches.
22
+ */
23
+ const COVER_SCORE = 128;
24
+
25
+ /**
26
+ * Light spaces remembered: the view each recent light space began with, so that a reading recorded
27
+ * under one that has since been replaced can be carried into the current one rather than refused.
28
+ * A reading is at most a few frames old — three readbacks in flight — and a light turning every frame
29
+ * replaces its light space every frame, so sixteen is room to spare.
30
+ */
31
+ const LIGHT_SPACE_HISTORY = 16;
32
+
33
+ /**
34
+ * Where a page of a level lands when carried from one light view to another, through a receiver
35
+ * point at the page's centre and a depth along the old light: back to the world through the old
36
+ * view's transpose — light views are rotations — and into the new view. Exact for the point,
37
+ * approximate for the page.
38
+ *
39
+ * @param {ArrayLike<number>} from view the page was asked for under
40
+ * @param {number} from_offset where that view starts in `from`
41
+ * @param {ArrayLike<number>} to view to carry it into
42
+ * @param {number} size world size of a page of the level
43
+ * @param {number} x page
44
+ * @param {number} y page
45
+ * @param {number} depth along the old light
46
+ * @param {Float64Array} out [x, y, depth] in the new view
47
+ */
48
+ function carry_page(from, from_offset, to, size, x, y, depth, out) {
49
+ const f = from_offset;
50
+
51
+ // light space as the old view saw it; its view z is the depth negated
52
+ const lx = (x + 0.5) * size - from[f + 12];
53
+ const ly = (y + 0.5) * size - from[f + 13];
54
+ const lz = -depth - from[f + 14];
55
+
56
+ const wx = from[f] * lx + from[f + 1] * ly + from[f + 2] * lz;
57
+ const wy = from[f + 4] * lx + from[f + 5] * ly + from[f + 6] * lz;
58
+ const wz = from[f + 8] * lx + from[f + 9] * ly + from[f + 10] * lz;
59
+
60
+ const nx = to[0] * wx + to[4] * wy + to[8] * wz + to[12];
61
+ const ny = to[1] * wx + to[5] * wy + to[9] * wz + to[13];
62
+ const nz = to[2] * wx + to[6] * wy + to[10] * wz + to[14];
63
+
64
+ out[0] = Math.floor(nx / size);
65
+ out[1] = Math.floor(ny / size);
66
+ out[2] = -nz;
67
+ }
68
+
69
+ /**
70
+ * A candidate's score.
71
+ *
72
+ * Updates compete by the importance of the pixels asking — their count, weighted by where they are
73
+ * on screen — times the frames they have waited: the weighting `compute_shadowmap_update_score`
74
+ * gives a local light's shadow map, area times staleness. A page
75
+ * kept stale by a mover is re-marked from zero each time it is redrawn, while a missing page's wait
76
+ * only grows until it arrives, so under constant motion the missing page still gets its turn: a
77
+ * strict "stale before missing" order starved it for as long as anything moved.
78
+ *
79
+ * In log2, so a float32 heap orders it exactly enough.
80
+ *
81
+ * @param {number} kind
82
+ * @param {number} importance of the pixels asking, as the demand pass sums it
83
+ * @param {number} waited frames
84
+ * @returns {number}
85
+ */
86
+ function candidate_score(kind, importance, waited) {
87
+ if (kind === KIND_COVER) {
88
+ return COVER_SCORE + Math.log2(1 + importance);
89
+ }
90
+
91
+ return Math.log2((1 + importance) * (1 + waited));
92
+ }
93
+
94
+ /**
95
+ * The CPU half of a virtual shadow map: what is resident where, what the GPU said it wanted and
96
+ * what it said went stale, and which pages to render this frame.
97
+ *
98
+ * It holds no GPU resource and draws nothing. The GPU's feedback comes in through
99
+ * {@link apply_reading}; the frame's pages go out through the job list {@link select} fills; and
100
+ * the rows the GPU reads — page-table entries, slot pages, window corners — change only as pages
101
+ * are installed, evicted and scrolled away, which the dirty lists say ({@link dirty_cell_count} and
102
+ * friends), so that writing them is O(what changed).
103
+ *
104
+ * **The clipmap.** `level_count` levels; a page of level `k` is `page_size · first_texel_size · 2^k`
105
+ * world units on a side, anchored to the world in light space. Each level keeps a window of
106
+ * `pages_per_level²` pages around the camera ({@link update_windows}), and a page of the window sits
107
+ * in cell `(x mod N, y mod N)` of the level — so a window that scrolls keeps every page still inside
108
+ * it where it was, and gives back only the pages it left behind.
109
+ *
110
+ * **The pool** is a {@link VTPhysicalSlots}: VT's slot grid, least-recently-touched eviction outside
111
+ * a protection window, and the finest page shed first on a tie.
112
+ *
113
+ * **What is rendered.** Each reading replaces the want set — it is a whole frame's snapshot — and
114
+ * {@link select} turns it into candidates for a max-heap, drained to the frame's budget:
115
+ * 1. first, a **cover** for every page that is missing with nothing coarser resident: one page
116
+ * {@link VSM_COVER_LEVEL_STEP} levels up, which gives every pixel under it a shadow at once,
117
+ * ordered by the importance of the pixels under it;
118
+ * 2. then the **updates** — a stale page something still looks at (wanted itself, or standing in
119
+ * for a wanted one), and a missing page something coarser stands in for meanwhile — ordered by
120
+ * the importance of the pixels asking times the frames waited, so that neither kind can starve
121
+ * the other.
122
+ *
123
+ * A pixel's importance is its weight under the screen importance mask, the automatic exposure's
124
+ * metering mask: full over most of the screen, less toward its edges.
125
+ */
126
+ export class VSMPageManager {
127
+
128
+ /**
129
+ * @type {number}
130
+ * @readonly
131
+ */
132
+ level_count;
133
+
134
+ /**
135
+ * A window's side, in pages. A power of two.
136
+ * @type {number}
137
+ * @readonly
138
+ */
139
+ pages_per_level;
140
+
141
+ /**
142
+ * World size of a level-0 texel.
143
+ * @type {number}
144
+ * @readonly
145
+ */
146
+ first_texel_size;
147
+
148
+ /**
149
+ * Texels on a page's side, without the border.
150
+ * @type {number}
151
+ * @readonly
152
+ */
153
+ page_size;
154
+
155
+ /**
156
+ * @type {number}
157
+ * @readonly
158
+ */
159
+ border;
160
+
161
+ /**
162
+ * The pool's slot bookkeeping.
163
+ * @type {VTPhysicalSlots}
164
+ * @readonly
165
+ */
166
+ slots;
167
+
168
+ /**
169
+ * Requests a reading holds at most — the feedback buffer's capacity.
170
+ * @type {number}
171
+ * @readonly
172
+ */
173
+ request_capacity;
174
+
175
+ /**
176
+ * Frames a touched page is safe from eviction for. Has to outlast the readback, or a page in
177
+ * view is evicted between the frame that asked for it and the reading that says so.
178
+ * @type {number}
179
+ * @readonly
180
+ */
181
+ protect_frames;
182
+
183
+ /**
184
+ * Per window cell of every level, the slot holding it, or -1.
185
+ * @type {Int32Array}
186
+ */
187
+ #cell_slot;
188
+
189
+ /**
190
+ * Per slot, the level of its page, or -1 for an empty slot.
191
+ * @type {Int32Array}
192
+ */
193
+ #slot_level;
194
+
195
+ /**
196
+ * Per slot, its page's world coordinates.
197
+ * @type {Int32Array}
198
+ */
199
+ #slot_x;
200
+
201
+ /**
202
+ * @type {Int32Array}
203
+ */
204
+ #slot_y;
205
+
206
+ /**
207
+ * Per slot, 1 when a mover crossed it after it was last rendered.
208
+ * @type {Uint8Array}
209
+ */
210
+ #slot_stale;
211
+
212
+ /**
213
+ * Per slot, the frame it was last rendered in, or -1.
214
+ * @type {Float64Array}
215
+ */
216
+ #slot_rendered;
217
+
218
+ /**
219
+ * Per slot, the frame of the reading that marked it stale — how long a stale page has waited.
220
+ * @type {Float64Array}
221
+ */
222
+ #slot_stale_since;
223
+
224
+ /**
225
+ * Per cell, the frame a missing page started waiting, and the {@link select} it was last offered
226
+ * in — a page is waiting only while every select offers it.
227
+ * @type {Float64Array}
228
+ */
229
+ #cell_wait_since;
230
+ #cell_wait_select;
231
+ #select_index = 0;
232
+
233
+ /**
234
+ * Per level, the window's corner in world pages.
235
+ * @type {Int32Array}
236
+ */
237
+ #origin_x;
238
+
239
+ /**
240
+ * @type {Int32Array}
241
+ */
242
+ #origin_y;
243
+
244
+ /**
245
+ * Whether {@link update_windows} has placed the windows yet.
246
+ * @type {boolean}
247
+ */
248
+ #windows_placed = false;
249
+
250
+ /**
251
+ * The want set: the latest reading's requests, in its order.
252
+ */
253
+ #want_level;
254
+ #want_x;
255
+ #want_y;
256
+ #want_importance;
257
+ #want_depth;
258
+ #want_count = 0;
259
+
260
+ /**
261
+ * Recent light spaces, oldest overwritten first: each one's view, and the frame it began in.
262
+ */
263
+ #history_views = new Float64Array(LIGHT_SPACE_HISTORY * 16);
264
+ #history_frames = new Float64Array(LIGHT_SPACE_HISTORY).fill(Infinity);
265
+ #history_next = 0;
266
+
267
+ /**
268
+ * Where {@link carry_page} leaves a carried page.
269
+ */
270
+ #carried = new Float64Array(3);
271
+
272
+ /**
273
+ * The frame of the latest reading applied; an older one arriving late is not applied.
274
+ * @type {number}
275
+ */
276
+ #last_reading_frame = -Infinity;
277
+
278
+ /**
279
+ * The light space the pages were drawn in: the light view, and its depth range.
280
+ * @type {Float64Array}
281
+ */
282
+ #view = new Float64Array(16);
283
+ #near = 0;
284
+ #far = 0;
285
+ #light_space_set = false;
286
+
287
+ /**
288
+ * Candidate bookkeeping, per cell of every level: the heap, the stamp of the {@link select} that
289
+ * offered the cell, its kind, and the importance behind it.
290
+ */
291
+ #heap;
292
+ #candidate_stamp;
293
+ #candidate_kind;
294
+ #candidate_importance;
295
+ #candidate_waited;
296
+ #stamp = 0;
297
+
298
+ /**
299
+ * The frame's jobs: a slot to render and the page it holds.
300
+ */
301
+ #job_slot;
302
+ #job_level;
303
+ #job_x;
304
+ #job_y;
305
+ #job_count = 0;
306
+
307
+ /**
308
+ * Rows that changed since {@link clear_dirty}.
309
+ */
310
+ #dirty_cells;
311
+ #dirty_cell_mark;
312
+ #dirty_cell_count = 0;
313
+ #dirty_slots;
314
+ #dirty_slot_mark;
315
+ #dirty_slot_count = 0;
316
+ #levels_dirty = false;
317
+
318
+ /**
319
+ * What the manager did, cumulative since construction except where noted.
320
+ */
321
+ stats = {
322
+ /** wants in the latest reading */
323
+ wants: 0,
324
+ /** wants the latest {@link select} found with nothing resident to stand in for them */
325
+ uncovered: 0,
326
+ /** candidates the latest {@link select} left for later frames, those it found no slot for included */
327
+ backlog: 0,
328
+ /** pages rendered by the latest {@link select} */
329
+ renders: 0,
330
+ /** movers in the latest reading, and whether there were more than the GPU could list */
331
+ movers: 0,
332
+ /** requests the GPU dropped past its capacity, in the latest reading */
333
+ requests_dropped: 0,
334
+ readings_applied: 0,
335
+ readings_dropped: 0,
336
+ installs: 0,
337
+ evictions: 0,
338
+ scroll_evictions: 0,
339
+ refused: 0,
340
+ stale_marked: 0,
341
+ invalidations: 0,
342
+ re_rendered: 0,
343
+ };
344
+
345
+ /**
346
+ * @param {Object} options
347
+ * @param {number} options.level_count
348
+ * @param {number} options.pages_per_level a power of two
349
+ * @param {number} options.first_texel_size world size of a level-0 texel
350
+ * @param {number} options.page_size texels on a page's side, without the border
351
+ * @param {number} options.border texels of border each side
352
+ * @param {number} options.pool_size texels on the pool's side
353
+ * @param {number} options.request_capacity requests a reading holds at most
354
+ * @param {number} options.protect_frames frames a touched page is safe from eviction for
355
+ */
356
+ constructor({
357
+ level_count,
358
+ pages_per_level,
359
+ first_texel_size,
360
+ page_size,
361
+ border,
362
+ pool_size,
363
+ request_capacity,
364
+ protect_frames,
365
+ }) {
366
+ assert.isPositiveInteger(level_count, 'level_count');
367
+ assert.lessThanOrEqual(level_count, VSM_LEVEL_LIMIT, 'level_count');
368
+ assert.isPositiveInteger(pages_per_level, 'pages_per_level');
369
+ assert.ok(isPowerOfTwo(pages_per_level), 'pages_per_level must be a power of two');
370
+ assert.greaterThanOrEqual(pages_per_level, 2, 'pages_per_level');
371
+ assert.isNumber(first_texel_size, 'first_texel_size');
372
+ assert.greaterThan(first_texel_size, 0, 'first_texel_size');
373
+ assert.isPositiveInteger(page_size, 'page_size');
374
+ assert.isNonNegativeInteger(border, 'border');
375
+ assert.isPositiveInteger(pool_size, 'pool_size');
376
+ assert.isPositiveInteger(request_capacity, 'request_capacity');
377
+ assert.isPositiveInteger(protect_frames, 'protect_frames');
378
+
379
+ this.level_count = level_count;
380
+ this.pages_per_level = pages_per_level;
381
+ this.first_texel_size = first_texel_size;
382
+ this.page_size = page_size;
383
+ this.border = border;
384
+ this.request_capacity = request_capacity;
385
+ this.protect_frames = protect_frames;
386
+
387
+ const slots = this.slots = new VTPhysicalSlots({ atlas_size: pool_size, page_size, border });
388
+
389
+ // a page-table entry names its slot in eight bits a side
390
+ assert.lessThanOrEqual(slots.slots_per_axis, VT_ENTRY_PHYS_MASK + 1, 'slots per pool side');
391
+
392
+ const cell_count = level_count * pages_per_level * pages_per_level;
393
+ const capacity = slots.capacity;
394
+
395
+ this.#cell_slot = new Int32Array(cell_count).fill(-1);
396
+
397
+ this.#slot_level = new Int32Array(capacity).fill(-1);
398
+ this.#slot_x = new Int32Array(capacity);
399
+ this.#slot_y = new Int32Array(capacity);
400
+ this.#slot_stale = new Uint8Array(capacity);
401
+ this.#slot_rendered = new Float64Array(capacity).fill(-1);
402
+ this.#slot_stale_since = new Float64Array(capacity);
403
+
404
+ this.#origin_x = new Int32Array(level_count);
405
+ this.#origin_y = new Int32Array(level_count);
406
+
407
+ this.#want_level = new Uint8Array(request_capacity);
408
+ this.#want_x = new Int32Array(request_capacity);
409
+ this.#want_y = new Int32Array(request_capacity);
410
+ this.#want_importance = new Uint32Array(request_capacity);
411
+ this.#want_depth = new Float32Array(request_capacity);
412
+
413
+ this.#heap = new IndexedFloatMaxHeap(cell_count);
414
+ this.#candidate_stamp = new Uint32Array(cell_count);
415
+ this.#candidate_kind = new Uint8Array(cell_count);
416
+ this.#candidate_importance = new Float64Array(cell_count);
417
+ this.#candidate_waited = new Float64Array(cell_count);
418
+
419
+ this.#cell_wait_since = new Float64Array(cell_count);
420
+ this.#cell_wait_select = new Float64Array(cell_count).fill(-1);
421
+
422
+ this.#job_slot = new Uint32Array(capacity);
423
+ this.#job_level = new Int32Array(capacity);
424
+ this.#job_x = new Int32Array(capacity);
425
+ this.#job_y = new Int32Array(capacity);
426
+
427
+ this.#dirty_cells = new Uint32Array(cell_count);
428
+ this.#dirty_cell_mark = new Uint8Array(cell_count);
429
+ this.#dirty_slots = new Uint32Array(capacity);
430
+ this.#dirty_slot_mark = new Uint8Array(capacity);
431
+ }
432
+
433
+ /**
434
+ * Cells of every level: the length of the page table.
435
+ * @returns {number}
436
+ */
437
+ get cell_count() {
438
+ return this.#cell_slot.length;
439
+ }
440
+
441
+ /**
442
+ * World units on the side of a page of a level.
443
+ *
444
+ * @param {number} level
445
+ * @returns {number}
446
+ */
447
+ page_world_size(level) {
448
+ return this.page_size * this.first_texel_size * 2 ** level;
449
+ }
450
+
451
+ /**
452
+ * The page-table row, from the map's first, of a world page's cell.
453
+ *
454
+ * @param {number} level
455
+ * @param {number} x world page
456
+ * @param {number} y world page
457
+ * @returns {number}
458
+ */
459
+ cell_of(level, x, y) {
460
+ const n = this.pages_per_level;
461
+ const mask = n - 1;
462
+
463
+ return (level * n + (y & mask)) * n + (x & mask);
464
+ }
465
+
466
+ /**
467
+ * @param {number} level
468
+ * @returns {number} the level's window corner, world pages
469
+ */
470
+ origin_x(level) {
471
+ return this.#origin_x[level];
472
+ }
473
+
474
+ /**
475
+ * @param {number} level
476
+ * @returns {number}
477
+ */
478
+ origin_y(level) {
479
+ return this.#origin_y[level];
480
+ }
481
+
482
+ /**
483
+ * @param {number} level
484
+ * @param {number} x
485
+ * @param {number} y
486
+ * @returns {boolean}
487
+ */
488
+ window_contains(level, x, y) {
489
+ const n = this.pages_per_level;
490
+ const rx = x - this.#origin_x[level];
491
+ const ry = y - this.#origin_y[level];
492
+
493
+ return this.#windows_placed && rx >= 0 && ry >= 0 && rx < n && ry < n;
494
+ }
495
+
496
+ /**
497
+ * The slot holding a world page, or -1 when it is not resident or not in its level's window.
498
+ *
499
+ * @param {number} level
500
+ * @param {number} x
501
+ * @param {number} y
502
+ * @returns {number}
503
+ */
504
+ slot_of_page(level, x, y) {
505
+ if (!this.window_contains(level, x, y)) {
506
+ return -1;
507
+ }
508
+
509
+ return this.#cell_slot[this.cell_of(level, x, y)];
510
+ }
511
+
512
+ /**
513
+ * Place every level's window around the camera, giving back the pages a window leaves behind.
514
+ *
515
+ * @param {number} light_x the camera, across the light
516
+ * @param {number} light_y
517
+ */
518
+ update_windows(light_x, light_y) {
519
+ assert.isFinite(light_x, 'light_x');
520
+ assert.isFinite(light_y, 'light_y');
521
+
522
+ const half = this.pages_per_level >> 1;
523
+
524
+ for (let level = 0; level < this.level_count; level++) {
525
+ const size = this.page_world_size(level);
526
+
527
+ const x = Math.floor(light_x / size) - half;
528
+ const y = Math.floor(light_y / size) - half;
529
+
530
+ const old_x = this.#origin_x[level];
531
+ const old_y = this.#origin_y[level];
532
+
533
+ if (this.#windows_placed) {
534
+ if (x === old_x && y === old_y) {
535
+ continue;
536
+ }
537
+
538
+ this.#evict_vacated(level, old_x, old_y, x, y);
539
+ }
540
+
541
+ this.#origin_x[level] = x;
542
+ this.#origin_y[level] = y;
543
+ this.#levels_dirty = true;
544
+ }
545
+
546
+ this.#windows_placed = true;
547
+ }
548
+
549
+ /**
550
+ * Give back every resident page of a level that its window, moving from one corner to the other,
551
+ * leaves behind. Walks the strips it vacates, or the whole level when it jumps a window or more.
552
+ *
553
+ * @param {number} level
554
+ * @param {number} old_x
555
+ * @param {number} old_y
556
+ * @param {number} new_x
557
+ * @param {number} new_y
558
+ */
559
+ #evict_vacated(level, old_x, old_y, new_x, new_y) {
560
+ const n = this.pages_per_level;
561
+
562
+ if (Math.abs(new_x - old_x) >= n || Math.abs(new_y - old_y) >= n) {
563
+ const first = level * n * n;
564
+
565
+ for (let cell = first; cell < first + n * n; cell++) {
566
+ const slot = this.#cell_slot[cell];
567
+
568
+ if (slot !== -1) {
569
+ this.#evict(slot);
570
+ this.stats.scroll_evictions++;
571
+ }
572
+ }
573
+
574
+ return;
575
+ }
576
+
577
+ // the columns of the old window outside the new one, all rows
578
+ const keep_x0 = Math.max(old_x, new_x);
579
+ const keep_x1 = Math.min(old_x, new_x) + n;
580
+
581
+ for (let x = old_x; x < old_x + n; x++) {
582
+ if (x >= keep_x0 && x < keep_x1) {
583
+ continue;
584
+ }
585
+
586
+ for (let y = old_y; y < old_y + n; y++) {
587
+ this.#evict_page_for_scroll(level, x, y);
588
+ }
589
+ }
590
+
591
+ // then the rows outside it, over the columns both windows share
592
+ const keep_y0 = Math.max(old_y, new_y);
593
+ const keep_y1 = Math.min(old_y, new_y) + n;
594
+
595
+ for (let y = old_y; y < old_y + n; y++) {
596
+ if (y >= keep_y0 && y < keep_y1) {
597
+ continue;
598
+ }
599
+
600
+ for (let x = keep_x0; x < keep_x1; x++) {
601
+ this.#evict_page_for_scroll(level, x, y);
602
+ }
603
+ }
604
+ }
605
+
606
+ /**
607
+ * @param {number} level
608
+ * @param {number} x
609
+ * @param {number} y
610
+ */
611
+ #evict_page_for_scroll(level, x, y) {
612
+ const slot = this.#cell_slot[this.cell_of(level, x, y)];
613
+
614
+ if (slot === -1) {
615
+ return;
616
+ }
617
+
618
+ this.#evict(slot);
619
+ this.stats.scroll_evictions++;
620
+ }
621
+
622
+ /**
623
+ * Take the light space the pages are drawn in. A light space that differs from the last one —
624
+ * the light turned, or the depth range moved — invalidates every page: the want set is carried
625
+ * into the new light space through the receiver point each want recorded, every page is given
626
+ * back, and readings recorded before `frame` are refused.
627
+ *
628
+ * @param {ArrayLike<number>} view world to light space, column-major, rotation only
629
+ * @param {number} near
630
+ * @param {number} far
631
+ * @param {number} frame
632
+ * @returns {boolean} whether the pages were invalidated
633
+ */
634
+ set_light_space(view, near, far, frame) {
635
+ assert.equal(view.length, 16, 'view.length');
636
+ assert.isFinite(near, 'near');
637
+ assert.isFinite(far, 'far');
638
+
639
+ const current = this.#view;
640
+
641
+ if (!this.#light_space_set) {
642
+ for (let i = 0; i < 16; i++) {
643
+ current[i] = view[i];
644
+ }
645
+
646
+ this.#near = near;
647
+ this.#far = far;
648
+ this.#light_space_set = true;
649
+
650
+ this.#remember_light_space(frame);
651
+
652
+ return false;
653
+ }
654
+
655
+ let changed = near !== this.#near || far !== this.#far;
656
+
657
+ for (let i = 0; i < 16 && !changed; i++) {
658
+ changed = view[i] !== current[i];
659
+ }
660
+
661
+ if (!changed) {
662
+ return false;
663
+ }
664
+
665
+ this.#reproject_wants(current, view);
666
+
667
+ for (let i = 0; i < 16; i++) {
668
+ current[i] = view[i];
669
+ }
670
+
671
+ this.#near = near;
672
+ this.#far = far;
673
+
674
+ this.#evict_all();
675
+
676
+ this.#remember_light_space(frame);
677
+ this.stats.invalidations++;
678
+
679
+ return true;
680
+ }
681
+
682
+ /**
683
+ * Remember the current light space as beginning in `frame`.
684
+ *
685
+ * @param {number} frame
686
+ */
687
+ #remember_light_space(frame) {
688
+ const i = this.#history_next;
689
+
690
+ this.#history_views.set(this.#view, i * 16);
691
+ this.#history_frames[i] = frame;
692
+
693
+ this.#history_next = (i + 1) % LIGHT_SPACE_HISTORY;
694
+ }
695
+
696
+ /**
697
+ * The remembered light space a reading recorded in `frame` was asked under: the latest one that
698
+ * began no later than it. -1 when it is older than every one remembered.
699
+ *
700
+ * @param {number} frame
701
+ * @returns {number} history index
702
+ */
703
+ #light_space_at(frame) {
704
+ let best = -1;
705
+ let best_frame = -Infinity;
706
+
707
+ for (let i = 0; i < LIGHT_SPACE_HISTORY; i++) {
708
+ const began = this.#history_frames[i];
709
+
710
+ if (began <= frame && began > best_frame) {
711
+ best = i;
712
+ best_frame = began;
713
+ }
714
+ }
715
+
716
+ return best;
717
+ }
718
+
719
+ /**
720
+ * Carry the want set from one light space to another, each want through its page centre and
721
+ * the receiver depth it recorded ({@link carry_page}). The next reading replaces it.
722
+ *
723
+ * @param {ArrayLike<number>} from
724
+ * @param {ArrayLike<number>} to
725
+ */
726
+ #reproject_wants(from, to) {
727
+ const count = this.#want_count;
728
+ const carried = this.#carried;
729
+
730
+ for (let i = 0; i < count; i++) {
731
+ carry_page(from, 0, to, this.page_world_size(this.#want_level[i]), this.#want_x[i], this.#want_y[i], this.#want_depth[i], carried);
732
+
733
+ this.#want_x[i] = carried[0];
734
+ this.#want_y[i] = carried[1];
735
+ this.#want_depth[i] = carried[2];
736
+ }
737
+ }
738
+
739
+ /**
740
+ * Give back every resident page.
741
+ */
742
+ #evict_all() {
743
+ const capacity = this.slots.capacity;
744
+
745
+ for (let slot = 0; slot < capacity; slot++) {
746
+ if (this.#slot_level[slot] !== -1) {
747
+ this.#evict(slot);
748
+ }
749
+ }
750
+ }
751
+
752
+ /**
753
+ * Take a frame's feedback: the pages wanted, which become the want set, and the slots a mover
754
+ * crossed. A reading recorded under a light space that has since been replaced has its wants
755
+ * carried into the current one through the view it was recorded under; one older than every
756
+ * light space remembered, or older than a reading already applied, is refused.
757
+ *
758
+ * @param {Uint32Array} words the feedback buffer, {@link VSM_FEEDBACK_LAYOUT}
759
+ * @param {Float32Array} floats the same words as f32
760
+ * @param {number} reading_frame the frame the GPU recorded it in
761
+ * @returns {boolean} whether it was applied
762
+ */
763
+ apply_reading(words, floats, reading_frame) {
764
+ assert.equal(words.buffer, floats.buffer, 'words and floats must view the same memory');
765
+ assert.equal(words.byteOffset, floats.byteOffset, 'words and floats must start together');
766
+
767
+ const recorded_under = reading_frame > this.#last_reading_frame ? this.#light_space_at(reading_frame) : -1;
768
+
769
+ if (recorded_under === -1) {
770
+ this.stats.readings_dropped++;
771
+ return false;
772
+ }
773
+
774
+ // the light space has changed since: each want goes through the view it was asked under
775
+ const carry = this.#history_frames[recorded_under] !== this.#history_frames[(this.#history_next + LIGHT_SPACE_HISTORY - 1) % LIGHT_SPACE_HISTORY];
776
+ const carried = this.#carried;
777
+
778
+ this.#last_reading_frame = reading_frame;
779
+ this.stats.readings_applied++;
780
+
781
+ const L = VSM_FEEDBACK_LAYOUT;
782
+ const capacity = this.request_capacity;
783
+ const asked = words[L.REQUEST_COUNT];
784
+ const count = Math.min(asked, capacity);
785
+
786
+ this.stats.requests_dropped = asked - count;
787
+ this.stats.movers = words[L.MOVER_COUNT];
788
+
789
+ let wants = 0;
790
+
791
+ for (let i = 0; i < count; i++) {
792
+ const base = L.HEADER_WORDS + i * L.REQUEST_WORDS;
793
+ const level = words[base + L.REQUEST_LEVEL];
794
+
795
+ if (level >= this.level_count) {
796
+ continue;
797
+ }
798
+
799
+ let x = words[base + L.REQUEST_X] | 0;
800
+ let y = words[base + L.REQUEST_Y] | 0;
801
+ let depth = floats[base + L.REQUEST_DEPTH];
802
+
803
+ if (carry) {
804
+ carry_page(this.#history_views, recorded_under * 16, this.#view, this.page_world_size(level), x, y, depth, carried);
805
+
806
+ x = carried[0];
807
+ y = carried[1];
808
+ depth = carried[2];
809
+ }
810
+
811
+ this.#want_level[wants] = level;
812
+ this.#want_x[wants] = x;
813
+ this.#want_y[wants] = y;
814
+ this.#want_importance[wants] = words[base + L.REQUEST_IMPORTANCE];
815
+ this.#want_depth[wants] = depth;
816
+
817
+ wants++;
818
+
819
+ // what the pixels sample — the page, or whatever stands in for it — is in use
820
+ const slot = this.slot_of_page(level, x, y);
821
+
822
+ if (slot !== -1) {
823
+ this.#touch(slot, reading_frame);
824
+ } else {
825
+ const fallback = this.#find_fallback(level, x, y);
826
+
827
+ if (fallback !== -1) {
828
+ this.#touch(fallback, reading_frame);
829
+ }
830
+ }
831
+ }
832
+
833
+ this.#want_count = wants;
834
+ this.stats.wants = wants;
835
+
836
+ const slot_capacity = this.slots.capacity;
837
+ const stale_offset = vsm_feedback_stale_offset(capacity);
838
+ const stale_count = Math.min(words[L.STALE_COUNT], slot_capacity);
839
+
840
+ for (let i = 0; i < stale_count; i++) {
841
+ const slot = words[stale_offset + i];
842
+
843
+ if (slot >= slot_capacity || this.#slot_level[slot] === -1) {
844
+ continue;
845
+ }
846
+
847
+ // a page drawn in the frame the mover was seen, or later, already shows it
848
+ if (this.#slot_rendered[slot] >= reading_frame) {
849
+ continue;
850
+ }
851
+
852
+ if (this.#slot_stale[slot] === 0) {
853
+ this.#slot_stale[slot] = 1;
854
+ this.#slot_stale_since[slot] = reading_frame;
855
+ this.stats.stale_marked++;
856
+ }
857
+ }
858
+
859
+ return true;
860
+ }
861
+
862
+ /**
863
+ * Touch a slot, never moving its last touch back: a late reading must not age a page a later
864
+ * frame used.
865
+ *
866
+ * @param {number} slot
867
+ * @param {number} frame
868
+ */
869
+ #touch(slot, frame) {
870
+ const slots = this.slots;
871
+
872
+ if (slots.last_touch(slot) < frame) {
873
+ slots.touch(slot, frame);
874
+ }
875
+ }
876
+
877
+ /**
878
+ * The resident page standing in for a world page: its nearest resident ancestor whose level's
879
+ * window holds it, or -1.
880
+ *
881
+ * @param {number} level
882
+ * @param {number} x
883
+ * @param {number} y
884
+ * @returns {number} slot
885
+ */
886
+ #find_fallback(level, x, y) {
887
+ for (let up = level + 1; up < this.level_count; up++) {
888
+ const shift = up - level;
889
+ const slot = this.slot_of_page(up, x >> shift, y >> shift);
890
+
891
+ if (slot !== -1) {
892
+ return slot;
893
+ }
894
+ }
895
+
896
+ return -1;
897
+ }
898
+
899
+ /**
900
+ * Offer a cell to this frame's heap. A cell offered twice keeps the higher kind, the sum of the
901
+ * importance behind it, and the longer wait.
902
+ *
903
+ * @param {number} cell
904
+ * @param {number} kind
905
+ * @param {number} importance
906
+ * @param {number} waited frames
907
+ */
908
+ #offer(cell, kind, importance, waited) {
909
+ const heap = this.#heap;
910
+
911
+ if (this.#candidate_stamp[cell] === this.#stamp) {
912
+ if (kind > this.#candidate_kind[cell]) {
913
+ this.#candidate_kind[cell] = kind;
914
+ }
915
+
916
+ if (waited > this.#candidate_waited[cell]) {
917
+ this.#candidate_waited[cell] = waited;
918
+ }
919
+
920
+ const total = this.#candidate_importance[cell] + importance;
921
+
922
+ this.#candidate_importance[cell] = total;
923
+
924
+ heap.update(cell, candidate_score(this.#candidate_kind[cell], total, this.#candidate_waited[cell]));
925
+
926
+ return;
927
+ }
928
+
929
+ this.#candidate_stamp[cell] = this.#stamp;
930
+ this.#candidate_kind[cell] = kind;
931
+ this.#candidate_importance[cell] = importance;
932
+ this.#candidate_waited[cell] = waited;
933
+
934
+ heap.insert(cell, candidate_score(kind, importance, waited));
935
+ }
936
+
937
+ /**
938
+ * How long a missing cell has waited: since the first of an unbroken run of selects that each
939
+ * offered it. A page that stopped being wanted, or arrived, starts again from nothing.
940
+ *
941
+ * @param {number} cell
942
+ * @param {number} frame
943
+ * @returns {number} frames
944
+ */
945
+ #missing_wait(cell, frame) {
946
+ const last = this.#cell_wait_select[cell];
947
+ const now = this.#select_index;
948
+
949
+ if (last !== now && last !== now - 1) {
950
+ this.#cell_wait_since[cell] = frame;
951
+ }
952
+
953
+ this.#cell_wait_select[cell] = now;
954
+
955
+ return frame - this.#cell_wait_since[cell];
956
+ }
957
+
958
+ /**
959
+ * @param {number} slot
960
+ * @param {number} frame
961
+ * @returns {number} frames the slot has been stale
962
+ */
963
+ #stale_wait(slot, frame) {
964
+ return Math.max(0, frame - this.#slot_stale_since[slot]);
965
+ }
966
+
967
+ /**
968
+ * Choose and claim this frame's pages: candidates from the want set into the heap, then drained
969
+ * best first until `budget` pages are to be rendered. A missing page takes a free slot, or the
970
+ * least recently touched one outside the protection window; when there is none it is refused
971
+ * and the next candidate is tried. Fills the job list.
972
+ *
973
+ * @param {number} frame
974
+ * @param {number} budget pages to render at most
975
+ */
976
+ select(frame, budget) {
977
+ assert.isNonNegativeInteger(budget, 'budget');
978
+
979
+ const heap = this.#heap;
980
+
981
+ heap.clear();
982
+ this.#job_count = 0;
983
+
984
+ this.#select_index++;
985
+ this.#stamp = (this.#stamp + 1) >>> 0;
986
+
987
+ if (this.#stamp === 0) {
988
+ // the stamps wrapped: no cell may still look offered
989
+ this.#candidate_stamp.fill(0);
990
+ this.#stamp = 1;
991
+ }
992
+
993
+ let uncovered = 0;
994
+
995
+ const level_count = this.level_count;
996
+
997
+ for (let i = 0; i < this.#want_count; i++) {
998
+ const level = this.#want_level[i];
999
+ const x = this.#want_x[i];
1000
+ const y = this.#want_y[i];
1001
+ const importance = this.#want_importance[i];
1002
+
1003
+ if (!this.window_contains(level, x, y)) {
1004
+ // scrolled out of its window since it was asked for
1005
+ continue;
1006
+ }
1007
+
1008
+ const cell = this.cell_of(level, x, y);
1009
+ const slot = this.#cell_slot[cell];
1010
+
1011
+ if (slot !== -1) {
1012
+ if (this.#slot_stale[slot] !== 0) {
1013
+ this.#offer(cell, KIND_UPDATE, importance, this.#stale_wait(slot, frame));
1014
+ }
1015
+
1016
+ continue;
1017
+ }
1018
+
1019
+ const waited = this.#missing_wait(cell, frame);
1020
+ const fallback = this.#find_fallback(level, x, y);
1021
+
1022
+ if (fallback !== -1) {
1023
+ this.#offer(cell, KIND_UPDATE, importance, waited);
1024
+
1025
+ if (this.#slot_stale[fallback] !== 0) {
1026
+ // what these pixels see meanwhile is stale too
1027
+ this.#offer(this.#cell_of_slot(fallback), KIND_UPDATE, importance, this.#stale_wait(fallback, frame));
1028
+ }
1029
+
1030
+ continue;
1031
+ }
1032
+
1033
+ uncovered++;
1034
+
1035
+ // the page itself, behind the cover: a budget with room for both fills it this frame
1036
+ this.#offer(cell, KIND_UPDATE, importance, waited);
1037
+
1038
+ let cover = Math.min(level + VSM_COVER_LEVEL_STEP, level_count - 1);
1039
+
1040
+ while (cover < level_count && !this.window_contains(cover, x >> (cover - level), y >> (cover - level))) {
1041
+ cover++;
1042
+ }
1043
+
1044
+ if (cover < level_count) {
1045
+ const shift = cover - level;
1046
+
1047
+ this.#offer(this.cell_of(cover, x >> shift, y >> shift), KIND_COVER, importance, 0);
1048
+ }
1049
+ }
1050
+
1051
+ this.stats.uncovered = uncovered;
1052
+
1053
+ const n = this.pages_per_level;
1054
+ const cells_per_level = n * n;
1055
+ const mask = n - 1;
1056
+
1057
+ let renders = 0;
1058
+
1059
+ // once no slot can be had, none can for the rest of the frame — nothing below frees one —
1060
+ // so the pages after it wait without a search of the pool each
1061
+ let pool_exhausted = false;
1062
+ let refused = 0;
1063
+
1064
+ while (renders < budget && heap.size > 0) {
1065
+ const cell = heap.pop_max();
1066
+ const slot = this.#cell_slot[cell];
1067
+
1068
+ if (slot !== -1) {
1069
+ if (this.#slot_stale[slot] !== 0) {
1070
+ this.#render(slot, frame);
1071
+ this.stats.re_rendered++;
1072
+ renders++;
1073
+ }
1074
+
1075
+ continue;
1076
+ }
1077
+
1078
+ const level = (cell / cells_per_level) | 0;
1079
+ const local = cell - level * cells_per_level;
1080
+ const cell_x = local & mask;
1081
+ const cell_y = (local / n) | 0;
1082
+
1083
+ // the window's page in that cell
1084
+ const origin_x = this.#origin_x[level];
1085
+ const origin_y = this.#origin_y[level];
1086
+ const x = origin_x + ((cell_x - origin_x) & mask);
1087
+ const y = origin_y + ((cell_y - origin_y) & mask);
1088
+
1089
+ const claimed = pool_exhausted ? -1 : this.#claim_slot(frame, level);
1090
+
1091
+ if (claimed === -1) {
1092
+ pool_exhausted = true;
1093
+ refused++;
1094
+ continue;
1095
+ }
1096
+
1097
+ this.#install(claimed, level, x, y, cell);
1098
+ this.#render(claimed, frame);
1099
+ renders++;
1100
+ }
1101
+
1102
+ this.stats.renders = renders;
1103
+ this.stats.refused += refused;
1104
+ this.stats.backlog = heap.size + refused;
1105
+ }
1106
+
1107
+ /**
1108
+ * @param {number} slot
1109
+ * @returns {number} the cell its page sits in
1110
+ */
1111
+ #cell_of_slot(slot) {
1112
+ return this.cell_of(this.#slot_level[slot], this.#slot_x[slot], this.#slot_y[slot]);
1113
+ }
1114
+
1115
+ /**
1116
+ * A free slot, or the least recently touched page's outside the protection window; -1 when
1117
+ * there is neither.
1118
+ *
1119
+ * @param {number} frame
1120
+ * @param {number} level the page coming in
1121
+ * @returns {number}
1122
+ */
1123
+ #claim_slot(frame, level) {
1124
+ const slots = this.slots;
1125
+
1126
+ const free = slots.allocate(frame, false, level);
1127
+
1128
+ if (free !== -1) {
1129
+ return free;
1130
+ }
1131
+
1132
+ const victim = slots.find_eviction_candidate(frame, this.protect_frames);
1133
+
1134
+ if (victim === -1) {
1135
+ return -1;
1136
+ }
1137
+
1138
+ this.#evict(victim);
1139
+ this.stats.evictions++;
1140
+
1141
+ return slots.allocate(frame, false, level);
1142
+ }
1143
+
1144
+ /**
1145
+ * @param {number} slot
1146
+ * @param {number} level
1147
+ * @param {number} x
1148
+ * @param {number} y
1149
+ * @param {number} cell
1150
+ */
1151
+ #install(slot, level, x, y, cell) {
1152
+ this.#cell_slot[cell] = slot;
1153
+
1154
+ this.#slot_level[slot] = level;
1155
+ this.#slot_x[slot] = x;
1156
+ this.#slot_y[slot] = y;
1157
+ this.#slot_stale[slot] = 0;
1158
+
1159
+ this.#mark_cell(cell);
1160
+ this.#mark_slot(slot);
1161
+
1162
+ this.stats.installs++;
1163
+ }
1164
+
1165
+ /**
1166
+ * @param {number} slot
1167
+ * @param {number} frame
1168
+ */
1169
+ #render(slot, frame) {
1170
+ const i = this.#job_count++;
1171
+
1172
+ this.#job_slot[i] = slot;
1173
+ this.#job_level[i] = this.#slot_level[slot];
1174
+ this.#job_x[i] = this.#slot_x[slot];
1175
+ this.#job_y[i] = this.#slot_y[slot];
1176
+
1177
+ this.#slot_stale[slot] = 0;
1178
+ this.#slot_rendered[slot] = frame;
1179
+ }
1180
+
1181
+ /**
1182
+ * @param {number} slot
1183
+ */
1184
+ #evict(slot) {
1185
+ const cell = this.#cell_of_slot(slot);
1186
+
1187
+ this.#cell_slot[cell] = -1;
1188
+ this.#mark_cell(cell);
1189
+
1190
+ this.#slot_level[slot] = -1;
1191
+ this.#slot_stale[slot] = 0;
1192
+ this.#slot_rendered[slot] = -1;
1193
+ this.#mark_slot(slot);
1194
+
1195
+ this.slots.free(slot);
1196
+ }
1197
+
1198
+ /**
1199
+ * @param {number} cell
1200
+ */
1201
+ #mark_cell(cell) {
1202
+ if (this.#dirty_cell_mark[cell] === 0) {
1203
+ this.#dirty_cell_mark[cell] = 1;
1204
+ this.#dirty_cells[this.#dirty_cell_count++] = cell;
1205
+ }
1206
+ }
1207
+
1208
+ /**
1209
+ * @param {number} slot
1210
+ */
1211
+ #mark_slot(slot) {
1212
+ if (this.#dirty_slot_mark[slot] === 0) {
1213
+ this.#dirty_slot_mark[slot] = 1;
1214
+ this.#dirty_slots[this.#dirty_slot_count++] = slot;
1215
+ }
1216
+ }
1217
+
1218
+ /**
1219
+ * The page-table entry of a cell: the slot holding it, as {@link vt_entry_pack} packs it, or
1220
+ * {@link VT_ENTRY_INVALID}.
1221
+ *
1222
+ * @param {number} cell
1223
+ * @returns {number}
1224
+ */
1225
+ cell_entry(cell) {
1226
+ const slot = this.#cell_slot[cell];
1227
+
1228
+ if (slot === -1) {
1229
+ return VT_ENTRY_INVALID;
1230
+ }
1231
+
1232
+ const per_axis = this.slots.slots_per_axis;
1233
+
1234
+ return vt_entry_pack(slot % per_axis, (slot / per_axis) | 0, this.#slot_level[slot]);
1235
+ }
1236
+
1237
+ /**
1238
+ * @param {number} cell
1239
+ * @returns {number} the slot holding the cell's page, or -1
1240
+ */
1241
+ cell_slot(cell) {
1242
+ return this.#cell_slot[cell];
1243
+ }
1244
+
1245
+ /**
1246
+ * @returns {number} cells whose entry changed since {@link clear_dirty}
1247
+ */
1248
+ get dirty_cell_count() {
1249
+ return this.#dirty_cell_count;
1250
+ }
1251
+
1252
+ /**
1253
+ * @param {number} i
1254
+ * @returns {number}
1255
+ */
1256
+ dirty_cell(i) {
1257
+ return this.#dirty_cells[i];
1258
+ }
1259
+
1260
+ /**
1261
+ * @returns {number} slots whose page changed since {@link clear_dirty}
1262
+ */
1263
+ get dirty_slot_count() {
1264
+ return this.#dirty_slot_count;
1265
+ }
1266
+
1267
+ /**
1268
+ * @param {number} i
1269
+ * @returns {number}
1270
+ */
1271
+ dirty_slot(i) {
1272
+ return this.#dirty_slots[i];
1273
+ }
1274
+
1275
+ /**
1276
+ * @returns {boolean} whether a window moved since {@link clear_dirty}
1277
+ */
1278
+ get levels_dirty() {
1279
+ return this.#levels_dirty;
1280
+ }
1281
+
1282
+ /**
1283
+ * Forget what changed: the rows have been written.
1284
+ */
1285
+ clear_dirty() {
1286
+ for (let i = 0; i < this.#dirty_cell_count; i++) {
1287
+ this.#dirty_cell_mark[this.#dirty_cells[i]] = 0;
1288
+ }
1289
+
1290
+ for (let i = 0; i < this.#dirty_slot_count; i++) {
1291
+ this.#dirty_slot_mark[this.#dirty_slots[i]] = 0;
1292
+ }
1293
+
1294
+ this.#dirty_cell_count = 0;
1295
+ this.#dirty_slot_count = 0;
1296
+ this.#levels_dirty = false;
1297
+ }
1298
+
1299
+ /**
1300
+ * @param {number} slot
1301
+ * @returns {number} the level of the slot's page, or -1 when it is empty
1302
+ */
1303
+ slot_level(slot) {
1304
+ return this.#slot_level[slot];
1305
+ }
1306
+
1307
+ /**
1308
+ * @param {number} slot
1309
+ * @returns {number}
1310
+ */
1311
+ slot_x(slot) {
1312
+ return this.#slot_x[slot];
1313
+ }
1314
+
1315
+ /**
1316
+ * @param {number} slot
1317
+ * @returns {number}
1318
+ */
1319
+ slot_y(slot) {
1320
+ return this.#slot_y[slot];
1321
+ }
1322
+
1323
+ /**
1324
+ * @param {number} slot
1325
+ * @returns {boolean} whether a mover crossed the slot's page after it was last rendered
1326
+ */
1327
+ slot_stale(slot) {
1328
+ return this.#slot_stale[slot] !== 0;
1329
+ }
1330
+
1331
+ /**
1332
+ * @param {number} slot
1333
+ * @returns {number} the frame it was last rendered in, or -1
1334
+ */
1335
+ slot_rendered_frame(slot) {
1336
+ return this.#slot_rendered[slot];
1337
+ }
1338
+
1339
+ /**
1340
+ * @returns {number} wants in the want set
1341
+ */
1342
+ get want_count() {
1343
+ return this.#want_count;
1344
+ }
1345
+
1346
+ /**
1347
+ * @param {number} i
1348
+ * @returns {number}
1349
+ */
1350
+ want_level(i) {
1351
+ return this.#want_level[i];
1352
+ }
1353
+
1354
+ /**
1355
+ * @param {number} i
1356
+ * @returns {number}
1357
+ */
1358
+ want_x(i) {
1359
+ return this.#want_x[i];
1360
+ }
1361
+
1362
+ /**
1363
+ * @param {number} i
1364
+ * @returns {number}
1365
+ */
1366
+ want_y(i) {
1367
+ return this.#want_y[i];
1368
+ }
1369
+
1370
+ /**
1371
+ * @returns {number} pages {@link select} chose to render
1372
+ */
1373
+ get job_count() {
1374
+ return this.#job_count;
1375
+ }
1376
+
1377
+ /**
1378
+ * @param {number} i
1379
+ * @returns {number}
1380
+ */
1381
+ job_slot(i) {
1382
+ return this.#job_slot[i];
1383
+ }
1384
+
1385
+ /**
1386
+ * @param {number} i
1387
+ * @returns {number}
1388
+ */
1389
+ job_level(i) {
1390
+ return this.#job_level[i];
1391
+ }
1392
+
1393
+ /**
1394
+ * @param {number} i
1395
+ * @returns {number}
1396
+ */
1397
+ job_x(i) {
1398
+ return this.#job_x[i];
1399
+ }
1400
+
1401
+ /**
1402
+ * @param {number} i
1403
+ * @returns {number}
1404
+ */
1405
+ job_y(i) {
1406
+ return this.#job_y[i];
1407
+ }
1408
+ }