@codexo/exojs 0.8.0 → 0.8.3

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 (498) hide show
  1. package/CHANGELOG.md +390 -208
  2. package/README.md +19 -19
  3. package/dist/esm/animation/Easing.js +13 -21
  4. package/dist/esm/animation/Easing.js.map +1 -1
  5. package/dist/esm/animation/Tween.d.ts +3 -3
  6. package/dist/esm/animation/Tween.js +7 -5
  7. package/dist/esm/animation/Tween.js.map +1 -1
  8. package/dist/esm/animation/TweenManager.js +1 -1
  9. package/dist/esm/animation/TweenManager.js.map +1 -1
  10. package/dist/esm/animation/types.js.map +1 -1
  11. package/dist/esm/audio/AbstractMedia.d.ts +1 -1
  12. package/dist/esm/audio/AbstractMedia.js +1 -1
  13. package/dist/esm/audio/AbstractMedia.js.map +1 -1
  14. package/dist/esm/audio/AudioAnalyser.d.ts +37 -1
  15. package/dist/esm/audio/AudioAnalyser.js +150 -2
  16. package/dist/esm/audio/AudioAnalyser.js.map +1 -1
  17. package/dist/esm/audio/AudioBus.d.ts +1 -1
  18. package/dist/esm/audio/AudioBus.js.map +1 -1
  19. package/dist/esm/audio/AudioFilter.js +3 -1
  20. package/dist/esm/audio/AudioFilter.js.map +1 -1
  21. package/dist/esm/audio/AudioListener.d.ts +1 -1
  22. package/dist/esm/audio/AudioListener.js +5 -3
  23. package/dist/esm/audio/AudioListener.js.map +1 -1
  24. package/dist/esm/audio/AudioManager.js.map +1 -1
  25. package/dist/esm/audio/BeatDetector.d.ts +66 -4
  26. package/dist/esm/audio/BeatDetector.js +145 -44
  27. package/dist/esm/audio/BeatDetector.js.map +1 -1
  28. package/dist/esm/audio/Envelope.d.ts +1 -1
  29. package/dist/esm/audio/Envelope.js +2 -2
  30. package/dist/esm/audio/Envelope.js.map +1 -1
  31. package/dist/esm/audio/Music.d.ts +1 -1
  32. package/dist/esm/audio/Music.js +1 -1
  33. package/dist/esm/audio/Music.js.map +1 -1
  34. package/dist/esm/audio/OscillatorSound.d.ts +2 -2
  35. package/dist/esm/audio/OscillatorSound.js.map +1 -1
  36. package/dist/esm/audio/Sound.d.ts +40 -2
  37. package/dist/esm/audio/Sound.js +69 -9
  38. package/dist/esm/audio/Sound.js.map +1 -1
  39. package/dist/esm/audio/audio-context.js +1 -1
  40. package/dist/esm/audio/audio-context.js.map +1 -1
  41. package/dist/esm/audio/crossFade.js +1 -1
  42. package/dist/esm/audio/crossFade.js.map +1 -1
  43. package/dist/esm/audio/dsp/mel.d.ts +2 -2
  44. package/dist/esm/audio/dsp/mel.js +70 -0
  45. package/dist/esm/audio/dsp/mel.js.map +1 -0
  46. package/dist/esm/audio/dsp/tempogram.d.ts +2 -2
  47. package/dist/esm/audio/filters/ChorusFilter.js +12 -4
  48. package/dist/esm/audio/filters/ChorusFilter.js.map +1 -1
  49. package/dist/esm/audio/filters/CompressorFilter.d.ts +9 -0
  50. package/dist/esm/audio/filters/CompressorFilter.js +11 -0
  51. package/dist/esm/audio/filters/CompressorFilter.js.map +1 -1
  52. package/dist/esm/audio/filters/DelayFilter.js.map +1 -1
  53. package/dist/esm/audio/filters/DuckingFilter.d.ts +1 -1
  54. package/dist/esm/audio/filters/DuckingFilter.js +13 -5
  55. package/dist/esm/audio/filters/DuckingFilter.js.map +1 -1
  56. package/dist/esm/audio/filters/EqualizerFilter.js.map +1 -1
  57. package/dist/esm/audio/filters/GranularFilter.js +30 -14
  58. package/dist/esm/audio/filters/GranularFilter.js.map +1 -1
  59. package/dist/esm/audio/filters/HighpassFilter.js.map +1 -1
  60. package/dist/esm/audio/filters/LowpassFilter.js.map +1 -1
  61. package/dist/esm/audio/filters/PitchShiftFilter.js +14 -10
  62. package/dist/esm/audio/filters/PitchShiftFilter.js.map +1 -1
  63. package/dist/esm/audio/filters/ReverbFilter.js.map +1 -1
  64. package/dist/esm/audio/filters/VocoderFilter.d.ts +2 -2
  65. package/dist/esm/audio/filters/VocoderFilter.js +20 -12
  66. package/dist/esm/audio/filters/VocoderFilter.js.map +1 -1
  67. package/dist/esm/audio/filters/WorkletFilter.js +5 -6
  68. package/dist/esm/audio/filters/WorkletFilter.js.map +1 -1
  69. package/dist/esm/audio/filters/index.d.ts +7 -7
  70. package/dist/esm/audio/index.d.ts +9 -9
  71. package/dist/esm/audio/worklet/registerWorklet.js +7 -3
  72. package/dist/esm/audio/worklet/registerWorklet.js.map +1 -1
  73. package/dist/esm/core/Application.d.ts +13 -13
  74. package/dist/esm/core/Application.js +24 -16
  75. package/dist/esm/core/Application.js.map +1 -1
  76. package/dist/esm/core/Bounds.d.ts +1 -1
  77. package/dist/esm/core/Bounds.js +1 -3
  78. package/dist/esm/core/Bounds.js.map +1 -1
  79. package/dist/esm/core/Clock.js +1 -1
  80. package/dist/esm/core/Clock.js.map +1 -1
  81. package/dist/esm/core/Color.js +2 -5
  82. package/dist/esm/core/Color.js.map +1 -1
  83. package/dist/esm/core/Scene.d.ts +10 -10
  84. package/dist/esm/core/Scene.js +1 -1
  85. package/dist/esm/core/Scene.js.map +1 -1
  86. package/dist/esm/core/SceneManager.d.ts +4 -4
  87. package/dist/esm/core/SceneManager.js +39 -33
  88. package/dist/esm/core/SceneManager.js.map +1 -1
  89. package/dist/esm/core/SceneNode.d.ts +8 -8
  90. package/dist/esm/core/SceneNode.js +39 -29
  91. package/dist/esm/core/SceneNode.js.map +1 -1
  92. package/dist/esm/core/Signal.d.ts +3 -3
  93. package/dist/esm/core/Signal.js +3 -3
  94. package/dist/esm/core/Signal.js.map +1 -1
  95. package/dist/esm/core/Time.js +6 -6
  96. package/dist/esm/core/Time.js.map +1 -1
  97. package/dist/esm/core/Timer.d.ts +1 -1
  98. package/dist/esm/core/Timer.js +1 -1
  99. package/dist/esm/core/Timer.js.map +1 -1
  100. package/dist/esm/core/capabilities.js +2 -4
  101. package/dist/esm/core/capabilities.js.map +1 -1
  102. package/dist/esm/core/index.d.ts +2 -2
  103. package/dist/esm/core/utils.d.ts +4 -4
  104. package/dist/esm/core/utils.js +10 -10
  105. package/dist/esm/core/utils.js.map +1 -1
  106. package/dist/esm/debug/BoundingBoxesLayer.d.ts +3 -3
  107. package/dist/esm/debug/BoundingBoxesLayer.js +6 -13
  108. package/dist/esm/debug/BoundingBoxesLayer.js.map +1 -1
  109. package/dist/esm/debug/DebugLayer.d.ts +1 -1
  110. package/dist/esm/debug/DebugLayer.js.map +1 -1
  111. package/dist/esm/debug/DebugOverlay.d.ts +2 -2
  112. package/dist/esm/debug/DebugOverlay.js +2 -2
  113. package/dist/esm/debug/DebugOverlay.js.map +1 -1
  114. package/dist/esm/debug/HitTestLayer.d.ts +3 -3
  115. package/dist/esm/debug/HitTestLayer.js +3 -10
  116. package/dist/esm/debug/HitTestLayer.js.map +1 -1
  117. package/dist/esm/debug/PerformanceLayer.d.ts +2 -2
  118. package/dist/esm/debug/PerformanceLayer.js +6 -6
  119. package/dist/esm/debug/PerformanceLayer.js.map +1 -1
  120. package/dist/esm/debug/PointerStackLayer.d.ts +3 -3
  121. package/dist/esm/debug/PointerStackLayer.js +3 -3
  122. package/dist/esm/debug/PointerStackLayer.js.map +1 -1
  123. package/dist/esm/debug/RenderPassInspectorLayer.d.ts +71 -0
  124. package/dist/esm/debug/RenderPassInspectorLayer.js +201 -0
  125. package/dist/esm/debug/RenderPassInspectorLayer.js.map +1 -0
  126. package/dist/esm/debug/index.d.ts +4 -3
  127. package/dist/esm/debug/index.js +4 -3
  128. package/dist/esm/debug/index.js.map +1 -1
  129. package/dist/esm/index.d.ts +1 -1
  130. package/dist/esm/index.js +101 -98
  131. package/dist/esm/index.js.map +1 -1
  132. package/dist/esm/input/ArcadeStickGamepadMapping.js.map +1 -1
  133. package/dist/esm/input/GameCubeGamepadMapping.d.ts +1 -1
  134. package/dist/esm/input/GameCubeGamepadMapping.js +1 -1
  135. package/dist/esm/input/GameCubeGamepadMapping.js.map +1 -1
  136. package/dist/esm/input/Gamepad.d.ts +7 -7
  137. package/dist/esm/input/Gamepad.js +10 -6
  138. package/dist/esm/input/Gamepad.js.map +1 -1
  139. package/dist/esm/input/GamepadAxis.js +1 -1
  140. package/dist/esm/input/GamepadAxis.js.map +1 -1
  141. package/dist/esm/input/GamepadButton.js +1 -1
  142. package/dist/esm/input/GamepadButton.js.map +1 -1
  143. package/dist/esm/input/GamepadDefinitions.d.ts +4 -4
  144. package/dist/esm/input/GamepadDefinitions.js +12 -14
  145. package/dist/esm/input/GamepadDefinitions.js.map +1 -1
  146. package/dist/esm/input/GamepadMapping.d.ts +5 -5
  147. package/dist/esm/input/GamepadMapping.js.map +1 -1
  148. package/dist/esm/input/GamepadPromptLayouts.d.ts +2 -2
  149. package/dist/esm/input/GamepadPromptLayouts.js +8 -8
  150. package/dist/esm/input/GamepadPromptLayouts.js.map +1 -1
  151. package/dist/esm/input/GenericDualAnalogGamepadMapping.js.map +1 -1
  152. package/dist/esm/input/GestureRecognizer.d.ts +1 -1
  153. package/dist/esm/input/GestureRecognizer.js.map +1 -1
  154. package/dist/esm/input/InputBinding.d.ts +3 -3
  155. package/dist/esm/input/InputBinding.js.map +1 -1
  156. package/dist/esm/input/InputManager.d.ts +9 -9
  157. package/dist/esm/input/InputManager.js +25 -16
  158. package/dist/esm/input/InputManager.js.map +1 -1
  159. package/dist/esm/input/InteractionEvent.d.ts +1 -1
  160. package/dist/esm/input/InteractionEvent.js.map +1 -1
  161. package/dist/esm/input/InteractionManager.d.ts +2 -2
  162. package/dist/esm/input/InteractionManager.js +25 -16
  163. package/dist/esm/input/InteractionManager.js.map +1 -1
  164. package/dist/esm/input/JoyConLeftGamepadMapping.js.map +1 -1
  165. package/dist/esm/input/JoyConRightGamepadMapping.js.map +1 -1
  166. package/dist/esm/input/PlayStationGamepadMapping.d.ts +1 -1
  167. package/dist/esm/input/PlayStationGamepadMapping.js +1 -1
  168. package/dist/esm/input/PlayStationGamepadMapping.js.map +1 -1
  169. package/dist/esm/input/Pointer.d.ts +2 -2
  170. package/dist/esm/input/Pointer.js +6 -6
  171. package/dist/esm/input/Pointer.js.map +1 -1
  172. package/dist/esm/input/SteamControllerGamepadMapping.d.ts +1 -1
  173. package/dist/esm/input/SteamControllerGamepadMapping.js +1 -1
  174. package/dist/esm/input/SteamControllerGamepadMapping.js.map +1 -1
  175. package/dist/esm/input/SteamDeckGamepadMapping.js.map +1 -1
  176. package/dist/esm/input/SwitchProGamepadMapping.d.ts +1 -1
  177. package/dist/esm/input/SwitchProGamepadMapping.js +1 -1
  178. package/dist/esm/input/SwitchProGamepadMapping.js.map +1 -1
  179. package/dist/esm/input/XboxGamepadMapping.d.ts +1 -1
  180. package/dist/esm/input/XboxGamepadMapping.js +1 -1
  181. package/dist/esm/input/XboxGamepadMapping.js.map +1 -1
  182. package/dist/esm/input/index.d.ts +16 -16
  183. package/dist/esm/input/interaction-hooks.js.map +1 -1
  184. package/dist/esm/input/types.js.map +1 -1
  185. package/dist/esm/math/AbstractVector.js +8 -9
  186. package/dist/esm/math/AbstractVector.js.map +1 -1
  187. package/dist/esm/math/Circle.d.ts +5 -5
  188. package/dist/esm/math/Circle.js +33 -21
  189. package/dist/esm/math/Circle.js.map +1 -1
  190. package/dist/esm/math/Collision.d.ts +2 -2
  191. package/dist/esm/math/Collision.js.map +1 -1
  192. package/dist/esm/math/Ellipse.d.ts +5 -5
  193. package/dist/esm/math/Ellipse.js +28 -19
  194. package/dist/esm/math/Ellipse.js.map +1 -1
  195. package/dist/esm/math/Flags.d.ts +4 -4
  196. package/dist/esm/math/Flags.js.map +1 -1
  197. package/dist/esm/math/Interval.js.map +1 -1
  198. package/dist/esm/math/Line.d.ts +5 -5
  199. package/dist/esm/math/Line.js +23 -15
  200. package/dist/esm/math/Line.js.map +1 -1
  201. package/dist/esm/math/Matrix.js +14 -16
  202. package/dist/esm/math/Matrix.js.map +1 -1
  203. package/dist/esm/math/ObservableSize.js.map +1 -1
  204. package/dist/esm/math/ObservableVector.d.ts +1 -1
  205. package/dist/esm/math/ObservableVector.js.map +1 -1
  206. package/dist/esm/math/PolarVector.js.map +1 -1
  207. package/dist/esm/math/Polygon.d.ts +11 -11
  208. package/dist/esm/math/Polygon.js +37 -26
  209. package/dist/esm/math/Polygon.js.map +1 -1
  210. package/dist/esm/math/PolygonLike.d.ts +1 -1
  211. package/dist/esm/math/Quadtree.js +2 -8
  212. package/dist/esm/math/Quadtree.js.map +1 -1
  213. package/dist/esm/math/Random.js +5 -5
  214. package/dist/esm/math/Random.js.map +1 -1
  215. package/dist/esm/math/Rectangle.d.ts +4 -4
  216. package/dist/esm/math/Rectangle.js +59 -41
  217. package/dist/esm/math/Rectangle.js.map +1 -1
  218. package/dist/esm/math/Segment.d.ts +1 -1
  219. package/dist/esm/math/Segment.js +4 -4
  220. package/dist/esm/math/Segment.js.map +1 -1
  221. package/dist/esm/math/ShapeLike.d.ts +1 -1
  222. package/dist/esm/math/Size.js +1 -2
  223. package/dist/esm/math/Size.js.map +1 -1
  224. package/dist/esm/math/Vector.d.ts +4 -4
  225. package/dist/esm/math/Vector.js +19 -13
  226. package/dist/esm/math/Vector.js.map +1 -1
  227. package/dist/esm/math/collision-detection.d.ts +3 -3
  228. package/dist/esm/math/collision-detection.js +49 -49
  229. package/dist/esm/math/collision-detection.js.map +1 -1
  230. package/dist/esm/math/collision-primitives.d.ts +8 -8
  231. package/dist/esm/math/collision-primitives.js +27 -33
  232. package/dist/esm/math/collision-primitives.js.map +1 -1
  233. package/dist/esm/math/geometry.d.ts +3 -3
  234. package/dist/esm/math/geometry.js +55 -48
  235. package/dist/esm/math/geometry.js.map +1 -1
  236. package/dist/esm/math/index.d.ts +16 -16
  237. package/dist/esm/math/swept-collision.d.ts +3 -3
  238. package/dist/esm/math/swept-collision.js +5 -8
  239. package/dist/esm/math/swept-collision.js.map +1 -1
  240. package/dist/esm/math/triangulate.js +34 -24
  241. package/dist/esm/math/triangulate.js.map +1 -1
  242. package/dist/esm/math/utils.d.ts +2 -2
  243. package/dist/esm/math/utils.js +7 -7
  244. package/dist/esm/math/utils.js.map +1 -1
  245. package/dist/esm/particles/ParticleSystem.d.ts +9 -9
  246. package/dist/esm/particles/ParticleSystem.js +29 -29
  247. package/dist/esm/particles/ParticleSystem.js.map +1 -1
  248. package/dist/esm/particles/distributions/BoxArea.js.map +1 -1
  249. package/dist/esm/particles/distributions/CircleArea.js.map +1 -1
  250. package/dist/esm/particles/distributions/ConeDirection.js.map +1 -1
  251. package/dist/esm/particles/distributions/Constant.js.map +1 -1
  252. package/dist/esm/particles/distributions/Curve.d.ts +1 -1
  253. package/dist/esm/particles/distributions/Curve.js.map +1 -1
  254. package/dist/esm/particles/distributions/Gradient.d.ts +1 -1
  255. package/dist/esm/particles/distributions/Gradient.js +1 -1
  256. package/dist/esm/particles/distributions/Gradient.js.map +1 -1
  257. package/dist/esm/particles/distributions/LineSegment.js.map +1 -1
  258. package/dist/esm/particles/distributions/Range.js.map +1 -1
  259. package/dist/esm/particles/distributions/VectorRange.js.map +1 -1
  260. package/dist/esm/particles/distributions/index.d.ts +9 -9
  261. package/dist/esm/particles/gpu/ParticleGpuState.d.ts +3 -3
  262. package/dist/esm/particles/gpu/ParticleGpuState.js +48 -21
  263. package/dist/esm/particles/gpu/ParticleGpuState.js.map +1 -1
  264. package/dist/esm/particles/index.d.ts +1 -1
  265. package/dist/esm/particles/modules/AlphaFadeOverLifetime.d.ts +2 -2
  266. package/dist/esm/particles/modules/AlphaFadeOverLifetime.js.map +1 -1
  267. package/dist/esm/particles/modules/ApplyForce.d.ts +1 -1
  268. package/dist/esm/particles/modules/ApplyForce.js.map +1 -1
  269. package/dist/esm/particles/modules/AttractToPoint.d.ts +1 -1
  270. package/dist/esm/particles/modules/AttractToPoint.js.map +1 -1
  271. package/dist/esm/particles/modules/BurstSpawn.d.ts +4 -4
  272. package/dist/esm/particles/modules/BurstSpawn.js +2 -2
  273. package/dist/esm/particles/modules/BurstSpawn.js.map +1 -1
  274. package/dist/esm/particles/modules/ColorOverLifetime.d.ts +2 -2
  275. package/dist/esm/particles/modules/ColorOverLifetime.js +1 -1
  276. package/dist/esm/particles/modules/ColorOverLifetime.js.map +1 -1
  277. package/dist/esm/particles/modules/ColorOverSpeed.d.ts +2 -2
  278. package/dist/esm/particles/modules/ColorOverSpeed.js +1 -1
  279. package/dist/esm/particles/modules/ColorOverSpeed.js.map +1 -1
  280. package/dist/esm/particles/modules/DeathModule.js.map +1 -1
  281. package/dist/esm/particles/modules/Drag.d.ts +1 -1
  282. package/dist/esm/particles/modules/Drag.js +1 -3
  283. package/dist/esm/particles/modules/Drag.js.map +1 -1
  284. package/dist/esm/particles/modules/OrbitalForce.d.ts +1 -1
  285. package/dist/esm/particles/modules/OrbitalForce.js.map +1 -1
  286. package/dist/esm/particles/modules/RateSpawn.d.ts +3 -3
  287. package/dist/esm/particles/modules/RateSpawn.js +2 -2
  288. package/dist/esm/particles/modules/RateSpawn.js.map +1 -1
  289. package/dist/esm/particles/modules/RepelFromPoint.d.ts +1 -1
  290. package/dist/esm/particles/modules/RepelFromPoint.js.map +1 -1
  291. package/dist/esm/particles/modules/RotateOverLifetime.d.ts +1 -1
  292. package/dist/esm/particles/modules/RotateOverLifetime.js +1 -3
  293. package/dist/esm/particles/modules/RotateOverLifetime.js.map +1 -1
  294. package/dist/esm/particles/modules/ScaleOverLifetime.d.ts +2 -2
  295. package/dist/esm/particles/modules/ScaleOverLifetime.js.map +1 -1
  296. package/dist/esm/particles/modules/SpawnModule.js.map +1 -1
  297. package/dist/esm/particles/modules/SpawnOnDeath.d.ts +1 -1
  298. package/dist/esm/particles/modules/SpawnOnDeath.js.map +1 -1
  299. package/dist/esm/particles/modules/Turbulence.d.ts +1 -1
  300. package/dist/esm/particles/modules/Turbulence.js.map +1 -1
  301. package/dist/esm/particles/modules/UpdateModule.js.map +1 -1
  302. package/dist/esm/particles/modules/VelocityOverLifetime.d.ts +2 -2
  303. package/dist/esm/particles/modules/VelocityOverLifetime.js.map +1 -1
  304. package/dist/esm/particles/modules/WgslContribution.d.ts +3 -3
  305. package/dist/esm/particles/modules/WgslContribution.js.map +1 -1
  306. package/dist/esm/particles/modules/index.d.ts +17 -17
  307. package/dist/esm/rendering/CallbackRenderPass.js.map +1 -1
  308. package/dist/esm/rendering/Container.d.ts +2 -2
  309. package/dist/esm/rendering/Container.js +10 -11
  310. package/dist/esm/rendering/Container.js.map +1 -1
  311. package/dist/esm/rendering/Drawable.js.map +1 -1
  312. package/dist/esm/rendering/RenderBackend.d.ts +6 -6
  313. package/dist/esm/rendering/RenderBackendType.js.map +1 -1
  314. package/dist/esm/rendering/RenderNode.d.ts +10 -10
  315. package/dist/esm/rendering/RenderNode.js +31 -31
  316. package/dist/esm/rendering/RenderNode.js.map +1 -1
  317. package/dist/esm/rendering/RenderStats.js.map +1 -1
  318. package/dist/esm/rendering/RenderTarget.d.ts +2 -2
  319. package/dist/esm/rendering/RenderTarget.js +5 -5
  320. package/dist/esm/rendering/RenderTarget.js.map +1 -1
  321. package/dist/esm/rendering/RenderTargetPass.d.ts +3 -3
  322. package/dist/esm/rendering/RenderTargetPass.js.map +1 -1
  323. package/dist/esm/rendering/Renderer.d.ts +2 -2
  324. package/dist/esm/rendering/RendererRegistry.d.ts +3 -3
  325. package/dist/esm/rendering/RendererRegistry.js +2 -3
  326. package/dist/esm/rendering/RendererRegistry.js.map +1 -1
  327. package/dist/esm/rendering/View.d.ts +3 -3
  328. package/dist/esm/rendering/View.js +17 -20
  329. package/dist/esm/rendering/View.js.map +1 -1
  330. package/dist/esm/rendering/filters/BlurFilter.d.ts +1 -1
  331. package/dist/esm/rendering/filters/BlurFilter.js +4 -9
  332. package/dist/esm/rendering/filters/BlurFilter.js.map +1 -1
  333. package/dist/esm/rendering/filters/ColorFilter.d.ts +1 -1
  334. package/dist/esm/rendering/filters/ColorFilter.js +3 -9
  335. package/dist/esm/rendering/filters/ColorFilter.js.map +1 -1
  336. package/dist/esm/rendering/filters/Filter.d.ts +1 -1
  337. package/dist/esm/rendering/filters/Filter.js.map +1 -1
  338. package/dist/esm/rendering/filters/LutFilter.d.ts +87 -0
  339. package/dist/esm/rendering/filters/LutFilter.js +261 -0
  340. package/dist/esm/rendering/filters/LutFilter.js.map +1 -0
  341. package/dist/esm/rendering/filters/WebGl2ShaderFilter.d.ts +2 -2
  342. package/dist/esm/rendering/filters/WebGl2ShaderFilter.js +7 -14
  343. package/dist/esm/rendering/filters/WebGl2ShaderFilter.js.map +1 -1
  344. package/dist/esm/rendering/filters/WebGpuShaderFilter.d.ts +1 -1
  345. package/dist/esm/rendering/filters/WebGpuShaderFilter.js +16 -11
  346. package/dist/esm/rendering/filters/WebGpuShaderFilter.js.map +1 -1
  347. package/dist/esm/rendering/index.d.ts +30 -27
  348. package/dist/esm/rendering/mesh/Mesh.d.ts +6 -1
  349. package/dist/esm/rendering/mesh/Mesh.js +3 -1
  350. package/dist/esm/rendering/mesh/Mesh.js.map +1 -1
  351. package/dist/esm/rendering/mesh/MeshShader.d.ts +183 -0
  352. package/dist/esm/rendering/mesh/MeshShader.js +231 -0
  353. package/dist/esm/rendering/mesh/MeshShader.js.map +1 -0
  354. package/dist/esm/rendering/primitives/Graphics.d.ts +3 -3
  355. package/dist/esm/rendering/primitives/Graphics.js +12 -12
  356. package/dist/esm/rendering/primitives/Graphics.js.map +1 -1
  357. package/dist/esm/rendering/shader/Shader.js.map +1 -1
  358. package/dist/esm/rendering/shader/ShaderAttribute.js.map +1 -1
  359. package/dist/esm/rendering/shader/ShaderUniform.js.map +1 -1
  360. package/dist/esm/rendering/shader/upgradeFragmentShaderToGl300.js +6 -8
  361. package/dist/esm/rendering/shader/upgradeFragmentShaderToGl300.js.map +1 -1
  362. package/dist/esm/rendering/sprite/AnimatedSprite.d.ts +3 -3
  363. package/dist/esm/rendering/sprite/AnimatedSprite.js.map +1 -1
  364. package/dist/esm/rendering/sprite/Sprite.d.ts +4 -4
  365. package/dist/esm/rendering/sprite/Sprite.js +51 -35
  366. package/dist/esm/rendering/sprite/Sprite.js.map +1 -1
  367. package/dist/esm/rendering/sprite/Spritesheet.d.ts +5 -9
  368. package/dist/esm/rendering/sprite/Spritesheet.js +1 -1
  369. package/dist/esm/rendering/sprite/Spritesheet.js.map +1 -1
  370. package/dist/esm/rendering/text/DynamicGlyphAtlas.js +3 -9
  371. package/dist/esm/rendering/text/DynamicGlyphAtlas.js.map +1 -1
  372. package/dist/esm/rendering/text/Text.js +9 -9
  373. package/dist/esm/rendering/text/Text.js.map +1 -1
  374. package/dist/esm/rendering/text/TextLayout.d.ts +2 -2
  375. package/dist/esm/rendering/text/TextLayout.js.map +1 -1
  376. package/dist/esm/rendering/text/TextStyle.js.map +1 -1
  377. package/dist/esm/rendering/text/atlas-singleton.js.map +1 -1
  378. package/dist/esm/rendering/texture/DataTexture.d.ts +115 -0
  379. package/dist/esm/rendering/texture/DataTexture.js +173 -0
  380. package/dist/esm/rendering/texture/DataTexture.js.map +1 -0
  381. package/dist/esm/rendering/texture/RenderTexture.d.ts +1 -1
  382. package/dist/esm/rendering/texture/RenderTexture.js +4 -1
  383. package/dist/esm/rendering/texture/RenderTexture.js.map +1 -1
  384. package/dist/esm/rendering/texture/Sampler.js.map +1 -1
  385. package/dist/esm/rendering/texture/Texture.d.ts +2 -2
  386. package/dist/esm/rendering/texture/Texture.js +8 -5
  387. package/dist/esm/rendering/texture/Texture.js.map +1 -1
  388. package/dist/esm/rendering/types.js.map +1 -1
  389. package/dist/esm/rendering/utils.js.map +1 -1
  390. package/dist/esm/rendering/video/Video.d.ts +3 -3
  391. package/dist/esm/rendering/video/Video.js +7 -6
  392. package/dist/esm/rendering/video/Video.js.map +1 -1
  393. package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.d.ts +6 -6
  394. package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.js +7 -10
  395. package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.js.map +1 -1
  396. package/dist/esm/rendering/webgl2/AbstractWebGl2Renderer.d.ts +1 -1
  397. package/dist/esm/rendering/webgl2/AbstractWebGl2Renderer.js +1 -2
  398. package/dist/esm/rendering/webgl2/AbstractWebGl2Renderer.js.map +1 -1
  399. package/dist/esm/rendering/webgl2/WebGl2Backend.d.ts +11 -11
  400. package/dist/esm/rendering/webgl2/WebGl2Backend.js +61 -22
  401. package/dist/esm/rendering/webgl2/WebGl2Backend.js.map +1 -1
  402. package/dist/esm/rendering/webgl2/WebGl2MaskCompositor.d.ts +2 -2
  403. package/dist/esm/rendering/webgl2/WebGl2MaskCompositor.js +6 -8
  404. package/dist/esm/rendering/webgl2/WebGl2MaskCompositor.js.map +1 -1
  405. package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.d.ts +7 -4
  406. package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.js +121 -43
  407. package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.js.map +1 -1
  408. package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.d.ts +1 -1
  409. package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.js +10 -18
  410. package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.js.map +1 -1
  411. package/dist/esm/rendering/webgl2/WebGl2RenderBuffer.d.ts +1 -1
  412. package/dist/esm/rendering/webgl2/WebGl2RenderBuffer.js.map +1 -1
  413. package/dist/esm/rendering/webgl2/WebGl2ShaderBlock.js.map +1 -1
  414. package/dist/esm/rendering/webgl2/WebGl2ShaderMappings.js.map +1 -1
  415. package/dist/esm/rendering/webgl2/WebGl2ShaderProgram.js +54 -22
  416. package/dist/esm/rendering/webgl2/WebGl2ShaderProgram.js.map +1 -1
  417. package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.d.ts +1 -1
  418. package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.js +11 -14
  419. package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.js.map +1 -1
  420. package/dist/esm/rendering/webgl2/WebGl2VertexArrayObject.d.ts +1 -1
  421. package/dist/esm/rendering/webgl2/WebGl2VertexArrayObject.js.map +1 -1
  422. package/dist/esm/rendering/webgpu/AbstractWebGpuRenderer.d.ts +1 -1
  423. package/dist/esm/rendering/webgpu/AbstractWebGpuRenderer.js +1 -2
  424. package/dist/esm/rendering/webgpu/AbstractWebGpuRenderer.js.map +1 -1
  425. package/dist/esm/rendering/webgpu/WebGpuBackend.d.ts +8 -7
  426. package/dist/esm/rendering/webgpu/WebGpuBackend.js +104 -37
  427. package/dist/esm/rendering/webgpu/WebGpuBackend.js.map +1 -1
  428. package/dist/esm/rendering/webgpu/WebGpuBlendState.js.map +1 -1
  429. package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.d.ts +2 -2
  430. package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js +3 -4
  431. package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js.map +1 -1
  432. package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.d.ts +15 -2
  433. package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.js +657 -102
  434. package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.js.map +1 -1
  435. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.d.ts +1 -1
  436. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js +100 -56
  437. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js.map +1 -1
  438. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.d.ts +2 -2
  439. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.js +45 -45
  440. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.js.map +1 -1
  441. package/dist/esm/rendering/webgpu/compute/WebGpuComputePipeline.d.ts +1 -1
  442. package/dist/esm/rendering/webgpu/compute/index.d.ts +2 -2
  443. package/dist/esm/resources/AbstractAssetFactory.d.ts +1 -1
  444. package/dist/esm/resources/AbstractAssetFactory.js.map +1 -1
  445. package/dist/esm/resources/AssetManifest.d.ts +1 -1
  446. package/dist/esm/resources/AssetManifest.js +2 -2
  447. package/dist/esm/resources/AssetManifest.js.map +1 -1
  448. package/dist/esm/resources/CacheFirstStrategy.d.ts +2 -2
  449. package/dist/esm/resources/CacheFirstStrategy.js.map +1 -1
  450. package/dist/esm/resources/CacheStrategy.d.ts +1 -1
  451. package/dist/esm/resources/FactoryRegistry.d.ts +1 -1
  452. package/dist/esm/resources/FactoryRegistry.js +2 -3
  453. package/dist/esm/resources/FactoryRegistry.js.map +1 -1
  454. package/dist/esm/resources/IndexedDbDatabase.d.ts +1 -1
  455. package/dist/esm/resources/IndexedDbDatabase.js +11 -14
  456. package/dist/esm/resources/IndexedDbDatabase.js.map +1 -1
  457. package/dist/esm/resources/IndexedDbStore.d.ts +1 -1
  458. package/dist/esm/resources/IndexedDbStore.js +2 -7
  459. package/dist/esm/resources/IndexedDbStore.js.map +1 -1
  460. package/dist/esm/resources/Loader.d.ts +9 -9
  461. package/dist/esm/resources/Loader.js +48 -35
  462. package/dist/esm/resources/Loader.js.map +1 -1
  463. package/dist/esm/resources/NetworkOnlyStrategy.d.ts +2 -2
  464. package/dist/esm/resources/NetworkOnlyStrategy.js.map +1 -1
  465. package/dist/esm/resources/factories/BinaryFactory.js.map +1 -1
  466. package/dist/esm/resources/factories/FontFactory.js +1 -1
  467. package/dist/esm/resources/factories/FontFactory.js.map +1 -1
  468. package/dist/esm/resources/factories/ImageFactory.js +4 -4
  469. package/dist/esm/resources/factories/ImageFactory.js.map +1 -1
  470. package/dist/esm/resources/factories/JsonFactory.d.ts +1 -1
  471. package/dist/esm/resources/factories/JsonFactory.js +1 -1
  472. package/dist/esm/resources/factories/JsonFactory.js.map +1 -1
  473. package/dist/esm/resources/factories/MusicFactory.d.ts +1 -1
  474. package/dist/esm/resources/factories/MusicFactory.js +4 -4
  475. package/dist/esm/resources/factories/MusicFactory.js.map +1 -1
  476. package/dist/esm/resources/factories/SoundFactory.d.ts +1 -1
  477. package/dist/esm/resources/factories/SoundFactory.js +3 -3
  478. package/dist/esm/resources/factories/SoundFactory.js.map +1 -1
  479. package/dist/esm/resources/factories/SvgFactory.js +3 -3
  480. package/dist/esm/resources/factories/SvgFactory.js.map +1 -1
  481. package/dist/esm/resources/factories/TextFactory.js +1 -1
  482. package/dist/esm/resources/factories/TextFactory.js.map +1 -1
  483. package/dist/esm/resources/factories/TextureFactory.d.ts +1 -1
  484. package/dist/esm/resources/factories/TextureFactory.js +3 -3
  485. package/dist/esm/resources/factories/TextureFactory.js.map +1 -1
  486. package/dist/esm/resources/factories/VideoFactory.d.ts +2 -2
  487. package/dist/esm/resources/factories/VideoFactory.js +5 -5
  488. package/dist/esm/resources/factories/VideoFactory.js.map +1 -1
  489. package/dist/esm/resources/factories/VttFactory.d.ts +2 -2
  490. package/dist/esm/resources/factories/VttFactory.js +6 -6
  491. package/dist/esm/resources/factories/VttFactory.js.map +1 -1
  492. package/dist/esm/resources/factories/WasmFactory.js.map +1 -1
  493. package/dist/esm/resources/index.d.ts +11 -11
  494. package/dist/esm/resources/utils.js +30 -25
  495. package/dist/esm/resources/utils.js.map +1 -1
  496. package/dist/exo.esm.js +26260 -24256
  497. package/dist/exo.esm.js.map +1 -1
  498. package/package.json +15 -4
