@codexo/exojs 0.8.4 → 0.10.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 (470) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +34 -13
  3. package/dist/esm/animation/Tween.d.ts +20 -3
  4. package/dist/esm/animation/Tween.js +16 -8
  5. package/dist/esm/animation/Tween.js.map +1 -1
  6. package/dist/esm/animation/TweenManager.d.ts +16 -1
  7. package/dist/esm/animation/TweenManager.js +27 -2
  8. package/dist/esm/animation/TweenManager.js.map +1 -1
  9. package/dist/esm/audio/AudioAnalyser.d.ts +2 -0
  10. package/dist/esm/audio/AudioAnalyser.js +24 -9
  11. package/dist/esm/audio/AudioAnalyser.js.map +1 -1
  12. package/dist/esm/audio/AudioBus.d.ts +1 -0
  13. package/dist/esm/audio/AudioBus.js +8 -4
  14. package/dist/esm/audio/AudioBus.js.map +1 -1
  15. package/dist/esm/audio/AudioListener.d.ts +1 -0
  16. package/dist/esm/audio/AudioListener.js +7 -5
  17. package/dist/esm/audio/AudioListener.js.map +1 -1
  18. package/dist/esm/audio/AudioManager.d.ts +8 -2
  19. package/dist/esm/audio/AudioManager.js +14 -1
  20. package/dist/esm/audio/AudioManager.js.map +1 -1
  21. package/dist/esm/audio/BeatDetector.d.ts +2 -0
  22. package/dist/esm/audio/BeatDetector.js +27 -661
  23. package/dist/esm/audio/BeatDetector.js.map +1 -1
  24. package/dist/esm/audio/Music.d.ts +1 -0
  25. package/dist/esm/audio/Music.js +7 -3
  26. package/dist/esm/audio/Music.js.map +1 -1
  27. package/dist/esm/audio/OscillatorSound.d.ts +1 -0
  28. package/dist/esm/audio/OscillatorSound.js +7 -3
  29. package/dist/esm/audio/OscillatorSound.js.map +1 -1
  30. package/dist/esm/audio/Sound.d.ts +1 -0
  31. package/dist/esm/audio/Sound.js +7 -3
  32. package/dist/esm/audio/Sound.js.map +1 -1
  33. package/dist/esm/audio/audio-context.d.ts +2 -2
  34. package/dist/esm/audio/audio-context.js +4 -4
  35. package/dist/esm/audio/audio-context.js.map +1 -1
  36. package/dist/esm/audio/filters/ChorusFilter.d.ts +1 -0
  37. package/dist/esm/audio/filters/ChorusFilter.js +7 -3
  38. package/dist/esm/audio/filters/ChorusFilter.js.map +1 -1
  39. package/dist/esm/audio/filters/CompressorFilter.d.ts +1 -0
  40. package/dist/esm/audio/filters/CompressorFilter.js +7 -3
  41. package/dist/esm/audio/filters/CompressorFilter.js.map +1 -1
  42. package/dist/esm/audio/filters/DelayFilter.d.ts +1 -0
  43. package/dist/esm/audio/filters/DelayFilter.js +7 -3
  44. package/dist/esm/audio/filters/DelayFilter.js.map +1 -1
  45. package/dist/esm/audio/filters/EqualizerFilter.d.ts +1 -0
  46. package/dist/esm/audio/filters/EqualizerFilter.js +7 -3
  47. package/dist/esm/audio/filters/EqualizerFilter.js.map +1 -1
  48. package/dist/esm/audio/filters/GranularFilter.js +1 -86
  49. package/dist/esm/audio/filters/GranularFilter.js.map +1 -1
  50. package/dist/esm/audio/filters/HighpassFilter.d.ts +1 -0
  51. package/dist/esm/audio/filters/HighpassFilter.js +7 -3
  52. package/dist/esm/audio/filters/HighpassFilter.js.map +1 -1
  53. package/dist/esm/audio/filters/LowpassFilter.d.ts +1 -0
  54. package/dist/esm/audio/filters/LowpassFilter.js +7 -3
  55. package/dist/esm/audio/filters/LowpassFilter.js.map +1 -1
  56. package/dist/esm/audio/filters/PitchShiftFilter.js +1 -71
  57. package/dist/esm/audio/filters/PitchShiftFilter.js.map +1 -1
  58. package/dist/esm/audio/filters/ReverbFilter.d.ts +1 -0
  59. package/dist/esm/audio/filters/ReverbFilter.js +7 -3
  60. package/dist/esm/audio/filters/ReverbFilter.js.map +1 -1
  61. package/dist/esm/audio/filters/VocoderFilter.js +1 -89
  62. package/dist/esm/audio/filters/VocoderFilter.js.map +1 -1
  63. package/dist/esm/audio/filters/WorkletFilter.d.ts +2 -0
  64. package/dist/esm/audio/filters/WorkletFilter.js +10 -5
  65. package/dist/esm/audio/filters/WorkletFilter.js.map +1 -1
  66. package/dist/esm/audio/index.d.ts +15 -10
  67. package/dist/esm/audio/worklet/registerWorklet.d.ts +1 -1
  68. package/dist/esm/audio/worklet/registerWorklet.js +2 -2
  69. package/dist/esm/audio/worklet/registerWorklet.js.map +1 -1
  70. package/dist/esm/audio/worklets/beat-detector.worklet.d.ts +1 -0
  71. package/dist/esm/audio/worklets/beat-detector.worklet.js +647 -0
  72. package/dist/esm/audio/worklets/beat-detector.worklet.js.map +1 -0
  73. package/dist/esm/audio/worklets/granular.worklet.d.ts +1 -0
  74. package/dist/esm/audio/worklets/granular.worklet.js +89 -0
  75. package/dist/esm/audio/worklets/granular.worklet.js.map +1 -0
  76. package/dist/esm/audio/worklets/pitch-shift.worklet.d.ts +1 -0
  77. package/dist/esm/audio/worklets/pitch-shift.worklet.js +74 -0
  78. package/dist/esm/audio/worklets/pitch-shift.worklet.js.map +1 -0
  79. package/dist/esm/audio/worklets/vocoder.worklet.d.ts +1 -0
  80. package/dist/esm/audio/worklets/vocoder.worklet.js +92 -0
  81. package/dist/esm/audio/worklets/vocoder.worklet.js.map +1 -0
  82. package/dist/esm/core/Application.d.ts +86 -29
  83. package/dist/esm/core/Application.js +157 -47
  84. package/dist/esm/core/Application.js.map +1 -1
  85. package/dist/esm/core/Color.d.ts +0 -8
  86. package/dist/esm/core/Color.js +0 -24
  87. package/dist/esm/core/Color.js.map +1 -1
  88. package/dist/esm/core/Perf.d.ts +23 -0
  89. package/dist/esm/core/Perf.js +49 -0
  90. package/dist/esm/core/Perf.js.map +1 -0
  91. package/dist/esm/core/Scene.d.ts +40 -80
  92. package/dist/esm/core/Scene.js +63 -43
  93. package/dist/esm/core/Scene.js.map +1 -1
  94. package/dist/esm/core/SceneManager.d.ts +11 -25
  95. package/dist/esm/core/SceneManager.js +37 -100
  96. package/dist/esm/core/SceneManager.js.map +1 -1
  97. package/dist/esm/core/SceneNode.d.ts +28 -17
  98. package/dist/esm/core/SceneNode.js +66 -42
  99. package/dist/esm/core/SceneNode.js.map +1 -1
  100. package/dist/esm/core/Signal.d.ts +24 -28
  101. package/dist/esm/core/Signal.js +64 -50
  102. package/dist/esm/core/Signal.js.map +1 -1
  103. package/dist/esm/core/Timer.d.ts +1 -0
  104. package/dist/esm/core/Timer.js +3 -0
  105. package/dist/esm/core/Timer.js.map +1 -1
  106. package/dist/esm/core/capabilities.d.ts +2 -0
  107. package/dist/esm/core/capabilities.js +15 -0
  108. package/dist/esm/core/capabilities.js.map +1 -1
  109. package/dist/esm/core/index.d.ts +1 -0
  110. package/dist/esm/core/types.d.ts +1 -1
  111. package/dist/esm/core/utils.d.ts +12 -0
  112. package/dist/esm/core/utils.js +18 -1
  113. package/dist/esm/core/utils.js.map +1 -1
  114. package/dist/esm/debug/BoundingBoxesLayer.js +1 -1
  115. package/dist/esm/debug/BoundingBoxesLayer.js.map +1 -1
  116. package/dist/esm/debug/HitTestLayer.js +1 -1
  117. package/dist/esm/debug/HitTestLayer.js.map +1 -1
  118. package/dist/esm/debug/PerformanceLayer.js +1 -2
  119. package/dist/esm/debug/PerformanceLayer.js.map +1 -1
  120. package/dist/esm/debug/PointerStackLayer.js +1 -2
  121. package/dist/esm/debug/PointerStackLayer.js.map +1 -1
  122. package/dist/esm/debug/RenderPassInspectorLayer.js +1 -2
  123. package/dist/esm/debug/RenderPassInspectorLayer.js.map +1 -1
  124. package/dist/esm/index.js +37 -10
  125. package/dist/esm/index.js.map +1 -1
  126. package/dist/esm/input/InputManager.d.ts +1 -0
  127. package/dist/esm/input/InputManager.js +30 -3
  128. package/dist/esm/input/InputManager.js.map +1 -1
  129. package/dist/esm/input/InteractionManager.d.ts +5 -2
  130. package/dist/esm/input/InteractionManager.js +29 -18
  131. package/dist/esm/input/InteractionManager.js.map +1 -1
  132. package/dist/esm/input/Pointer.js +3 -2
  133. package/dist/esm/input/Pointer.js.map +1 -1
  134. package/dist/esm/input/internal/interactionManagerRegistry.d.ts +9 -0
  135. package/dist/esm/input/internal/interactionManagerRegistry.js +10 -0
  136. package/dist/esm/input/internal/interactionManagerRegistry.js.map +1 -0
  137. package/dist/esm/math/AbstractVector.d.ts +1 -7
  138. package/dist/esm/math/AbstractVector.js +6 -20
  139. package/dist/esm/math/AbstractVector.js.map +1 -1
  140. package/dist/esm/math/Circle.js +0 -2
  141. package/dist/esm/math/Circle.js.map +1 -1
  142. package/dist/esm/math/Collision.d.ts +9 -3
  143. package/dist/esm/math/Ellipse.d.ts +2 -5
  144. package/dist/esm/math/Ellipse.js +10 -7
  145. package/dist/esm/math/Ellipse.js.map +1 -1
  146. package/dist/esm/math/ObservableVector.d.ts +1 -1
  147. package/dist/esm/math/ObservableVector.js +3 -3
  148. package/dist/esm/math/ObservableVector.js.map +1 -1
  149. package/dist/esm/math/Polygon.d.ts +0 -2
  150. package/dist/esm/math/Polygon.js +1 -9
  151. package/dist/esm/math/Polygon.js.map +1 -1
  152. package/dist/esm/math/Rectangle.js +0 -2
  153. package/dist/esm/math/Rectangle.js.map +1 -1
  154. package/dist/esm/math/collision-detection.d.ts +19 -4
  155. package/dist/esm/math/collision-detection.js +61 -4
  156. package/dist/esm/math/collision-detection.js.map +1 -1
  157. package/dist/esm/math/swept-collision.d.ts +16 -12
  158. package/dist/esm/math/swept-collision.js +109 -19
  159. package/dist/esm/math/swept-collision.js.map +1 -1
  160. package/dist/esm/particles/ParticleSystem.d.ts +8 -5
  161. package/dist/esm/particles/ParticleSystem.js +10 -6
  162. package/dist/esm/particles/ParticleSystem.js.map +1 -1
  163. package/dist/esm/particles/distributions/{Gradient.d.ts → ColorGradient.d.ts} +5 -5
  164. package/dist/esm/particles/distributions/{Gradient.js → ColorGradient.js} +5 -5
  165. package/dist/esm/particles/distributions/ColorGradient.js.map +1 -0
  166. package/dist/esm/particles/distributions/Distribution.d.ts +2 -2
  167. package/dist/esm/particles/distributions/index.d.ts +2 -2
  168. package/dist/esm/particles/gpu/ParticleGpuState.js +1 -1
  169. package/dist/esm/particles/index.d.ts +1 -0
  170. package/dist/esm/particles/modules/ColorOverLifetime.d.ts +3 -3
  171. package/dist/esm/particles/modules/ColorOverLifetime.js.map +1 -1
  172. package/dist/esm/particles/modules/ColorOverSpeed.d.ts +3 -3
  173. package/dist/esm/particles/modules/ColorOverSpeed.js.map +1 -1
  174. package/dist/esm/particles/modules/UpdateModule.d.ts +2 -2
  175. package/dist/esm/particles/modules/UpdateModule.js +1 -1
  176. package/dist/esm/particles/modules/WgslContribution.d.ts +2 -2
  177. package/dist/esm/rendering/CallbackRenderPass.d.ts +1 -0
  178. package/dist/esm/rendering/CallbackRenderPass.js +1 -0
  179. package/dist/esm/rendering/CallbackRenderPass.js.map +1 -1
  180. package/dist/esm/rendering/Camera.d.ts +33 -0
  181. package/dist/esm/rendering/Camera.js +38 -0
  182. package/dist/esm/rendering/Camera.js.map +1 -0
  183. package/dist/esm/rendering/Container.d.ts +7 -25
  184. package/dist/esm/rendering/Container.js +25 -87
  185. package/dist/esm/rendering/Container.js.map +1 -1
  186. package/dist/esm/rendering/Drawable.d.ts +8 -10
  187. package/dist/esm/rendering/Drawable.js +12 -20
  188. package/dist/esm/rendering/Drawable.js.map +1 -1
  189. package/dist/esm/rendering/RenderBackend.d.ts +19 -0
  190. package/dist/esm/rendering/RenderNode.d.ts +82 -11
  191. package/dist/esm/rendering/RenderNode.js +133 -163
  192. package/dist/esm/rendering/RenderNode.js.map +1 -1
  193. package/dist/esm/rendering/RenderPass.d.ts +1 -0
  194. package/dist/esm/rendering/RenderStats.d.ts +9 -0
  195. package/dist/esm/rendering/RenderStats.js +2 -0
  196. package/dist/esm/rendering/RenderStats.js.map +1 -1
  197. package/dist/esm/rendering/RenderTarget.d.ts +13 -0
  198. package/dist/esm/rendering/RenderTarget.js +13 -0
  199. package/dist/esm/rendering/RenderTarget.js.map +1 -1
  200. package/dist/esm/rendering/RenderTargetPass.js +17 -0
  201. package/dist/esm/rendering/RenderTargetPass.js.map +1 -1
  202. package/dist/esm/rendering/RendererRegistry.d.ts +1 -0
  203. package/dist/esm/rendering/RendererRegistry.js +1 -0
  204. package/dist/esm/rendering/RendererRegistry.js.map +1 -1
  205. package/dist/esm/rendering/RenderingContext.d.ts +87 -0
  206. package/dist/esm/rendering/RenderingContext.js +157 -0
  207. package/dist/esm/rendering/RenderingContext.js.map +1 -0
  208. package/dist/esm/rendering/TransformBuffer.d.ts +38 -0
  209. package/dist/esm/rendering/TransformBuffer.js +116 -0
  210. package/dist/esm/rendering/TransformBuffer.js.map +1 -0
  211. package/dist/esm/rendering/View.d.ts +23 -0
  212. package/dist/esm/rendering/View.js +42 -0
  213. package/dist/esm/rendering/View.js.map +1 -1
  214. package/dist/esm/rendering/filters/WebGpuShaderFilter.js +5 -12
  215. package/dist/esm/rendering/filters/WebGpuShaderFilter.js.map +1 -1
  216. package/dist/esm/rendering/geometry/Geometry.d.ts +40 -0
  217. package/dist/esm/rendering/geometry/Geometry.js +228 -0
  218. package/dist/esm/rendering/geometry/Geometry.js.map +1 -0
  219. package/dist/esm/rendering/geometry/GeometryAttribute.d.ts +32 -0
  220. package/dist/esm/rendering/geometry/QuadGeometry.d.ts +5 -0
  221. package/dist/esm/rendering/gradient/Gradient.d.ts +34 -0
  222. package/dist/esm/rendering/gradient/Gradient.js +114 -0
  223. package/dist/esm/rendering/gradient/Gradient.js.map +1 -0
  224. package/dist/esm/rendering/gradient/LinearGradient.d.ts +10 -0
  225. package/dist/esm/rendering/gradient/LinearGradient.js +26 -0
  226. package/dist/esm/rendering/gradient/LinearGradient.js.map +1 -0
  227. package/dist/esm/rendering/gradient/RadialGradient.d.ts +10 -0
  228. package/dist/esm/rendering/gradient/RadialGradient.js +25 -0
  229. package/dist/esm/rendering/gradient/RadialGradient.js.map +1 -0
  230. package/dist/esm/rendering/index.d.ts +103 -59
  231. package/dist/esm/rendering/material/Material.d.ts +114 -0
  232. package/dist/esm/rendering/material/Material.js +111 -0
  233. package/dist/esm/rendering/material/Material.js.map +1 -0
  234. package/dist/esm/rendering/material/MaterialKey.d.ts +18 -0
  235. package/dist/esm/rendering/material/MaterialKey.js +82 -0
  236. package/dist/esm/rendering/material/MaterialKey.js.map +1 -0
  237. package/dist/esm/rendering/material/MeshMaterial.d.ts +16 -0
  238. package/dist/esm/rendering/material/MeshMaterial.js +21 -0
  239. package/dist/esm/rendering/material/MeshMaterial.js.map +1 -0
  240. package/dist/esm/rendering/{mesh/MeshShader.d.ts → material/ShaderSource.d.ts} +30 -62
  241. package/dist/esm/rendering/{mesh/MeshShader.js → material/ShaderSource.js} +36 -62
  242. package/dist/esm/rendering/material/ShaderSource.js.map +1 -0
  243. package/dist/esm/rendering/material/SpriteMaterial.d.ts +15 -0
  244. package/dist/esm/rendering/material/SpriteMaterial.js +20 -0
  245. package/dist/esm/rendering/material/SpriteMaterial.js.map +1 -0
  246. package/dist/esm/rendering/mesh/Mesh.d.ts +29 -12
  247. package/dist/esm/rendering/mesh/Mesh.js +123 -4
  248. package/dist/esm/rendering/mesh/Mesh.js.map +1 -1
  249. package/dist/esm/rendering/pass/RenderPassCoordinator.d.ts +63 -0
  250. package/dist/esm/rendering/pass/RenderPassDescriptor.d.ts +48 -0
  251. package/dist/esm/rendering/pass/RenderPassDescriptor.js +16 -0
  252. package/dist/esm/rendering/pass/RenderPassDescriptor.js.map +1 -0
  253. package/dist/esm/rendering/plan/RenderCommand.d.ts +67 -0
  254. package/dist/esm/rendering/plan/RenderCommand.js +94 -0
  255. package/dist/esm/rendering/plan/RenderCommand.js.map +1 -0
  256. package/dist/esm/rendering/plan/RenderEffectExecutor.d.ts +10 -0
  257. package/dist/esm/rendering/plan/RenderEffectExecutor.js +159 -0
  258. package/dist/esm/rendering/plan/RenderEffectExecutor.js.map +1 -0
  259. package/dist/esm/rendering/plan/RenderPlan.d.ts +23 -0
  260. package/dist/esm/rendering/plan/RenderPlan.js +12 -0
  261. package/dist/esm/rendering/plan/RenderPlan.js.map +1 -0
  262. package/dist/esm/rendering/plan/RenderPlanBuilder.d.ts +31 -0
  263. package/dist/esm/rendering/plan/RenderPlanBuilder.js +242 -0
  264. package/dist/esm/rendering/plan/RenderPlanBuilder.js.map +1 -0
  265. package/dist/esm/rendering/plan/RenderPlanOptimizer.d.ts +10 -0
  266. package/dist/esm/rendering/plan/RenderPlanOptimizer.js +180 -0
  267. package/dist/esm/rendering/plan/RenderPlanOptimizer.js.map +1 -0
  268. package/dist/esm/rendering/plan/RenderPlanPlayer.d.ts +9 -0
  269. package/dist/esm/rendering/plan/RenderPlanPlayer.js +56 -0
  270. package/dist/esm/rendering/plan/RenderPlanPlayer.js.map +1 -0
  271. package/dist/esm/rendering/plan/RenderScope.d.ts +70 -0
  272. package/dist/esm/rendering/plan/RenderScope.js +16 -0
  273. package/dist/esm/rendering/plan/RenderScope.js.map +1 -0
  274. package/dist/esm/rendering/plan/playRenderTree.d.ts +4 -0
  275. package/dist/esm/rendering/plan/playRenderTree.js +19 -0
  276. package/dist/esm/rendering/plan/playRenderTree.js.map +1 -0
  277. package/dist/esm/rendering/shader/Shader.d.ts +1 -0
  278. package/dist/esm/rendering/shader/Shader.js +1 -0
  279. package/dist/esm/rendering/shader/Shader.js.map +1 -1
  280. package/dist/esm/rendering/shader/ShaderUniform.d.ts +1 -0
  281. package/dist/esm/rendering/shader/ShaderUniform.js +1 -0
  282. package/dist/esm/rendering/shader/ShaderUniform.js.map +1 -1
  283. package/dist/esm/rendering/sprite/Sprite.d.ts +25 -3
  284. package/dist/esm/rendering/sprite/Sprite.js +48 -15
  285. package/dist/esm/rendering/sprite/Sprite.js.map +1 -1
  286. package/dist/esm/rendering/sprite/spriteMaterialSources.d.ts +36 -0
  287. package/dist/esm/rendering/sprite/spriteMaterialSources.js +128 -0
  288. package/dist/esm/rendering/sprite/spriteMaterialSources.js.map +1 -0
  289. package/dist/esm/rendering/text/AbstractText.d.ts +36 -0
  290. package/dist/esm/rendering/text/AbstractText.js +49 -0
  291. package/dist/esm/rendering/text/AbstractText.js.map +1 -0
  292. package/dist/esm/rendering/text/BitmapText.d.ts +97 -0
  293. package/dist/esm/rendering/text/BitmapText.js +220 -0
  294. package/dist/esm/rendering/text/BitmapText.js.map +1 -0
  295. package/dist/esm/rendering/text/BmFont.d.ts +50 -0
  296. package/dist/esm/rendering/text/BmFont.js +24 -0
  297. package/dist/esm/rendering/text/BmFont.js.map +1 -0
  298. package/dist/esm/rendering/text/GlyphAtlas.d.ts +104 -0
  299. package/dist/esm/rendering/text/GlyphAtlas.js +347 -0
  300. package/dist/esm/rendering/text/GlyphAtlas.js.map +1 -0
  301. package/dist/esm/rendering/text/GlyphAtlasPool.d.ts +40 -0
  302. package/dist/esm/rendering/text/GlyphAtlasPool.js +67 -0
  303. package/dist/esm/rendering/text/GlyphAtlasPool.js.map +1 -0
  304. package/dist/esm/rendering/text/GlyphSdf.d.ts +92 -0
  305. package/dist/esm/rendering/text/GlyphSdf.js +220 -0
  306. package/dist/esm/rendering/text/GlyphSdf.js.map +1 -0
  307. package/dist/esm/rendering/text/HTMLText.d.ts +107 -0
  308. package/dist/esm/rendering/text/HTMLText.js +284 -0
  309. package/dist/esm/rendering/text/HTMLText.js.map +1 -0
  310. package/dist/esm/rendering/text/LayoutOptions.d.ts +30 -0
  311. package/dist/esm/rendering/text/Text.d.ts +89 -20
  312. package/dist/esm/rendering/text/Text.js +176 -101
  313. package/dist/esm/rendering/text/Text.js.map +1 -1
  314. package/dist/esm/rendering/text/TextLayout.d.ts +20 -8
  315. package/dist/esm/rendering/text/TextLayout.js +234 -25
  316. package/dist/esm/rendering/text/TextLayout.js.map +1 -1
  317. package/dist/esm/rendering/text/TextStyle.d.ts +154 -87
  318. package/dist/esm/rendering/text/TextStyle.js +257 -203
  319. package/dist/esm/rendering/text/TextStyle.js.map +1 -1
  320. package/dist/esm/rendering/text/types.d.ts +73 -13
  321. package/dist/esm/rendering/texture/DataTexture.d.ts +5 -0
  322. package/dist/esm/rendering/texture/DataTexture.js +7 -0
  323. package/dist/esm/rendering/texture/DataTexture.js.map +1 -1
  324. package/dist/esm/rendering/texture/Texture.d.ts +1 -0
  325. package/dist/esm/rendering/texture/Texture.js +1 -0
  326. package/dist/esm/rendering/texture/Texture.js.map +1 -1
  327. package/dist/esm/rendering/types.d.ts +3 -1
  328. package/dist/esm/rendering/types.js +2 -0
  329. package/dist/esm/rendering/types.js.map +1 -1
  330. package/dist/esm/rendering/video/Video.d.ts +5 -8
  331. package/dist/esm/rendering/video/Video.js +12 -13
  332. package/dist/esm/rendering/video/Video.js.map +1 -1
  333. package/dist/esm/rendering/webgl2/WebGl2Backend.d.ts +40 -0
  334. package/dist/esm/rendering/webgl2/WebGl2Backend.js +344 -27
  335. package/dist/esm/rendering/webgl2/WebGl2Backend.js.map +1 -1
  336. package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.d.ts +22 -2
  337. package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.js +404 -112
  338. package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.js.map +1 -1
  339. package/dist/esm/rendering/webgl2/WebGl2PassCoordinator.d.ts +57 -0
  340. package/dist/esm/rendering/webgl2/WebGl2PassCoordinator.js +79 -0
  341. package/dist/esm/rendering/webgl2/WebGl2PassCoordinator.js.map +1 -0
  342. package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.d.ts +12 -0
  343. package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.js +214 -58
  344. package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.js.map +1 -1
  345. package/dist/esm/rendering/webgl2/WebGl2StencilClipper.d.ts +34 -0
  346. package/dist/esm/rendering/webgl2/WebGl2StencilClipper.js +169 -0
  347. package/dist/esm/rendering/webgl2/WebGl2StencilClipper.js.map +1 -0
  348. package/dist/esm/rendering/webgl2/WebGl2TextRenderer.d.ts +56 -0
  349. package/dist/esm/rendering/webgl2/WebGl2TextRenderer.js +486 -0
  350. package/dist/esm/rendering/webgl2/WebGl2TextRenderer.js.map +1 -0
  351. package/dist/esm/rendering/webgl2/glsl/mesh.frag.js +1 -1
  352. package/dist/esm/rendering/webgl2/glsl/mesh.vert.js +1 -1
  353. package/dist/esm/rendering/webgl2/glsl/stencil-clip.frag.js +4 -0
  354. package/dist/esm/rendering/webgl2/glsl/stencil-clip.frag.js.map +1 -0
  355. package/dist/esm/rendering/webgl2/glsl/stencil-clip.vert.js +4 -0
  356. package/dist/esm/rendering/webgl2/glsl/stencil-clip.vert.js.map +1 -0
  357. package/dist/esm/rendering/webgl2/glsl/text-color.frag.js +4 -0
  358. package/dist/esm/rendering/webgl2/glsl/text-color.frag.js.map +1 -0
  359. package/dist/esm/rendering/webgl2/glsl/text-msdf.frag.js +4 -0
  360. package/dist/esm/rendering/webgl2/glsl/text-msdf.frag.js.map +1 -0
  361. package/dist/esm/rendering/webgl2/glsl/text-sdf.frag.js +4 -0
  362. package/dist/esm/rendering/webgl2/glsl/text-sdf.frag.js.map +1 -0
  363. package/dist/esm/rendering/webgl2/glsl/text.vert.js +4 -0
  364. package/dist/esm/rendering/webgl2/glsl/text.vert.js.map +1 -0
  365. package/dist/esm/rendering/webgpu/WebGpuBackend.d.ts +50 -0
  366. package/dist/esm/rendering/webgpu/WebGpuBackend.js +151 -27
  367. package/dist/esm/rendering/webgpu/WebGpuBackend.js.map +1 -1
  368. package/dist/esm/rendering/webgpu/WebGpuBlendState.js +26 -0
  369. package/dist/esm/rendering/webgpu/WebGpuBlendState.js.map +1 -1
  370. package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js +22 -17
  371. package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js.map +1 -1
  372. package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.d.ts +26 -2
  373. package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.js +511 -89
  374. package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.js.map +1 -1
  375. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js +13 -17
  376. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js.map +1 -1
  377. package/dist/esm/rendering/webgpu/WebGpuPassCoordinator.d.ts +141 -0
  378. package/dist/esm/rendering/webgpu/WebGpuPassCoordinator.js +270 -0
  379. package/dist/esm/rendering/webgpu/WebGpuPassCoordinator.js.map +1 -0
  380. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.d.ts +16 -0
  381. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.js +344 -27
  382. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.js.map +1 -1
  383. package/dist/esm/rendering/webgpu/WebGpuStencilClipper.d.ts +57 -0
  384. package/dist/esm/rendering/webgpu/WebGpuStencilClipper.js +257 -0
  385. package/dist/esm/rendering/webgpu/WebGpuStencilClipper.js.map +1 -0
  386. package/dist/esm/rendering/webgpu/WebGpuStencilState.d.ts +14 -0
  387. package/dist/esm/rendering/webgpu/WebGpuStencilState.js +36 -0
  388. package/dist/esm/rendering/webgpu/WebGpuStencilState.js.map +1 -0
  389. package/dist/esm/rendering/webgpu/WebGpuTextRenderer.d.ts +70 -0
  390. package/dist/esm/rendering/webgpu/WebGpuTextRenderer.js +775 -0
  391. package/dist/esm/rendering/webgpu/WebGpuTextRenderer.js.map +1 -0
  392. package/dist/esm/rendering/webgpu/WebGpuTransformStorage.d.ts +16 -0
  393. package/dist/esm/rendering/webgpu/WebGpuTransformStorage.js +57 -0
  394. package/dist/esm/rendering/webgpu/WebGpuTransformStorage.js.map +1 -0
  395. package/dist/esm/rendering/webgpu/compute/WebGpuComputePipeline.js +96 -0
  396. package/dist/esm/rendering/webgpu/compute/WebGpuComputePipeline.js.map +1 -0
  397. package/dist/esm/rendering/webgpu/compute/WebGpuStorageBuffer.js +68 -0
  398. package/dist/esm/rendering/webgpu/compute/WebGpuStorageBuffer.js.map +1 -0
  399. package/dist/esm/resources/Asset.d.ts +23 -0
  400. package/dist/esm/resources/Asset.js +23 -0
  401. package/dist/esm/resources/Asset.js.map +1 -0
  402. package/dist/esm/resources/AssetDefinitions.d.ts +137 -0
  403. package/dist/esm/resources/Assets.d.ts +35 -0
  404. package/dist/esm/resources/Assets.js +32 -0
  405. package/dist/esm/resources/Assets.js.map +1 -0
  406. package/dist/esm/resources/IndexedDbDatabase.js +17 -1
  407. package/dist/esm/resources/IndexedDbDatabase.js.map +1 -1
  408. package/dist/esm/resources/IndexedDbStore.js +17 -1
  409. package/dist/esm/resources/IndexedDbStore.js.map +1 -1
  410. package/dist/esm/resources/JsonStore.d.ts +18 -0
  411. package/dist/esm/resources/JsonStore.js +62 -0
  412. package/dist/esm/resources/JsonStore.js.map +1 -0
  413. package/dist/esm/resources/Loader.d.ts +244 -18
  414. package/dist/esm/resources/Loader.js +456 -50
  415. package/dist/esm/resources/Loader.js.map +1 -1
  416. package/dist/esm/resources/LoadingQueue.d.ts +28 -0
  417. package/dist/esm/resources/LoadingQueue.js +59 -0
  418. package/dist/esm/resources/LoadingQueue.js.map +1 -0
  419. package/dist/esm/resources/factories/BmFontFactory.d.ts +25 -0
  420. package/dist/esm/resources/factories/BmFontFactory.js +96 -0
  421. package/dist/esm/resources/factories/BmFontFactory.js.map +1 -0
  422. package/dist/esm/resources/factories/CsvFactory.d.ts +35 -0
  423. package/dist/esm/resources/factories/CsvFactory.js +87 -0
  424. package/dist/esm/resources/factories/CsvFactory.js.map +1 -0
  425. package/dist/esm/resources/factories/ImageFactory.d.ts +14 -8
  426. package/dist/esm/resources/factories/ImageFactory.js +13 -6
  427. package/dist/esm/resources/factories/ImageFactory.js.map +1 -1
  428. package/dist/esm/resources/factories/MusicFactory.d.ts +8 -2
  429. package/dist/esm/resources/factories/MusicFactory.js +25 -14
  430. package/dist/esm/resources/factories/MusicFactory.js.map +1 -1
  431. package/dist/esm/resources/factories/SoundFactory.d.ts +2 -2
  432. package/dist/esm/resources/factories/SoundFactory.js.map +1 -1
  433. package/dist/esm/resources/factories/SubtitleFactory.d.ts +28 -0
  434. package/dist/esm/resources/factories/SubtitleFactory.js +203 -0
  435. package/dist/esm/resources/factories/SubtitleFactory.js.map +1 -0
  436. package/dist/esm/resources/factories/SvgFactory.d.ts +18 -1
  437. package/dist/esm/resources/factories/SvgFactory.js +21 -2
  438. package/dist/esm/resources/factories/SvgFactory.js.map +1 -1
  439. package/dist/esm/resources/factories/TextureFactory.d.ts +4 -4
  440. package/dist/esm/resources/factories/TextureFactory.js +8 -4
  441. package/dist/esm/resources/factories/TextureFactory.js.map +1 -1
  442. package/dist/esm/resources/factories/VideoFactory.d.ts +8 -2
  443. package/dist/esm/resources/factories/VideoFactory.js +27 -20
  444. package/dist/esm/resources/factories/VideoFactory.js.map +1 -1
  445. package/dist/esm/resources/factories/XmlFactory.d.ts +24 -0
  446. package/dist/esm/resources/factories/XmlFactory.js +37 -0
  447. package/dist/esm/resources/factories/XmlFactory.js.map +1 -0
  448. package/dist/esm/resources/index.d.ts +9 -1
  449. package/dist/esm/resources/tokens.d.ts +49 -3
  450. package/dist/esm/resources/tokens.js +50 -4
  451. package/dist/esm/resources/tokens.js.map +1 -1
  452. package/dist/exo.esm.js +15382 -8078
  453. package/dist/exo.esm.js.map +1 -1
  454. package/package.json +34 -16
  455. package/dist/esm/input/interaction-hooks.d.ts +0 -34
  456. package/dist/esm/input/interaction-hooks.js +0 -35
  457. package/dist/esm/input/interaction-hooks.js.map +0 -1
  458. package/dist/esm/particles/distributions/Gradient.js.map +0 -1
  459. package/dist/esm/rendering/mesh/MeshShader.js.map +0 -1
  460. package/dist/esm/rendering/text/DynamicGlyphAtlas.d.ts +0 -33
  461. package/dist/esm/rendering/text/DynamicGlyphAtlas.js +0 -134
  462. package/dist/esm/rendering/text/DynamicGlyphAtlas.js.map +0 -1
  463. package/dist/esm/rendering/text/atlas-singleton.d.ts +0 -7
  464. package/dist/esm/rendering/text/atlas-singleton.js +0 -17
  465. package/dist/esm/rendering/text/atlas-singleton.js.map +0 -1
  466. package/dist/esm/resources/factories/VttFactory.d.ts +0 -24
  467. package/dist/esm/resources/factories/VttFactory.js +0 -158
  468. package/dist/esm/resources/factories/VttFactory.js.map +0 -1
  469. package/dist/esm/vendor/webgl-debug.js +0 -1160
  470. package/dist/esm/vendor/webgl-debug.js.map +0 -1
