@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.
- package/CHANGELOG.md +99 -0
- package/README.md +34 -13
- package/dist/esm/animation/Tween.d.ts +20 -3
- package/dist/esm/animation/Tween.js +16 -8
- package/dist/esm/animation/Tween.js.map +1 -1
- package/dist/esm/animation/TweenManager.d.ts +16 -1
- package/dist/esm/animation/TweenManager.js +27 -2
- package/dist/esm/animation/TweenManager.js.map +1 -1
- package/dist/esm/audio/AudioAnalyser.d.ts +2 -0
- package/dist/esm/audio/AudioAnalyser.js +24 -9
- package/dist/esm/audio/AudioAnalyser.js.map +1 -1
- package/dist/esm/audio/AudioBus.d.ts +1 -0
- package/dist/esm/audio/AudioBus.js +8 -4
- package/dist/esm/audio/AudioBus.js.map +1 -1
- package/dist/esm/audio/AudioListener.d.ts +1 -0
- package/dist/esm/audio/AudioListener.js +7 -5
- package/dist/esm/audio/AudioListener.js.map +1 -1
- package/dist/esm/audio/AudioManager.d.ts +8 -2
- package/dist/esm/audio/AudioManager.js +14 -1
- package/dist/esm/audio/AudioManager.js.map +1 -1
- package/dist/esm/audio/BeatDetector.d.ts +2 -0
- package/dist/esm/audio/BeatDetector.js +27 -661
- package/dist/esm/audio/BeatDetector.js.map +1 -1
- package/dist/esm/audio/Music.d.ts +1 -0
- package/dist/esm/audio/Music.js +7 -3
- package/dist/esm/audio/Music.js.map +1 -1
- package/dist/esm/audio/OscillatorSound.d.ts +1 -0
- package/dist/esm/audio/OscillatorSound.js +7 -3
- package/dist/esm/audio/OscillatorSound.js.map +1 -1
- package/dist/esm/audio/Sound.d.ts +1 -0
- package/dist/esm/audio/Sound.js +7 -3
- package/dist/esm/audio/Sound.js.map +1 -1
- package/dist/esm/audio/audio-context.d.ts +2 -2
- package/dist/esm/audio/audio-context.js +4 -4
- package/dist/esm/audio/audio-context.js.map +1 -1
- package/dist/esm/audio/filters/ChorusFilter.d.ts +1 -0
- package/dist/esm/audio/filters/ChorusFilter.js +7 -3
- package/dist/esm/audio/filters/ChorusFilter.js.map +1 -1
- package/dist/esm/audio/filters/CompressorFilter.d.ts +1 -0
- package/dist/esm/audio/filters/CompressorFilter.js +7 -3
- package/dist/esm/audio/filters/CompressorFilter.js.map +1 -1
- package/dist/esm/audio/filters/DelayFilter.d.ts +1 -0
- package/dist/esm/audio/filters/DelayFilter.js +7 -3
- package/dist/esm/audio/filters/DelayFilter.js.map +1 -1
- package/dist/esm/audio/filters/EqualizerFilter.d.ts +1 -0
- package/dist/esm/audio/filters/EqualizerFilter.js +7 -3
- package/dist/esm/audio/filters/EqualizerFilter.js.map +1 -1
- package/dist/esm/audio/filters/GranularFilter.js +1 -86
- package/dist/esm/audio/filters/GranularFilter.js.map +1 -1
- package/dist/esm/audio/filters/HighpassFilter.d.ts +1 -0
- package/dist/esm/audio/filters/HighpassFilter.js +7 -3
- package/dist/esm/audio/filters/HighpassFilter.js.map +1 -1
- package/dist/esm/audio/filters/LowpassFilter.d.ts +1 -0
- package/dist/esm/audio/filters/LowpassFilter.js +7 -3
- package/dist/esm/audio/filters/LowpassFilter.js.map +1 -1
- package/dist/esm/audio/filters/PitchShiftFilter.js +1 -71
- package/dist/esm/audio/filters/PitchShiftFilter.js.map +1 -1
- package/dist/esm/audio/filters/ReverbFilter.d.ts +1 -0
- package/dist/esm/audio/filters/ReverbFilter.js +7 -3
- package/dist/esm/audio/filters/ReverbFilter.js.map +1 -1
- package/dist/esm/audio/filters/VocoderFilter.js +1 -89
- package/dist/esm/audio/filters/VocoderFilter.js.map +1 -1
- package/dist/esm/audio/filters/WorkletFilter.d.ts +2 -0
- package/dist/esm/audio/filters/WorkletFilter.js +10 -5
- package/dist/esm/audio/filters/WorkletFilter.js.map +1 -1
- package/dist/esm/audio/index.d.ts +15 -10
- package/dist/esm/audio/worklet/registerWorklet.d.ts +1 -1
- package/dist/esm/audio/worklet/registerWorklet.js +2 -2
- package/dist/esm/audio/worklet/registerWorklet.js.map +1 -1
- package/dist/esm/audio/worklets/beat-detector.worklet.d.ts +1 -0
- package/dist/esm/audio/worklets/beat-detector.worklet.js +647 -0
- package/dist/esm/audio/worklets/beat-detector.worklet.js.map +1 -0
- package/dist/esm/audio/worklets/granular.worklet.d.ts +1 -0
- package/dist/esm/audio/worklets/granular.worklet.js +89 -0
- package/dist/esm/audio/worklets/granular.worklet.js.map +1 -0
- package/dist/esm/audio/worklets/pitch-shift.worklet.d.ts +1 -0
- package/dist/esm/audio/worklets/pitch-shift.worklet.js +74 -0
- package/dist/esm/audio/worklets/pitch-shift.worklet.js.map +1 -0
- package/dist/esm/audio/worklets/vocoder.worklet.d.ts +1 -0
- package/dist/esm/audio/worklets/vocoder.worklet.js +92 -0
- package/dist/esm/audio/worklets/vocoder.worklet.js.map +1 -0
- package/dist/esm/core/Application.d.ts +86 -29
- package/dist/esm/core/Application.js +157 -47
- package/dist/esm/core/Application.js.map +1 -1
- package/dist/esm/core/Color.d.ts +0 -8
- package/dist/esm/core/Color.js +0 -24
- package/dist/esm/core/Color.js.map +1 -1
- package/dist/esm/core/Perf.d.ts +23 -0
- package/dist/esm/core/Perf.js +49 -0
- package/dist/esm/core/Perf.js.map +1 -0
- package/dist/esm/core/Scene.d.ts +40 -80
- package/dist/esm/core/Scene.js +63 -43
- package/dist/esm/core/Scene.js.map +1 -1
- package/dist/esm/core/SceneManager.d.ts +11 -25
- package/dist/esm/core/SceneManager.js +37 -100
- package/dist/esm/core/SceneManager.js.map +1 -1
- package/dist/esm/core/SceneNode.d.ts +28 -17
- package/dist/esm/core/SceneNode.js +66 -42
- package/dist/esm/core/SceneNode.js.map +1 -1
- package/dist/esm/core/Signal.d.ts +24 -28
- package/dist/esm/core/Signal.js +64 -50
- package/dist/esm/core/Signal.js.map +1 -1
- package/dist/esm/core/Timer.d.ts +1 -0
- package/dist/esm/core/Timer.js +3 -0
- package/dist/esm/core/Timer.js.map +1 -1
- package/dist/esm/core/capabilities.d.ts +2 -0
- package/dist/esm/core/capabilities.js +15 -0
- package/dist/esm/core/capabilities.js.map +1 -1
- package/dist/esm/core/index.d.ts +1 -0
- package/dist/esm/core/types.d.ts +1 -1
- package/dist/esm/core/utils.d.ts +12 -0
- package/dist/esm/core/utils.js +18 -1
- package/dist/esm/core/utils.js.map +1 -1
- package/dist/esm/debug/BoundingBoxesLayer.js +1 -1
- package/dist/esm/debug/BoundingBoxesLayer.js.map +1 -1
- package/dist/esm/debug/HitTestLayer.js +1 -1
- package/dist/esm/debug/HitTestLayer.js.map +1 -1
- package/dist/esm/debug/PerformanceLayer.js +1 -2
- package/dist/esm/debug/PerformanceLayer.js.map +1 -1
- package/dist/esm/debug/PointerStackLayer.js +1 -2
- package/dist/esm/debug/PointerStackLayer.js.map +1 -1
- package/dist/esm/debug/RenderPassInspectorLayer.js +1 -2
- package/dist/esm/debug/RenderPassInspectorLayer.js.map +1 -1
- package/dist/esm/index.js +37 -10
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/input/InputManager.d.ts +1 -0
- package/dist/esm/input/InputManager.js +30 -3
- package/dist/esm/input/InputManager.js.map +1 -1
- package/dist/esm/input/InteractionManager.d.ts +5 -2
- package/dist/esm/input/InteractionManager.js +29 -18
- package/dist/esm/input/InteractionManager.js.map +1 -1
- package/dist/esm/input/Pointer.js +3 -2
- package/dist/esm/input/Pointer.js.map +1 -1
- package/dist/esm/input/internal/interactionManagerRegistry.d.ts +9 -0
- package/dist/esm/input/internal/interactionManagerRegistry.js +10 -0
- package/dist/esm/input/internal/interactionManagerRegistry.js.map +1 -0
- package/dist/esm/math/AbstractVector.d.ts +1 -7
- package/dist/esm/math/AbstractVector.js +6 -20
- package/dist/esm/math/AbstractVector.js.map +1 -1
- package/dist/esm/math/Circle.js +0 -2
- package/dist/esm/math/Circle.js.map +1 -1
- package/dist/esm/math/Collision.d.ts +9 -3
- package/dist/esm/math/Ellipse.d.ts +2 -5
- package/dist/esm/math/Ellipse.js +10 -7
- package/dist/esm/math/Ellipse.js.map +1 -1
- package/dist/esm/math/ObservableVector.d.ts +1 -1
- package/dist/esm/math/ObservableVector.js +3 -3
- package/dist/esm/math/ObservableVector.js.map +1 -1
- package/dist/esm/math/Polygon.d.ts +0 -2
- package/dist/esm/math/Polygon.js +1 -9
- package/dist/esm/math/Polygon.js.map +1 -1
- package/dist/esm/math/Rectangle.js +0 -2
- package/dist/esm/math/Rectangle.js.map +1 -1
- package/dist/esm/math/collision-detection.d.ts +19 -4
- package/dist/esm/math/collision-detection.js +61 -4
- package/dist/esm/math/collision-detection.js.map +1 -1
- package/dist/esm/math/swept-collision.d.ts +16 -12
- package/dist/esm/math/swept-collision.js +109 -19
- package/dist/esm/math/swept-collision.js.map +1 -1
- package/dist/esm/particles/ParticleSystem.d.ts +8 -5
- package/dist/esm/particles/ParticleSystem.js +10 -6
- package/dist/esm/particles/ParticleSystem.js.map +1 -1
- package/dist/esm/particles/distributions/{Gradient.d.ts → ColorGradient.d.ts} +5 -5
- package/dist/esm/particles/distributions/{Gradient.js → ColorGradient.js} +5 -5
- package/dist/esm/particles/distributions/ColorGradient.js.map +1 -0
- package/dist/esm/particles/distributions/Distribution.d.ts +2 -2
- package/dist/esm/particles/distributions/index.d.ts +2 -2
- package/dist/esm/particles/gpu/ParticleGpuState.js +1 -1
- package/dist/esm/particles/index.d.ts +1 -0
- package/dist/esm/particles/modules/ColorOverLifetime.d.ts +3 -3
- package/dist/esm/particles/modules/ColorOverLifetime.js.map +1 -1
- package/dist/esm/particles/modules/ColorOverSpeed.d.ts +3 -3
- package/dist/esm/particles/modules/ColorOverSpeed.js.map +1 -1
- package/dist/esm/particles/modules/UpdateModule.d.ts +2 -2
- package/dist/esm/particles/modules/UpdateModule.js +1 -1
- package/dist/esm/particles/modules/WgslContribution.d.ts +2 -2
- package/dist/esm/rendering/CallbackRenderPass.d.ts +1 -0
- package/dist/esm/rendering/CallbackRenderPass.js +1 -0
- package/dist/esm/rendering/CallbackRenderPass.js.map +1 -1
- package/dist/esm/rendering/Camera.d.ts +33 -0
- package/dist/esm/rendering/Camera.js +38 -0
- package/dist/esm/rendering/Camera.js.map +1 -0
- package/dist/esm/rendering/Container.d.ts +7 -25
- package/dist/esm/rendering/Container.js +25 -87
- package/dist/esm/rendering/Container.js.map +1 -1
- package/dist/esm/rendering/Drawable.d.ts +8 -10
- package/dist/esm/rendering/Drawable.js +12 -20
- package/dist/esm/rendering/Drawable.js.map +1 -1
- package/dist/esm/rendering/RenderBackend.d.ts +19 -0
- package/dist/esm/rendering/RenderNode.d.ts +82 -11
- package/dist/esm/rendering/RenderNode.js +133 -163
- package/dist/esm/rendering/RenderNode.js.map +1 -1
- package/dist/esm/rendering/RenderPass.d.ts +1 -0
- package/dist/esm/rendering/RenderStats.d.ts +9 -0
- package/dist/esm/rendering/RenderStats.js +2 -0
- package/dist/esm/rendering/RenderStats.js.map +1 -1
- package/dist/esm/rendering/RenderTarget.d.ts +13 -0
- package/dist/esm/rendering/RenderTarget.js +13 -0
- package/dist/esm/rendering/RenderTarget.js.map +1 -1
- package/dist/esm/rendering/RenderTargetPass.js +17 -0
- package/dist/esm/rendering/RenderTargetPass.js.map +1 -1
- package/dist/esm/rendering/RendererRegistry.d.ts +1 -0
- package/dist/esm/rendering/RendererRegistry.js +1 -0
- package/dist/esm/rendering/RendererRegistry.js.map +1 -1
- package/dist/esm/rendering/RenderingContext.d.ts +87 -0
- package/dist/esm/rendering/RenderingContext.js +157 -0
- package/dist/esm/rendering/RenderingContext.js.map +1 -0
- package/dist/esm/rendering/TransformBuffer.d.ts +38 -0
- package/dist/esm/rendering/TransformBuffer.js +116 -0
- package/dist/esm/rendering/TransformBuffer.js.map +1 -0
- package/dist/esm/rendering/View.d.ts +23 -0
- package/dist/esm/rendering/View.js +42 -0
- package/dist/esm/rendering/View.js.map +1 -1
- package/dist/esm/rendering/filters/WebGpuShaderFilter.js +5 -12
- package/dist/esm/rendering/filters/WebGpuShaderFilter.js.map +1 -1
- package/dist/esm/rendering/geometry/Geometry.d.ts +40 -0
- package/dist/esm/rendering/geometry/Geometry.js +228 -0
- package/dist/esm/rendering/geometry/Geometry.js.map +1 -0
- package/dist/esm/rendering/geometry/GeometryAttribute.d.ts +32 -0
- package/dist/esm/rendering/geometry/QuadGeometry.d.ts +5 -0
- package/dist/esm/rendering/gradient/Gradient.d.ts +34 -0
- package/dist/esm/rendering/gradient/Gradient.js +114 -0
- package/dist/esm/rendering/gradient/Gradient.js.map +1 -0
- package/dist/esm/rendering/gradient/LinearGradient.d.ts +10 -0
- package/dist/esm/rendering/gradient/LinearGradient.js +26 -0
- package/dist/esm/rendering/gradient/LinearGradient.js.map +1 -0
- package/dist/esm/rendering/gradient/RadialGradient.d.ts +10 -0
- package/dist/esm/rendering/gradient/RadialGradient.js +25 -0
- package/dist/esm/rendering/gradient/RadialGradient.js.map +1 -0
- package/dist/esm/rendering/index.d.ts +103 -59
- package/dist/esm/rendering/material/Material.d.ts +114 -0
- package/dist/esm/rendering/material/Material.js +111 -0
- package/dist/esm/rendering/material/Material.js.map +1 -0
- package/dist/esm/rendering/material/MaterialKey.d.ts +18 -0
- package/dist/esm/rendering/material/MaterialKey.js +82 -0
- package/dist/esm/rendering/material/MaterialKey.js.map +1 -0
- package/dist/esm/rendering/material/MeshMaterial.d.ts +16 -0
- package/dist/esm/rendering/material/MeshMaterial.js +21 -0
- package/dist/esm/rendering/material/MeshMaterial.js.map +1 -0
- package/dist/esm/rendering/{mesh/MeshShader.d.ts → material/ShaderSource.d.ts} +30 -62
- package/dist/esm/rendering/{mesh/MeshShader.js → material/ShaderSource.js} +36 -62
- package/dist/esm/rendering/material/ShaderSource.js.map +1 -0
- package/dist/esm/rendering/material/SpriteMaterial.d.ts +15 -0
- package/dist/esm/rendering/material/SpriteMaterial.js +20 -0
- package/dist/esm/rendering/material/SpriteMaterial.js.map +1 -0
- package/dist/esm/rendering/mesh/Mesh.d.ts +29 -12
- package/dist/esm/rendering/mesh/Mesh.js +123 -4
- package/dist/esm/rendering/mesh/Mesh.js.map +1 -1
- package/dist/esm/rendering/pass/RenderPassCoordinator.d.ts +63 -0
- package/dist/esm/rendering/pass/RenderPassDescriptor.d.ts +48 -0
- package/dist/esm/rendering/pass/RenderPassDescriptor.js +16 -0
- package/dist/esm/rendering/pass/RenderPassDescriptor.js.map +1 -0
- package/dist/esm/rendering/plan/RenderCommand.d.ts +67 -0
- package/dist/esm/rendering/plan/RenderCommand.js +94 -0
- package/dist/esm/rendering/plan/RenderCommand.js.map +1 -0
- package/dist/esm/rendering/plan/RenderEffectExecutor.d.ts +10 -0
- package/dist/esm/rendering/plan/RenderEffectExecutor.js +159 -0
- package/dist/esm/rendering/plan/RenderEffectExecutor.js.map +1 -0
- package/dist/esm/rendering/plan/RenderPlan.d.ts +23 -0
- package/dist/esm/rendering/plan/RenderPlan.js +12 -0
- package/dist/esm/rendering/plan/RenderPlan.js.map +1 -0
- package/dist/esm/rendering/plan/RenderPlanBuilder.d.ts +31 -0
- package/dist/esm/rendering/plan/RenderPlanBuilder.js +242 -0
- package/dist/esm/rendering/plan/RenderPlanBuilder.js.map +1 -0
- package/dist/esm/rendering/plan/RenderPlanOptimizer.d.ts +10 -0
- package/dist/esm/rendering/plan/RenderPlanOptimizer.js +180 -0
- package/dist/esm/rendering/plan/RenderPlanOptimizer.js.map +1 -0
- package/dist/esm/rendering/plan/RenderPlanPlayer.d.ts +9 -0
- package/dist/esm/rendering/plan/RenderPlanPlayer.js +56 -0
- package/dist/esm/rendering/plan/RenderPlanPlayer.js.map +1 -0
- package/dist/esm/rendering/plan/RenderScope.d.ts +70 -0
- package/dist/esm/rendering/plan/RenderScope.js +16 -0
- package/dist/esm/rendering/plan/RenderScope.js.map +1 -0
- package/dist/esm/rendering/plan/playRenderTree.d.ts +4 -0
- package/dist/esm/rendering/plan/playRenderTree.js +19 -0
- package/dist/esm/rendering/plan/playRenderTree.js.map +1 -0
- package/dist/esm/rendering/shader/Shader.d.ts +1 -0
- package/dist/esm/rendering/shader/Shader.js +1 -0
- package/dist/esm/rendering/shader/Shader.js.map +1 -1
- package/dist/esm/rendering/shader/ShaderUniform.d.ts +1 -0
- package/dist/esm/rendering/shader/ShaderUniform.js +1 -0
- package/dist/esm/rendering/shader/ShaderUniform.js.map +1 -1
- package/dist/esm/rendering/sprite/Sprite.d.ts +25 -3
- package/dist/esm/rendering/sprite/Sprite.js +48 -15
- package/dist/esm/rendering/sprite/Sprite.js.map +1 -1
- package/dist/esm/rendering/sprite/spriteMaterialSources.d.ts +36 -0
- package/dist/esm/rendering/sprite/spriteMaterialSources.js +128 -0
- package/dist/esm/rendering/sprite/spriteMaterialSources.js.map +1 -0
- package/dist/esm/rendering/text/AbstractText.d.ts +36 -0
- package/dist/esm/rendering/text/AbstractText.js +49 -0
- package/dist/esm/rendering/text/AbstractText.js.map +1 -0
- package/dist/esm/rendering/text/BitmapText.d.ts +97 -0
- package/dist/esm/rendering/text/BitmapText.js +220 -0
- package/dist/esm/rendering/text/BitmapText.js.map +1 -0
- package/dist/esm/rendering/text/BmFont.d.ts +50 -0
- package/dist/esm/rendering/text/BmFont.js +24 -0
- package/dist/esm/rendering/text/BmFont.js.map +1 -0
- package/dist/esm/rendering/text/GlyphAtlas.d.ts +104 -0
- package/dist/esm/rendering/text/GlyphAtlas.js +347 -0
- package/dist/esm/rendering/text/GlyphAtlas.js.map +1 -0
- package/dist/esm/rendering/text/GlyphAtlasPool.d.ts +40 -0
- package/dist/esm/rendering/text/GlyphAtlasPool.js +67 -0
- package/dist/esm/rendering/text/GlyphAtlasPool.js.map +1 -0
- package/dist/esm/rendering/text/GlyphSdf.d.ts +92 -0
- package/dist/esm/rendering/text/GlyphSdf.js +220 -0
- package/dist/esm/rendering/text/GlyphSdf.js.map +1 -0
- package/dist/esm/rendering/text/HTMLText.d.ts +107 -0
- package/dist/esm/rendering/text/HTMLText.js +284 -0
- package/dist/esm/rendering/text/HTMLText.js.map +1 -0
- package/dist/esm/rendering/text/LayoutOptions.d.ts +30 -0
- package/dist/esm/rendering/text/Text.d.ts +89 -20
- package/dist/esm/rendering/text/Text.js +176 -101
- package/dist/esm/rendering/text/Text.js.map +1 -1
- package/dist/esm/rendering/text/TextLayout.d.ts +20 -8
- package/dist/esm/rendering/text/TextLayout.js +234 -25
- package/dist/esm/rendering/text/TextLayout.js.map +1 -1
- package/dist/esm/rendering/text/TextStyle.d.ts +154 -87
- package/dist/esm/rendering/text/TextStyle.js +257 -203
- package/dist/esm/rendering/text/TextStyle.js.map +1 -1
- package/dist/esm/rendering/text/types.d.ts +73 -13
- package/dist/esm/rendering/texture/DataTexture.d.ts +5 -0
- package/dist/esm/rendering/texture/DataTexture.js +7 -0
- package/dist/esm/rendering/texture/DataTexture.js.map +1 -1
- package/dist/esm/rendering/texture/Texture.d.ts +1 -0
- package/dist/esm/rendering/texture/Texture.js +1 -0
- package/dist/esm/rendering/texture/Texture.js.map +1 -1
- package/dist/esm/rendering/types.d.ts +3 -1
- package/dist/esm/rendering/types.js +2 -0
- package/dist/esm/rendering/types.js.map +1 -1
- package/dist/esm/rendering/video/Video.d.ts +5 -8
- package/dist/esm/rendering/video/Video.js +12 -13
- package/dist/esm/rendering/video/Video.js.map +1 -1
- package/dist/esm/rendering/webgl2/WebGl2Backend.d.ts +40 -0
- package/dist/esm/rendering/webgl2/WebGl2Backend.js +344 -27
- package/dist/esm/rendering/webgl2/WebGl2Backend.js.map +1 -1
- package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.d.ts +22 -2
- package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.js +404 -112
- package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.js.map +1 -1
- package/dist/esm/rendering/webgl2/WebGl2PassCoordinator.d.ts +57 -0
- package/dist/esm/rendering/webgl2/WebGl2PassCoordinator.js +79 -0
- package/dist/esm/rendering/webgl2/WebGl2PassCoordinator.js.map +1 -0
- package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.d.ts +12 -0
- package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.js +214 -58
- package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.js.map +1 -1
- package/dist/esm/rendering/webgl2/WebGl2StencilClipper.d.ts +34 -0
- package/dist/esm/rendering/webgl2/WebGl2StencilClipper.js +169 -0
- package/dist/esm/rendering/webgl2/WebGl2StencilClipper.js.map +1 -0
- package/dist/esm/rendering/webgl2/WebGl2TextRenderer.d.ts +56 -0
- package/dist/esm/rendering/webgl2/WebGl2TextRenderer.js +486 -0
- package/dist/esm/rendering/webgl2/WebGl2TextRenderer.js.map +1 -0
- package/dist/esm/rendering/webgl2/glsl/mesh.frag.js +1 -1
- package/dist/esm/rendering/webgl2/glsl/mesh.vert.js +1 -1
- package/dist/esm/rendering/webgl2/glsl/stencil-clip.frag.js +4 -0
- package/dist/esm/rendering/webgl2/glsl/stencil-clip.frag.js.map +1 -0
- package/dist/esm/rendering/webgl2/glsl/stencil-clip.vert.js +4 -0
- package/dist/esm/rendering/webgl2/glsl/stencil-clip.vert.js.map +1 -0
- package/dist/esm/rendering/webgl2/glsl/text-color.frag.js +4 -0
- package/dist/esm/rendering/webgl2/glsl/text-color.frag.js.map +1 -0
- package/dist/esm/rendering/webgl2/glsl/text-msdf.frag.js +4 -0
- package/dist/esm/rendering/webgl2/glsl/text-msdf.frag.js.map +1 -0
- package/dist/esm/rendering/webgl2/glsl/text-sdf.frag.js +4 -0
- package/dist/esm/rendering/webgl2/glsl/text-sdf.frag.js.map +1 -0
- package/dist/esm/rendering/webgl2/glsl/text.vert.js +4 -0
- package/dist/esm/rendering/webgl2/glsl/text.vert.js.map +1 -0
- package/dist/esm/rendering/webgpu/WebGpuBackend.d.ts +50 -0
- package/dist/esm/rendering/webgpu/WebGpuBackend.js +151 -27
- package/dist/esm/rendering/webgpu/WebGpuBackend.js.map +1 -1
- package/dist/esm/rendering/webgpu/WebGpuBlendState.js +26 -0
- package/dist/esm/rendering/webgpu/WebGpuBlendState.js.map +1 -1
- package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js +22 -17
- package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js.map +1 -1
- package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.d.ts +26 -2
- package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.js +511 -89
- package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.js.map +1 -1
- package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js +13 -17
- package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js.map +1 -1
- package/dist/esm/rendering/webgpu/WebGpuPassCoordinator.d.ts +141 -0
- package/dist/esm/rendering/webgpu/WebGpuPassCoordinator.js +270 -0
- package/dist/esm/rendering/webgpu/WebGpuPassCoordinator.js.map +1 -0
- package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.d.ts +16 -0
- package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.js +344 -27
- package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.js.map +1 -1
- package/dist/esm/rendering/webgpu/WebGpuStencilClipper.d.ts +57 -0
- package/dist/esm/rendering/webgpu/WebGpuStencilClipper.js +257 -0
- package/dist/esm/rendering/webgpu/WebGpuStencilClipper.js.map +1 -0
- package/dist/esm/rendering/webgpu/WebGpuStencilState.d.ts +14 -0
- package/dist/esm/rendering/webgpu/WebGpuStencilState.js +36 -0
- package/dist/esm/rendering/webgpu/WebGpuStencilState.js.map +1 -0
- package/dist/esm/rendering/webgpu/WebGpuTextRenderer.d.ts +70 -0
- package/dist/esm/rendering/webgpu/WebGpuTextRenderer.js +775 -0
- package/dist/esm/rendering/webgpu/WebGpuTextRenderer.js.map +1 -0
- package/dist/esm/rendering/webgpu/WebGpuTransformStorage.d.ts +16 -0
- package/dist/esm/rendering/webgpu/WebGpuTransformStorage.js +57 -0
- package/dist/esm/rendering/webgpu/WebGpuTransformStorage.js.map +1 -0
- package/dist/esm/rendering/webgpu/compute/WebGpuComputePipeline.js +96 -0
- package/dist/esm/rendering/webgpu/compute/WebGpuComputePipeline.js.map +1 -0
- package/dist/esm/rendering/webgpu/compute/WebGpuStorageBuffer.js +68 -0
- package/dist/esm/rendering/webgpu/compute/WebGpuStorageBuffer.js.map +1 -0
- package/dist/esm/resources/Asset.d.ts +23 -0
- package/dist/esm/resources/Asset.js +23 -0
- package/dist/esm/resources/Asset.js.map +1 -0
- package/dist/esm/resources/AssetDefinitions.d.ts +137 -0
- package/dist/esm/resources/Assets.d.ts +35 -0
- package/dist/esm/resources/Assets.js +32 -0
- package/dist/esm/resources/Assets.js.map +1 -0
- package/dist/esm/resources/IndexedDbDatabase.js +17 -1
- package/dist/esm/resources/IndexedDbDatabase.js.map +1 -1
- package/dist/esm/resources/IndexedDbStore.js +17 -1
- package/dist/esm/resources/IndexedDbStore.js.map +1 -1
- package/dist/esm/resources/JsonStore.d.ts +18 -0
- package/dist/esm/resources/JsonStore.js +62 -0
- package/dist/esm/resources/JsonStore.js.map +1 -0
- package/dist/esm/resources/Loader.d.ts +244 -18
- package/dist/esm/resources/Loader.js +456 -50
- package/dist/esm/resources/Loader.js.map +1 -1
- package/dist/esm/resources/LoadingQueue.d.ts +28 -0
- package/dist/esm/resources/LoadingQueue.js +59 -0
- package/dist/esm/resources/LoadingQueue.js.map +1 -0
- package/dist/esm/resources/factories/BmFontFactory.d.ts +25 -0
- package/dist/esm/resources/factories/BmFontFactory.js +96 -0
- package/dist/esm/resources/factories/BmFontFactory.js.map +1 -0
- package/dist/esm/resources/factories/CsvFactory.d.ts +35 -0
- package/dist/esm/resources/factories/CsvFactory.js +87 -0
- package/dist/esm/resources/factories/CsvFactory.js.map +1 -0
- package/dist/esm/resources/factories/ImageFactory.d.ts +14 -8
- package/dist/esm/resources/factories/ImageFactory.js +13 -6
- package/dist/esm/resources/factories/ImageFactory.js.map +1 -1
- package/dist/esm/resources/factories/MusicFactory.d.ts +8 -2
- package/dist/esm/resources/factories/MusicFactory.js +25 -14
- package/dist/esm/resources/factories/MusicFactory.js.map +1 -1
- package/dist/esm/resources/factories/SoundFactory.d.ts +2 -2
- package/dist/esm/resources/factories/SoundFactory.js.map +1 -1
- package/dist/esm/resources/factories/SubtitleFactory.d.ts +28 -0
- package/dist/esm/resources/factories/SubtitleFactory.js +203 -0
- package/dist/esm/resources/factories/SubtitleFactory.js.map +1 -0
- package/dist/esm/resources/factories/SvgFactory.d.ts +18 -1
- package/dist/esm/resources/factories/SvgFactory.js +21 -2
- package/dist/esm/resources/factories/SvgFactory.js.map +1 -1
- package/dist/esm/resources/factories/TextureFactory.d.ts +4 -4
- package/dist/esm/resources/factories/TextureFactory.js +8 -4
- package/dist/esm/resources/factories/TextureFactory.js.map +1 -1
- package/dist/esm/resources/factories/VideoFactory.d.ts +8 -2
- package/dist/esm/resources/factories/VideoFactory.js +27 -20
- package/dist/esm/resources/factories/VideoFactory.js.map +1 -1
- package/dist/esm/resources/factories/XmlFactory.d.ts +24 -0
- package/dist/esm/resources/factories/XmlFactory.js +37 -0
- package/dist/esm/resources/factories/XmlFactory.js.map +1 -0
- package/dist/esm/resources/index.d.ts +9 -1
- package/dist/esm/resources/tokens.d.ts +49 -3
- package/dist/esm/resources/tokens.js +50 -4
- package/dist/esm/resources/tokens.js.map +1 -1
- package/dist/exo.esm.js +15382 -8078
- package/dist/exo.esm.js.map +1 -1
- package/package.json +34 -16
- package/dist/esm/input/interaction-hooks.d.ts +0 -34
- package/dist/esm/input/interaction-hooks.js +0 -35
- package/dist/esm/input/interaction-hooks.js.map +0 -1
- package/dist/esm/particles/distributions/Gradient.js.map +0 -1
- package/dist/esm/rendering/mesh/MeshShader.js.map +0 -1
- package/dist/esm/rendering/text/DynamicGlyphAtlas.d.ts +0 -33
- package/dist/esm/rendering/text/DynamicGlyphAtlas.js +0 -134
- package/dist/esm/rendering/text/DynamicGlyphAtlas.js.map +0 -1
- package/dist/esm/rendering/text/atlas-singleton.d.ts +0 -7
- package/dist/esm/rendering/text/atlas-singleton.js +0 -17
- package/dist/esm/rendering/text/atlas-singleton.js.map +0 -1
- package/dist/esm/resources/factories/VttFactory.d.ts +0 -24
- package/dist/esm/resources/factories/VttFactory.js +0 -158
- package/dist/esm/resources/factories/VttFactory.js.map +0 -1
- package/dist/esm/vendor/webgl-debug.js +0 -1160
- 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
|
-
|
|
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.
|
|
96
|
-
if (customShader !== null && customShader.wgsl === null) {
|
|
97
|
-
throw new Error('
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
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
|
|
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.
|
|
219
|
-
defaultUniformF32[offsetWords + 1] = tint.
|
|
220
|
-
defaultUniformF32[offsetWords + 2] = tint.
|
|
221
|
-
defaultUniformF32[offsetWords + 3] = tint.
|
|
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
|
|
231
|
-
for (const [
|
|
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 !==
|
|
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(
|
|
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
|
|
262
|
-
//
|
|
263
|
-
this._uploadUserUniforms(
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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('
|
|
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.
|
|
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
|
-
|
|
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 = [
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
616
|
-
let resources = this._customShaders.get(
|
|
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('
|
|
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,
|
|
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
|
|
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(
|
|
683
|
-
// When the user calls
|
|
684
|
-
|
|
685
|
-
const r = this._customShaders.get(
|
|
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(
|
|
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(
|
|
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.
|
|
807
|
-
data[off + 1] = tint.
|
|
808
|
-
data[off + 2] = tint.
|
|
809
|
-
data[off + 3] = tint.
|
|
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
|
-
|
|
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,
|
|
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
|
|
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(
|
|
1302
|
+
_uploadUserUniforms(material, resources) {
|
|
905
1303
|
const device = this._device;
|
|
906
|
-
const
|
|
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,
|
|
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
|
|
951
|
-
|
|
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
|
|
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
|