@woosh/meep-engine 3.16.0 → 3.17.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 (410) hide show
  1. package/editor/particles/effect/ParticleEffectDocument.d.ts +21 -6
  2. package/editor/particles/effect/ParticleEffectDocument.d.ts.map +1 -1
  3. package/editor/particles/effect/ParticleEffectDocument.js +38 -17
  4. package/editor/view/node-graph/NodeGraphClipboard.d.ts +186 -0
  5. package/editor/view/node-graph/NodeGraphClipboard.d.ts.map +1 -0
  6. package/editor/view/node-graph/NodeGraphClipboard.js +231 -0
  7. package/editor/view/node-graph/NodeGraphEditorView.d.ts +59 -1
  8. package/editor/view/node-graph/NodeGraphEditorView.d.ts.map +1 -1
  9. package/editor/view/node-graph/NodeGraphEditorView.js +248 -5
  10. package/editor/view/node-graph/NodeGraphView.d.ts.map +1 -1
  11. package/editor/view/node-graph/NodeGraphView.js +84 -8
  12. package/editor/view/node-graph/actions/connection_bind_actions.d.ts +23 -0
  13. package/editor/view/node-graph/actions/connection_bind_actions.d.ts.map +1 -0
  14. package/editor/view/node-graph/actions/connection_bind_actions.js +39 -0
  15. package/editor/view/particles/effect/ParticleCodeEditorView.d.ts.map +1 -1
  16. package/editor/view/particles/effect/ParticleCodeEditorView.js +4 -3
  17. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts +20 -0
  18. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts.map +1 -0
  19. package/editor/view/particles/effect/ParticleCurveEditorView.js +574 -0
  20. package/editor/view/particles/effect/ParticleEmitterInspectorView.d.ts.map +1 -1
  21. package/editor/view/particles/effect/ParticleEmitterInspectorView.js +27 -1
  22. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts +24 -0
  23. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts.map +1 -0
  24. package/editor/view/particles/effect/ParticleGradientEditorView.js +472 -0
  25. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts +5 -0
  26. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts.map +1 -1
  27. package/editor/view/particles/effect/ParticleGraphEditorView.js +61 -31
  28. package/editor/view/particles/effect/ParticleNodeParametersView.d.ts.map +1 -1
  29. package/editor/view/particles/effect/ParticleNodeParametersView.js +33 -0
  30. package/editor/view/particles/effect/particle-editor.css +123 -35
  31. package/package.json +1 -1
  32. package/src/core/binary/allocator/OffsetAllocator.d.ts +25 -16
  33. package/src/core/binary/allocator/OffsetAllocator.d.ts.map +1 -1
  34. package/src/core/binary/allocator/OffsetAllocator.js +10 -1
  35. package/src/core/collection/array/typed/float32_array_hash.d.ts +21 -0
  36. package/src/core/collection/array/typed/float32_array_hash.d.ts.map +1 -0
  37. package/src/core/collection/array/typed/float32_array_hash.js +38 -0
  38. package/src/core/graph/csr/CSRGraph.d.ts +8 -1
  39. package/src/core/graph/csr/CSRGraph.d.ts.map +1 -1
  40. package/src/core/graph/csr/CSRGraph.js +14 -6
  41. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts +24 -0
  42. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts.map +1 -1
  43. package/src/core/model/node-graph/visual/layout/LayoutProblem.js +27 -0
  44. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts +26 -0
  45. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts.map +1 -1
  46. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.js +28 -0
  47. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts +92 -3
  48. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts.map +1 -1
  49. package/src/core/model/node-graph/visual/layout/RouteGrid.js +405 -242
  50. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts +50 -5
  51. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts.map +1 -1
  52. package/src/core/model/node-graph/visual/layout/RouteSearch.js +160 -24
  53. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts +7 -0
  54. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts.map +1 -1
  55. package/src/core/model/node-graph/visual/layout/RouteSet.js +8 -0
  56. package/src/core/model/node-graph/visual/layout/layout_assign_coordinates.js +9 -3
  57. package/src/core/model/node-graph/visual/layout/layout_build_problem.d.ts.map +1 -1
  58. package/src/core/model/node-graph/visual/layout/layout_build_problem.js +7 -0
  59. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts +9 -3
  60. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts.map +1 -1
  61. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.js +17 -3
  62. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts +9 -6
  63. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts.map +1 -1
  64. package/src/core/model/node-graph/visual/layout/layout_node_graph.js +49 -7
  65. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts +11 -1
  66. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts.map +1 -1
  67. package/src/core/model/node-graph/visual/layout/layout_order_layers.js +135 -16
  68. package/src/core/model/node-graph/visual/layout/route_connections.d.ts +29 -0
  69. package/src/core/model/node-graph/visual/layout/route_connections.d.ts.map +1 -1
  70. package/src/core/model/node-graph/visual/layout/route_connections.js +716 -309
  71. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts +3 -2
  72. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts.map +1 -1
  73. package/src/core/model/node-graph/visual/layout/route_grid_build.js +7 -15
  74. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts +6 -0
  75. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts.map +1 -1
  76. package/src/core/model/node-graph/visual/layout/route_nudge.js +42 -13
  77. package/src/engine/animation/AnimationUtils.js +246 -246
  78. package/src/engine/animation/Animations.js +74 -74
  79. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2078 -2078
  80. package/src/engine/ecs/EventType.d.ts +0 -1
  81. package/src/engine/ecs/EventType.js +1 -26
  82. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts +1 -1
  83. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts.map +1 -1
  84. package/src/engine/ecs/ik/ik_write_local_rotation.js +3 -7
  85. package/src/engine/ecs/parent/EntityNode.d.ts.map +1 -1
  86. package/src/engine/ecs/parent/EntityNode.js +4 -9
  87. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  88. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +360 -364
  89. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts +41 -0
  90. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts.map +1 -0
  91. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.js +40 -0
  92. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +8 -0
  93. package/src/engine/ecs/transform/t64_announce_change.d.ts +10 -7
  94. package/src/engine/ecs/transform/t64_announce_change.d.ts.map +1 -1
  95. package/src/engine/ecs/transform/t64_announce_change.js +12 -12
  96. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts +30 -0
  97. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts.map +1 -0
  98. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.js +29 -0
  99. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts.map +1 -1
  100. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +13 -21
  101. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
  102. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +4 -8
  103. package/src/engine/graphics3/LightSystem.d.ts.map +1 -1
  104. package/src/engine/graphics3/LightSystem.js +4 -8
  105. package/src/engine/graphics3/MeshSystem.d.ts.map +1 -1
  106. package/src/engine/graphics3/MeshSystem.js +9 -19
  107. package/src/engine/graphics3/ParticleEmitterSystem.d.ts.map +1 -1
  108. package/src/engine/graphics3/ParticleEmitterSystem.js +4 -8
  109. package/src/engine/graphics3/ShadedGeometrySystem.d.ts.map +1 -1
  110. package/src/engine/graphics3/ShadedGeometrySystem.js +4 -10
  111. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts +1 -1
  112. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts.map +1 -1
  113. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.js +4 -8
  114. package/src/engine/intelligence/behavior/ecs/OrbitingBehavior.js +218 -218
  115. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  116. package/src/engine/physics/ecs/PhysicsSystem.js +2 -5
  117. package/src/engine/sound/ecs/SoundListenerSystem.d.ts.map +1 -1
  118. package/src/engine/sound/ecs/SoundListenerSystem.js +6 -8
  119. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts +0 -4
  120. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts.map +1 -1
  121. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +5 -11
  122. package/src/shade/playground/particle_editor/README.md +43 -2
  123. package/src/shade/playground/particle_system/particle_prototype.d.ts.map +1 -1
  124. package/src/shade/playground/particle_system/particle_prototype.js +4 -5
  125. package/src/shade/playground/particle_system/particle_scene.js +4 -4
  126. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  127. package/src/shade/renderer/Renderer.js +13 -1
  128. package/src/shade/renderer/particles/DESIGN.md +104 -19
  129. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +64 -50
  130. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  131. package/src/shade/renderer/particles/GPUParticleSystem.js +176 -78
  132. package/src/shade/renderer/particles/ParticleConstants.d.ts +59 -23
  133. package/src/shade/renderer/particles/ParticleConstants.d.ts.map +1 -1
  134. package/src/shade/renderer/particles/ParticleConstants.js +66 -23
  135. package/src/shade/renderer/particles/ParticleRenderMode.d.ts +19 -0
  136. package/src/shade/renderer/particles/ParticleRenderMode.d.ts.map +1 -0
  137. package/src/shade/renderer/particles/ParticleRenderMode.js +33 -0
  138. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts.map +1 -1
  139. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.js +167 -166
  140. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts +37 -0
  141. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts.map +1 -0
  142. package/src/shade/renderer/particles/data/particle_emitter_record.js +109 -0
  143. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.d.ts +3 -3
  144. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.js +303 -303
  145. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +18 -0
  146. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -1
  147. package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +152 -133
  148. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +0 -11
  149. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
  150. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +720 -621
  151. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
  152. package/src/shade/renderer/particles/graph/compile_particle_graph.js +8 -3
  153. package/src/shade/renderer/particles/graph/flatten_particle_graph.d.ts.map +1 -1
  154. package/src/shade/renderer/particles/graph/flatten_particle_graph.js +244 -213
  155. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.d.ts.map +1 -1
  156. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.js +6 -0
  157. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.d.ts.map +1 -1
  158. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.js +294 -319
  159. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.d.ts.map +1 -1
  160. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.js +524 -517
  161. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts +22 -0
  162. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts.map +1 -0
  163. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.js +517 -0
  164. package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
  165. package/src/shade/renderer/particles/graph/groups/simplex_reference.js +185 -159
  166. package/src/shade/renderer/particles/graph/make_particle_group.d.ts.map +1 -1
  167. package/src/shade/renderer/particles/graph/make_particle_group.js +2 -20
  168. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts +17 -0
  169. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts.map +1 -0
  170. package/src/shade/renderer/particles/graph/particle_compare_ops.js +22 -0
  171. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts +28 -8
  172. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts.map +1 -1
  173. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.js +49 -25
  174. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts +40 -0
  175. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts.map +1 -1
  176. package/src/shade/renderer/particles/graph/particle_node_parameters.js +312 -234
  177. package/src/shade/renderer/particles/graph/particle_node_presentation.d.ts.map +1 -1
  178. package/src/shade/renderer/particles/graph/particle_node_presentation.js +59 -3
  179. package/src/shade/renderer/particles/graph/particle_ramp.d.ts +230 -0
  180. package/src/shade/renderer/particles/graph/particle_ramp.d.ts.map +1 -0
  181. package/src/shade/renderer/particles/graph/particle_ramp.js +403 -0
  182. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts +7 -1
  183. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts.map +1 -1
  184. package/src/shade/renderer/particles/graph/validate_particle_graph.js +66 -10
  185. package/src/shade/renderer/particles/graph_particles.d.ts +23 -69
  186. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  187. package/src/shade/renderer/particles/graph_particles.js +105 -262
  188. package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
  189. package/src/shade/renderer/particles/graph_particles_avboit.js +3 -2
  190. package/src/shade/renderer/particles/graph_particles_billboard.d.ts +37 -0
  191. package/src/shade/renderer/particles/graph_particles_billboard.d.ts.map +1 -0
  192. package/src/shade/renderer/particles/graph_particles_billboard.js +93 -0
  193. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +51 -22
  194. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -1
  195. package/src/shade/renderer/particles/isa/InstructionStream.js +98 -38
  196. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts +49 -8
  197. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts.map +1 -1
  198. package/src/shade/renderer/particles/isa/ParticleProgram.js +95 -150
  199. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts +306 -64
  200. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts.map +1 -1
  201. package/src/shade/renderer/particles/isa/ParticleVMISA.js +505 -136
  202. package/src/shade/renderer/particles/isa/particle_assembly.d.ts +36 -12
  203. package/src/shade/renderer/particles/isa/particle_assembly.d.ts.map +1 -1
  204. package/src/shade/renderer/particles/isa/particle_assembly.js +354 -139
  205. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts +9 -0
  206. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts.map +1 -0
  207. package/src/shade/renderer/particles/isa/validate_particle_program.js +128 -0
  208. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts +23 -0
  209. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts.map +1 -1
  210. package/src/shade/renderer/particles/layout/ParticleLayout.js +68 -0
  211. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts +77 -0
  212. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts.map +1 -0
  213. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.js +219 -0
  214. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts +83 -0
  215. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts.map +1 -0
  216. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.js +283 -0
  217. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts +133 -0
  218. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts.map +1 -0
  219. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.js +334 -0
  220. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts +117 -0
  221. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts.map +1 -0
  222. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.js +70 -0
  223. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts +97 -0
  224. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts.map +1 -0
  225. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.js +462 -0
  226. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts +116 -0
  227. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts.map +1 -0
  228. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.js +687 -0
  229. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts +57 -0
  230. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts.map +1 -0
  231. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.js +860 -0
  232. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts +115 -0
  233. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts.map +1 -0
  234. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.js +264 -0
  235. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts +81 -42
  236. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts.map +1 -1
  237. package/src/shade/renderer/particles/optimizer/optimize_particle_program.js +477 -478
  238. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts +100 -0
  239. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts.map +1 -0
  240. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.js +236 -0
  241. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts +139 -59
  242. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts.map +1 -1
  243. package/src/shade/renderer/particles/optimizer/particle_program_analysis.js +764 -647
  244. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts +40 -7
  245. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts.map +1 -1
  246. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.js +376 -213
  247. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts +30 -3
  248. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts.map +1 -1
  249. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.js +337 -223
  250. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts +24 -38
  251. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  252. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +166 -206
  253. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts +99 -0
  254. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts.map +1 -0
  255. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.js +313 -0
  256. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts +3 -2
  257. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts.map +1 -1
  258. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.js +198 -197
  259. package/src/shade/renderer/particles/optimizer/pass_eliminate_dead_code.d.ts.map +1 -1
  260. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts +19 -0
  261. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
  262. package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +678 -561
  263. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts +22 -0
  264. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts.map +1 -0
  265. package/src/shade/renderer/particles/optimizer/pass_inline_constants.js +221 -0
  266. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.d.ts.map +1 -1
  267. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.js +63 -72
  268. package/src/shade/renderer/particles/optimizer/pass_pack_registers.d.ts.map +1 -1
  269. package/src/shade/renderer/particles/optimizer/pass_pack_registers.js +87 -86
  270. package/src/shade/renderer/particles/optimizer/pass_peephole.d.ts.map +1 -1
  271. package/src/shade/renderer/particles/optimizer/pass_peephole.js +392 -271
  272. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.d.ts.map +1 -1
  273. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.js +361 -268
  274. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts +4 -0
  275. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts.map +1 -0
  276. package/src/shade/renderer/particles/optimizer/pass_schedule.js +244 -0
  277. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.d.ts.map +1 -1
  278. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.js +154 -151
  279. package/src/shade/renderer/particles/particle_test_fixtures.d.ts +20 -0
  280. package/src/shade/renderer/particles/particle_test_fixtures.d.ts.map +1 -1
  281. package/src/shade/renderer/particles/particle_test_fixtures.js +229 -203
  282. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts +99 -25
  283. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts.map +1 -1
  284. package/src/shade/renderer/particles/runtime/EmitterRegistry.js +438 -286
  285. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts +109 -0
  286. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts.map +1 -0
  287. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.js +127 -0
  288. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +87 -102
  289. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
  290. package/src/shade/renderer/particles/runtime/ParticleEmitter.js +184 -163
  291. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts +26 -0
  292. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts.map +1 -1
  293. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts +130 -0
  294. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts.map +1 -0
  295. package/src/shade/renderer/particles/runtime/ProgramHeap.js +595 -0
  296. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts +91 -0
  297. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts.map +1 -0
  298. package/src/shade/renderer/particles/runtime/ProgramPlacement.js +106 -0
  299. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +5 -2
  300. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
  301. package/src/shade/renderer/particles/runtime/create_particle_effect.js +22 -19
  302. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts +28 -0
  303. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts.map +1 -0
  304. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.js +136 -0
  305. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts +35 -0
  306. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts.map +1 -0
  307. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.js +133 -0
  308. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts +18 -9
  309. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  310. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +20 -80
  311. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts +33 -0
  312. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts.map +1 -0
  313. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.js +114 -0
  314. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts +8 -0
  315. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  316. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +33 -101
  317. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts +45 -0
  318. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts.map +1 -0
  319. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.js +123 -0
  320. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts +51 -0
  321. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts.map +1 -0
  322. package/src/shade/renderer/particles/sort/graph_particle_sort.js +120 -0
  323. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts +6 -0
  324. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts.map +1 -0
  325. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.js +16 -0
  326. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +4 -1
  327. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  328. package/src/shade/renderer/particles/vm/ParticleVMReference.js +261 -140
  329. package/src/shade/renderer/particles/vm/chunk_particle_vm.d.ts.map +1 -1
  330. package/src/shade/renderer/particles/vm/chunk_particle_vm.js +217 -255
  331. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts +43 -0
  332. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts.map +1 -0
  333. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.js +55 -0
  334. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts +62 -0
  335. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts.map +1 -0
  336. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.js +86 -0
  337. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts +22 -0
  338. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts.map +1 -0
  339. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.js +32 -0
  340. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts +9 -0
  341. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts.map +1 -0
  342. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.js +19 -0
  343. package/src/shade/renderer/shader/chunk/noise/psrdnoise/README.md +65 -0
  344. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts +24 -0
  345. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts.map +1 -0
  346. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.js +83 -0
  347. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts +25 -0
  348. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts.map +1 -0
  349. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.js +42 -0
  350. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts +31 -0
  351. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts.map +1 -0
  352. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.js +80 -0
  353. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts +26 -0
  354. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts.map +1 -0
  355. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.js +109 -0
  356. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts +38 -0
  357. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts.map +1 -0
  358. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.js +129 -0
  359. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts +21 -0
  360. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts.map +1 -0
  361. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.js +35 -0
  362. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts +19 -0
  363. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts.map +1 -0
  364. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.js +28 -0
  365. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts +13 -0
  366. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts.map +1 -0
  367. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.js +22 -0
  368. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts +22 -0
  369. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts.map +1 -0
  370. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.js +59 -0
  371. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts +23 -0
  372. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts.map +1 -0
  373. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.js +74 -0
  374. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts +34 -1
  375. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts.map +1 -1
  376. package/src/shade/wgsl/emulator/ComputeShaderEmulator.js +41 -1
  377. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts +3 -2
  378. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts.map +1 -1
  379. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +49 -10
  380. package/src/view/minimap/dom/MinimapCameraView.d.ts.map +1 -1
  381. package/src/view/minimap/dom/MinimapCameraView.js +4 -9
  382. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts +0 -15
  383. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts.map +0 -1
  384. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts +0 -45
  385. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts.map +0 -1
  386. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts +0 -24
  387. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts.map +0 -1
  388. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts +0 -19
  389. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts.map +0 -1
  390. package/src/engine/ecs/transform/__probe.d.ts +0 -4
  391. package/src/engine/ecs/transform/__probe.d.ts.map +0 -1
  392. package/src/engine/ecs/util/hideEntityGracefully.d.ts +0 -9
  393. package/src/engine/ecs/util/hideEntityGracefully.d.ts.map +0 -1
  394. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts +0 -27
  395. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts.map +0 -1
  396. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts +0 -14
  397. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +0 -1
  398. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts +0 -156
  399. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts.map +0 -1
  400. package/src/shade/renderer/particles/runtime/Emitter.d.ts +0 -219
  401. package/src/shade/renderer/particles/runtime/Emitter.d.ts.map +0 -1
  402. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts +0 -77
  403. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts.map +0 -1
  404. package/src/shade/renderer/particles/runtime/ProgramArena.js +0 -155
  405. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts +0 -15
  406. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts.map +0 -1
  407. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts +0 -34
  408. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts.map +0 -1
  409. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts +0 -32
  410. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts.map +0 -1