@@ -3,7 +3,9 @@ import { Texture } from '../texture/Texture.js';
3
3
  import { BlendModes } from '../types.js';
4
4
  import { AbstractWebGpuRenderer } from './AbstractWebGpuRenderer.js';
5
5
  import { getWebGpuBlendState } from './WebGpuBlendState.js';
6
+ import { stencilContentDepthStencilState } from './WebGpuStencilState.js';
6
7
 
8
+ /* eslint-disable max-lines */
7
9
  /// <reference types="@webgpu/types" />
8
10
  const meshShaderSource = `
9
11
  struct VertexInput {
@@ -47,6 +49,65 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
47
49
  return vec4<f32>(modulated.rgb * modulated.a, modulated.a);
48
50
  }
49
51
  `;
52
+ const instancedMeshShaderSource = `
53
+ struct VertexInput {
54
+ @location(0) position: vec2<f32>,
55
+ @location(1) texcoord: vec2<f32>,
56
+ @location(2) color: vec4<f32>,
57
+ @location(6) nodeIndex: u32,
58
+ };
59
+
60
+ struct VertexOutput {
61
+ @builtin(position) position: vec4<f32>,
62
+ @location(0) texcoord: vec2<f32>,
63
+ @location(1) color: vec4<f32>,
64
+ @location(2) tint: vec4<f32>,
65
+ @location(3) @interpolate(flat) premultiplySample: u32,
66
+ };
67
+
68
+ struct TransformSlot {
69
+ m0: vec4<f32>,
70
+ m1: vec4<f32>,
71
+ m2: vec4<f32>,
72
+ };
73
+
74
+ struct TransformUniforms {
75
+ projection: mat3x3<f32>,
76
+ flags: vec4<f32>,
77
+ };
78
+
79
+ @group(0) @binding(0) var<uniform> uniforms: TransformUniforms;
80
+ @group(0) @binding(1) var<storage, read> transforms: array<TransformSlot>;
81
+
82
+ @group(1) @binding(0) var meshTexture: texture_2d<f32>;
83
+ @group(1) @binding(1) var meshSampler: sampler;
84
+
85
+ @vertex
86
+ fn vertexMain(input: VertexInput) -> VertexOutput {
87
+ let slot = transforms[input.nodeIndex];
88
+ let world = vec3<f32>(
89
+ slot.m0.x * input.position.x + slot.m0.z * input.position.y + slot.m1.x,
90
+ slot.m0.y * input.position.x + slot.m0.w * input.position.y + slot.m1.y,
91
+ 1.0
92
+ );
93
+
94
+ var output: VertexOutput;
95
+ output.position = vec4<f32>((uniforms.projection * world).xy, 0.0, 1.0);
96
+ output.texcoord = input.texcoord;
97
+ output.color = input.color;
98
+ output.tint = slot.m2;
99
+ output.premultiplySample = u32(uniforms.flags.x);
100
+ return output;
101
+ }
102
+
103
+ @fragment
104
+ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
105
+ let sample = textureSample(meshTexture, meshSampler, input.texcoord);
106
+ let resolvedSample = select(sample, vec4(sample.rgb * sample.a, sample.a), input.premultiplySample == 1u);
107
+ let modulated = resolvedSample * input.color * input.tint;
108
+ return vec4<f32>(modulated.rgb * modulated.a, modulated.a);
109
+ }
110
+ `;
50
111
  // Per-vertex layout (20 bytes): pos f32x2 + uv f32x2 + color u8x4-norm.
