@codexo/exojs-particles 0.15.3 → 0.16.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 (211) hide show
  1. package/README.md +21 -43
  2. package/dist/esm/ParticleStorage.d.ts +188 -0
  3. package/dist/esm/ParticleStorage.d.ts.map +1 -0
  4. package/dist/esm/ParticleStorage.js +208 -0
  5. package/dist/esm/ParticleStorage.js.map +1 -0
  6. package/dist/esm/ParticleSystem.d.ts +189 -60
  7. package/dist/esm/ParticleSystem.d.ts.map +1 -0
  8. package/dist/esm/ParticleSystem.js +660 -560
  9. package/dist/esm/ParticleSystem.js.map +1 -1
  10. package/dist/esm/distributions/BoxArea.d.ts +1 -0
  11. package/dist/esm/distributions/BoxArea.d.ts.map +1 -0
  12. package/dist/esm/distributions/BoxArea.js +38 -44
  13. package/dist/esm/distributions/BoxArea.js.map +1 -1
  14. package/dist/esm/distributions/CircleArea.d.ts +1 -0
  15. package/dist/esm/distributions/CircleArea.d.ts.map +1 -0
  16. package/dist/esm/distributions/CircleArea.js +30 -28
  17. package/dist/esm/distributions/CircleArea.js.map +1 -1
  18. package/dist/esm/distributions/ColorGradient.d.ts +3 -2
  19. package/dist/esm/distributions/ColorGradient.d.ts.map +1 -0
  20. package/dist/esm/distributions/ColorGradient.js +70 -75
  21. package/dist/esm/distributions/ColorGradient.js.map +1 -1
  22. package/dist/esm/distributions/ConeDirection.d.ts +2 -1
  23. package/dist/esm/distributions/ConeDirection.d.ts.map +1 -0
  24. package/dist/esm/distributions/ConeDirection.js +41 -39
  25. package/dist/esm/distributions/ConeDirection.js.map +1 -1
  26. package/dist/esm/distributions/Constant.d.ts +1 -0
  27. package/dist/esm/distributions/Constant.d.ts.map +1 -0
  28. package/dist/esm/distributions/Constant.js +32 -32
  29. package/dist/esm/distributions/Constant.js.map +1 -1
  30. package/dist/esm/distributions/Curve.d.ts +2 -1
  31. package/dist/esm/distributions/Curve.d.ts.map +1 -0
  32. package/dist/esm/distributions/Curve.js +46 -55
  33. package/dist/esm/distributions/Curve.js.map +1 -1
  34. package/dist/esm/distributions/Distribution.d.ts +4 -3
  35. package/dist/esm/distributions/Distribution.d.ts.map +1 -0
  36. package/dist/esm/distributions/LineSegment.d.ts +1 -0
  37. package/dist/esm/distributions/LineSegment.d.ts.map +1 -0
  38. package/dist/esm/distributions/LineSegment.js +25 -23
  39. package/dist/esm/distributions/LineSegment.js.map +1 -1
  40. package/dist/esm/distributions/Range.d.ts +1 -0
  41. package/dist/esm/distributions/Range.d.ts.map +1 -0
  42. package/dist/esm/distributions/Range.js +18 -16
  43. package/dist/esm/distributions/Range.js.map +1 -1
  44. package/dist/esm/distributions/VectorRange.d.ts +1 -0
  45. package/dist/esm/distributions/VectorRange.d.ts.map +1 -0
  46. package/dist/esm/distributions/VectorRange.js +29 -27
  47. package/dist/esm/distributions/VectorRange.js.map +1 -1
  48. package/dist/esm/distributions/index.d.ts +1 -0
  49. package/dist/esm/distributions/index.d.ts.map +1 -0
  50. package/dist/esm/gpu/ParticleGpuState.d.ts +143 -18
  51. package/dist/esm/gpu/ParticleGpuState.d.ts.map +1 -0
  52. package/dist/esm/gpu/ParticleGpuState.js +655 -497
  53. package/dist/esm/gpu/ParticleGpuState.js.map +1 -1
  54. package/dist/esm/gpu/shaders/particle-simulate.wgsl.js +6 -0
  55. package/dist/esm/gpu/shaders/particle-simulate.wgsl.js.map +1 -0
  56. package/dist/esm/index.d.ts +1 -0
  57. package/dist/esm/index.d.ts.map +1 -0
  58. package/dist/esm/index.js +39 -32
  59. package/dist/esm/modules/AlphaFadeOverLifetime.d.ts +4 -3
  60. package/dist/esm/modules/AlphaFadeOverLifetime.d.ts.map +1 -0
  61. package/dist/esm/modules/AlphaFadeOverLifetime.js +68 -55
  62. package/dist/esm/modules/AlphaFadeOverLifetime.js.map +1 -1
  63. package/dist/esm/modules/ApplyForce.d.ts +4 -3
  64. package/dist/esm/modules/ApplyForce.d.ts.map +1 -0
  65. package/dist/esm/modules/ApplyForce.js +49 -43
  66. package/dist/esm/modules/ApplyForce.js.map +1 -1
  67. package/dist/esm/modules/AttractToPoint.d.ts +4 -3
  68. package/dist/esm/modules/AttractToPoint.d.ts.map +1 -0
  69. package/dist/esm/modules/AttractToPoint.js +73 -62
  70. package/dist/esm/modules/AttractToPoint.js.map +1 -1
  71. package/dist/esm/modules/BurstSpawn.d.ts +6 -18
  72. package/dist/esm/modules/BurstSpawn.d.ts.map +1 -0
  73. package/dist/esm/modules/BurstSpawn.js +56 -91
  74. package/dist/esm/modules/BurstSpawn.js.map +1 -1
  75. package/dist/esm/modules/ColorOverLifetime.d.ts +4 -3
  76. package/dist/esm/modules/ColorOverLifetime.d.ts.map +1 -0
  77. package/dist/esm/modules/ColorOverLifetime.js +65 -53
  78. package/dist/esm/modules/ColorOverLifetime.js.map +1 -1
  79. package/dist/esm/modules/ColorOverSpeed.d.ts +5 -4
  80. package/dist/esm/modules/ColorOverSpeed.d.ts.map +1 -0
  81. package/dist/esm/modules/ColorOverSpeed.js +90 -75
  82. package/dist/esm/modules/ColorOverSpeed.js.map +1 -1
  83. package/dist/esm/modules/DeathModule.d.ts +26 -10
  84. package/dist/esm/modules/DeathModule.d.ts.map +1 -0
  85. package/dist/esm/modules/DeathModule.js +38 -22
  86. package/dist/esm/modules/DeathModule.js.map +1 -1
  87. package/dist/esm/modules/Drag.d.ts +3 -2
  88. package/dist/esm/modules/Drag.d.ts.map +1 -0
  89. package/dist/esm/modules/Drag.js +43 -37
  90. package/dist/esm/modules/Drag.js.map +1 -1
  91. package/dist/esm/modules/OrbitalForce.d.ts +4 -3
  92. package/dist/esm/modules/OrbitalForce.d.ts.map +1 -0
  93. package/dist/esm/modules/OrbitalForce.js +70 -58
  94. package/dist/esm/modules/OrbitalForce.js.map +1 -1
  95. package/dist/esm/modules/RateSpawn.d.ts +7 -22
  96. package/dist/esm/modules/RateSpawn.d.ts.map +1 -0
  97. package/dist/esm/modules/RateSpawn.js +39 -71
  98. package/dist/esm/modules/RateSpawn.js.map +1 -1
  99. package/dist/esm/modules/RepelFromPoint.d.ts +4 -3
  100. package/dist/esm/modules/RepelFromPoint.d.ts.map +1 -0
  101. package/dist/esm/modules/RepelFromPoint.js +72 -62
  102. package/dist/esm/modules/RepelFromPoint.js.map +1 -1
  103. package/dist/esm/modules/RotateOverLifetime.d.ts +3 -2
  104. package/dist/esm/modules/RotateOverLifetime.d.ts.map +1 -0
  105. package/dist/esm/modules/RotateOverLifetime.js +40 -36
  106. package/dist/esm/modules/RotateOverLifetime.js.map +1 -1
  107. package/dist/esm/modules/ScaleOverLifetime.d.ts +5 -4
  108. package/dist/esm/modules/ScaleOverLifetime.d.ts.map +1 -0
  109. package/dist/esm/modules/ScaleOverLifetime.js +60 -50
  110. package/dist/esm/modules/ScaleOverLifetime.js.map +1 -1
  111. package/dist/esm/modules/SpawnModule.d.ts +15 -12
  112. package/dist/esm/modules/SpawnModule.d.ts.map +1 -0
  113. package/dist/esm/modules/SpawnModule.js +32 -28
  114. package/dist/esm/modules/SpawnModule.js.map +1 -1
  115. package/dist/esm/modules/SpawnOnDeath.d.ts +13 -9
  116. package/dist/esm/modules/SpawnOnDeath.d.ts.map +1 -0
  117. package/dist/esm/modules/SpawnOnDeath.js +41 -43
  118. package/dist/esm/modules/SpawnOnDeath.js.map +1 -1
  119. package/dist/esm/modules/Turbulence.d.ts +4 -3
  120. package/dist/esm/modules/Turbulence.d.ts.map +1 -0
  121. package/dist/esm/modules/Turbulence.js +101 -89
  122. package/dist/esm/modules/Turbulence.js.map +1 -1
  123. package/dist/esm/modules/UpdateModule.d.ts +21 -13
  124. package/dist/esm/modules/UpdateModule.d.ts.map +1 -0
  125. package/dist/esm/modules/UpdateModule.js +65 -63
  126. package/dist/esm/modules/UpdateModule.js.map +1 -1
  127. package/dist/esm/modules/VelocityOverLifetime.d.ts +7 -6
  128. package/dist/esm/modules/VelocityOverLifetime.d.ts.map +1 -0
  129. package/dist/esm/modules/VelocityOverLifetime.js +75 -73
  130. package/dist/esm/modules/VelocityOverLifetime.js.map +1 -1
  131. package/dist/esm/modules/WgslContribution.d.ts +16 -15
  132. package/dist/esm/modules/WgslContribution.d.ts.map +1 -0
  133. package/dist/esm/modules/WgslContribution.js +37 -29
  134. package/dist/esm/modules/WgslContribution.js.map +1 -1
  135. package/dist/esm/modules/index.d.ts +3 -1
  136. package/dist/esm/modules/index.d.ts.map +1 -0
  137. package/dist/esm/modules/spawnFields.d.ts +32 -0
  138. package/dist/esm/modules/spawnFields.d.ts.map +1 -0
  139. package/dist/esm/modules/spawnFields.js +31 -0
  140. package/dist/esm/modules/spawnFields.js.map +1 -0
  141. package/dist/esm/particlesBuildInfo.d.ts +1 -0
  142. package/dist/esm/particlesBuildInfo.d.ts.map +1 -0
  143. package/dist/esm/particlesBuildInfo.js +6 -4
  144. package/dist/esm/particlesBuildInfo.js.map +1 -1
  145. package/dist/esm/particlesExtension.d.ts +13 -5
  146. package/dist/esm/particlesExtension.d.ts.map +1 -0
  147. package/dist/esm/particlesExtension.js +38 -42
  148. package/dist/esm/particlesExtension.js.map +1 -1
  149. package/dist/esm/public.d.ts +11 -0
  150. package/dist/esm/public.d.ts.map +1 -0
  151. package/dist/esm/renderModes/MeshParticles.d.ts +128 -0
  152. package/dist/esm/renderModes/MeshParticles.d.ts.map +1 -0
  153. package/dist/esm/renderModes/MeshParticles.js +263 -0
  154. package/dist/esm/renderModes/MeshParticles.js.map +1 -0
  155. package/dist/esm/renderModes/ParticleBufferLayout.d.ts +59 -0
  156. package/dist/esm/renderModes/ParticleBufferLayout.d.ts.map +1 -0
  157. package/dist/esm/renderModes/ParticleBufferLayout.js +99 -0
  158. package/dist/esm/renderModes/ParticleBufferLayout.js.map +1 -0
  159. package/dist/esm/renderModes/ParticleInstanceWriter.d.ts +58 -0
  160. package/dist/esm/renderModes/ParticleInstanceWriter.d.ts.map +1 -0
  161. package/dist/esm/renderModes/ParticleInstanceWriter.js +173 -0
  162. package/dist/esm/renderModes/ParticleInstanceWriter.js.map +1 -0
  163. package/dist/esm/renderModes/ParticleMaterial.d.ts +18 -0
  164. package/dist/esm/renderModes/ParticleMaterial.d.ts.map +1 -0
  165. package/dist/esm/renderModes/ParticleMaterial.js +24 -0
  166. package/dist/esm/renderModes/ParticleMaterial.js.map +1 -0
  167. package/dist/esm/renderModes/ParticleRenderMode.d.ts +114 -0
  168. package/dist/esm/renderModes/ParticleRenderMode.d.ts.map +1 -0
  169. package/dist/esm/renderModes/ParticleRenderMode.js +99 -0
  170. package/dist/esm/renderModes/ParticleRenderMode.js.map +1 -0
  171. package/dist/esm/renderModes/QuadParticles.d.ts +55 -0
  172. package/dist/esm/renderModes/QuadParticles.d.ts.map +1 -0
  173. package/dist/esm/renderModes/QuadParticles.js +95 -0
  174. package/dist/esm/renderModes/QuadParticles.js.map +1 -0
  175. package/dist/esm/renderModes/RibbonParticles.d.ts +96 -0
  176. package/dist/esm/renderModes/RibbonParticles.d.ts.map +1 -0
  177. package/dist/esm/renderModes/RibbonParticles.js +202 -0
  178. package/dist/esm/renderModes/RibbonParticles.js.map +1 -0
  179. package/dist/esm/renderModes/shaders/mesh-particles.wgsl.js +6 -0
  180. package/dist/esm/renderModes/shaders/mesh-particles.wgsl.js.map +1 -0
  181. package/dist/esm/renderModes/shaders/mesh.vert.js +6 -0
  182. package/dist/esm/renderModes/shaders/mesh.vert.js.map +1 -0
  183. package/dist/esm/renderModes/shaders/quad-particles.wgsl.js +6 -0
  184. package/dist/esm/renderModes/shaders/quad-particles.wgsl.js.map +1 -0
  185. package/dist/esm/renderModes/shaders/ribbon-particles.wgsl.js +6 -0
  186. package/dist/esm/renderModes/shaders/ribbon-particles.wgsl.js.map +1 -0
  187. package/dist/esm/renderModes/shaders/ribbon.frag.js +6 -0
  188. package/dist/esm/renderModes/shaders/ribbon.frag.js.map +1 -0
  189. package/dist/esm/renderModes/shaders/ribbon.vert.js +6 -0
  190. package/dist/esm/renderModes/shaders/ribbon.vert.js.map +1 -0
  191. package/dist/esm/renderers/WebGl2ParticleRenderer.d.ts +48 -20
  192. package/dist/esm/renderers/WebGl2ParticleRenderer.d.ts.map +1 -0
  193. package/dist/esm/renderers/WebGl2ParticleRenderer.js +317 -321
  194. package/dist/esm/renderers/WebGl2ParticleRenderer.js.map +1 -1
  195. package/dist/esm/renderers/WebGpuParticleRenderer.d.ts +99 -18
  196. package/dist/esm/renderers/WebGpuParticleRenderer.d.ts.map +1 -0
  197. package/dist/esm/renderers/WebGpuParticleRenderer.js +542 -551
  198. package/dist/esm/renderers/WebGpuParticleRenderer.js.map +1 -1
  199. package/dist/esm/renderers/shaders/particle.frag.js +6 -0
  200. package/dist/esm/renderers/shaders/particle.frag.js.map +1 -0
  201. package/dist/esm/renderers/shaders/particle.vert.js +6 -0
  202. package/dist/esm/renderers/shaders/particle.vert.js.map +1 -0
  203. package/package.json +8 -15
  204. package/dist/esm/index.js.map +0 -1
  205. package/dist/esm/register.d.ts +0 -1
  206. package/dist/esm/register.js +0 -43
  207. package/dist/esm/register.js.map +0 -1
  208. package/dist/esm/renderers/glsl/particle.frag.js +0 -4
  209. package/dist/esm/renderers/glsl/particle.frag.js.map +0 -1
  210. package/dist/esm/renderers/glsl/particle.vert.js +0 -4
  211. package/dist/esm/renderers/glsl/particle.vert.js.map +0 -1