@@ -1,7 +1,7 @@
1
1
  import { Matrix } from '../../math/Matrix.js';
2
- import { AbstractWebGpuRenderer } from './AbstractWebGpuRenderer.js';
3
- import { BlendModes } from '../types.js';
4
2
  import { Texture } from '../texture/Texture.js';
3
+ import { BlendModes } from '../types.js';
4
+ import { AbstractWebGpuRenderer } from './AbstractWebGpuRenderer.js';
5
5
  import { getWebGpuBlendState } from './WebGpuBlendState.js';
6
6
 
7
7
  /// <reference types="@webgpu/types" />
@@ -48,16 +48,27 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
48
48
  }
49
49
  `;
50
50
  // Per-vertex layout (20 bytes): pos f32x2 + uv f32x2 + color u8x4-norm.
51
- // CPU bakes the (view * globalTransform) into position so the vertex
52
- // shader stays branchless and uniform-free except for the per-mesh tint.
51
+ // Default-shader path bakes the (view * globalTransform) into position so the
52
+ // vertex shader stays branchless and uniform-free except for the per-mesh tint.
53
+ // Custom-shader path keeps positions in LOCAL space — the user's vertex
54
+ // shader receives mesh transforms via the auto-bound u_mesh uniform block.
53
55
  const vertexStrideBytes = 20;
54
56
  const wordsPerVertex = vertexStrideBytes / 4;
55
57
  const tintByteLength = 32; // vec4 tint + vec4 flags (only flags.x used)
58
+ // Custom-shader uniform layout:
59
+ // mat3x3<f32> projection — 48 bytes (3 vec3 columns padded to vec4 in WGSL)
60
+ // mat3x3<f32> translation — 48 bytes
61
+ // vec4<f32> tint — 16 bytes
62
+ // Total: 112 bytes; aligned up to 256 for dynamic offset.
63
+ const customMeshUniformBytes = 112;
64
+ const meshUniformAlignment = 256;
65
+ const maxCustomTextureSlots = 7; // user texture uniforms; group 2 binding 1..N
56
66
  class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
57
67
  _combinedTransform = new Matrix();
58
68
  _drawCalls = [];
59
69
  _pipelines = new Map();
60
70
  _textureBindGroups = new Map();
71
+ _customShaders = new Map();
61
72
  _device = null;
62
73
  _shaderModule = null;
63
74
  _uniformBindGroupLayout = null;
@@ -81,6 +92,10 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
81
92
  if (backend === null) {
82
93
  throw new Error('WebGpuMeshRenderer is not connected to a backend.');
83
94
  }
95
+ const customShader = mesh.shader;
96
+ if (customShader !== null && customShader.wgsl === null) {
97
+ throw new Error('MeshShader has no `wgsl` source; cannot render through the WebGPU backend.');
98
+ }
84
99
  const vertexCount = mesh.vertexCount;
85
100
  if (vertexCount === 0) {
86
101
  return;
@@ -88,15 +103,26 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
88
103
  const blendMode = mesh.blendMode;
89
104
  backend.setBlendMode(blendMode);
90
105
  const meshTexture = mesh.texture ?? Texture.white;
91
- // Texture.white is a 1x1 canvas-backed Texture; backend.shouldPremultiplyTextureSample
92
- // expects RenderTexture-or-Texture. Both branches are valid here.
106
+ // backend.shouldPremultiplyTextureSample expects RenderTexture-or-Texture.
107
+ // Both branches are valid here. Premultiply flag is ignored by custom
108
+ // shaders (they handle premultiplication themselves), but we still record
109
+ // it so the default path uses the right value.
93
110
  const premultiplySample = backend.shouldPremultiplyTextureSample(meshTexture);
94
111
  const indexCount = mesh.indexCount;
95
- // Plan offsets within the shared per-frame buffers; actual data
96
- // packing happens in flush() after all drawcalls are known so a
97
- // single writeBuffer per resource covers the whole frame.
112
+ let customDrawIndex = -1;
113
+ if (customShader !== null) {
114
+ const resources = this._getOrCreateCustomShaderResources(customShader);
115
+ customDrawIndex = resources.drawCount;
116
+ resources.drawCount++;
117
+ resources.totalVertices += vertexCount;
118
+ resources.totalIndices += indexCount;
119
+ }
120
+ // Plan offsets within the shared (default) or per-shader (custom) buffers;
121
+ // actual data packing happens in flush() after all drawcalls are known so
122
+ // a single writeBuffer per resource covers the whole frame.
98
123
  const drawCall = {
99
124
  mesh,
125
+ customShader,
100
126
  blendMode,
101
127
  texture: meshTexture,
102
128
  premultiplySample,
@@ -104,6 +130,7 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
104
130
  vertexCount,
105
131
  indexByteOffset: 0,
106
132
  indexCount,
133
+ customDrawIndex,
107
134
  };
108
135
  // Use mutable record (interface readonly is for type safety against
109
136
  // callers; the renderer fills these slots in flush()).
@@ -132,88 +159,194 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
132
159
  pass.end();
133
160
  backend.submit(encoder.finish());
134
161
  }
135
- this._drawCallCount = 0;
162
+ this._resetFrame();
136
163
  return;
137
164
  }
138
- // Phase 1: compute layout offsets for the whole frame.
139
- let totalVertices = 0;
140
- let totalIndices = 0;
165
+ // Phase 1: compute layout offsets (default vs. custom paths use separate
166
+ // buffers, so default offsets are independent of custom offsets).
167
+ let defaultVertices = 0;
168
+ let defaultIndices = 0;
169
+ const customVertexCursors = new Map(); // running vertex count per shader
170
+ const customIndexCursors = new Map();
141
171
  for (let i = 0; i < this._drawCallCount; i++) {
142
172
  const dc = this._drawCalls[i];
143
- dc.vertexByteOffset = totalVertices * vertexStrideBytes;
144
- dc.indexByteOffset = totalIndices * Uint16Array.BYTES_PER_ELEMENT;
145
- totalVertices += dc.vertexCount;
146
- totalIndices += dc.indexCount;
147
- }
148
- // Phase 2: ensure capacities for the totals.
149
- this._ensureVertexCapacity(totalVertices);
150
- this._ensureIndexCapacity(totalIndices);
151
- this._ensureUniformCapacity(this._drawCallCount);
152
- // Phase 3: pack vertex + index + uniform CPU-side data.
153
- const uniformBytes = this._drawCallCount * this._uniformAlignment;
154
- const uniformData = new ArrayBuffer(uniformBytes);
155
- const uniformF32 = new Float32Array(uniformData);
173
+ if (dc.customShader === null) {
174
+ dc.vertexByteOffset = defaultVertices * vertexStrideBytes;
175
+ dc.indexByteOffset = defaultIndices * Uint16Array.BYTES_PER_ELEMENT;
176
+ defaultVertices += dc.vertexCount;
177
+ defaultIndices += dc.indexCount;
178
+ }
179
+ else {
180
+ const vCursor = customVertexCursors.get(dc.customShader) ?? 0;
181
+ const iCursor = customIndexCursors.get(dc.customShader) ?? 0;
182
+ dc.vertexByteOffset = vCursor * vertexStrideBytes;
183
+ dc.indexByteOffset = iCursor * Uint16Array.BYTES_PER_ELEMENT;
184
+ customVertexCursors.set(dc.customShader, vCursor + dc.vertexCount);
185
+ customIndexCursors.set(dc.customShader, iCursor + dc.indexCount);
186
+ }
187
+ }
188
+ // Phase 2: ensure capacities for the totals (default path).
189
+ this._ensureVertexCapacity(defaultVertices);
190
+ this._ensureIndexCapacity(defaultIndices);
191
+ // Default-path uniform buffer holds (tint vec4 + flags vec4) per draw call;
192
+ // each custom-shader resource manages its own.
193
+ const defaultDrawCalls = this._drawCallCount - this._totalCustomDraws();
194
+ this._ensureUniformCapacity(defaultDrawCalls);
195
+ // Phase 3: pack default-path vertex/index/uniform data.
196
+ const defaultUniformBytes = defaultDrawCalls * this._uniformAlignment;
197
+ const defaultUniformData = defaultUniformBytes > 0 ? new ArrayBuffer(defaultUniformBytes) : null;
198
+ const defaultUniformF32 = defaultUniformData !== null ? new Float32Array(defaultUniformData) : null;
199
+ let defaultUniformIndex = 0;
156
200
  for (let i = 0; i < this._drawCallCount; i++) {
157
201
  const dc = this._drawCalls[i];
158
- this._writeMeshVertices(backend, dc.mesh, dc.vertexByteOffset / vertexStrideBytes);
159
- if (dc.mesh.indices !== null) {
160
- this._packedIndexData.set(dc.mesh.indices, dc.indexByteOffset / Uint16Array.BYTES_PER_ELEMENT);
202
+ if (dc.customShader === null) {
203
+ // Default path: CPU-bake transform into vertex positions.
204
+ this._writeMeshVertices(backend, dc.mesh, dc.vertexByteOffset / vertexStrideBytes, /* bake */ true);
205
+ if (dc.mesh.indices !== null) {
206
+ this._packedIndexData.set(dc.mesh.indices, dc.indexByteOffset / Uint16Array.BYTES_PER_ELEMENT);
207
+ }
208
+ else {
209
+ const start = dc.indexByteOffset / Uint16Array.BYTES_PER_ELEMENT;
210
+ for (let j = 0; j < dc.indexCount; j++) {
211
+ this._packedIndexData[start + j] = j;
212
+ }
213
+ }
214
+ // Pack tint+flags for default path.
215
+ if (defaultUniformF32 !== null) {
216
+ const offsetWords = (defaultUniformIndex * this._uniformAlignment) / Float32Array.BYTES_PER_ELEMENT;
217
+ const tint = dc.mesh.tint;
218
+ defaultUniformF32[offsetWords + 0] = tint.red;
219
+ defaultUniformF32[offsetWords + 1] = tint.green;
220
+ defaultUniformF32[offsetWords + 2] = tint.blue;
221
+ defaultUniformF32[offsetWords + 3] = tint.alpha;
222
+ defaultUniformF32[offsetWords + 4] = dc.premultiplySample ? 1 : 0;
223
+ defaultUniformF32[offsetWords + 5] = 0;
224
+ defaultUniformF32[offsetWords + 6] = 0;
225
+ defaultUniformF32[offsetWords + 7] = 0;
226
+ }
227
+ defaultUniformIndex++;
161
228
  }
162
- else {
163
- const start = dc.indexByteOffset / Uint16Array.BYTES_PER_ELEMENT;
164
- for (let j = 0; j < dc.indexCount; j++) {
165
- this._packedIndexData[start + j] = j;
229
+ }
230
+ // Phase 3b: pack custom-path vertex/index/uniform data per shader.
231
+ for (const [shader, resources] of this._customShaders) {
232
+ if (resources.drawCount === 0) {
233
+ continue;
234
+ }
235
+ this._ensureCustomCapacities(resources);
236
+ // Pack vertices/indices in local space (no CPU bake).
237
+ let vWritten = 0;
238
+ let iWritten = 0;
239
+ let drawCursor = 0;
240
+ for (let i = 0; i < this._drawCallCount; i++) {
241
+ const dc = this._drawCalls[i];
242
+ if (dc.customShader !== shader)
243
+ continue;
244
+ this._writeMeshVerticesIntoBuffer(dc.mesh, vWritten, resources.vertexFloatView, resources.vertexUintView);
245
+ if (dc.mesh.indices !== null) {
246
+ resources.indexData.set(dc.mesh.indices, iWritten);
247
+ }
248
+ else {
249
+ for (let j = 0; j < dc.indexCount; j++) {
250
+ resources.indexData[iWritten + j] = j;
251
+ }
166
252
  }
253
+ // Write mesh-uniform slot (proj/trans/tint) with dynamic offset.
254
+ this._writeCustomMeshUniform(shader, resources, drawCursor, dc.mesh, backend);
255
+ vWritten += dc.vertexCount;
256
+ iWritten += dc.indexCount;
257
+ drawCursor++;
167
258
  }
168
- const uniformOffsetWords = (i * this._uniformAlignment) / Float32Array.BYTES_PER_ELEMENT;
169
- const tint = dc.mesh.tint;
170
- uniformF32[uniformOffsetWords + 0] = tint.red;
171
- uniformF32[uniformOffsetWords + 1] = tint.green;
172
- uniformF32[uniformOffsetWords + 2] = tint.blue;
173
- uniformF32[uniformOffsetWords + 3] = tint.alpha;
174
- uniformF32[uniformOffsetWords + 4] = dc.premultiplySample ? 1 : 0;
175
- uniformF32[uniformOffsetWords + 5] = 0;
176
- uniformF32[uniformOffsetWords + 6] = 0;
177
- uniformF32[uniformOffsetWords + 7] = 0;
178
- }
179
- // Phase 4: single writeBuffer per resource for the whole frame.
180
- device.queue.writeBuffer(this._vertexBuffer, 0, this._vertexData, 0, totalVertices * vertexStrideBytes);
181
- device.queue.writeBuffer(this._indexBuffer, 0, this._packedIndexData.buffer, this._packedIndexData.byteOffset, totalIndices * Uint16Array.BYTES_PER_ELEMENT);
182
- device.queue.writeBuffer(this._uniformBuffer, 0, uniformData, 0, uniformBytes);
183
- // Phase 5: single render pass with one drawIndexed per mesh.
184
- const encoder = device.createCommandEncoder();
259
+ device.queue.writeBuffer(resources.vertexBuffer, 0, resources.vertexData, 0, resources.totalVertices * vertexStrideBytes);
260
+ device.queue.writeBuffer(resources.indexBuffer, 0, resources.indexData.buffer, resources.indexData.byteOffset, resources.totalIndices * Uint16Array.BYTES_PER_ELEMENT);
261
+ // Build/refresh user uniform UBO from shader.uniforms (re-built every
262
+ // frame so mutations to shader.uniforms.X are picked up).
263
+ this._uploadUserUniforms(shader, resources);
264
+ }
265
+ // Phase 4: single writeBuffer per resource for the default path.
266
+ if (defaultVertices > 0) {
267
+ device.queue.writeBuffer(this._vertexBuffer, 0, this._vertexData, 0, defaultVertices * vertexStrideBytes);
268
+ device.queue.writeBuffer(this._indexBuffer, 0, this._packedIndexData.buffer, this._packedIndexData.byteOffset, defaultIndices * Uint16Array.BYTES_PER_ELEMENT);
269
+ }
270
+ if (defaultUniformData !== null) {
271
+ device.queue.writeBuffer(this._uniformBuffer, 0, defaultUniformData, 0, defaultUniformBytes);
272
+ }
273
+ // Phase 5: single render pass with one drawIndexed per mesh, switching
274
+ // pipeline+bind groups between default and custom paths as needed.
275
+ const encoder = device.createCommandEncoder({ label: 'WebGpuMeshRenderer' });
185
276
  const pass = encoder.beginRenderPass({
186
277
  colorAttachments: [backend.createColorAttachment()],
278
+ label: 'WebGpuMeshRenderer pass',
187
279
  });
188
280
  backend.stats.renderPasses++;
189
281
  if (scissor !== null) {
190
282
  pass.setScissorRect(scissor.x, scissor.y, scissor.width, scissor.height);
191
283
  }
284
+ const renderTargetFormat = backend.renderTargetFormat;
285
+ let lastShader = null;
192
286
  let lastBlendMode = null;
193
287
  let lastFormat = null;
194
288
  let lastTexture = null;
195
- const renderTargetFormat = backend.renderTargetFormat;
289
+ let defaultDrawCursor = 0;
290
+ const customDrawCursors = new Map();
196
291
  for (let i = 0; i < this._drawCallCount; i++) {
197
292
  const dc = this._drawCalls[i];
198
- if (dc.blendMode !== lastBlendMode || renderTargetFormat !== lastFormat) {
199
- lastBlendMode = dc.blendMode;
200
- lastFormat = renderTargetFormat;
201
- pass.setPipeline(this._getPipeline({ blendMode: dc.blendMode, format: renderTargetFormat }));
293
+ if (dc.customShader === null) {
294
+ // ----- Default path -----
295
+ const needsPipeline = lastShader !== 'default' || dc.blendMode !== lastBlendMode || renderTargetFormat !== lastFormat;
296
+ if (needsPipeline) {
297
+ pass.setPipeline(this._getPipeline({ blendMode: dc.blendMode, format: renderTargetFormat }));
298
+ lastShader = 'default';
299
+ lastBlendMode = dc.blendMode;
300
+ lastFormat = renderTargetFormat;
301
+ // Pipeline switch invalidates bind group state assumptions.
302
+ lastTexture = null;
303
+ }
304
+ pass.setBindGroup(0, this._uniformBindGroup, [defaultDrawCursor * this._uniformAlignment]);
305
+ if (dc.texture !== lastTexture) {
306
+ lastTexture = dc.texture;
307
+ pass.setBindGroup(1, this._getTextureBindGroup(backend, dc.texture));
308
+ }
309
+ pass.setVertexBuffer(0, this._vertexBuffer, dc.vertexByteOffset);
310
+ pass.setIndexBuffer(this._indexBuffer, 'uint16', dc.indexByteOffset);
311
+ pass.drawIndexed(dc.indexCount);
312
+ defaultDrawCursor++;
202
313
  }
203
- pass.setBindGroup(0, this._uniformBindGroup, [i * this._uniformAlignment]);
204
- if (dc.texture !== lastTexture) {
205
- lastTexture = dc.texture;
206
- pass.setBindGroup(1, this._getTextureBindGroup(backend, dc.texture));
314
+ else {
315
+ // ----- Custom path -----
316
+ const resources = this._customShaders.get(dc.customShader);
317
+ const needsPipeline = lastShader !== dc.customShader || dc.blendMode !== lastBlendMode || renderTargetFormat !== lastFormat;
318
+ // Wrap each custom-shader draw in a debug group so capture tools
319
+ // (Spector.js, Chrome DevTools' WebGPU panel) show meaningful
320
+ // labels for the otherwise-anonymous mesh draws inside the
321
+ // batched render pass.
322
+ pass.pushDebugGroup('MeshShader (custom)');
323
+ if (needsPipeline) {
324
+ pass.setPipeline(this._getOrCreateCustomPipeline(resources, dc.blendMode, renderTargetFormat));
325
+ lastShader = dc.customShader;
326
+ lastBlendMode = dc.blendMode;
327
+ lastFormat = renderTargetFormat;
328
+ lastTexture = null;
329
+ // User bind group is shader-scoped; rebind once per shader switch.
330
+ pass.setBindGroup(2, this._buildUserBindGroup(backend, dc.customShader, resources));
331
+ }
332
+ const cursor = customDrawCursors.get(dc.customShader) ?? 0;
333
+ pass.setBindGroup(0, resources.meshUniformBindGroup, [cursor * meshUniformAlignment]);
334
+ if (dc.texture !== lastTexture) {
335
+ lastTexture = dc.texture;
336
+ pass.setBindGroup(1, this._getOrCreateCustomMeshTextureBindGroup(resources, backend, dc.texture));
337
+ }
338
+ pass.setVertexBuffer(0, resources.vertexBuffer, dc.vertexByteOffset);
339
+ pass.setIndexBuffer(resources.indexBuffer, 'uint16', dc.indexByteOffset);
340
+ pass.drawIndexed(dc.indexCount);
341
+ pass.popDebugGroup();
342
+ customDrawCursors.set(dc.customShader, cursor + 1);
207
343
  }
208
- pass.setVertexBuffer(0, this._vertexBuffer, dc.vertexByteOffset);
209
- pass.setIndexBuffer(this._indexBuffer, 'uint16', dc.indexByteOffset);
210
- pass.drawIndexed(dc.indexCount);
211
344
  backend.stats.batches++;
212
345
  backend.stats.drawCalls++;
213
346
  }
214
347
  pass.end();
215
348
  backend.submit(encoder.finish());
216
- this._drawCallCount = 0;
349
+ this._resetFrame();
217
350
  }
218
351
  destroy() {
219
352
  this.disconnect();
@@ -227,21 +360,14 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
227
360
  if (typeof device.createRenderPipelineAsync !== 'function') {
228
361
  return;
229
362
  }
230
- const blendModes = [
231
- BlendModes.Normal,
232
- BlendModes.Additive,
233
- BlendModes.Subtract,
234
- BlendModes.Multiply,
235
- BlendModes.Screen,
236
- ];
363
+ const blendModes = [BlendModes.Normal, BlendModes.Additive, BlendModes.Subtract, BlendModes.Multiply, BlendModes.Screen];
237
364
  const promises = [];
238
365
  for (const blendMode of blendModes) {
239
366
  for (const format of formats) {
240
367
  const key = `${blendMode}:${format}`;
241
368
  if (this._pipelines.has(key))
242
369
  continue;
243
- promises.push(device.createRenderPipelineAsync(this._buildPipelineDescriptor(blendMode, format))
244
- .then((pipeline) => {
370
+ promises.push(device.createRenderPipelineAsync(this._buildPipelineDescriptor(blendMode, format)).then(pipeline => {
245
371
  this._pipelines.set(key, pipeline);
246
372
  }));
247
373
  }
@@ -255,11 +381,13 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
255
381
  this._device = backend.device;
256
382
  this._shaderModule = this._device.createShaderModule({ code: meshShaderSource });
257
383
  this._uniformBindGroupLayout = this._device.createBindGroupLayout({
258
- entries: [{
384
+ entries: [
385
+ {
259
386
  binding: 0,
260
387
  visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
261
388
  buffer: { type: 'uniform', hasDynamicOffset: true },
262
- }],
389
+ },
390
+ ],
263
391
  });
264
392
  this._textureBindGroupLayout = this._device.createBindGroupLayout({
265
393
  entries: [
@@ -294,6 +422,15 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
294
422
  this._textureBindGroupLayout = null;
295
423
  this._uniformBindGroupLayout = null;
296
424
  this._shaderModule = null;
425
+ // Custom shaders are owned by user code (one MeshShader can be shared
426
+ // across multiple Mesh instances). Their resources are released when the
427
+ // user calls shader.destroy(), which fires our _onDispose callback. On
428
+ // backend disconnect we eagerly release everything to avoid GPU leaks
429
+ // even if the user keeps the shader reference around.
430
+ for (const resources of this._customShaders.values()) {
431
+ this._releaseCustomShaderResources(resources);
432
+ }
433
+ this._customShaders.clear();
297
434
  this._device = null;
298
435
  this._backend = null;
299
436
  this._drawCallCount = 0;
@@ -301,32 +438,47 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
301
438
  this._indexBufferCapacity = 0;
302
439
  this._uniformBufferCapacity = 0;
303
440
  }
304
- _writeMeshVertices(backend, mesh, vertexStart) {
305
- // Bake (view * globalTransform) into vertex positions on the CPU,
306
- // matching the primitive renderer's no-uniforms approach.
307
- const matrix = this._combinedTransform
308
- .copy(mesh.getGlobalTransform())
309
- .combine(backend.view.getTransform());
310
- const a = matrix.a;
311
- const b = matrix.b;
312
- const c = matrix.c;
313
- const d = matrix.d;
314
- const tx = matrix.x;
315
- const ty = matrix.y;
441
+ // ---------------------------------------------------------------------------
442
+ // Default-path helpers
443
+ // ---------------------------------------------------------------------------
444
+ _writeMeshVertices(backend, mesh, vertexStart, bake) {
316
445
  const vertices = mesh.vertices;
317
446
  const uvs = mesh.uvs;
318
447
  const colors = mesh.colors;
319
448
  const vertexCount = mesh.vertexCount;
320
- for (let i = 0; i < vertexCount; i++) {
321
- const sourceIndex = i * 2;
322
- const targetIndex = (vertexStart + i) * wordsPerVertex;
323
- const px = vertices[sourceIndex];
324
- const py = vertices[sourceIndex + 1];
325
- this._float32View[targetIndex + 0] = a * px + b * py + tx;
326
- this._float32View[targetIndex + 1] = c * px + d * py + ty;
327
- this._float32View[targetIndex + 2] = uvs !== null ? uvs[sourceIndex] : 0;
328
- this._float32View[targetIndex + 3] = uvs !== null ? uvs[sourceIndex + 1] : 0;
329
- this._uint32View[targetIndex + 4] = colors !== null ? colors[i] : 0xFFFFFFFF;
449
+ if (bake) {
450
+ // Bake (view * globalTransform) into vertex positions on the CPU,
451
+ // matching the primitive renderer's no-uniforms approach.
452
+ const matrix = this._combinedTransform.copy(mesh.getGlobalTransform()).combine(backend.view.getTransform());
453
+ const a = matrix.a;
454
+ const b = matrix.b;
455
+ const c = matrix.c;
456
+ const d = matrix.d;
457
+ const tx = matrix.x;
458
+ const ty = matrix.y;
459
+ for (let i = 0; i < vertexCount; i++) {
460
+ const sourceIndex = i * 2;
461
+ const targetIndex = (vertexStart + i) * wordsPerVertex;
462
+ const px = vertices[sourceIndex];
463
+ const py = vertices[sourceIndex + 1];
464
+ this._float32View[targetIndex + 0] = a * px + b * py + tx;
465
+ this._float32View[targetIndex + 1] = c * px + d * py + ty;
466
+ this._float32View[targetIndex + 2] = uvs !== null ? uvs[sourceIndex] : 0;
467
+ this._float32View[targetIndex + 3] = uvs !== null ? uvs[sourceIndex + 1] : 0;
468
+ this._uint32View[targetIndex + 4] = colors !== null ? colors[i] : 0xffffffff;
469
+ }
470
+ }
471
+ else {
472
+ // Should not happen — default path always bakes. Defensive no-op.
473
+ for (let i = 0; i < vertexCount; i++) {
474
+ const sourceIndex = i * 2;
475
+ const targetIndex = (vertexStart + i) * wordsPerVertex;
476
+ this._float32View[targetIndex + 0] = vertices[sourceIndex];
477
+ this._float32View[targetIndex + 1] = vertices[sourceIndex + 1];
478
+ this._float32View[targetIndex + 2] = uvs !== null ? uvs[sourceIndex] : 0;
479
+ this._float32View[targetIndex + 3] = uvs !== null ? uvs[sourceIndex + 1] : 0;
480
+ this._uint32View[targetIndex + 4] = colors !== null ? colors[i] : 0xffffffff;
481
+ }
330
482
  }
331
483
  }
332
484
  _getPipeline(key) {
@@ -344,7 +496,8 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
344
496
  vertex: {
345
497
  module: this._shaderModule,
346
498
  entryPoint: 'vertexMain',
347
- buffers: [{
499
+ buffers: [
500
+ {
348
501
  arrayStride: vertexStrideBytes,
349
502
  stepMode: 'vertex',
350
503
  attributes: [
@@ -352,16 +505,19 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
352
505
  { shaderLocation: 1, offset: 8, format: 'float32x2' },
353
506
  { shaderLocation: 2, offset: 16, format: 'unorm8x4' },
354
507
  ],
355
- }],
508
+ },
509
+ ],
356
510
  },
357
511
  fragment: {
358
512
  module: this._shaderModule,
359
513
  entryPoint: 'fragmentMain',
360
- targets: [{
514
+ targets: [
515
+ {
361
516
  format,
362
517
  blend: getWebGpuBlendState(blendMode),
363
518
  writeMask: GPUColorWrite.ALL,
364
- }],
519
+ },
520
+ ],
365
521
  },
366
522
  primitive: {
367
523
  topology: 'triangle-list',
@@ -416,6 +572,9 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
416
572
  }
417
573
  }
418
574
  _ensureUniformCapacity(drawCallCount) {
575
+ if (drawCallCount === 0) {
576
+ return;
577
+ }
419
578
  const requiredBytes = drawCallCount * this._uniformAlignment;
420
579
  if (requiredBytes > this._uniformBufferCapacity) {
421
580
  this._uniformBuffer?.destroy();
@@ -426,13 +585,409 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
426
585
  });
427
586
  this._uniformBindGroup = this._device.createBindGroup({
428
587
  layout: this._uniformBindGroupLayout,
429
- entries: [{
588
+ entries: [
589
+ {
430
590
  binding: 0,
431
591
  resource: { buffer: this._uniformBuffer, size: tintByteLength },
432
- }],
592
+ },
593
+ ],
594
+ });
595
+ }
596
+ }
597
+ // ---------------------------------------------------------------------------
598
+ // Custom-path helpers
599
+ // ---------------------------------------------------------------------------
600
+ _totalCustomDraws() {
601
+ let total = 0;
602
+ for (const resources of this._customShaders.values()) {
603
+ total += resources.drawCount;
604
+ }
605
+ return total;
606
+ }
607
+ _resetFrame() {
608
+ this._drawCallCount = 0;
609
+ for (const resources of this._customShaders.values()) {
610
+ resources.drawCount = 0;
611
+ resources.totalVertices = 0;
612
+ resources.totalIndices = 0;
613
+ }
614
+ }
615
+ _getOrCreateCustomShaderResources(shader) {
616
+ let resources = this._customShaders.get(shader);
617
+ if (resources !== undefined) {
618
+ return resources;
619
+ }
620
+ if (this._device === null) {
621
+ throw new Error('WebGpuMeshRenderer is not connected to a backend.');
622
+ }
623
+ if (shader.wgsl === null) {
624
+ throw new Error('MeshShader has no `wgsl` source; cannot render through the WebGPU backend.');
625
+ }
626
+ const device = this._device;
627
+ const shaderModule = device.createShaderModule({ code: shader.wgsl });
628
+ const meshUniformLayout = device.createBindGroupLayout({
629
+ entries: [
630
+ {
631
+ binding: 0,
632
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
633
+ buffer: { type: 'uniform', hasDynamicOffset: true },
634
+ },
635
+ ],
636
+ });
637
+ const meshTextureLayout = device.createBindGroupLayout({
638
+ entries: [
639
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },
640
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
641
+ ],
642
+ });
643
+ const userLayout = this._buildUserBindGroupLayout(device, shader);
644
+ const pipelineLayout = device.createPipelineLayout({
645
+ bindGroupLayouts: [meshUniformLayout, meshTextureLayout, userLayout],
646
+ });
647
+ const sampler = device.createSampler({
648
+ magFilter: 'linear',
649
+ minFilter: 'linear',
650
+ addressModeU: 'clamp-to-edge',
651
+ addressModeV: 'clamp-to-edge',
652
+ });
653
+ const initialVertexCount = 64;
654
+ const initialIndexCount = 192;
655
+ const vertexData = new ArrayBuffer(initialVertexCount * vertexStrideBytes);
656
+ resources = {
657
+ shaderModule,
658
+ meshUniformLayout,
659
+ meshTextureLayout,
660
+ userLayout,
661
+ pipelineLayout,
662
+ pipelines: new Map(),
663
+ vertexBuffer: null,
664
+ indexBuffer: null,
665
+ vertexBufferCapacity: 0,
666
+ indexBufferCapacity: 0,
667
+ vertexData,
668
+ vertexFloatView: new Float32Array(vertexData),
669
+ vertexUintView: new Uint32Array(vertexData),
670
+ indexData: new Uint16Array(initialIndexCount),
671
+ meshUniformBuffer: null,
672
+ meshUniformBufferCapacity: 0,
673
+ meshUniformBindGroup: null,
674
+ userUniformBuffer: null,
675
+ userUniformBufferCapacity: 0,
676
+ meshTextureBindGroups: new Map(),
677
+ sampler,
678
+ drawCount: 0,
679
+ totalVertices: 0,
680
+ totalIndices: 0,
681
+ };
682
+ this._customShaders.set(shader, resources);
683
+ // When the user calls shader.destroy(), evict and release.
684
+ shader._onDispose(() => {
685
+ const r = this._customShaders.get(shader);
686
+ if (r !== undefined) {
687
+ this._releaseCustomShaderResources(r);
688
+ this._customShaders.delete(shader);
689
+ }
690
+ });
691
+ return resources;
692
+ }
693
+ _ensureCustomCapacities(resources) {
694
+ const device = this._device;
695
+ // Vertex buffer
696
+ const vertexBytes = resources.totalVertices * vertexStrideBytes;
697
+ if (vertexBytes > resources.vertexData.byteLength) {
698
+ const newSize = Math.max(vertexBytes, resources.vertexData.byteLength * 2);
699
+ resources.vertexData = new ArrayBuffer(newSize);
700
+ resources.vertexFloatView = new Float32Array(resources.vertexData);
701
+ resources.vertexUintView = new Uint32Array(resources.vertexData);
702
+ }
703
+ if (vertexBytes > resources.vertexBufferCapacity) {
704
+ resources.vertexBuffer?.destroy();
705
+ resources.vertexBufferCapacity = Math.max(vertexBytes, resources.vertexBufferCapacity * 2 || vertexStrideBytes);
706
+ resources.vertexBuffer = device.createBuffer({
707
+ size: resources.vertexBufferCapacity,
708
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
709
+ });
710
+ }
711
+ // Index buffer
712
+ const indexBytes = resources.totalIndices * Uint16Array.BYTES_PER_ELEMENT;
713
+ if (resources.indexData.length < resources.totalIndices) {
714
+ resources.indexData = new Uint16Array(Math.max(resources.totalIndices, resources.indexData.length * 2));
715
+ }
716
+ if (indexBytes > resources.indexBufferCapacity) {
717
+ resources.indexBuffer?.destroy();
718
+ resources.indexBufferCapacity = Math.max(indexBytes, resources.indexBufferCapacity * 2 || Uint16Array.BYTES_PER_ELEMENT);
719
+ resources.indexBuffer = device.createBuffer({
720
+ size: resources.indexBufferCapacity,
721
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
722
+ });
723
+ }
724
+ // Mesh-uniform UBO (proj/trans/tint per draw, 256-byte aligned).
725
+ const meshUniformBytes = resources.drawCount * meshUniformAlignment;
726
+ if (meshUniformBytes > resources.meshUniformBufferCapacity) {
727
+ resources.meshUniformBuffer?.destroy();
728
+ resources.meshUniformBufferCapacity = Math.max(meshUniformBytes, resources.meshUniformBufferCapacity * 2 || meshUniformAlignment);
729
+ resources.meshUniformBuffer = device.createBuffer({
730
+ size: resources.meshUniformBufferCapacity,
731
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
732
+ });
733
+ resources.meshUniformBindGroup = device.createBindGroup({
734
+ layout: resources.meshUniformLayout,
735
+ entries: [
736
+ {
737
+ binding: 0,
738
+ resource: { buffer: resources.meshUniformBuffer, size: customMeshUniformBytes },
739
+ },
740
+ ],
741
+ });
742
+ }
743
+ }
744
+ _writeMeshVerticesIntoBuffer(mesh, vertexStart, floatView, uintView) {
745
+ const vertices = mesh.vertices;
746
+ const uvs = mesh.uvs;
747
+ const colors = mesh.colors;
748
+ const vertexCount = mesh.vertexCount;
749
+ for (let i = 0; i < vertexCount; i++) {
750
+ const sourceIndex = i * 2;
751
+ const targetIndex = (vertexStart + i) * wordsPerVertex;
752
+ floatView[targetIndex + 0] = vertices[sourceIndex];
753
+ floatView[targetIndex + 1] = vertices[sourceIndex + 1];
754
+ floatView[targetIndex + 2] = uvs !== null ? uvs[sourceIndex] : 0;
755
+ floatView[targetIndex + 3] = uvs !== null ? uvs[sourceIndex + 1] : 0;
756
+ uintView[targetIndex + 4] = colors !== null ? colors[i] : 0xffffffff;
757
+ }
758
+ }
759
+ _writeCustomMeshUniform(_shader, resources, drawCursor, mesh, backend) {
760
+ // Layout: mat3x3 projection (48B) + mat3x3 translation (48B) + vec4 tint (16B) = 112B.
761
+ // WGSL mat3x3 stores 3 vec3 columns padded to vec4 alignment.
762
+ const slotBytes = meshUniformAlignment;
763
+ const slotFloats = slotBytes / Float32Array.BYTES_PER_ELEMENT;
764
+ const data = new Float32Array(slotFloats);
765
+ const proj = backend.view.getTransform();
766
+ const trans = mesh.getGlobalTransform();
767
+ // mat3 (column-major): [a, c, tx | b, d, ty | 0, 0, 1] in 2D.
768
+ // WGSL mat3x3 has each column padded to vec4. Store as:
769
+ // col0 = [a, b, 0, 0] / [c, d, 0, 0] / ...
770
+ // ExoJS Matrix stores: a, b, c, d, x, y. Standard 2D affine is:
771
+ // [a c tx]
772
+ // [b d ty]
773
+ // [0 0 1 ]
774
+ // Column-major mat3: col0 = (a, b, 0), col1 = (c, d, 0), col2 = (tx, ty, 1).
775
+ let off = 0;
776
+ // projection
777
+ data[off + 0] = proj.a;
778
+ data[off + 1] = proj.b;
779
+ data[off + 2] = 0;
780
+ data[off + 3] = 0; // pad
781
+ data[off + 4] = proj.c;
782
+ data[off + 5] = proj.d;
783
+ data[off + 6] = 0;
784
+ data[off + 7] = 0; // pad
785
+ data[off + 8] = proj.x;
786
+ data[off + 9] = proj.y;
787
+ data[off + 10] = 1;
788
+ data[off + 11] = 0; // pad
789
+ off += 12;
790
+ // translation
791
+ data[off + 0] = trans.a;
792
+ data[off + 1] = trans.b;
793
+ data[off + 2] = 0;
794
+ data[off + 3] = 0;
795
+ data[off + 4] = trans.c;
796
+ data[off + 5] = trans.d;
797
+ data[off + 6] = 0;
798
+ data[off + 7] = 0;
799
+ data[off + 8] = trans.x;
800
+ data[off + 9] = trans.y;
801
+ data[off + 10] = 1;
802
+ data[off + 11] = 0;
803
+ off += 12;
804
+ // tint (vec4)
805
+ const tint = mesh.tint;
806
+ data[off + 0] = tint.red;
807
+ data[off + 1] = tint.green;
808
+ data[off + 2] = tint.blue;
809
+ data[off + 3] = tint.alpha;
810
+ this._device.queue.writeBuffer(resources.meshUniformBuffer, drawCursor * slotBytes, data);
811
+ }
812
+ _getOrCreateCustomPipeline(resources, blendMode, format) {
813
+ const cacheKey = `${blendMode}:${format}`;
814
+ let pipeline = resources.pipelines.get(cacheKey);
815
+ if (pipeline === undefined) {
816
+ pipeline = this._device.createRenderPipeline({
817
+ layout: resources.pipelineLayout,
818
+ vertex: {
819
+ module: resources.shaderModule,
820
+ entryPoint: 'vertexMain',
821
+ buffers: [
822
+ {
823
+ arrayStride: vertexStrideBytes,
824
+ stepMode: 'vertex',
825
+ attributes: [
826
+ { shaderLocation: 0, offset: 0, format: 'float32x2' },
827
+ { shaderLocation: 1, offset: 8, format: 'float32x2' },
828
+ { shaderLocation: 2, offset: 16, format: 'unorm8x4' },
829
+ ],
830
+ },
831
+ ],
832
+ },
833
+ fragment: {
834
+ module: resources.shaderModule,
835
+ entryPoint: 'fragmentMain',
836
+ targets: [
837
+ {
838
+ format,
839
+ blend: getWebGpuBlendState(blendMode),
840
+ writeMask: GPUColorWrite.ALL,
841
+ },
842
+ ],
843
+ },
844
+ primitive: {
845
+ topology: 'triangle-list',
846
+ cullMode: 'none',
847
+ },
848
+ });
849
+ resources.pipelines.set(cacheKey, pipeline);
850
+ }
851
+ return pipeline;
852
+ }
853
+ _getOrCreateCustomMeshTextureBindGroup(resources, backend, texture) {
854
+ let group = resources.meshTextureBindGroups.get(texture);
855
+ if (group === undefined) {
856
+ const binding = backend.getTextureBinding(texture);
857
+ group = this._device.createBindGroup({
858
+ layout: resources.meshTextureLayout,
859
+ entries: [
860
+ { binding: 0, resource: binding.view },
861
+ { binding: 1, resource: binding.sampler },
862
+ ],
863
+ });
864
+ resources.meshTextureBindGroups.set(texture, group);
865
+ }
866
+ return group;
867
+ }
868
+ _buildUserBindGroupLayout(device, shader) {
869
+ const entries = [];
870
+ const userUniforms = shader.uniforms;
871
+ Object.values(userUniforms).some(v => !isTextureUniform(v));
872
+ // Binding 0 always reserved for the user UBO (even if empty), so the
873
+ // bind-group layout is stable across user-uniform mutations.
874
+ entries.push({
875
+ binding: 0,
876
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
877
+ buffer: { type: 'uniform' },
878
+ });
879
+ let bindingIndex = 1;
880
+ let textureCount = 0;
881
+ for (const value of Object.values(userUniforms)) {
882
+ if (!isTextureUniform(value)) {
883
+ continue;
884
+ }
885
+ if (textureCount >= maxCustomTextureSlots) {
886
+ throw new Error(`MeshShader requested more than ${maxCustomTextureSlots} user texture uniforms.`);
887
+ }
888
+ entries.push({
889
+ binding: bindingIndex,
890
+ visibility: GPUShaderStage.FRAGMENT,
891
+ texture: { sampleType: 'float' },
892
+ });
893
+ bindingIndex++;
894
+ entries.push({
895
+ binding: bindingIndex,
896
+ visibility: GPUShaderStage.FRAGMENT,
897
+ sampler: { type: 'filtering' },
898
+ });
899
+ bindingIndex++;
900
+ textureCount++;
901
+ }
902
+ return device.createBindGroupLayout({ entries });
903
+ }
904
+ _uploadUserUniforms(_shader, resources) {
905
+ const device = this._device;
906
+ const uniforms = _shader.uniforms;
907
+ const scalarValues = Object.values(uniforms).filter(v => !isTextureUniform(v));
908
+ // Always create a UBO (even if empty) since binding 0 of the user layout
909
+ // is fixed. Min size 16 bytes to satisfy WebGPU's minimum buffer size.
910
+ const slotCount = Math.max(scalarValues.length, 1);
911
+ const bufferBytes = slotCount * 16;
912
+ if (resources.userUniformBuffer === null || resources.userUniformBufferCapacity < bufferBytes) {
913
+ resources.userUniformBuffer?.destroy();
914
+ resources.userUniformBufferCapacity = bufferBytes;
915
+ resources.userUniformBuffer = device.createBuffer({
916
+ size: bufferBytes,
917
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
433
918
  });
434
919
  }
920
+ const data = new Float32Array(bufferBytes / 4);
921
+ let slot = 0;
922
+ for (const value of scalarValues) {
923
+ const baseFloatIndex = slot * 4;
924
+ if (typeof value === 'number') {
925
+ data[baseFloatIndex] = value;
926
+ }
927
+ else if (value instanceof Float32Array) {
928
+ data.set(value, baseFloatIndex);
929
+ }
930
+ else if (value instanceof Int32Array) {
931
+ for (let i = 0; i < value.length; i++) {
932
+ data[baseFloatIndex + i] = value[i];
933
+ }
934
+ }
935
+ else {
936
+ const arr = value;
937
+ for (let i = 0; i < arr.length; i++) {
938
+ data[baseFloatIndex + i] = arr[i];
939
+ }
940
+ }
941
+ slot++;
942
+ }
943
+ device.queue.writeBuffer(resources.userUniformBuffer, 0, data);
435
944
  }
945
+ _buildUserBindGroup(backend, shader, resources) {
946
+ const device = this._device;
947
+ const entries = [];
948
+ entries.push({ binding: 0, resource: { buffer: resources.userUniformBuffer } });
949
+ let bindingIndex = 1;
950
+ for (const value of Object.values(shader.uniforms)) {
951
+ if (!isTextureUniform(value)) {
952
+ continue;
953
+ }
954
+ const binding = backend.getTextureBinding(value);
955
+ entries.push({ binding: bindingIndex, resource: binding.view });
956
+ bindingIndex++;
957
+ entries.push({ binding: bindingIndex, resource: binding.sampler });
958
+ bindingIndex++;
959
+ }
960
+ return device.createBindGroup({
961
+ layout: resources.userLayout,
962
+ entries,
963
+ });
964
+ }
965
+ _releaseCustomShaderResources(resources) {
966
+ resources.vertexBuffer?.destroy();
967
+ resources.indexBuffer?.destroy();
968
+ resources.meshUniformBuffer?.destroy();
969
+ resources.userUniformBuffer?.destroy();
970
+ resources.pipelines.clear();
971
+ resources.meshTextureBindGroups.clear();
972
+ resources.vertexBuffer = null;
973
+ resources.indexBuffer = null;
974
+ resources.meshUniformBuffer = null;
975
+ resources.userUniformBuffer = null;
976
+ resources.meshUniformBindGroup = null;
977
+ resources.vertexBufferCapacity = 0;
978
+ resources.indexBufferCapacity = 0;
979
+ resources.meshUniformBufferCapacity = 0;
980
+ resources.userUniformBufferCapacity = 0;
981
+ }
982
+ }
983
+ function isTextureUniform(value) {
984
+ return (typeof value === 'object' &&
985
+ value !== null &&
986
+ 'width' in value &&
987
+ 'height' in value &&
988
+ !(value instanceof Float32Array) &&
989
+ !(value instanceof Int32Array) &&
990
+ !Array.isArray(value));
436
991
  }
437
992
 
438
993
  export { WebGpuMeshRenderer };