51
112
  // Default-shader path bakes the (view * globalTransform) into position so the
52
113
  // vertex shader stays branchless and uniform-free except for the per-mesh tint.
@@ -55,29 +116,53 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
55
116
  const vertexStrideBytes = 20;
56
117
  const wordsPerVertex = vertexStrideBytes / 4;
57
118
  const tintByteLength = 32; // vec4 tint + vec4 flags (only flags.x used)
119
+ const transformUniformByteLength = 64; // mat3x3<f32> (48B) + vec4<f32> flags (16B)
58
120
  // Custom-shader uniform layout:
59
121
  // mat3x3<f32> projection — 48 bytes (3 vec3 columns padded to vec4 in WGSL)
60
122
  // mat3x3<f32> translation — 48 bytes
61
123
  // vec4<f32> tint — 16 bytes
62
124
  // Total: 112 bytes; aligned up to 256 for dynamic offset.
63
125
  const customMeshUniformBytes = 112;
126
+ /**
127
+ * Cache key for the default + instanced (static-batch) pipeline maps. The
128
+ * stencil dimension keeps the clip and no-clip variants distinct, mirroring the
129
+ * sprite renderer: a stencil pipeline carries depth/stencil state and is only
130
+ * valid in a pass with the matching attachment, so the two are never
131
+ * interchangeable.
132
+ */
133
+ function meshPipelineCacheKey(blendMode, format, stencil) {
134
+ return `${blendMode}:${format}:${stencil ? 's' : 'n'}`;
135
+ }
64
136
  const meshUniformAlignment = 256;