@@ -1,325 +1,321 @@
1
- import { AbstractWebGl2Renderer, Shader, RenderingPrimitives, createWebGl2ShaderProgram, WebGl2RenderBuffer, BufferTypes, BufferUsage, WebGl2VertexArrayObject } from '@codexo/exojs/renderer-sdk';
2
- import fragmentSource from './glsl/particle.frag.js';
3
- import vertexSource from './glsl/particle.vert.js';
1
+ import { assertVertexGeometryCompatible } from "../renderModes/ParticleBufferLayout.js";
2
+ import { AbstractWebGl2Renderer, BufferTypes, BufferUsage, RenderingPrimitives, Shader, WebGl2RenderBuffer, WebGl2VertexArrayObject, createWebGl2ShaderProgram, uploadBufferRange, uploadBufferStore } from "@codexo/exojs/renderer-sdk";
4
3
 
4
+ //#region src/renderers/WebGl2ParticleRenderer.ts
5
+ const resolvePrimitive = (topology) => {
6
+ switch (topology) {
7
+ case "triangle-list": return RenderingPrimitives.Triangles;
8
+ case "triangle-strip": return RenderingPrimitives.TriangleStrip;
9
+ }
10
+ };
11
+ const usageByGeometryUsage = {
12
+ static: BufferUsage.StaticDraw,
13
+ dynamic: BufferUsage.DynamicDraw,
14
+ stream: BufferUsage.StreamDraw
15
+ };
5
16
  /**
6
- * Instanced particle renderer for WebGL2.
7
- *
8
- * One ParticleSystem = one batch. Each `render(system)` flushes any
9
- * pending batch, sets the system-level uniforms (transform, local
10
- * bounds, texture), and packs every active particle into the per-instance
11
- * buffer. The next `flush()` issues a single `drawElementsInstanced`.
12
- *
13
- * Per-instance layout (40 bytes per particle, 6 attributes):
14
- * ```
15
- * translation f32x2 (offset 0, 8 bytes) particle position (system-local)
16
- * scale f32x2 (offset 8, 8 bytes)
17
- * rotation f32 (offset 16, 4 bytes) degrees
18
- * color u8x4 (offset 20, 4 bytes) RGBA tint, normalised
19
- * uvMin f32x2 (offset 24, 8 bytes) pre-resolved frame uvMin
20
- * uvMax f32x2 (offset 32, 8 bytes) pre-resolved frame uvMax
21
- * ```
22
- *
23
- * UVs are baked per-particle so the system can carry an atlas of frames
24
- * `system.frames` declares the rectangles; each particle's
25
- * `textureIndex` selects one. The pack loop resolves frame-rectangle to
26
- * UVs once per particle per frame; no per-instance shader-side indexing
27
- * needed.
28
- */
29
- const instanceStrideBytes = 40;
30
- const wordsPerInstance = instanceStrideBytes / Uint32Array.BYTES_PER_ELEMENT;
31
- const indicesPerQuad = 6;
32
- const quadIndices = new Uint16Array([0, 1, 2, 0, 2, 3]);
33
- class WebGl2ParticleRenderer extends AbstractWebGl2Renderer {
34
- /**
35
- * The particle system's transform is bound as a `u_systemTransform` uniform and
36
- * each particle is positioned system-locally, so this renderer never reads the
37
- * shared {@link TransformBuffer}; the render-group upload boundary skips writing
38
- * transform records for particle draws.
39
- * @internal
40
- */
41
- _consumesSharedTransform = false;
42
- _shader;
43
- _batchSize;
44
- _instanceData;
45
- _instanceFloat32;
46
- _instanceUint32;
47
- _instanceCount = 0;
48
- _currentTexture = null;
49
- _currentBlendMode = null;
50
- _currentView = null;
51
- _currentViewId = -1;
52
- _instanceBuffer = null;
53
- _indexBuffer = null;
54
- _vao = null;
55
- _connection = null;
56
- constructor(batchSize) {
57
- super();
58
- this._batchSize = batchSize;
59
- this._shader = new Shader(vertexSource, fragmentSource);
60
- this._instanceData = new ArrayBuffer(batchSize * instanceStrideBytes);
61
- this._instanceFloat32 = new Float32Array(this._instanceData);
62
- this._instanceUint32 = new Uint32Array(this._instanceData);
63
- }
64
- render(system) {
65
- const backend = this.getBackend();
66
- const { texture, blendMode } = system;
67
- const textureChanged = texture !== this._currentTexture;
68
- const blendModeChanged = blendMode !== this._currentBlendMode;
69
- // System transform / texture / UV / local-bounds are uniforms, so
70
- // mixing systems in one batch is invalid. Flush any prior system
71
- // before setting up this one.
72
- this.flush();
73
- if (textureChanged) {
74
- this._currentTexture = texture;
75
- backend.bindTexture(texture);
76
- }
77
- if (blendModeChanged) {
78
- this._currentBlendMode = blendMode;
79
- backend.setBlendMode(blendMode);
80
- }
81
- // System-level uniforms are set before packing so the eventual
82
- // flush() can sync them in one go.
83
- const localBounds = system.vertices;
84
- this._shader.getUniform('u_systemTransform').setValue(system.getGlobalTransform().toArray(false));
85
- this._shader.getUniform('u_localBounds').setValue(localBounds);
86
- const f32 = this._instanceFloat32;
87
- const u32 = this._instanceUint32;
88
- const { posX, posY, scaleX, scaleY, rotations, color, textureIndex, alive } = system;
89
- const limit = Math.min(system.liveCount, this._batchSize);
90
- // Pre-compute frame UVs from system.frames + texture; falls back
91
- // to the system.textureFrame when no atlas is declared.
92
- const { uvMins, uvMaxs } = this._computeFrameUvs(system);
93
- const frameCount = uvMins.length / 2;
94
- const fallbackFrame = frameCount > 0 ? 0 : 0;
95
- let writeIndex = 0;
96
- for (let i = 0; i < limit; i++) {
97
- // Skip dead slots in GPU-mode systems where the live range can
98
- // contain holes.
99
- if (alive[i] === 0) {
100
- continue;
101
- }
102
- const offset = writeIndex * wordsPerInstance;
103
- const frame = textureIndex[i] < frameCount ? textureIndex[i] : fallbackFrame;
104
- const uvBase = frame * 2;
105
- f32[offset + 0] = posX[i];
106
- f32[offset + 1] = posY[i];
107
- f32[offset + 2] = scaleX[i];
108
- f32[offset + 3] = scaleY[i];
109
- f32[offset + 4] = rotations[i];
110
- u32[offset + 5] = color[i];
111
- f32[offset + 6] = uvMins[uvBase + 0];
112
- f32[offset + 7] = uvMins[uvBase + 1];
113
- f32[offset + 8] = uvMaxs[uvBase + 0];
114
- f32[offset + 9] = uvMaxs[uvBase + 1];
115
- writeIndex++;
116
- }
117
- this._instanceCount = writeIndex;
118
- return this;
119
- }
120
- /**
121
- * Compute (uvMin, uvMax) pairs for every declared frame on the system.
122
- * Pulled lazily and cached per (system, texture-version) to avoid the
123
- * arithmetic in the hot pack loop. Falls back to a single entry from
124
- * `system.textureFrame` when no atlas is declared.
125
- */
126
- _computeFrameUvs(system) {
127
- const frames = system.frames;
128
- const tex = system.texture;
129
- const texW = tex.width;
130
- const texH = tex.height;
131
- const flipY = tex.flipY;
132
- const count = frames.length === 0 ? 1 : frames.length;
133
- // Re-allocate scratch when capacity grows.
134
- if (this._uvMinsScratch.length < count * 2) {
135
- this._uvMinsScratch = new Float32Array(count * 2);
136
- this._uvMaxsScratch = new Float32Array(count * 2);
137
- }
138
- const mins = this._uvMinsScratch;
139
- const maxs = this._uvMaxsScratch;
140
- if (frames.length === 0) {
141
- const f = system.textureFrame;
142
- const minU = f.left / texW;
143
- const maxU = f.right / texW;
144
- const topV = f.top / texH;
145
- const bottomV = f.bottom / texH;
146
- mins[0] = minU;
147
- mins[1] = flipY ? bottomV : topV;
148
- maxs[0] = maxU;
149
- maxs[1] = flipY ? topV : bottomV;
150
- return { uvMins: mins, uvMaxs: maxs };
151
- }
152
- for (let i = 0; i < frames.length; i++) {
153
- const f = frames[i];
154
- const o = i * 2;
155
- const minU = f.left / texW;
156
- const maxU = f.right / texW;
157
- const topV = f.top / texH;
158
- const bottomV = f.bottom / texH;
159
- mins[o + 0] = minU;
160
- mins[o + 1] = flipY ? bottomV : topV;
161
- maxs[o + 0] = maxU;
162
- maxs[o + 1] = flipY ? topV : bottomV;
163
- }
164
- return { uvMins: mins, uvMaxs: maxs };
165
- }
166
- _uvMinsScratch = new Float32Array(2);
167
- _uvMaxsScratch = new Float32Array(2);
168
- flush() {
169
- const backend = this.getBackendOrNull();
170
- const instanceBuffer = this._instanceBuffer;
171
- const indexBuffer = this._indexBuffer;
172
- const vao = this._vao;
173
- if (this._instanceCount === 0 || backend === null || instanceBuffer === null || indexBuffer === null || vao === null) {
174
- return;
175
- }
176
- const view = backend.view;
177
- if (this._currentView !== view || this._currentViewId !== view.updateId) {
178
- this._currentView = view;
179
- this._currentViewId = view.updateId;
180
- this._shader.getUniform('u_projection').setValue(view.getTransform().toArray(false));
181
- }
182
- this._shader.sync();
183
- backend.bindVertexArrayObject(vao);
184
- instanceBuffer.upload(this._instanceFloat32.subarray(0, this._instanceCount * wordsPerInstance));
185
- vao.drawInstanced(indicesPerQuad, 0, this._instanceCount, RenderingPrimitives.Triangles);
186
- backend.stats.batches++;
187
- backend.stats.drawCalls++;
188
- this._instanceCount = 0;
189
- }
190
- onConnect(backend) {
191
- const gl = backend.context;
192
- this._shader.connect(createWebGl2ShaderProgram(gl));
193
- this._connection = this._createConnection(gl);
194
- this._indexBuffer = new WebGl2RenderBuffer(BufferTypes.ElementArrayBuffer, quadIndices, BufferUsage.StaticDraw).connect(this._createBufferRuntime(this._connection));
195
- this._instanceBuffer = new WebGl2RenderBuffer(BufferTypes.ArrayBuffer, this._instanceData, BufferUsage.DynamicDraw).connect(this._createBufferRuntime(this._connection));
196
- this._shader.sync();
197
- this._vao = new WebGl2VertexArrayObject()
198
- .addIndex(this._indexBuffer)
199
- .addAttribute(this._instanceBuffer, this._shader.getAttribute('a_translation'), gl.FLOAT, false, instanceStrideBytes, 0, false, 1)
200
- .addAttribute(this._instanceBuffer, this._shader.getAttribute('a_scale'), gl.FLOAT, false, instanceStrideBytes, 8, false, 1)
201
- .addAttribute(this._instanceBuffer, this._shader.getAttribute('a_rotation'), gl.FLOAT, false, instanceStrideBytes, 16, false, 1)
202
- .addAttribute(this._instanceBuffer, this._shader.getAttribute('a_color'), gl.UNSIGNED_BYTE, true, instanceStrideBytes, 20, false, 1)
203
- .addAttribute(this._instanceBuffer, this._shader.getAttribute('a_uvMin'), gl.FLOAT, false, instanceStrideBytes, 24, false, 1)
204
- .addAttribute(this._instanceBuffer, this._shader.getAttribute('a_uvMax'), gl.FLOAT, false, instanceStrideBytes, 32, false, 1)
205
- .connect(this._createVaoRuntime(this._connection));
206
- }
207
- onDisconnect() {
208
- this._shader.disconnect();
209
- this._instanceBuffer?.destroy();
210
- this._instanceBuffer = null;
211
- this._indexBuffer?.destroy();
212
- this._indexBuffer = null;
213
- this._vao?.destroy();
214
- this._vao = null;
215
- this._connection = null;
216
- this._currentTexture = null;
217
- this._currentBlendMode = null;
218
- this._currentView = null;
219
- this._currentViewId = -1;
220
- this._instanceCount = 0;
221
- }
222
- destroy() {
223
- this.disconnect();
224
- this._shader.destroy();
225
- }
226
- _createConnection(gl) {
227
- const vaoHandle = gl.createVertexArray();
228
- if (vaoHandle === null) {
229
- throw new Error('WebGl2ParticleRenderer: could not create vertex array object.');
230
- }
231
- return {
232
- gl,
233
- buffers: new Map(),
234
- vaoHandle,
235
- };
236
- }
237
- _createBufferRuntime(connection) {
238
- const handle = connection.gl.createBuffer();
239
- if (handle === null) {
240
- throw new Error('WebGl2ParticleRenderer: could not create render buffer.');
241
- }
242
- return {
243
- bind: (buffer) => {
244
- connection.gl.bindBuffer(buffer.type, handle);
245
- },
246
- upload: (buffer, offset) => {
247
- const gl = connection.gl;
248
- const data = buffer.data;
249
- const state = connection.buffers.get(buffer);
250
- gl.bindBuffer(buffer.type, handle);
251
- if (state && state.dataByteLength >= data.byteLength) {
252
- gl.bufferSubData(buffer.type, offset, data);
253
- state.dataByteLength = data.byteLength;
254
- }
255
- else {
256
- gl.bufferData(buffer.type, data, buffer.usage);
257
- connection.buffers.set(buffer, { handle, dataByteLength: data.byteLength });
258
- }
259
- },
260
- destroy: (buffer) => {
261
- connection.gl.deleteBuffer(handle);
262
- connection.buffers.delete(buffer);
263
- buffer.disconnect();
264
- },
265
- };
266
- }
267
- _createVaoRuntime(connection) {
268
- let appliedVersion = -1;
269
- return {
270
- bind: (vao) => {
271
- const gl = connection.gl;
272
- gl.bindVertexArray(connection.vaoHandle);
273
- if (appliedVersion !== vao.version) {
274
- let lastBuffer = null;
275
- for (const attribute of vao.attributes) {
276
- if (lastBuffer !== attribute.buffer) {
277
- attribute.buffer.bind();
278
- lastBuffer = attribute.buffer;
279
- }
280
- if (attribute.integer) {
281
- gl.vertexAttribIPointer(attribute.location, attribute.size, attribute.type, attribute.stride, attribute.start);
282
- }
283
- else {
284
- gl.vertexAttribPointer(attribute.location, attribute.size, attribute.type, attribute.normalized, attribute.stride, attribute.start);
285
- }
286
- gl.enableVertexAttribArray(attribute.location);
287
- gl.vertexAttribDivisor(attribute.location, attribute.divisor);
288
- }
289
- if (vao.indexBuffer) {
290
- vao.indexBuffer.bind();
291
- }
292
- appliedVersion = vao.version;
293
- }
294
- },
295
- unbind: () => {
296
- connection.gl.bindVertexArray(null);
297
- },
298
- draw: (vao, size, start, type) => {
299
- const gl = connection.gl;
300
- if (vao.indexBuffer) {
301
- gl.drawElements(type, size, gl.UNSIGNED_SHORT, start);
302
- }
303
- else {
304
- gl.drawArrays(type, start, size);
305
- }
306
- },
307
- drawInstanced: (vao, count, start, instanceCount, type) => {
308
- const gl = connection.gl;
309
- if (vao.indexBuffer) {
310
- gl.drawElementsInstanced(type, count, gl.UNSIGNED_SHORT, start, instanceCount);
311
- }
312
- else {
313
- gl.drawArraysInstanced(type, start, count, instanceCount);
314
- }
315
- },
316
- destroy: (vao) => {
317
- connection.gl.deleteVertexArray(connection.vaoHandle);
318
- vao.disconnect();
319
- },
320
- };
321
- }
322
- }
17
+ * Attribute component types that reach the shader as raw integers rather than
18
+ * as floats. Everything else - including normalised integer types such as the
19
+ * quad mode's `u8x4` colour - goes through `vertexAttribPointer` and arrives as
20
+ * a float, so only these two need `vertexAttribIPointer`.
21
+ */
22
+ const integerAttributeTypes = /* @__PURE__ */ new Set(["u32", "i32"]);
23
+ const resolveAttributeType = (gl, type) => {
24
+ switch (type) {
25
+ case "f32": return gl.FLOAT;
26
+ case "u8": return gl.UNSIGNED_BYTE;
27
+ case "u16": return gl.UNSIGNED_SHORT;
28
+ case "u32": return gl.UNSIGNED_INT;
29
+ case "i32": return gl.INT;
30
+ }
31
+ };
32
+ /**
33
+ * Particle renderer for WebGL2.
34
+ *
35
+ * One ParticleSystem = one batch. The system's {@link ParticleRenderMode} owns
36
+ * the *how* - vertex layout, shader pair, draw model and the loop that fills
37
+ * the buffer - and this renderer is the executor: each `render(system)` flushes
38
+ * any pending batch, sets the system-level uniforms (transform, local bounds,
39
+ * texture) and asks the mode to build its vertex data. The next `flush()`
40
+ * uploads that data and issues the single draw the mode declares.
41
+ *
42
+ * Everything mode-specific is read off the mode's `dataLayout`/`Material`, so a
43
+ * new primitive is a new mode rather than a change here: the vertex array
44
+ * object is wired from the layout's named attributes, the draw is instanced or
45
+ * plain per `ParticleRenderMode.instanced`, and its primitive comes from the
46
+ * topology.
47
+ *
48
+ * A mode declaring a `vertexGeometry` adds a second buffer to that vertex array
49
+ * object, stepping per vertex (divisor 0) beside the per-instance one, and its
50
+ * geometry supplies the topology and indices instead.
51
+ */
52
+ var WebGl2ParticleRenderer = class extends AbstractWebGl2Renderer {
53
+ /**
54
+ * The particle system's transform is bound as a `u_systemTransform` uniform and
55
+ * each particle is positioned system-locally, so this renderer never reads the
56
+ * shared {@link TransformBuffer}; the render-group upload boundary skips writing
57
+ * transform records for particle draws.
58
+ * @internal
59
+ */
60
+ _consumesSharedTransform = false;
61
+ /** Particles the GL-side vertex store is pre-sized for. A hint, not a limit. */
62
+ _batchSize;
63
+ _resources = /* @__PURE__ */ new Map();
64
+ /**
65
+ * Materials this renderer already registered a dispose listener on.
66
+ *
67
+ * `Material` has no unsubscribe, and a disconnect clears {@link _resources}
68
+ * without clearing the material's callback set - so registering on every
69
+ * resource creation would leave one dead closure per material behind on
70
+ * every device-loss/reconnect cycle. The listener stays valid across those
71
+ * cycles (it resolves the entry through the map when it fires), so it is
72
+ * registered once and this set remembers that. Weak, so a material dropped
73
+ * without `destroy()` stays collectable.
74
+ */
75
+ _disposeListenerRegistered = /* @__PURE__ */ new WeakSet();
76
+ _drawCount = 0;
77
+ _pendingMode = null;
78
+ _pendingResources = null;
79
+ _currentTexture = null;
80
+ _currentBlendMode = null;
81
+ _connection = null;
82
+ constructor(batchSize) {
83
+ super();
84
+ this._batchSize = batchSize;
85
+ }
86
+ render(system) {
87
+ const backend = this.getBackend();
88
+ const { texture, blendMode } = system;
89
+ const textureChanged = texture !== this._currentTexture;
90
+ const blendModeChanged = blendMode !== this._currentBlendMode;
91
+ this.flush();
92
+ if (textureChanged) {
93
+ this._currentTexture = texture;
94
+ backend.bindTexture(texture);
95
+ }
96
+ if (blendModeChanged) {
97
+ this._currentBlendMode = blendMode;
98
+ backend.setBlendMode(blendMode);
99
+ }
100
+ const mode = system.renderMode;
101
+ const resources = this._getOrCreateResources(mode);
102
+ const shader = resources.shader;
103
+ if (shader.uniforms.has("u_systemTransform")) shader.getUniform("u_systemTransform").setValue(system.getGlobalTransform().toArray(false));
104
+ if (shader.uniforms.has("u_localBounds")) shader.getUniform("u_localBounds").setValue(system.vertices);
105
+ mode.build(system, system._storage);
106
+ this._pendingMode = mode;
107
+ this._pendingResources = resources;
108
+ this._drawCount = mode.count;
109
+ return this;
110
+ }
111
+ flush() {
112
+ const backend = this.getBackendOrNull();
113
+ const mode = this._pendingMode;
114
+ const resources = this._pendingResources;
115
+ if (this._drawCount === 0 || backend === null || mode === null || resources === null) return;
116
+ const view = backend.view;
117
+ if (resources.view !== view || resources.viewId !== view.updateId) {
118
+ resources.view = view;
119
+ resources.viewId = view.updateId;
120
+ if (resources.shader.uniforms.has("u_projection")) resources.shader.getUniform("u_projection").setValue(view.getTransform().toArray(false));
121
+ }
122
+ resources.shader.sync();
123
+ backend.bindVertexArrayObject(resources.vao);
124
+ const meshGeometry = mode.vertexGeometry;
125
+ if (meshGeometry !== null && resources.meshBuffer !== null && resources.meshVersion !== meshGeometry.version) {
126
+ resources.meshVersion = meshGeometry.version;
127
+ resources.meshBuffer.upload(meshGeometry.vertexData);
128
+ }
129
+ resources.vertexBuffer.upload(this._resolveUploadSource(mode, resources), 0, this._drawCount * resources.stride);
130
+ const sampler = mode.material.sampler;
131
+ if (sampler !== null) backend.bindMaterialSampler(sampler, 0);
132
+ if (resources.instanced) resources.vao.drawInstanced(resources.indexCount, 0, this._drawCount, resources.primitive);
133
+ else resources.vao.draw(resources.indexBuffer !== null ? resources.indexCount : this._drawCount, 0, resources.primitive);
134
+ if (sampler !== null) backend.unbindMaterialSampler(0);
135
+ backend.stats.batches++;
136
+ backend.stats.drawCalls++;
137
+ this._drawCount = 0;
138
+ this._pendingMode = null;
139
+ this._pendingResources = null;
140
+ }
141
+ onConnect(backend) {
142
+ this._connection = {
143
+ gl: backend.context,
144
+ buffers: /* @__PURE__ */ new Map()
145
+ };
146
+ }
147
+ onDisconnect() {
148
+ for (const resources of this._resources.values()) this._destroyResources(resources);
149
+ this._resources.clear();
150
+ this._connection = null;
151
+ this._currentTexture = null;
152
+ this._currentBlendMode = null;
153
+ this._drawCount = 0;
154
+ this._pendingMode = null;
155
+ this._pendingResources = null;
156
+ }
157
+ destroy() {
158
+ this.disconnect();
159
+ }
160
+ /**
161
+ * The mode's scratch buffer as bytes. The view is cached and only rebuilt
162
+ * when the mode swaps in a larger backing buffer; how much of it this draw
163
+ * uploads is expressed as an element count at the upload call, so a steady
164
+ * frame allocates nothing at all.
165
+ */
166
+ _resolveUploadSource(mode, resources) {
167
+ const data = mode.data;
168
+ if (resources.source !== data) {
169
+ resources.source = data;
170
+ resources.bytes = new Uint8Array(data);
171
+ }
172
+ return resources.bytes;
173
+ }
174
+ _getOrCreateResources(mode) {
175
+ const connection = this._connection;
176
+ if (connection === null) throw new Error("WebGl2ParticleRenderer is not connected to a backend.");
177
+ const material = mode.material;
178
+ const cached = this._resources.get(material);
179
+ if (cached !== void 0) return cached;
180
+ const created = this._createResources(mode, material, connection);
181
+ this._resources.set(material, created);
182
+ if (!this._disposeListenerRegistered.has(material)) {
183
+ this._disposeListenerRegistered.add(material);
184
+ material._onDispose(() => {
185
+ this._disposeListenerRegistered.delete(material);
186
+ const stored = this._resources.get(material);
187
+ if (stored === void 0) return;
188
+ if (this._pendingResources === stored) {
189
+ this._drawCount = 0;
190
+ this._pendingMode = null;
191
+ this._pendingResources = null;
192
+ }
193
+ this._destroyResources(stored);
194
+ this._resources.delete(material);
195
+ });
196
+ }
197
+ return created;
198
+ }
199
+ _createResources(mode, material, connection) {
200
+ const gl = connection.gl;
201
+ const glsl = material.shader.glsl;
202
+ if (glsl === null) throw new Error("Particle material shader has no `glsl` source; cannot render through the WebGL2 backend.");
203
+ const layout = mode.dataLayout;
204
+ const meshGeometry = mode.vertexGeometry;
205
+ assertVertexGeometryCompatible(layout, meshGeometry, mode.instanced, mode.constructor.name);
206
+ const shader = new Shader(glsl.vertex, glsl.fragment);
207
+ shader.connect(createWebGl2ShaderProgram(gl));
208
+ shader.sync();
209
+ const indices = meshGeometry !== null ? meshGeometry.indices : layout.indices;
210
+ const topology = meshGeometry !== null ? meshGeometry.topology : layout.topology;
211
+ const indexCount = meshGeometry !== null ? meshGeometry.indexCount : layout.indexCount;
212
+ const indexBuffer = indices !== null ? new WebGl2RenderBuffer(BufferTypes.ElementArrayBuffer, indices, BufferUsage.StaticDraw).connect(this._createBufferRuntime(connection)) : null;
213
+ const vertexBuffer = new WebGl2RenderBuffer(BufferTypes.ArrayBuffer, new ArrayBuffer(this._batchSize * layout.stride), usageByGeometryUsage[layout.usage]).connect(this._createBufferRuntime(connection));
214
+ const meshBuffer = meshGeometry !== null ? new WebGl2RenderBuffer(BufferTypes.ArrayBuffer, meshGeometry.vertexData, usageByGeometryUsage[meshGeometry.usage]).connect(this._createBufferRuntime(connection)) : null;
215
+ const vaoHandle = gl.createVertexArray();
216
+ if (vaoHandle === null) throw new Error("WebGl2ParticleRenderer: could not create vertex array object.");
217
+ const divisor = mode.instanced ? 1 : 0;
218
+ const vao = new WebGl2VertexArrayObject();
219
+ if (indexBuffer !== null) vao.addIndex(indexBuffer);
220
+ for (const attribute of layout.attributes) vao.addAttribute(vertexBuffer, shader.getAttribute(attribute.name), resolveAttributeType(gl, attribute.type), attribute.normalized, layout.stride, attribute.offset, !attribute.normalized && integerAttributeTypes.has(attribute.type), divisor);
221
+ if (meshGeometry !== null && meshBuffer !== null) for (const attribute of meshGeometry.attributes) vao.addAttribute(meshBuffer, shader.getAttribute(attribute.name), resolveAttributeType(gl, attribute.type), attribute.normalized, meshGeometry.stride, attribute.offset, !attribute.normalized && integerAttributeTypes.has(attribute.type), 0);
222
+ vao.connect(this._createVaoRuntime(connection, vaoHandle, indices instanceof Uint32Array ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT));
223
+ return {
224
+ shader,
225
+ vao,
226
+ vertexBuffer,
227
+ meshBuffer,
228
+ meshVersion: meshGeometry?.version ?? -1,
229
+ indexBuffer,
230
+ stride: layout.stride,
231
+ primitive: resolvePrimitive(topology),
232
+ instanced: mode.instanced,
233
+ indexCount,
234
+ bytes: /* @__PURE__ */ new Uint8Array(0),
235
+ source: null,
236
+ view: null,
237
+ viewId: -1
238
+ };
239
+ }
240
+ _destroyResources(resources) {
241
+ resources.vao.destroy();
242
+ resources.vertexBuffer.destroy();
243
+ resources.meshBuffer?.destroy();
244
+ resources.indexBuffer?.destroy();
245
+ resources.shader.destroy();
246
+ }
247
+ _createBufferRuntime(connection) {
248
+ const handle = connection.gl.createBuffer();
249
+ if (handle === null) throw new Error("WebGl2ParticleRenderer: could not create render buffer.");
250
+ return {
251
+ bind: (buffer) => {
252
+ connection.gl.bindBuffer(buffer.type, handle);
253
+ },
254
+ upload: (buffer, offset) => {
255
+ const gl = connection.gl;
256
+ const state = connection.buffers.get(buffer);
257
+ gl.bindBuffer(buffer.type, handle);
258
+ if (state && state.dataByteLength >= buffer.uploadByteLength) {
259
+ uploadBufferRange(gl, buffer, offset);
260
+ state.dataByteLength = buffer.uploadByteLength;
261
+ } else {
262
+ uploadBufferStore(gl, buffer);
263
+ connection.buffers.set(buffer, {
264
+ handle,
265
+ dataByteLength: buffer.uploadByteLength
266
+ });
267
+ }
268
+ },
269
+ destroy: (buffer) => {
270
+ connection.gl.deleteBuffer(handle);
271
+ connection.buffers.delete(buffer);
272
+ buffer.disconnect();
273
+ }
274
+ };
275
+ }
276
+ _createVaoRuntime(connection, vaoHandle, indexType) {
277
+ let appliedVersion = -1;
278
+ return {
279
+ bind: (vao) => {
280
+ const gl = connection.gl;
281
+ gl.bindVertexArray(vaoHandle);
282
+ if (appliedVersion !== vao.version) {
283
+ let lastBuffer = null;
284
+ for (const attribute of vao.attributes) {
285
+ if (lastBuffer !== attribute.buffer) {
286
+ attribute.buffer.bind();
287
+ lastBuffer = attribute.buffer;
288
+ }
289
+ if (attribute.integer) gl.vertexAttribIPointer(attribute.location, attribute.size, attribute.type, attribute.stride, attribute.start);
290
+ else gl.vertexAttribPointer(attribute.location, attribute.size, attribute.type, attribute.normalized, attribute.stride, attribute.start);
291
+ gl.enableVertexAttribArray(attribute.location);
292
+ gl.vertexAttribDivisor(attribute.location, attribute.divisor);
293
+ }
294
+ if (vao.indexBuffer) vao.indexBuffer.bind();
295
+ appliedVersion = vao.version;
296
+ }
297
+ },
298
+ unbind: () => {
299
+ connection.gl.bindVertexArray(null);
300
+ },
301
+ draw: (vao, size, start, type) => {
302
+ const gl = connection.gl;
303
+ if (vao.indexBuffer) gl.drawElements(type, size, indexType, start);
304
+ else gl.drawArrays(type, start, size);
305
+ },
306
+ drawInstanced: (vao, count, start, instanceCount, type) => {
307
+ const gl = connection.gl;
308
+ if (vao.indexBuffer) gl.drawElementsInstanced(type, count, indexType, start, instanceCount);
309
+ else gl.drawArraysInstanced(type, start, count, instanceCount);
310
+ },
311
+ destroy: (vao) => {
312
+ connection.gl.deleteVertexArray(vaoHandle);
313
+ vao.disconnect();
314
+ }
315
+ };
316
+ }
317
+ };
323
318
 
319
+ //#endregion
324
320
  export { WebGl2ParticleRenderer };
325
- //# sourceMappingURL=WebGl2ParticleRenderer.js.map
321
+ //# sourceMappingURL=WebGl2ParticleRenderer.js.map