@@ -1,271 +1,392 @@
1
- import { VM_MAX_WIDTH, VM_OP, vm_encode_word0 } from "../isa/ParticleVMISA.js";
2
- import {
3
- ParticleValueNumbering,
4
- decode_particle_instructions,
5
- particle_instruction_is_well_formed,
6
- particle_run_is_dead_after,
7
- particle_run_is_touched_between,
8
- } from "./particle_program_analysis.js";
9
-
10
- /** @type {string} */
11
- export const PARTICLE_PASS_PEEPHOLE = "peephole";
12
-
13
- /**
14
- * Local rewrites over a couple of neighbouring instructions — the ones that need no analysis beyond
15
- * "these two are really one".
16
- *
17
- * ## Constant loads
18
- *
19
- * A frontend emits one load per literal, because it lowers one value at a time. Where two of them
20
- * land in adjacent registers from adjacent pool words, they are one load: the pool is addressed by
21
- * word, so a two-word run is as loadable as a one-word one. The graph compiler's `mad` node — a
22
- * multiplier and an addend, interned side by side — is exactly this shape.
23
- *
24
- * ## Multiplying by a broadcast
25
- *
26
- * `MUL` against a value whose every lane is the same is `SCALE`, which reads one lane and takes the
27
- * broadcast out of the program. Widening a scalar to meet a wider operation is the compiler's
28
- * standard move, and `SCALE` is the opcode that exists so it does not have to.
29
- *
30
- * ## Fusing a multiply and an add — off by default
31
- *
32
- * `MUL` then `ADD` is `MAD`, one instruction rather than two. It is also, unavoidably, a DIFFERENT
33
- * NUMBER: the pair rounds the product to `f32` before adding, and `MAD` does not — and on hardware
34
- * that contracts it into an FMA, does not round it at all. That is the ordinary compiler trade and
35
- * it is a perfectly reasonable one to take for a particle effect, but it is not the bit-identity the
36
- * rest of this optimizer holds itself to, so it is a switch rather than a default:
37
- * `{ passes: { peephole: { fuse_multiply_add: true } } }`.
38
- *
39
- * @type {import("./optimize_particle_program.js").ParticleOptimizerPass}
40
- */
41
- export function pass_peephole(instructions, context) {
42
- const options = {
43
- merge_constant_loads: true,
44
- multiply_by_broadcast: true,
45
- fuse_multiply_add: false,
46
- ...context,
47
- };
48
-
49
- let current = instructions;
50
- let changed = false;
51
-
52
- if (options.multiply_by_broadcast) {
53
- const result = scale_by_broadcast(current, context.register_slots);
54
-
55
- if (result !== null) {
56
- current = result;
57
- changed = true;
58
- }
59
- }
60
-
61
- for (const rewrite of [
62
- options.merge_constant_loads ? merge_one_constant_load : null,
63
- options.fuse_multiply_add ? fuse_one_multiply_add : null,
64
- ]) {
65
- if (rewrite === null) {
66
- continue;
67
- }
68
-
69
- // Applied one at a time and re-derived: each rewrite deletes an instruction, and re-deriving
70
- // a program of this size is cheaper than keeping indices and liveness honest across a splice.
71
- for (let attempt = 0; attempt < instructions.length; attempt++) {
72
- const result = rewrite(current);
73
-
74
- if (result === null) {
75
- break;
76
- }
77
-
78
- current = result;
79
- changed = true;
80
- }
81
- }
82
-
83
- return changed ? current : null;
84
- }
85
-
86
- /**
87
- * `MUL` by a value whose lanes all hold the same number is `SCALE` of the register that number came
88
- * from — which usually leaves the broadcast that widened it dead.
89
- *
90
- * @param {number[][]} instructions
91
- * @param {number} slots
92
- * @returns {number[][]|null}
93
- */
94
- function scale_by_broadcast(instructions, slots) {
95
- const decoded = decode_particle_instructions(instructions);
96
- const values = new ParticleValueNumbering(slots);
97
-
98
- /** @type {number[][]} */
99
- const out = [];
100
-
101
- let changed = false;
102
-
103
- for (const instruction of decoded) {
104
- let words = instruction.words;
105
-
106
- if (instruction.op === VM_OP.MUL && instruction.operation_width > 1) {
107
- // Either operand may be the broadcast one; `SCALE` keeps the vector in slot 0.
108
- const candidate = as_scale(instruction, instruction.reads[1], instruction.reads[0], values)
109
- ?? as_scale(instruction, instruction.reads[0], instruction.reads[1], values);
110
-
111
- if (candidate !== null) {
112
- words = candidate;
113
- changed = true;
114
- }
115
- }
116
-
117
- out.push(words);
118
- values.publish(words);
119
- }
120
-
121
- return changed ? out : null;
122
- }
123
-
124
- /**
125
- * @param {import("./particle_program_analysis.js").ParticleDecodedInstruction} instruction
126
- * @param {import("./particle_program_analysis.js").ParticleOperandRun} broadcast the operand hoped
127
- * to hold one value across every lane
128
- * @param {import("./particle_program_analysis.js").ParticleOperandRun} vector the other one
129
- * @param {ParticleValueNumbering} values
130
- * @returns {number[]|null}
131
- */
132
- function as_scale(instruction, broadcast, vector, values) {
133
- const ids = values.ids_of(broadcast.base, broadcast.span);
134
-
135
- if (ids === null || ids.some(id => id !== ids[0])) {
136
- return null;
137
- }
138
-
139
- // The lane the value was computed in, which is the single slot SCALE will read.
140
- const scalar = values.origin_of(broadcast.base, 1);
141
-
142
- if (scalar < 0) {
143
- return null;
144
- }
145
-
146
- const words = [
147
- vm_encode_word0(VM_OP.SCALE, instruction.width),
148
- instruction.write.base,
149
- vector.base,
150
- scalar,
151
- 0,
152
- ];
153
-
154
- return particle_instruction_is_well_formed(words) ? words : null;
155
- }
156
-
157
- /**
158
- * Two constant loads that fill adjacent registers from adjacent pool words are one load.
159
- *
160
- * @param {number[][]} instructions
161
- * @returns {number[][]|null}
162
- */
163
- function merge_one_constant_load(instructions) {
164
- const decoded = decode_particle_instructions(instructions);
165
-
166
- for (let first = 0; first < decoded.length; first++) {
167
- const a = decoded[first];
168
-
169
- if (a.op !== VM_OP.LOAD_CONST) {
170
- continue;
171
- }
172
-
173
- for (let second = first + 1; second < decoded.length; second++) {
174
- const b = decoded[second];
175
-
176
- if (b.op === VM_OP.HALT) {
177
- break;
178
- }
179
-
180
- if (b.op !== VM_OP.LOAD_CONST
181
- || b.words[1] !== a.words[1] + a.operation_width
182
- || b.words[2] !== a.words[2] + a.operation_width
183
- || a.operation_width + b.operation_width > VM_MAX_WIDTH) {
184
- continue;
185
- }
186
-
187
- const base = a.words[1];
188
- const span = a.operation_width + b.operation_width;
189
-
190
- // The second load moves back to the first one's position, so nothing in between may
191
- // read or write either register run.
192
- if (particle_run_is_touched_between(decoded, first + 1, second, base, span)) {
193
- continue;
194
- }
195
-
196
- const words = [vm_encode_word0(VM_OP.LOAD_CONST, span), base, a.words[2], 0, 0];
197
-
198
- if (!particle_instruction_is_well_formed(words)) {
199
- continue;
200
- }
201
-
202
- const out = instructions.slice();
203
-
204
- out[first] = words;
205
- out.splice(second, 1);
206
-
207
- return out;
208
- }
209
- }
210
-
211
- return null;
212
- }
213
-
214
- /**
215
- * `MUL` into a register that the very next instruction adds to something is `MAD`.
216
- *
217
- * Adjacent only: the pair is what a frontend emits for `a * b + c`, and looking further would buy
218
- * little for a rewrite that is off by default anyway.
219
- *
220
- * @param {number[][]} instructions
221
- * @returns {number[][]|null}
222
- */
223
- function fuse_one_multiply_add(instructions) {
224
- const decoded = decode_particle_instructions(instructions);
225
-
226
- for (let i = 0; i + 1 < decoded.length; i++) {
227
- const multiply = decoded[i];
228
- const add = decoded[i + 1];
229
-
230
- if (multiply.op !== VM_OP.MUL || add.op !== VM_OP.ADD || multiply.width !== add.width) {
231
- continue;
232
- }
233
-
234
- const product = multiply.write.base;
235
- const span = multiply.write.span;
236
-
237
- // The addend is whichever operand of the ADD is not the product.
238
- const addend = add.reads.find(read => read.base !== product);
239
- const uses_product = add.reads.some(read => read.base === product);
240
-
241
- if (!uses_product || addend === undefined) {
242
- continue;
243
- }
244
-
245
- // The product must be nobody else's: the MAD does not leave it in a register.
246
- if (product !== add.write.base && !particle_run_is_dead_after(decoded, i + 1, product, span)) {
247
- continue;
248
- }
249
-
250
- const words = [
251
- vm_encode_word0(VM_OP.MAD, multiply.width),
252
- add.write.base,
253
- multiply.reads[0].base,
254
- multiply.reads[1].base,
255
- addend.base,
256
- ];
257
-
258
- if (!particle_instruction_is_well_formed(words)) {
259
- continue;
260
- }
261
-
262
- const out = instructions.slice();
263
-
264
- out[i] = words;
265
- out.splice(i + 1, 1);
266
-
267
- return out;
268
- }
269
-
270
- return null;
271
- }
1
+ import { VM_KIND, VM_MAX_WIDTH, VM_OP, vm_f32_value, vm_immediate, vm_pack_instruction, vm_register } from "../isa/ParticleVMISA.js";
2
+ import { PARTICLE_EXACT_INTEGER_LIMIT, ParticleIntegerBounds } from "./particle_integer_bounds.js";
3
+ import {
4
+ decode_particle_instructions,
5
+ particle_instruction_is_well_formed,
6
+ particle_run_is_dead_after,
7
+ particle_run_is_touched_between,
8
+ particle_run_is_written_between,
9
+ particle_runs_overlap,
10
+ } from "./particle_program_analysis.js";
11
+
12
+ /** @type {string} */
13
+ export const PARTICLE_PASS_PEEPHOLE = "peephole";
14
+
15
+ /**
16
+ * Local rewrites over a couple of neighbouring instructions — the ones that need no analysis beyond
17
+ * "these two are really one".
18
+ *
19
+ * ## Constant loads
20
+ *
21
+ * A frontend emits one load per literal, because it lowers one value at a time. Where two of them
22
+ * land in adjacent registers from adjacent pool words, they are one load: the pool is addressed by
23
+ * word, so a two-word run is as loadable as a one-word one. The graph compiler's `mad` node — a
24
+ * multiplier and an addend, interned side by side — is exactly this shape.
25
+ *
26
+ * ## Copies
27
+ *
28
+ * The same rewrite over `MOV`, and for the same reason: a `vec3` node is a copy per lane, because
29
+ * writing one lane on its own is the only thing a lane-at-a-time constructor can do, and three
30
+ * copies of consecutive slots into consecutive slots are one copy of the run, in whatever order the
31
+ * lanes were filled. Two copies of the same literal into adjacent slots are one copy too. It matters beyond the instructions it saves — a wide
32
+ * copy is a whole value moving, which is the shape {@link ./pass_propagate_copies.js} can then
33
+ * forward into its producer and delete outright, and three narrow ones are not.
34
+ *
35
+ * ## Fusing a multiply and an add
36
+ *
37
+ * `MUL` then `ADD` (or `SUB`) is `MAD`, one instruction rather than two. It is also, in general, a
38
+ * DIFFERENT NUMBER: the pair rounds the product to `f32` before adding, and `MAD` does not — and on
39
+ * hardware that contracts it into an FMA, does not round it at all. That is the ordinary compiler
40
+ * trade and it is a perfectly reasonable one to take for a particle effect, but it is not the
41
+ * bit-identity the rest of this optimizer holds itself to, so it is a switch rather than a default:
42
+ * `{ fuse_multiply_add: true }`.
43
+ *
44
+ * The exception, on by default, is a product that provably needs no rounding: an integer within
45
+ * 2^24, which {@link ParticleIntegerBounds} proves for the masks effects are made of —
46
+ * `cmp * 2 - 1`, `sign * step + p`, a lane count times a flag. Those pairs are fused with nothing
47
+ * given up, and `fuse_exact_multiply_add: false` switches even that off.
48
+ *
49
+ * @type {import("./optimize_particle_program.js").ParticleOptimizerPass}
50
+ */
51
+ export function pass_peephole(instructions, context) {
52
+ const options = {
53
+ merge_constant_loads: true,
54
+ merge_moves: true,
55
+ fuse_exact_multiply_add: true,
56
+ fuse_multiply_add: false,
57
+ ...context,
58
+ };
59
+
60
+ let current = instructions;
61
+ let changed = false;
62
+
63
+ for (const rewrite of [
64
+ options.merge_constant_loads ? merge_one_constant_load : null,
65
+ options.merge_moves ? merge_one_move : null,
66
+ options.fuse_exact_multiply_add ? (stream) => fuse_one_multiply_add(stream, context, true) : null,
67
+ options.fuse_multiply_add ? (stream) => fuse_one_multiply_add(stream, context, false) : null,
68
+ ]) {
69
+ if (rewrite === null) {
70
+ continue;
71
+ }
72
+
73
+ // Applied one at a time and re-derived: each rewrite deletes an instruction, and re-deriving
74
+ // a program of this size is cheaper than keeping indices and liveness honest across a splice.
75
+ for (let attempt = 0; attempt < instructions.length; attempt++) {
76
+ const result = rewrite(current);
77
+
78
+ if (result === null) {
79
+ break;
80
+ }
81
+
82
+ current = result;
83
+ changed = true;
84
+ }
85
+ }
86
+
87
+ return changed ? current : null;
88
+ }
89
+
90
+ /**
91
+ * Two constant loads that fill adjacent registers from adjacent pool words are one load. The pool
92
+ * cannot change under them, so there is nothing to ask about the source.
93
+ *
94
+ * @param {number[][]} instructions
95
+ * @returns {number[][]|null}
96
+ */
97
+ function merge_one_constant_load(instructions) {
98
+ return merge_one_pair(
99
+ instructions,
100
+ VM_OP.LOAD_CONST,
101
+ (a, b) => (b.words[1] === a.words[1] + a.operation_width ? a.words[1] : null),
102
+ () => true
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Two copies that fill adjacent registers from adjacent registers — or from one literal — are one
108
+ * copy.
109
+ *
110
+ * The second one's register source has to survive the move: it is read at the first one's position
111
+ * now, so anything that OVERWRITES it between the two — the first copy itself included, which is
112
+ * what `MOV r1, r0; MOV r2, r1` is — makes the merge a different program. Reads in between are
113
+ * nobody's business, and a literal cannot change at all.
114
+ *
115
+ * @param {number[][]} instructions
116
+ * @returns {number[][]|null}
117
+ */
118
+ function merge_one_move(instructions) {
119
+ return merge_one_pair(
120
+ instructions,
121
+ VM_OP.MOV,
122
+ (a, b) => {
123
+ const [from_a] = a.sources;
124
+ const [from_b] = b.sources;
125
+
126
+ if (from_a.kind !== from_b.kind) {
127
+ return null;
128
+ }
129
+
130
+ if (from_a.kind === VM_KIND.IMM) {
131
+ return from_a.word === from_b.word ? from_a.word : null;
132
+ }
133
+
134
+ // Plain runs only: a swizzled copy is not a run of its source.
135
+ const plain = from_a.word === vm_register(from_a.base) && from_b.word === vm_register(from_b.base);
136
+
137
+ return plain && from_b.base === from_a.base + a.operation_width ? from_a.word : null;
138
+ },
139
+ (decoded, first, second, b) => b.sources[0].kind === VM_KIND.IMM
140
+ || !particle_run_is_written_between(decoded, first, second, b.sources[0].base, b.operation_width)
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Two instructions of one opcode that fill adjacent destination runs from sources that combine, with
146
+ * nothing in between to stop them being one instruction of the combined width. Which of them fills
147
+ * the lower lanes does not matter; the combined instruction starts where the lower one did.
148
+ *
149
+ * The second one moves back to the first one's position, so nothing between them may read or write
150
+ * the run the two of them now fill together — and whatever else that opcode needs of the gap is the
151
+ * caller's to state.
152
+ *
153
+ * @param {number[][]} instructions
154
+ * @param {number} op the opcode both instructions carry: one that writes its width from its first
155
+ * source alone, `MOV` or `LOAD_CONST`
156
+ * @param {function(import("./particle_program_analysis.js").ParticleDecodedInstruction, import("./particle_program_analysis.js").ParticleDecodedInstruction): number|null} combined_source
157
+ * given the instruction filling the lower lanes and the one filling the upper, the first source
158
+ * word of the merged instruction, or `null` when the two sources do not combine
159
+ * @param {function(import("./particle_program_analysis.js").ParticleDecodedInstruction[], number, number, import("./particle_program_analysis.js").ParticleDecodedInstruction): boolean} source_survives
160
+ * whether the second instruction's source still holds what it did, read at the first's position
161
+ * @returns {number[][]|null}
162
+ */
163
+ function merge_one_pair(instructions, op, combined_source, source_survives) {
164
+ const decoded = decode_particle_instructions(instructions);
165
+
166
+ for (let first = 0; first < decoded.length; first++) {
167
+ const a = decoded[first];
168
+
169
+ if (a.op !== op) {
170
+ continue;
171
+ }
172
+
173
+ for (let second = first + 1; second < decoded.length; second++) {
174
+ const b = decoded[second];
175
+
176
+ if (b.op === VM_OP.HALT) {
177
+ break;
178
+ }
179
+
180
+ if (b.op !== op || a.operation_width + b.operation_width > VM_MAX_WIDTH) {
181
+ continue;
182
+ }
183
+
184
+ // Either may be the one filling the lower lanes: a run built from the top down is the
185
+ // same run, and a lane-at-a-time lowering fills whichever lane it reaches first.
186
+ const above = b.dst === a.dst + a.operation_width;
187
+ const below = b.dst + b.operation_width === a.dst;
188
+
189
+ if (!above && !below) {
190
+ continue;
191
+ }
192
+
193
+ const [lower, upper] = above ? [a, b] : [b, a];
194
+ const source = combined_source(lower, upper);
195
+
196
+ if (source === null) {
197
+ continue;
198
+ }
199
+
200
+ const base = lower.dst;
201
+ const span = a.operation_width + b.operation_width;
202
+
203
+ if (particle_run_is_touched_between(decoded, first + 1, second, base, span)
204
+ || !source_survives(decoded, first, second, b)) {
205
+ continue;
206
+ }
207
+
208
+ const kind = a.sources.length > 0 ? a.sources[0].kind : VM_KIND.NONE;
209
+ const words = vm_pack_instruction(op, span, base, [kind, VM_KIND.NONE, VM_KIND.NONE], [source, 0, 0]);
210
+
211
+ if (!particle_instruction_is_well_formed(words)) {
212
+ continue;
213
+ }
214
+
215
+ const out = instructions.slice();
216
+
217
+ out[first] = words;
218
+ out.splice(second, 1);
219
+
220
+ return out;
221
+ }
222
+ }
223
+
224
+ return null;
225
+ }
226
+
227
+ /**
228
+ * `MUL` into a register that an `ADD` or a `SUB` below consumes is `MAD`.
229
+ *
230
+ * The consumer may be anywhere below the multiply, as long as the product is nobody else's on the
231
+ * way — nothing between them reads or writes it — and the two factors still hold what they did, so
232
+ * the fused instruction reads at the consumer's position what the multiply read at its own. The
233
+ * consumer has to read the product as it lies: a swizzled read reorders lanes the multiply computed
234
+ * in order. Afterwards the product must be dead, since the `MAD` does not leave it anywhere.
235
+ *
236
+ * With `exact`, only a product {@link ParticleIntegerBounds} proves to be an integer within
237
+ * {@link PARTICLE_EXACT_INTEGER_LIMIT} is fused. Such a product needs no rounding, so the fused
238
+ * form is the same number as the pair on every executor, FMA or not. Without it any pair is fused,
239
+ * which is the `fuse_multiply_add` switch and a different number.
240
+ *
241
+ * A `SUB` is fused by negating a literal, which is exact: `p - c` is `MAD(a, b, -c)` when `c` is a
242
+ * literal, and `c - p` is `MAD(a, -b, c)` when one factor is. A register cannot be negated in place,
243
+ * so the other shapes are left.
244
+ *
245
+ * @param {number[][]} instructions
246
+ * @param {import("./optimize_particle_program.js").ParticleOptimizerContext} context
247
+ * @param {boolean} exact
248
+ * @returns {number[][]|null}
249
+ */
250
+ function fuse_one_multiply_add(instructions, context, exact) {
251
+ const decoded = decode_particle_instructions(instructions);
252
+ const bounds = exact ? new ParticleIntegerBounds(context.constants ?? [], context.register_slots) : null;
253
+
254
+ for (let i = 0; i < decoded.length; i++) {
255
+ const multiply = decoded[i];
256
+
257
+ if (multiply.op === VM_OP.MUL && (bounds === null || product_is_exact(multiply, bounds))) {
258
+ const fused = fuse_multiply_into_consumer(decoded, i);
259
+
260
+ if (fused !== null) {
261
+ const out = instructions.slice();
262
+
263
+ out[fused.consumer] = fused.words;
264
+ out.splice(i, 1);
265
+
266
+ return out;
267
+ }
268
+ }
269
+
270
+ // Published after the search, so the factors are bounded as the multiply read them.
271
+ bounds?.publish(multiply.words);
272
+ }
273
+
274
+ return null;
275
+ }
276
+
277
+ /**
278
+ * @param {import("./particle_program_analysis.js").ParticleDecodedInstruction} multiply
279
+ * @param {ParticleIntegerBounds} bounds as they stand before the multiply
280
+ * @returns {boolean} whether its product is an integer small enough to need no rounding
281
+ */
282
+ function product_is_exact(multiply, bounds) {
283
+ const [a, b] = multiply.sources;
284
+ const width = multiply.operation_width;
285
+
286
+ // `<=` rather than `!(>)`: an unknown factor times a zero bound is `NaN`, and not exact.
287
+ return bounds.bound_of_source(a, width) * bounds.bound_of_source(b, width) <= PARTICLE_EXACT_INTEGER_LIMIT;
288
+ }
289
+
290
+ /**
291
+ * The consumer of the product of the `MUL` at `index`, and the `MAD` that replaces it there.
292
+ *
293
+ * @param {import("./particle_program_analysis.js").ParticleDecodedInstruction[]} decoded
294
+ * @param {number} index
295
+ * @returns {{consumer: number, words: number[]}|null} `null` when the product is read by something
296
+ * other than one fusable consumer, or the factors do not survive to it
297
+ */
298
+ function fuse_multiply_into_consumer(decoded, index) {
299
+ const multiply = decoded[index];
300
+ const product = multiply.write.base;
301
+ const span = multiply.write.span;
302
+ const is_product = (source) => source.kind === VM_KIND.REG && source.word === vm_register(product);
303
+ const touches_product = (source) => source.kind === VM_KIND.REG
304
+ && particle_runs_overlap(source.base, source.span, product, span);
305
+
306
+ for (let j = index + 1; j < decoded.length; j++) {
307
+ const consumer = decoded[j];
308
+
309
+ if (consumer.op === VM_OP.HALT) {
310
+ return null;
311
+ }
312
+
313
+ const position = (consumer.op === VM_OP.ADD || consumer.op === VM_OP.SUB) && consumer.width === multiply.width
314
+ ? consumer.sources.findIndex(is_product)
315
+ : -1;
316
+
317
+ if (position < 0) {
318
+ // Anything else that touches the product wants it as a value of its own.
319
+ if ((consumer.write !== null && particle_runs_overlap(consumer.write.base, consumer.write.span, product, span))
320
+ || consumer.reads.some(touches_product)) {
321
+ return null;
322
+ }
323
+
324
+ continue;
325
+ }
326
+
327
+ const other = consumer.sources[1 - position];
328
+
329
+ if (touches_product(other)) {
330
+ return null;
331
+ }
332
+
333
+ const factors_survive = multiply.reads.every(
334
+ factor => !particle_run_is_written_between(decoded, index + 1, j, factor.base, factor.span)
335
+ );
336
+ const covered = consumer.write.base <= product && consumer.write.base + consumer.write.span >= product + span;
337
+
338
+ if (!factors_survive || (!covered && !particle_run_is_dead_after(decoded, j, product, span))) {
339
+ return null;
340
+ }
341
+
342
+ const words = fused_words(multiply, consumer, position, other);
343
+
344
+ return words !== null && particle_instruction_is_well_formed(words) ? { consumer: j, words } : null;
345
+ }
346
+
347
+ return null;
348
+ }
349
+
350
+ /**
351
+ * @param {import("./particle_program_analysis.js").ParticleDecodedInstruction} multiply
352
+ * @param {import("./particle_program_analysis.js").ParticleDecodedInstruction} consumer an `ADD` or `SUB`
353
+ * @param {number} position which of the consumer's operands is the product
354
+ * @param {import("./particle_program_analysis.js").ParticleOperandRun} other its other operand
355
+ * @returns {number[]|null} the `MAD`, or `null` when the shape needs a register negated
356
+ */
357
+ function fused_words(multiply, consumer, position, other) {
358
+ let [a, b] = multiply.sources;
359
+ let addend = other;
360
+
361
+ if (consumer.op === VM_OP.SUB) {
362
+ if (position === 0) {
363
+ // p - c: negate the addend.
364
+ if (addend.kind !== VM_KIND.IMM) {
365
+ return null;
366
+ }
367
+
368
+ addend = negated(addend);
369
+ } else if (b.kind === VM_KIND.IMM) {
370
+ // c - p: negate a factor.
371
+ b = negated(b);
372
+ } else if (a.kind === VM_KIND.IMM) {
373
+ a = negated(a);
374
+ } else {
375
+ return null;
376
+ }
377
+ }
378
+
379
+ return vm_pack_instruction(
380
+ VM_OP.MAD, multiply.width, consumer.write.base,
381
+ [a.kind, b.kind, addend.kind],
382
+ [a.word, b.word, addend.word]
383
+ );
384
+ }
385
+
386
+ /**
387
+ * @param {import("./particle_program_analysis.js").ParticleOperandRun} literal an `IMM` operand
388
+ * @returns {{kind: number, word: number}} the same literal, negated
389
+ */
390
+ function negated(literal) {
391
+ return vm_immediate(-vm_f32_value(literal.word));
392
+ }