65
137
  const maxCustomTextureSlots = 7; // user texture uniforms; group 2 binding 1..N
66
138
  class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
67
139
  _combinedTransform = new Matrix();
68
140
  _drawCalls = [];
69
141
  _pipelines = new Map();
70
- _textureBindGroups = new Map();
142
+ _instancedPipelines = new Map();
143
+ _staticGeometryCache = new Map();
144
+ _textureBindGroups = new WeakMap();
71
145
  _customShaders = new Map();
72
146
  _device = null;
73
147
  _shaderModule = null;
148
+ _instancedShaderModule = null;
74
149
  _uniformBindGroupLayout = null;
150
+ _instancedTransformBindGroupLayout = null;
75
151
  _textureBindGroupLayout = null;
76
152
  _pipelineLayout = null;
153
+ _instancedPipelineLayout = null;
77
154
  _vertexBuffer = null;
78
155
  _indexBuffer = null;
79
156
  _uniformBuffer = null;
80
157
  _uniformBindGroup = null;
158
+ _instancedUniformBuffer = null;
159
+ _instancedUniformBufferCapacity = 0;
160
+ _instancedUniformScratch = new Float32Array(transformUniformByteLength / Float32Array.BYTES_PER_ELEMENT);
161
+ _instancedNodeIndexBuffer = null;
162
+ _instancedNodeIndexBufferCapacity = 0;
163
+ _instancedNodeIndexData = new Uint32Array(0);
164
+ _instancedTransformBindGroup = null;
165
+ _instancedTransformStorageBuffer = null;
81
166
  _uniformAlignment = 256;
82
167
  _vertexBufferCapacity = 0;
83
168
  _indexBufferCapacity = 0;
@@ -92,17 +177,31 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
92
177
  if (backend === null) {
93
178
  throw new Error('WebGpuMeshRenderer is not connected to a backend.');
94
179
  }
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.');
180
+ const customShader = mesh.material;
181
+ if (customShader !== null && customShader.shader.wgsl === null) {
182
+ throw new Error('Mesh material shader has no `wgsl` source; cannot render through the WebGPU backend.');
183
+ }
184
+ if (customShader !== null && backend._passCoordinator.stencilActive) {
185
+ // The WebGPU geometric stencil MVP supports clipping default-material
186
+ // Sprites and Mesh/Graphics (which select stencil-enabled pipeline
187
+ // variants below); a custom MeshMaterial under a Geometry clip would need
188
+ // its own stencil pipeline variants and is not supported yet. Throw at
189
+ // collection time (inside the clip scope's try) so the surrounding
190
+ // push/pop balances cleanly, rather than at flush time where the pop has
191
+ // not yet run.
192
+ throw new Error('WebGPU geometry stencil clipping currently supports default-material Sprites, Meshes, and Graphics. A custom-material Mesh under a Geometry clip (RenderNode.clip with a Geometry clipShape) is not supported yet. Use a Rectangle clipShape (scissor) or the WebGL2 backend instead.');
98
193
  }
99
194
  const vertexCount = mesh.vertexCount;
100
195
  if (vertexCount === 0) {
101
196
  return;
102
197
  }
103
- const blendMode = mesh.blendMode;
198
+ // The material owns its blend mode; the mesh's own blendMode overrides it
199
+ // when set away from the default (Normal). Default-path meshes keep their
200
+ // own blendMode verbatim.
201
+ const blendMode = customShader !== null && mesh.blendMode === BlendModes.Normal ? customShader.blendMode : mesh.blendMode;
104
202
  backend.setBlendMode(blendMode);
105
203
  const meshTexture = mesh.texture ?? Texture.white;
204
+ const command = backend.activeDrawCommand;
106
205
  // backend.shouldPremultiplyTextureSample expects RenderTexture-or-Texture.
107
206
  // Both branches are valid here. Premultiply flag is ignored by custom
108
207
  // shaders (they handle premultiplication themselves), but we still record
@@ -123,6 +222,7 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
123
222
  const drawCall = {
124
223
  mesh,
125
224
  customShader,
225
+ command,
126
226
  blendMode,
127
227
  texture: meshTexture,
128
228
  premultiplySample,
@@ -151,13 +251,8 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
151
251
  // Honor a pending clear with an empty pass so createColorAttachment
152
252
  // consumes the clear-state once.
153
253
  if (backend.clearRequested) {
154
- const encoder = device.createCommandEncoder();
155
- const pass = encoder.beginRenderPass({
156
- colorAttachments: [backend.createColorAttachment()],
157
- });
158
- backend.stats.renderPasses++;
159
- pass.end();
160
- backend.submit(encoder.finish());
254
+ backend._passCoordinator.acquirePass();
255
+ backend._passCoordinator.endPass();
161
256
  }
162
257
  this._resetFrame();
163
258
  return;
@@ -166,7 +261,7 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
166
261
  // buffers, so default offsets are independent of custom offsets).
167
262
  let defaultVertices = 0;
168
263
  let defaultIndices = 0;
169
- const customVertexCursors = new Map(); // running vertex count per shader
264
+ const customVertexCursors = new Map(); // running vertex count per material
170
265
  const customIndexCursors = new Map();
171
266
  for (let i = 0; i < this._drawCallCount; i++) {
172
267
  const dc = this._drawCalls[i];
@@ -192,6 +287,7 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
192
287
  // each custom-shader resource manages its own.
193
288
  const defaultDrawCalls = this._drawCallCount - this._totalCustomDraws();
194
289
  this._ensureUniformCapacity(defaultDrawCalls);
290
+ this._ensureInstancedUniformCapacity(this._drawCallCount);
195
291
  // Phase 3: pack default-path vertex/index/uniform data.
196
292
  const defaultUniformBytes = defaultDrawCalls * this._uniformAlignment;
197
293
  const defaultUniformData = defaultUniformBytes > 0 ? new ArrayBuffer(defaultUniformBytes) : null;
@@ -215,10 +311,10 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
215
311
  if (defaultUniformF32 !== null) {
216
312
  const offsetWords = (defaultUniformIndex * this._uniformAlignment) / Float32Array.BYTES_PER_ELEMENT;
217
313
  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;
314
+ defaultUniformF32[offsetWords + 0] = tint.r;
315
+ defaultUniformF32[offsetWords + 1] = tint.g;
316
+ defaultUniformF32[offsetWords + 2] = tint.b;
317
+ defaultUniformF32[offsetWords + 3] = tint.a;
222
318
  defaultUniformF32[offsetWords + 4] = dc.premultiplySample ? 1 : 0;
223
319
  defaultUniformF32[offsetWords + 5] = 0;
224
320
  defaultUniformF32[offsetWords + 6] = 0;
@@ -227,8 +323,8 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
227
323
  defaultUniformIndex++;
228
324
  }
229
325
  }
230
- // Phase 3b: pack custom-path vertex/index/uniform data per shader.
231
- for (const [shader, resources] of this._customShaders) {
326
+ // Phase 3b: pack custom-path vertex/index/uniform data per material.
327
+ for (const [material, resources] of this._customShaders) {
232
328
  if (resources.drawCount === 0) {
233
329
  continue;
234
330
  }
@@ -239,7 +335,7 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
239
335
  let drawCursor = 0;
240
336
  for (let i = 0; i < this._drawCallCount; i++) {
241
337
  const dc = this._drawCalls[i];
242
- if (dc.customShader !== shader)
338
+ if (dc.customShader !== material)
243
339
  continue;
244
340
  this._writeMeshVerticesIntoBuffer(dc.mesh, vWritten, resources.vertexFloatView, resources.vertexUintView);
245
341
  if (dc.mesh.indices !== null) {
@@ -251,16 +347,16 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
251
347
  }
252
348
  }
253
349
  // Write mesh-uniform slot (proj/trans/tint) with dynamic offset.
254
- this._writeCustomMeshUniform(shader, resources, drawCursor, dc.mesh, backend);
350
+ this._writeCustomMeshUniform(material, resources, drawCursor, dc.mesh, backend);
255
351
  vWritten += dc.vertexCount;
256
352
  iWritten += dc.indexCount;
257
353
  drawCursor++;
258
354
  }
259
355
  device.queue.writeBuffer(resources.vertexBuffer, 0, resources.vertexData, 0, resources.totalVertices * vertexStrideBytes);
260
356
  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);
357
+ // Build/refresh user uniform UBO from the material (re-built every frame
358
+ // so mutations to material.uniforms.X are picked up).
359
+ this._uploadUserUniforms(material, resources);
264
360
  }
265
361
  // Phase 4: single writeBuffer per resource for the default path.
266
362
  if (defaultVertices > 0) {
@@ -271,30 +367,66 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
271
367
  device.queue.writeBuffer(this._uniformBuffer, 0, defaultUniformData, 0, defaultUniformBytes);
272
368
  }
273
369
  // 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' });
276
- const pass = encoder.beginRenderPass({
277
- colorAttachments: [backend.createColorAttachment()],
278
- label: 'WebGpuMeshRenderer pass',
279
- });
280
- backend.stats.renderPasses++;
281
- if (scissor !== null) {
282
- pass.setScissorRect(scissor.x, scissor.y, scissor.width, scissor.height);
283
- }
370
+ // pipeline+bind groups between default and custom paths as needed. The
371
+ // coordinator owns the GPU pass (load/clear resolution, pass count and
372
+ // scissor are applied there) and ends + submits it below.
373
+ const pass = backend._passCoordinator.acquirePass().pass;
284
374
  const renderTargetFormat = backend.renderTargetFormat;
375
+ // A clip scope flushes the active renderer on push/pop, so every draw call
376
+ // in this batch shares one stencil state — read it once. While active, the
377
+ // coordinator's pass carries a depth/stencil attachment, so the default and
378
+ // static-batch pipelines must select their stencil-enabled variants. The
379
+ // custom path never reaches here under a clip (render() throws at collection
380
+ // time), so its pipelines stay stencil-free.
381
+ const stencil = backend._passCoordinator.stencilActive;
285
382
  let lastShader = null;
286
383
  let lastBlendMode = null;
287
384
  let lastFormat = null;
288
385
  let lastTexture = null;
289
386
  let defaultDrawCursor = 0;
387
+ let instancedDrawCursor = 0;
290
388
  const customDrawCursors = new Map();
291
389
  for (let i = 0; i < this._drawCallCount; i++) {
292
390
  const dc = this._drawCalls[i];
293
391
  if (dc.customShader === null) {
392
+ const batchLength = this._getStaticBatchLength(i);
393
+ if (batchLength >= 2) {
394
+ const needsPipeline = lastShader !== 'instanced' || dc.blendMode !== lastBlendMode || renderTargetFormat !== lastFormat;
395
+ if (needsPipeline) {
396
+ pass.setPipeline(this._getInstancedPipeline({ blendMode: dc.blendMode, format: renderTargetFormat, stencil }));
397
+ lastShader = 'instanced';
398
+ lastBlendMode = dc.blendMode;
399
+ lastFormat = renderTargetFormat;
400
+ lastTexture = null;
401
+ }
402
+ const maxNodeIndex = this._uploadInstancedNodeIndices(i, batchLength);
403
+ const storage = backend.getTransformStorageBuffer(maxNodeIndex + 1);
404
+ this._writeInstancedUniformSlot(instancedDrawCursor, backend, dc.premultiplySample);
405
+ pass.setBindGroup(0, this._getOrCreateInstancedTransformBindGroup(storage.buffer), [instancedDrawCursor * this._uniformAlignment]);
406
+ if (dc.texture !== lastTexture) {
407
+ lastTexture = dc.texture;
408
+ pass.setBindGroup(1, this._getTextureBindGroup(backend, dc.texture));
409
+ }
410
+ const staticGeometry = this._getOrCreateStaticGeometryEntry(dc.mesh);
411
+ const instanceNodeIndexBuffer = this._instancedNodeIndexBuffer;
412
+ if (instanceNodeIndexBuffer === null) {
413
+ throw new Error('Instanced node-index buffer must be initialized before drawing.');
414
+ }
415
+ pass.setVertexBuffer(0, staticGeometry.vertexBuffer);
416
+ pass.setVertexBuffer(1, instanceNodeIndexBuffer);
417
+ pass.setIndexBuffer(staticGeometry.indexBuffer, 'uint16');
418
+ pass.drawIndexed(staticGeometry.indexCount, batchLength);
419
+ backend.stats.batches++;
420
+ backend.stats.drawCalls++;
421
+ defaultDrawCursor += batchLength;
422
+ instancedDrawCursor++;
423
+ i += batchLength - 1;
424
+ continue;
425
+ }
294
426
  // ----- Default path -----
295
427
  const needsPipeline = lastShader !== 'default' || dc.blendMode !== lastBlendMode || renderTargetFormat !== lastFormat;
296
428
  if (needsPipeline) {
297
- pass.setPipeline(this._getPipeline({ blendMode: dc.blendMode, format: renderTargetFormat }));
429
+ pass.setPipeline(this._getPipeline({ blendMode: dc.blendMode, format: renderTargetFormat, stencil }));
298
430
  lastShader = 'default';
299
431
  lastBlendMode = dc.blendMode;
300
432
  lastFormat = renderTargetFormat;
@@ -319,7 +451,7 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
319
451
  // (Spector.js, Chrome DevTools' WebGPU panel) show meaningful
320
452
  // labels for the otherwise-anonymous mesh draws inside the
321
453
  // batched render pass.
322
- pass.pushDebugGroup('MeshShader (custom)');
454
+ pass.pushDebugGroup('MeshMaterial (custom)');
323
455
  if (needsPipeline) {
324
456
  pass.setPipeline(this._getOrCreateCustomPipeline(resources, dc.blendMode, renderTargetFormat));
325
457
  lastShader = dc.customShader;
@@ -333,7 +465,7 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
333
465
  pass.setBindGroup(0, resources.meshUniformBindGroup, [cursor * meshUniformAlignment]);
334
466
  if (dc.texture !== lastTexture) {
335
467
  lastTexture = dc.texture;
336
- pass.setBindGroup(1, this._getOrCreateCustomMeshTextureBindGroup(resources, backend, dc.texture));
468
+ pass.setBindGroup(1, this._getOrCreateMeshTextureBindGroup(resources, backend, dc.texture));
337
469
  }
338
470
  pass.setVertexBuffer(0, resources.vertexBuffer, dc.vertexByteOffset);
339
471
  pass.setIndexBuffer(resources.indexBuffer, 'uint16', dc.indexByteOffset);
@@ -344,8 +476,7 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
344
476
  backend.stats.batches++;
345
477
  backend.stats.drawCalls++;
346
478
  }
347
- pass.end();
348
- backend.submit(encoder.finish());
479
+ backend._passCoordinator.endPass();
349
480
  this._resetFrame();
350
481
  }
351
482
  destroy() {
@@ -360,16 +491,32 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
360
491
  if (typeof device.createRenderPipelineAsync !== 'function') {
361
492
  return;
362
493
  }
363
- const blendModes = [BlendModes.Normal, BlendModes.Additive, BlendModes.Subtract, BlendModes.Multiply, BlendModes.Screen];
494
+ const blendModes = [
495
+ BlendModes.Normal,
496
+ BlendModes.Additive,
497
+ BlendModes.Subtract,
498
+ BlendModes.Multiply,
499
+ BlendModes.Screen,
500
+ BlendModes.Darken,
501
+ BlendModes.Lighten,
502
+ ];
364
503
  const promises = [];
365
504
  for (const blendMode of blendModes) {
366
505
  for (const format of formats) {
367
- const key = `${blendMode}:${format}`;
506
+ // Prewarm only the no-clip variants; the stencil pipelines are created
507
+ // lazily on the first clipped draw (a rare path not worth the upfront
508
+ // compile cost for every blend-mode × format combination).
509
+ const key = meshPipelineCacheKey(blendMode, format, false);
368
510
  if (this._pipelines.has(key))
369
511
  continue;
370
512
  promises.push(device.createRenderPipelineAsync(this._buildPipelineDescriptor(blendMode, format)).then(pipeline => {
371
513
  this._pipelines.set(key, pipeline);
372
514
  }));
515
+ if (!this._instancedPipelines.has(key)) {
516
+ promises.push(device.createRenderPipelineAsync(this._buildInstancedPipelineDescriptor(blendMode, format)).then(pipeline => {
517
+ this._instancedPipelines.set(key, pipeline);
518
+ }));
519
+ }
373
520
  }
374
521
  }
375
522
  await Promise.all(promises);
@@ -380,6 +527,7 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
380
527
  }
381
528
  this._device = backend.device;
382
529
  this._shaderModule = this._device.createShaderModule({ code: meshShaderSource });
530
+ this._instancedShaderModule = this._device.createShaderModule({ code: instancedMeshShaderSource });
383
531
  this._uniformBindGroupLayout = this._device.createBindGroupLayout({
384
532
  entries: [
385
533
  {
@@ -406,27 +554,59 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
406
554
  this._pipelineLayout = this._device.createPipelineLayout({
407
555
  bindGroupLayouts: [this._uniformBindGroupLayout, this._textureBindGroupLayout],
408
556
  });
557
+ this._instancedTransformBindGroupLayout = this._device.createBindGroupLayout({
558
+ entries: [
559
+ {
560
+ binding: 0,
561
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
562
+ buffer: { type: 'uniform', hasDynamicOffset: true },
563
+ },
564
+ {
565
+ binding: 1,
566
+ visibility: GPUShaderStage.VERTEX,
567
+ buffer: { type: 'read-only-storage' },
568
+ },
569
+ ],
570
+ });
571
+ this._instancedPipelineLayout = this._device.createPipelineLayout({
572
+ bindGroupLayouts: [this._instancedTransformBindGroupLayout, this._textureBindGroupLayout],
573
+ });
409
574
  }
410
575
  onDisconnect() {
411
576
  this.flush();
412
577
  this._vertexBuffer?.destroy();
413
578
  this._indexBuffer?.destroy();
414
579
  this._uniformBuffer?.destroy();
580
+ this._instancedUniformBuffer?.destroy();
581
+ this._instancedNodeIndexBuffer?.destroy();
415
582
  this._pipelines.clear();
416
- this._textureBindGroups.clear();
583
+ this._instancedPipelines.clear();
584
+ this._textureBindGroups = new WeakMap();
585
+ for (const entry of this._staticGeometryCache.values()) {
586
+ entry.vertexBuffer.destroy();
587
+ entry.indexBuffer.destroy();
588
+ }
589
+ this._staticGeometryCache.clear();
417
590
  this._vertexBuffer = null;
418
591
  this._indexBuffer = null;
419
592
  this._uniformBuffer = null;
420
593
  this._uniformBindGroup = null;
594
+ this._instancedUniformBuffer = null;
595
+ this._instancedNodeIndexBuffer = null;
596
+ this._instancedTransformBindGroup = null;
597
+ this._instancedTransformStorageBuffer = null;
421
598
  this._pipelineLayout = null;
599
+ this._instancedPipelineLayout = null;
422
600
  this._textureBindGroupLayout = null;
423
601
  this._uniformBindGroupLayout = null;
602
+ this._instancedTransformBindGroupLayout = null;
424
603
  this._shaderModule = null;
425
- // Custom shaders are owned by user code (one MeshShader can be shared
604
+ this._instancedShaderModule = null;
605
+ // Custom materials are owned by user code (one MeshMaterial can be shared
426
606
  // across multiple Mesh instances). Their resources are released when the
427
- // user calls shader.destroy(), which fires our _onDispose callback. On
607
+ // user calls material.destroy(), which fires our _onDispose callback. On
428
608
  // backend disconnect we eagerly release everything to avoid GPU leaks
429
- // even if the user keeps the shader reference around.
609
+ // even if the user keeps the material reference around.
430
610
  for (const resources of this._customShaders.values()) {
431
611
  this._releaseCustomShaderResources(resources);
432
612
  }
@@ -437,6 +617,9 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
437
617
  this._vertexBufferCapacity = 0;
438
618
  this._indexBufferCapacity = 0;
439
619
  this._uniformBufferCapacity = 0;
620
+ this._instancedUniformBufferCapacity = 0;
621
+ this._instancedNodeIndexBufferCapacity = 0;
622
+ this._instancedNodeIndexData = new Uint32Array(0);
440
623
  }
441
624
  // ---------------------------------------------------------------------------
442
625
  // Default-path helpers
@@ -482,16 +665,16 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
482
665
  }
483
666
  }
484
667
  _getPipeline(key) {
485
- const cacheKey = `${key.blendMode}:${key.format}`;
668
+ const cacheKey = meshPipelineCacheKey(key.blendMode, key.format, key.stencil);
486
669
  let pipeline = this._pipelines.get(cacheKey);
487
670
  if (!pipeline) {
488
- pipeline = this._device.createRenderPipeline(this._buildPipelineDescriptor(key.blendMode, key.format));
671
+ pipeline = this._device.createRenderPipeline(this._buildPipelineDescriptor(key.blendMode, key.format, key.stencil));
489
672
  this._pipelines.set(cacheKey, pipeline);
490
673
  }
491
674
  return pipeline;
492
675
  }
493
- _buildPipelineDescriptor(blendMode, format) {
494
- return {
676
+ _buildPipelineDescriptor(blendMode, format, stencil = false) {
677
+ const descriptor = {
495
678
  layout: this._pipelineLayout,
496
679
  vertex: {
497
680
  module: this._shaderModule,
@@ -524,6 +707,10 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
524
707
  cullMode: 'none',
525
708
  },
526
709
  };
710
+ if (stencil) {
711
+ descriptor.depthStencil = stencilContentDepthStencilState();
712
+ }
713
+ return descriptor;
527
714
  }
528
715
  _getTextureBindGroup(backend, texture) {
529
716
  let group = this._textureBindGroups.get(texture);
@@ -540,6 +727,223 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
540
727
  }
541
728
  return group;
542
729
  }
730
+ _getStaticBatchLength(startIndex) {
731
+ const first = this._drawCalls[startIndex];
732
+ if (!this._isStaticBatchCandidate(first)) {
733
+ return 1;
734
+ }
735
+ let length = 1;
736
+ for (let i = startIndex + 1; i < this._drawCallCount; i++) {
737
+ const next = this._drawCalls[i];
738
+ if (!this._isSameStaticBatch(first, next)) {
739
+ break;
740
+ }
741
+ length++;
742
+ }
743
+ return length;
744
+ }
745
+ _isStaticBatchCandidate(drawCall) {
746
+ const command = drawCall.command;
747
+ return drawCall.customShader === null && command?.groupIndex !== undefined && drawCall.mesh.geometry?.usage === 'static';
748
+ }
749
+ _isSameStaticBatch(left, right) {
750
+ if (!this._isStaticBatchCandidate(left) || !this._isStaticBatchCandidate(right)) {
751
+ return false;
752
+ }
753
+ return (left.command.groupIndex === right.command.groupIndex &&
754
+ left.mesh.geometry === right.mesh.geometry &&
755
+ left.texture === right.texture &&
756
+ left.blendMode === right.blendMode &&
757
+ left.command.material.pipelineKey === right.command.material.pipelineKey &&
758
+ left.command.material.bindKey === right.command.material.bindKey);
759
+ }
760
+ _uploadInstancedNodeIndices(startIndex, batchLength) {
761
+ this._ensureInstancedNodeIndexCapacity(batchLength);
762
+ let maxNodeIndex = 0;
763
+ for (let i = 0; i < batchLength; i++) {
764
+ const nodeIndex = this._drawCalls[startIndex + i].command.nodeIndex >>> 0;
765
+ this._instancedNodeIndexData[i] = nodeIndex;
766
+ if (nodeIndex > maxNodeIndex) {
767
+ maxNodeIndex = nodeIndex;
768
+ }
769
+ }
770
+ this._device.queue.writeBuffer(this._instancedNodeIndexBuffer, 0, this._instancedNodeIndexData.buffer, this._instancedNodeIndexData.byteOffset, batchLength * Uint32Array.BYTES_PER_ELEMENT);
771
+ return maxNodeIndex;
772
+ }
773
+ _ensureInstancedNodeIndexCapacity(instanceCount) {
774
+ const requiredBytes = instanceCount * Uint32Array.BYTES_PER_ELEMENT;
775
+ if (this._instancedNodeIndexData.length < instanceCount) {
776
+ this._instancedNodeIndexData = new Uint32Array(Math.max(instanceCount, this._instancedNodeIndexData.length * 2 || 1));
777
+ }
778
+ if (requiredBytes > this._instancedNodeIndexBufferCapacity) {
779
+ this._instancedNodeIndexBuffer?.destroy();
780
+ this._instancedNodeIndexBufferCapacity = Math.max(requiredBytes, this._instancedNodeIndexBufferCapacity * 2 || Uint32Array.BYTES_PER_ELEMENT);
781
+ this._instancedNodeIndexBuffer = this._device.createBuffer({
782
+ size: this._instancedNodeIndexBufferCapacity,
783
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
784
+ });
785
+ }
786
+ }
787
+ _ensureInstancedUniformCapacity(drawCallCount) {
788
+ if (drawCallCount === 0) {
789
+ return;
790
+ }
791
+ const requiredBytes = drawCallCount * this._uniformAlignment;
792
+ if (requiredBytes > this._instancedUniformBufferCapacity) {
793
+ this._instancedUniformBuffer?.destroy();
794
+ this._instancedUniformBufferCapacity = Math.max(requiredBytes, this._instancedUniformBufferCapacity * 2 || this._uniformAlignment);
795
+ this._instancedUniformBuffer = this._device.createBuffer({
796
+ size: this._instancedUniformBufferCapacity,
797
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
798
+ });
799
+ this._instancedTransformBindGroup = null;
800
+ this._instancedTransformStorageBuffer = null;
801
+ }
802
+ }
803
+ _writeInstancedUniformSlot(slot, backend, premultiplySample) {
804
+ const data = this._instancedUniformScratch;
805
+ const projection = backend.view.getTransform();
806
+ data.fill(0);
807
+ data[0] = projection.a;
808
+ data[1] = projection.b;
809
+ data[4] = projection.c;
810
+ data[5] = projection.d;
811
+ data[8] = projection.x;
812
+ data[9] = projection.y;
813
+ data[10] = 1;
814
+ data[12] = premultiplySample ? 1 : 0;
815
+ this._device.queue.writeBuffer(this._instancedUniformBuffer, slot * this._uniformAlignment, data.buffer, data.byteOffset, transformUniformByteLength);
816
+ }
817
+ _getOrCreateInstancedTransformBindGroup(storageBuffer) {
818
+ if (this._instancedTransformBindGroup !== null && this._instancedTransformStorageBuffer === storageBuffer) {
819
+ return this._instancedTransformBindGroup;
820
+ }
821
+ this._instancedTransformStorageBuffer = storageBuffer;
822
+ this._instancedTransformBindGroup = this._device.createBindGroup({
823
+ layout: this._instancedTransformBindGroupLayout,
824
+ entries: [
825
+ {
826
+ binding: 0,
827
+ resource: {
828
+ buffer: this._instancedUniformBuffer,
829
+ size: transformUniformByteLength,
830
+ },
831
+ },
832
+ {
833
+ binding: 1,
834
+ resource: {
835
+ buffer: storageBuffer,
836
+ },
837
+ },
838
+ ],
839
+ });
840
+ return this._instancedTransformBindGroup;
841
+ }
842
+ _getInstancedPipeline(key) {
843
+ const cacheKey = meshPipelineCacheKey(key.blendMode, key.format, key.stencil);
844
+ let pipeline = this._instancedPipelines.get(cacheKey);
845
+ if (!pipeline) {
846
+ pipeline = this._device.createRenderPipeline(this._buildInstancedPipelineDescriptor(key.blendMode, key.format, key.stencil));
847
+ this._instancedPipelines.set(cacheKey, pipeline);
848
+ }
849
+ return pipeline;
850
+ }
851
+ _buildInstancedPipelineDescriptor(blendMode, format, stencil = false) {
852
+ const descriptor = {
853
+ layout: this._instancedPipelineLayout,
854
+ vertex: {
855
+ module: this._instancedShaderModule,
856
+ entryPoint: 'vertexMain',
857
+ buffers: [
858
+ {
859
+ arrayStride: vertexStrideBytes,
860
+ stepMode: 'vertex',
861
+ attributes: [
862
+ { shaderLocation: 0, offset: 0, format: 'float32x2' },
863
+ { shaderLocation: 1, offset: 8, format: 'float32x2' },
864
+ { shaderLocation: 2, offset: 16, format: 'unorm8x4' },
865
+ ],
866
+ },
867
+ {
868
+ arrayStride: Uint32Array.BYTES_PER_ELEMENT,
869
+ stepMode: 'instance',
870
+ attributes: [{ shaderLocation: 6, offset: 0, format: 'uint32' }],
871
+ },
872
+ ],
873
+ },
874
+ fragment: {
875
+ module: this._instancedShaderModule,
876
+ entryPoint: 'fragmentMain',
877
+ targets: [
878
+ {
879
+ format,
880
+ blend: getWebGpuBlendState(blendMode),
881
+ writeMask: GPUColorWrite.ALL,
882
+ },
883
+ ],
884
+ },
885
+ primitive: {
886
+ topology: 'triangle-list',
887
+ cullMode: 'none',
888
+ },
889
+ };
890
+ if (stencil) {
891
+ descriptor.depthStencil = stencilContentDepthStencilState();
892
+ }
893
+ return descriptor;
894
+ }
895
+ _getOrCreateStaticGeometryEntry(mesh) {
896
+ const geometry = mesh.geometry;
897
+ if (geometry?.usage !== 'static') {
898
+ throw new Error('Static mesh batching requires Geometry with usage="static".');
899
+ }
900
+ const existing = this._staticGeometryCache.get(geometry);
901
+ if (existing !== undefined) {
902
+ return existing;
903
+ }
904
+ const vertexData = new ArrayBuffer(mesh.vertexCount * vertexStrideBytes);
905
+ const vertexFloatView = new Float32Array(vertexData);
906
+ const vertexUintView = new Uint32Array(vertexData);
907
+ this._writeMeshVerticesIntoBuffer(mesh, 0, vertexFloatView, vertexUintView);
908
+ const indexData = new Uint16Array(mesh.indexCount);
909
+ if (mesh.indices !== null) {
910
+ indexData.set(mesh.indices, 0);
911
+ }
912
+ else {
913
+ for (let i = 0; i < mesh.indexCount; i++) {
914
+ indexData[i] = i;
915
+ }
916
+ }
917
+ const vertexBuffer = this._device.createBuffer({
918
+ size: vertexData.byteLength,
919
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
920
+ });
921
+ const indexBuffer = this._device.createBuffer({
922
+ size: indexData.byteLength,
923
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
924
+ });
925
+ this._device.queue.writeBuffer(vertexBuffer, 0, vertexData, 0, vertexData.byteLength);
926
+ this._device.queue.writeBuffer(indexBuffer, 0, indexData.buffer, indexData.byteOffset, indexData.byteLength);
927
+ const disposeListener = () => {
928
+ const entry = this._staticGeometryCache.get(geometry);
929
+ if (entry === undefined) {
930
+ return;
931
+ }
932
+ entry.vertexBuffer.destroy();
933
+ entry.indexBuffer.destroy();
934
+ this._staticGeometryCache.delete(geometry);
935
+ };
936
+ geometry._onDispose(disposeListener);
937
+ const created = {
938
+ geometry,
939
+ vertexBuffer,
940
+ indexBuffer,
941
+ indexCount: mesh.indexCount,
942
+ disposeListener,
943
+ };
944
+ this._staticGeometryCache.set(geometry, created);
945
+ return created;
946
+ }
543
947
  _ensureVertexCapacity(vertexCount) {
544
948
  const requiredBytes = vertexCount * vertexStrideBytes;
545
949
  if (requiredBytes > this._vertexData.byteLength) {
@@ -612,19 +1016,19 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
612
1016
  resources.totalIndices = 0;
613
1017
  }
614
1018
  }
615
- _getOrCreateCustomShaderResources(shader) {
616
- let resources = this._customShaders.get(shader);
1019
+ _getOrCreateCustomShaderResources(material) {
1020
+ let resources = this._customShaders.get(material);
617
1021
  if (resources !== undefined) {
618
1022
  return resources;
619
1023
  }
620
1024
  if (this._device === null) {
621
1025
  throw new Error('WebGpuMeshRenderer is not connected to a backend.');
622
1026
  }
623
- if (shader.wgsl === null) {
624
- throw new Error('MeshShader has no `wgsl` source; cannot render through the WebGPU backend.');
1027
+ if (material.shader.wgsl === null) {
1028
+ throw new Error('Mesh material shader has no `wgsl` source; cannot render through the WebGPU backend.');
625
1029
  }
626
1030
  const device = this._device;
627
- const shaderModule = device.createShaderModule({ code: shader.wgsl });
1031
+ const shaderModule = device.createShaderModule({ code: material.shader.wgsl });
628
1032
  const meshUniformLayout = device.createBindGroupLayout({
629
1033
  entries: [
630
1034
  {
@@ -640,7 +1044,7 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
640
1044
  { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
641
1045
  ],
642
1046
  });
643
- const userLayout = this._buildUserBindGroupLayout(device, shader);
1047
+ const userLayout = this._buildUserBindGroupLayout(device, material);
644
1048
  const pipelineLayout = device.createPipelineLayout({
645
1049
  bindGroupLayouts: [meshUniformLayout, meshTextureLayout, userLayout],
646
1050
  });
@@ -673,19 +1077,19 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
673
1077
  meshUniformBindGroup: null,
674
1078
  userUniformBuffer: null,
675
1079
  userUniformBufferCapacity: 0,
676
- meshTextureBindGroups: new Map(),
1080
+ meshTextureBindGroups: new WeakMap(),
677
1081
  sampler,
678
1082
  drawCount: 0,
679
1083
  totalVertices: 0,
680
1084
  totalIndices: 0,
681
1085
  };
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);
1086
+ this._customShaders.set(material, resources);
1087
+ // When the user calls material.destroy(), evict and release.
1088
+ material._onDispose(() => {
1089
+ const r = this._customShaders.get(material);
686
1090
  if (r !== undefined) {
687
1091
  this._releaseCustomShaderResources(r);
688
- this._customShaders.delete(shader);
1092
+ this._customShaders.delete(material);
689
1093
  }
690
1094
  });
691
1095
  return resources;
@@ -756,7 +1160,7 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
756
1160
  uintView[targetIndex + 4] = colors !== null ? colors[i] : 0xffffffff;
757
1161
  }
758
1162
  }
759
- _writeCustomMeshUniform(_shader, resources, drawCursor, mesh, backend) {
1163
+ _writeCustomMeshUniform(_material, resources, drawCursor, mesh, backend) {
760
1164
  // Layout: mat3x3 projection (48B) + mat3x3 translation (48B) + vec4 tint (16B) = 112B.
761
1165
  // WGSL mat3x3 stores 3 vec3 columns padded to vec4 alignment.
762
1166
  const slotBytes = meshUniformAlignment;
@@ -803,10 +1207,10 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
803
1207
  off += 12;
804
1208
  // tint (vec4)
805
1209
  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;
1210
+ data[off + 0] = tint.r;
1211
+ data[off + 1] = tint.g;
1212
+ data[off + 2] = tint.b;
1213
+ data[off + 3] = tint.a;
810
1214
  this._device.queue.writeBuffer(resources.meshUniformBuffer, drawCursor * slotBytes, data);
811
1215
  }
812
1216
  _getOrCreateCustomPipeline(resources, blendMode, format) {
@@ -850,7 +1254,7 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
850
1254
  }
851
1255
  return pipeline;
852
1256
  }
853
- _getOrCreateCustomMeshTextureBindGroup(resources, backend, texture) {
1257
+ _getOrCreateMeshTextureBindGroup(resources, backend, texture) {
854
1258
  let group = resources.meshTextureBindGroups.get(texture);
855
1259
  if (group === undefined) {
856
1260
  const binding = backend.getTextureBinding(texture);
@@ -865,10 +1269,8 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
865
1269
  }
866
1270
  return group;
867
1271
  }
868
- _buildUserBindGroupLayout(device, shader) {
1272
+ _buildUserBindGroupLayout(device, material) {
869
1273
  const entries = [];
870
- const userUniforms = shader.uniforms;
871
- Object.values(userUniforms).some(v => !isTextureUniform(v));
872
1274
  // Binding 0 always reserved for the user UBO (even if empty), so the
873
1275
  // bind-group layout is stable across user-uniform mutations.
874
1276
  entries.push({
@@ -876,15 +1278,12 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
876
1278
  visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
877
1279
  buffer: { type: 'uniform' },
878
1280
  });
1281
+ const textureBindings = collectTextureBindings(material);
1282
+ if (textureBindings.length > maxCustomTextureSlots) {
1283
+ throw new Error(`Mesh material requested more than ${maxCustomTextureSlots} user texture bindings.`);
1284
+ }
879
1285
  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
- }
1286
+ for (let t = 0; t < textureBindings.length; t++) {
888
1287
  entries.push({
889
1288
  binding: bindingIndex,
890
1289
  visibility: GPUShaderStage.FRAGMENT,
@@ -897,14 +1296,12 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
897
1296
  sampler: { type: 'filtering' },
898
1297
  });
899
1298
  bindingIndex++;
900
- textureCount++;
901
1299
  }
902
1300
  return device.createBindGroupLayout({ entries });
903
1301
  }
904
- _uploadUserUniforms(_shader, resources) {
1302
+ _uploadUserUniforms(material, resources) {
905
1303
  const device = this._device;
906
- const uniforms = _shader.uniforms;
907
- const scalarValues = Object.values(uniforms).filter(v => !isTextureUniform(v));
1304
+ const scalarValues = collectScalarUniforms(material);
908
1305
  // Always create a UBO (even if empty) since binding 0 of the user layout
909
1306
  // is fixed. Min size 16 bytes to satisfy WebGPU's minimum buffer size.
910
1307
  const slotCount = Math.max(scalarValues.length, 1);
@@ -942,16 +1339,13 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
942
1339
  }
943
1340
  device.queue.writeBuffer(resources.userUniformBuffer, 0, data);
944
1341
  }
945
- _buildUserBindGroup(backend, shader, resources) {
1342
+ _buildUserBindGroup(backend, material, resources) {
946
1343
  const device = this._device;
947
1344
  const entries = [];
948
1345
  entries.push({ binding: 0, resource: { buffer: resources.userUniformBuffer } });
949
1346
  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);
1347
+ for (const texture of collectTextureBindings(material)) {
1348
+ const binding = backend.getTextureBinding(texture);
955
1349
  entries.push({ binding: bindingIndex, resource: binding.view });
956
1350
  bindingIndex++;
957
1351
  entries.push({ binding: bindingIndex, resource: binding.sampler });
@@ -968,7 +1362,7 @@ class WebGpuMeshRenderer extends AbstractWebGpuRenderer {
968
1362
  resources.meshUniformBuffer?.destroy();
969
1363
  resources.userUniformBuffer?.destroy();
970
1364
  resources.pipelines.clear();
971
- resources.meshTextureBindGroups.clear();
1365
+ resources.meshTextureBindGroups = new WeakMap();
972
1366
  resources.vertexBuffer = null;
973
1367
  resources.indexBuffer = null;
974
1368
  resources.meshUniformBuffer = null;
@@ -989,6 +1383,34 @@ function isTextureUniform(value) {
989
1383
  !(value instanceof Int32Array) &&
990
1384
  !Array.isArray(value));
991
1385
  }
1386
+ /** Scalar/vector/matrix uniforms (texture values excluded) in declaration order. */
1387
+ function collectScalarUniforms(material) {
1388
+ const result = [];
1389
+ for (const value of Object.values(material.uniforms)) {
1390
+ if (!isTextureUniform(value)) {
1391
+ result.push(value);
1392
+ }
1393
+ }
1394
+ return result;
1395
+ }
1396
+ /**
1397
+ * Texture bindings claimed by the material, in a stable order: texture-valued
1398
+ * entries of `uniforms` first (declaration order), then the dedicated
1399
+ * `textures` map (declaration order). The WGSL source must declare its
1400
+ * `@group(2)` texture/sampler pairs in this same order.
1401
+ */
1402
+ function collectTextureBindings(material) {
1403
+ const result = [];
1404
+ for (const value of Object.values(material.uniforms)) {
1405
+ if (isTextureUniform(value)) {
1406
+ result.push(value);
1407
+ }
1408
+ }
1409
+ for (const texture of Object.values(material.textures)) {
1410
+ result.push(texture);
1411
+ }
1412
+ return result;
1413
+ }
992
1414
 
993
1415
  export { WebGpuMeshRenderer };
994
1416
  //# sourceMappingURL=WebGpuMeshRenderer.js.map