@codexo/exojs 0.4.0 → 0.6.1

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 (138) hide show
  1. package/CHANGELOG.md +464 -163
  2. package/README.md +156 -141
  3. package/dist/esm/audio/AudioAnalyser.d.ts +0 -1
  4. package/dist/esm/audio/AudioAnalyser.js +0 -2
  5. package/dist/esm/audio/AudioAnalyser.js.map +1 -1
  6. package/dist/esm/core/Application.d.ts +4 -4
  7. package/dist/esm/core/Application.js +19 -19
  8. package/dist/esm/core/Application.js.map +1 -1
  9. package/dist/esm/core/Scene.d.ts +59 -24
  10. package/dist/esm/core/Scene.js +60 -18
  11. package/dist/esm/core/Scene.js.map +1 -1
  12. package/dist/esm/core/SceneManager.js +15 -9
  13. package/dist/esm/core/SceneManager.js.map +1 -1
  14. package/dist/esm/core/SceneNode.d.ts +45 -5
  15. package/dist/esm/core/SceneNode.js +136 -7
  16. package/dist/esm/core/SceneNode.js.map +1 -1
  17. package/dist/esm/index.js +3 -4
  18. package/dist/esm/index.js.map +1 -1
  19. package/dist/esm/math/index.d.ts +0 -1
  20. package/dist/esm/rendering/CallbackRenderPass.d.ts +3 -3
  21. package/dist/esm/rendering/CallbackRenderPass.js +2 -2
  22. package/dist/esm/rendering/CallbackRenderPass.js.map +1 -1
  23. package/dist/esm/rendering/Container.d.ts +10 -11
  24. package/dist/esm/rendering/Container.js +5 -5
  25. package/dist/esm/rendering/Container.js.map +1 -1
  26. package/dist/esm/rendering/Drawable.d.ts +2 -2
  27. package/dist/esm/rendering/Drawable.js +5 -5
  28. package/dist/esm/rendering/Drawable.js.map +1 -1
  29. package/dist/esm/rendering/{SceneRenderRuntime.d.ts → RenderBackend.d.ts} +21 -3
  30. package/dist/esm/rendering/RenderNode.d.ts +41 -5
  31. package/dist/esm/rendering/RenderNode.js +89 -24
  32. package/dist/esm/rendering/RenderNode.js.map +1 -1
  33. package/dist/esm/rendering/RenderPass.d.ts +2 -2
  34. package/dist/esm/rendering/RenderTargetPass.d.ts +3 -3
  35. package/dist/esm/rendering/RenderTargetPass.js +9 -9
  36. package/dist/esm/rendering/RenderTargetPass.js.map +1 -1
  37. package/dist/esm/rendering/Renderer.d.ts +3 -3
  38. package/dist/esm/rendering/RendererRegistry.d.ts +13 -7
  39. package/dist/esm/rendering/RendererRegistry.js +18 -10
  40. package/dist/esm/rendering/RendererRegistry.js.map +1 -1
  41. package/dist/esm/rendering/filters/BlurFilter.d.ts +2 -2
  42. package/dist/esm/rendering/filters/BlurFilter.js +5 -5
  43. package/dist/esm/rendering/filters/BlurFilter.js.map +1 -1
  44. package/dist/esm/rendering/filters/ColorFilter.d.ts +2 -2
  45. package/dist/esm/rendering/filters/ColorFilter.js +3 -3
  46. package/dist/esm/rendering/filters/ColorFilter.js.map +1 -1
  47. package/dist/esm/rendering/filters/Filter.d.ts +2 -2
  48. package/dist/esm/rendering/index.d.ts +6 -6
  49. package/dist/esm/rendering/primitives/Graphics.d.ts +3 -3
  50. package/dist/esm/rendering/primitives/Graphics.js.map +1 -1
  51. package/dist/esm/rendering/shader/Shader.d.ts +3 -3
  52. package/dist/esm/rendering/shader/Shader.js +10 -10
  53. package/dist/esm/rendering/text/Text.d.ts +2 -2
  54. package/dist/esm/rendering/text/Text.js +2 -2
  55. package/dist/esm/rendering/text/Text.js.map +1 -1
  56. package/dist/esm/rendering/texture/Sampler.d.ts +0 -3
  57. package/dist/esm/rendering/texture/Sampler.js +5 -7
  58. package/dist/esm/rendering/texture/Sampler.js.map +1 -1
  59. package/dist/esm/rendering/types.d.ts +4 -0
  60. package/dist/esm/rendering/types.js +4 -0
  61. package/dist/esm/rendering/types.js.map +1 -1
  62. package/dist/esm/rendering/video/Video.d.ts +2 -2
  63. package/dist/esm/rendering/video/Video.js +2 -2
  64. package/dist/esm/rendering/video/Video.js.map +1 -1
  65. package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.d.ts +2 -2
  66. package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.js +35 -11
  67. package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.js.map +1 -1
  68. package/dist/esm/rendering/webgl2/AbstractWebGl2Renderer.d.ts +13 -13
  69. package/dist/esm/rendering/webgl2/AbstractWebGl2Renderer.js +20 -20
  70. package/dist/esm/rendering/webgl2/AbstractWebGl2Renderer.js.map +1 -1
  71. package/dist/esm/rendering/webgl2/{WebGl2RenderManager.d.ts → WebGl2Backend.d.ts} +15 -12
  72. package/dist/esm/rendering/webgl2/{WebGl2RenderManager.js → WebGl2Backend.js} +60 -38
  73. package/dist/esm/rendering/webgl2/WebGl2Backend.js.map +1 -0
  74. package/dist/esm/rendering/webgl2/WebGl2MaskCompositor.d.ts +31 -0
  75. package/dist/esm/rendering/webgl2/WebGl2MaskCompositor.js +186 -0
  76. package/dist/esm/rendering/webgl2/WebGl2MaskCompositor.js.map +1 -0
  77. package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.d.ts +38 -7
  78. package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.js +281 -90
  79. package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.js.map +1 -1
  80. package/dist/esm/rendering/webgl2/WebGl2PrimitiveRenderer.d.ts +2 -2
  81. package/dist/esm/rendering/webgl2/WebGl2PrimitiveRenderer.js +15 -10
  82. package/dist/esm/rendering/webgl2/WebGl2PrimitiveRenderer.js.map +1 -1
  83. package/dist/esm/rendering/webgl2/WebGl2ShaderMappings.js +12 -0
  84. package/dist/esm/rendering/webgl2/WebGl2ShaderMappings.js.map +1 -1
  85. package/dist/esm/rendering/webgl2/WebGl2ShaderProgram.d.ts +2 -0
  86. package/dist/esm/rendering/webgl2/{WebGl2ShaderRuntime.js → WebGl2ShaderProgram.js} +58 -18
  87. package/dist/esm/rendering/webgl2/WebGl2ShaderProgram.js.map +1 -0
  88. package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.d.ts +26 -7
  89. package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.js +260 -62
  90. package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.js.map +1 -1
  91. package/dist/esm/rendering/webgl2/WebGl2VertexArrayObject.d.ts +24 -1
  92. package/dist/esm/rendering/webgl2/WebGl2VertexArrayObject.js +6 -2
  93. package/dist/esm/rendering/webgl2/WebGl2VertexArrayObject.js.map +1 -1
  94. package/dist/esm/rendering/webgl2/glsl/mask-compose.frag.js +4 -0
  95. package/dist/esm/rendering/webgl2/glsl/mask-compose.frag.js.map +1 -0
  96. package/dist/esm/rendering/webgl2/glsl/mask-compose.vert.js +4 -0
  97. package/dist/esm/rendering/webgl2/glsl/mask-compose.vert.js.map +1 -0
  98. package/dist/esm/rendering/webgl2/glsl/particle.vert.js +1 -1
  99. package/dist/esm/rendering/webgl2/glsl/sprite.frag.js +1 -1
  100. package/dist/esm/rendering/webgl2/glsl/sprite.vert.js +1 -1
  101. package/dist/esm/rendering/webgpu/AbstractWebGpuRenderer.d.ts +9 -9
  102. package/dist/esm/rendering/webgpu/AbstractWebGpuRenderer.js +18 -18
  103. package/dist/esm/rendering/webgpu/AbstractWebGpuRenderer.js.map +1 -1
  104. package/dist/esm/rendering/webgpu/{WebGpuRenderManager.d.ts → WebGpuBackend.d.ts} +17 -14
  105. package/dist/esm/rendering/webgpu/{WebGpuRenderManager.js → WebGpuBackend.js} +74 -40
  106. package/dist/esm/rendering/webgpu/WebGpuBackend.js.map +1 -0
  107. package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.d.ts +37 -0
  108. package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js +279 -0
  109. package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js.map +1 -0
  110. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.d.ts +2 -3
  111. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js +65 -82
  112. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js.map +1 -1
  113. package/dist/esm/rendering/webgpu/WebGpuPrimitiveRenderer.d.ts +2 -3
  114. package/dist/esm/rendering/webgpu/WebGpuPrimitiveRenderer.js +24 -25
  115. package/dist/esm/rendering/webgpu/WebGpuPrimitiveRenderer.js.map +1 -1
  116. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.d.ts +28 -13
  117. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.js +410 -382
  118. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.js.map +1 -1
  119. package/dist/esm/resources/Loader.js +5 -3
  120. package/dist/esm/resources/Loader.js.map +1 -1
  121. package/dist/exo.esm.js +2500 -1399
  122. package/dist/exo.esm.js.map +1 -1
  123. package/package.json +20 -11
  124. package/dist/esm/math/Transformable.d.ts +0 -47
  125. package/dist/esm/math/Transformable.js +0 -140
  126. package/dist/esm/math/Transformable.js.map +0 -1
  127. package/dist/esm/rendering/webgl2/WebGl2RenderManager.js.map +0 -1
  128. package/dist/esm/rendering/webgl2/WebGl2RendererRuntime.d.ts +0 -15
  129. package/dist/esm/rendering/webgl2/WebGl2ShaderRuntime.d.ts +0 -2
  130. package/dist/esm/rendering/webgl2/WebGl2ShaderRuntime.js.map +0 -1
  131. package/dist/esm/rendering/webgpu/WebGpuRenderManager.js.map +0 -1
  132. package/dist/esm/rendering/webgpu/WebGpuRendererRuntime.d.ts +0 -8
  133. package/dist/exo.esm.min.js +0 -2
  134. package/dist/exo.esm.min.js.map +0 -1
  135. package/dist/exo.global.js +0 -17328
  136. package/dist/exo.global.js.map +0 -1
  137. package/dist/exo.global.min.js +0 -2
  138. package/dist/exo.global.min.js.map +0 -1
@@ -0,0 +1,186 @@
1
+ import { Shader } from '../shader/Shader.js';
2
+ import { createWebGl2ShaderProgram } from './WebGl2ShaderProgram.js';
3
+ import { WebGl2VertexArrayObject } from './WebGl2VertexArrayObject.js';
4
+ import { WebGl2RenderBuffer } from './WebGl2RenderBuffer.js';
5
+ import { BufferTypes, BufferUsage } from '../types.js';
6
+ import vertexSource from './glsl/mask-compose.vert.js';
7
+ import fragmentSource from './glsl/mask-compose.frag.js';
8
+
9
+ // 4 floats per vertex: position(x, y) + texcoord(u, v).
10
+ const vertexStrideBytes = 16;
11
+ const quadIndices = new Uint16Array([0, 1, 2, 0, 2, 3]);
12
+ /**
13
+ * Single-quad two-texture compositor used by `WebGl2Backend.composeWithAlphaMask`.
14
+ *
15
+ * Renders the content texture onto the active render target with each
16
+ * output texel's alpha multiplied by the mask texture's alpha at the
17
+ * same UV. Both textures are sampled with stretched-fit UVs over the
18
+ * destination rectangle.
19
+ *
20
+ * Intentionally not a {@link AbstractWebGl2Renderer} subclass: this
21
+ * compositor is invoked directly by the manager for non-Drawable
22
+ * compositing operations and never participates in the renderer
23
+ * registry dispatch path.
24
+ */
25
+ class WebGl2MaskCompositor {
26
+ _shader = new Shader(vertexSource, fragmentSource);
27
+ _vertexData = new ArrayBuffer(4 * vertexStrideBytes);
28
+ _float32View = new Float32Array(this._vertexData);
29
+ _contentSamplerSlot = new Int32Array([0]);
30
+ _maskSamplerSlot = new Int32Array([1]);
31
+ _connection = null;
32
+ connect(backend) {
33
+ if (this._connection !== null) {
34
+ return;
35
+ }
36
+ const gl = backend.context;
37
+ const vaoHandle = gl.createVertexArray();
38
+ if (vaoHandle === null) {
39
+ throw new Error('WebGl2MaskCompositor: could not create vertex array object.');
40
+ }
41
+ this._shader.connect(createWebGl2ShaderProgram(gl));
42
+ const bufferHandles = new Map();
43
+ const indexBuffer = new WebGl2RenderBuffer(BufferTypes.ElementArrayBuffer, quadIndices, BufferUsage.StaticDraw)
44
+ .connect(this._createBufferRuntime(gl, bufferHandles));
45
+ const vertexBuffer = new WebGl2RenderBuffer(BufferTypes.ArrayBuffer, this._vertexData, BufferUsage.DynamicDraw)
46
+ .connect(this._createBufferRuntime(gl, bufferHandles));
47
+ // Force shader finalize so getAttribute() below sees a populated
48
+ // attribute table; the async-compile path defers extraction until
49
+ // the first sync() call.
50
+ this._shader.sync();
51
+ const vao = new WebGl2VertexArrayObject()
52
+ .addIndex(indexBuffer)
53
+ .addAttribute(vertexBuffer, this._shader.getAttribute('a_position'), gl.FLOAT, false, vertexStrideBytes, 0)
54
+ .addAttribute(vertexBuffer, this._shader.getAttribute('a_texcoord'), gl.FLOAT, false, vertexStrideBytes, 8)
55
+ .connect(this._createVaoRuntime(gl, vaoHandle));
56
+ this._connection = { gl, vaoHandle, vao, indexBuffer, vertexBuffer, bufferHandles };
57
+ }
58
+ disconnect() {
59
+ const connection = this._connection;
60
+ if (connection === null) {
61
+ return;
62
+ }
63
+ connection.indexBuffer.destroy();
64
+ connection.vertexBuffer.destroy();
65
+ connection.vao.destroy();
66
+ this._shader.disconnect();
67
+ this._connection = null;
68
+ }
69
+ compose(backend, content, mask, x, y, width, height, blendMode) {
70
+ const connection = this._connection;
71
+ if (connection === null) {
72
+ throw new Error('WebGl2MaskCompositor: not connected.');
73
+ }
74
+ // Update the quad vertices for this destination rect. UVs are 0..1
75
+ // mapped over (left, top) → (right, bottom) with an explicit Y-flip
76
+ // for render-texture sampling: the mask path samples textures
77
+ // already authored as RGBA in render-texture orientation.
78
+ this._writeQuadVertices(x, y, x + width, y + height);
79
+ // Bind the compositor program. Setting projection + sampler uniforms
80
+ // each call because they need to match the current render target.
81
+ backend.bindShader(this._shader);
82
+ const view = backend.view;
83
+ const projection = view.getTransform().toArray(false);
84
+ this._shader.getUniform('u_projection').setValue(projection);
85
+ this._shader.getUniform('u_content').setValue(this._contentSamplerSlot);
86
+ this._shader.getUniform('u_mask').setValue(this._maskSamplerSlot);
87
+ this._shader.sync();
88
+ backend.bindTexture(content, 0);
89
+ backend.bindTexture(mask, 1);
90
+ backend.setBlendMode(blendMode);
91
+ backend.bindVertexArrayObject(connection.vao);
92
+ connection.vertexBuffer.upload(this._float32View);
93
+ connection.vao.draw(6, 0);
94
+ backend.stats.batches++;
95
+ backend.stats.drawCalls++;
96
+ // Reset the active texture unit to 0 to avoid leaking unit 1 into
97
+ // subsequent renderer state.
98
+ backend.bindTexture(null, 1);
99
+ }
100
+ _writeQuadVertices(left, top, right, bottom) {
101
+ const view = this._float32View;
102
+ // Vertex 0: top-left (UV 0, 0)
103
+ view[0] = left;
104
+ view[1] = top;
105
+ view[2] = 0;
106
+ view[3] = 0;
107
+ // Vertex 1: top-right (UV 1, 0)
108
+ view[4] = right;
109
+ view[5] = top;
110
+ view[6] = 1;
111
+ view[7] = 0;
112
+ // Vertex 2: bottom-right (UV 1, 1)
113
+ view[8] = right;
114
+ view[9] = bottom;
115
+ view[10] = 1;
116
+ view[11] = 1;
117
+ // Vertex 3: bottom-left (UV 0, 1)
118
+ view[12] = left;
119
+ view[13] = bottom;
120
+ view[14] = 0;
121
+ view[15] = 1;
122
+ }
123
+ _createBufferRuntime(gl, handles) {
124
+ const handle = gl.createBuffer();
125
+ if (handle === null) {
126
+ throw new Error('WebGl2MaskCompositor: could not create render buffer.');
127
+ }
128
+ return {
129
+ bind: (buffer) => {
130
+ gl.bindBuffer(buffer.type, handle);
131
+ },
132
+ upload: (buffer) => {
133
+ const data = buffer.data;
134
+ gl.bindBuffer(buffer.type, handle);
135
+ gl.bufferData(buffer.type, data, buffer.usage);
136
+ handles.set(buffer, handle);
137
+ },
138
+ destroy: (buffer) => {
139
+ gl.deleteBuffer(handle);
140
+ handles.delete(buffer);
141
+ buffer.disconnect();
142
+ },
143
+ };
144
+ }
145
+ _createVaoRuntime(gl, vaoHandle) {
146
+ let appliedVersion = -1;
147
+ return {
148
+ bind: (vao) => {
149
+ gl.bindVertexArray(vaoHandle);
150
+ if (appliedVersion !== vao.version) {
151
+ let lastBuffer = null;
152
+ for (const attribute of vao.attributes) {
153
+ if (lastBuffer !== attribute.buffer) {
154
+ attribute.buffer.bind();
155
+ lastBuffer = attribute.buffer;
156
+ }
157
+ gl.vertexAttribPointer(attribute.location, attribute.size, attribute.type, attribute.normalized, attribute.stride, attribute.start);
158
+ gl.enableVertexAttribArray(attribute.location);
159
+ }
160
+ if (vao.indexBuffer) {
161
+ vao.indexBuffer.bind();
162
+ }
163
+ appliedVersion = vao.version;
164
+ }
165
+ },
166
+ unbind: () => {
167
+ gl.bindVertexArray(null);
168
+ },
169
+ draw: (vao, size, start, type) => {
170
+ if (vao.indexBuffer) {
171
+ gl.drawElements(type, size, gl.UNSIGNED_SHORT, start);
172
+ }
173
+ else {
174
+ gl.drawArrays(type, start, size);
175
+ }
176
+ },
177
+ destroy: (vao) => {
178
+ gl.deleteVertexArray(vaoHandle);
179
+ vao.disconnect();
180
+ },
181
+ };
182
+ }
183
+ }
184
+
185
+ export { WebGl2MaskCompositor };
186
+ //# sourceMappingURL=WebGl2MaskCompositor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebGl2MaskCompositor.js","sources":["../../../../../src/rendering/webgl2/WebGl2MaskCompositor.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -1,11 +1,42 @@
1
- import type { WebGl2RenderBuffer } from './WebGl2RenderBuffer';
2
- import { WebGl2VertexArrayObject } from './WebGl2VertexArrayObject';
1
+ import { AbstractWebGl2Renderer } from './AbstractWebGl2Renderer';
3
2
  import type { ParticleSystem } from '@/particles/ParticleSystem';
4
- import { AbstractWebGl2BatchedRenderer } from './AbstractWebGl2BatchedRenderer';
5
- import type { View } from '../View';
6
- export declare class WebGl2ParticleRenderer extends AbstractWebGl2BatchedRenderer {
3
+ import type { WebGl2Backend } from './WebGl2Backend';
4
+ export declare class WebGl2ParticleRenderer extends AbstractWebGl2Renderer<ParticleSystem> {
5
+ private readonly _shader;
6
+ private readonly _batchSize;
7
+ private readonly _instanceData;
8
+ private readonly _instanceFloat32;
9
+ private readonly _instanceUint32;
10
+ private _instanceCount;
11
+ private _currentTexture;
12
+ private _currentBlendMode;
13
+ private _currentView;
14
+ private _currentViewId;
15
+ private _instanceBuffer;
16
+ private _indexBuffer;
17
+ private _vao;
18
+ private _connection;
7
19
  constructor(batchSize: number);
8
20
  render(system: ParticleSystem): this;
9
- protected createVao(gl: WebGL2RenderingContext, indexBuffer: WebGl2RenderBuffer, vertexBuffer: WebGl2RenderBuffer): WebGl2VertexArrayObject;
10
- protected updateView(view: View): this;
21
+ flush(): void;
22
+ protected onConnect(backend: WebGl2Backend): void;
23
+ protected onDisconnect(): void;
24
+ destroy(): void;
25
+ /**
26
+ * Convert the system's per-corner packed-u32 texCoords into the
27
+ * (uMin, vMin, uMax, vMax) bounds the new vertex shader expects.
28
+ * Already accounts for flipY (the system's `texCoords` baked it in).
29
+ *
30
+ * The four packed u32s, by corner index, are:
31
+ * [0] TL = (uMin/uMax | vMin/vMax depending on flipY)
32
+ * [1] TR
33
+ * [2] BR
34
+ * [3] BL
35
+ * We need (uMin, vMin, uMax, vMax) — the corner-extreme values.
36
+ */
37
+ private _uvBoundsScratch;
38
+ private _unpackUvBounds;
39
+ private _createConnection;
40
+ private _createBufferRuntime;
41
+ private _createVaoRuntime;
11
42
  }
@@ -1,108 +1,299 @@
1
+ import { AbstractWebGl2Renderer } from './AbstractWebGl2Renderer.js';
2
+ import { Shader } from '../shader/Shader.js';
3
+ import { createWebGl2ShaderProgram } from './WebGl2ShaderProgram.js';
4
+ import { WebGl2RenderBuffer } from './WebGl2RenderBuffer.js';
1
5
  import { WebGl2VertexArrayObject } from './WebGl2VertexArrayObject.js';
6
+ import { RenderingPrimitives, BufferTypes, BufferUsage } from '../types.js';
2
7
  import vertexSource from './glsl/particle.vert.js';
3
8
  import fragmentSource from './glsl/particle.frag.js';
4
- import { AbstractWebGl2BatchedRenderer } from './AbstractWebGl2BatchedRenderer.js';
5
9
 
6
- class WebGl2ParticleRenderer extends AbstractWebGl2BatchedRenderer {
10
+ /**
11
+ * Instanced particle renderer for WebGL2.
12
+ *
13
+ * One ParticleSystem = one batch. Each `render(system)` flushes any
14
+ * pending batch, sets the system-level uniforms (transform, local
15
+ * bounds, UV bounds, texture), and packs every active particle into
16
+ * the per-instance buffer. The next `flush()` issues a single
17
+ * `drawElementsInstanced` for that system.
18
+ *
19
+ * Per-instance layout (24 bytes per particle, 4 attributes):
20
+ * ```
21
+ * translation f32x2 (offset 0, 8 bytes) particle position (system-local)
22
+ * scale f32x2 (offset 8, 8 bytes)
23
+ * rotation f32 (offset 16, 4 bytes) degrees
24
+ * color u8x4 (offset 20, 4 bytes) RGBA tint, normalised
25
+ * ```
26
+ *
27
+ * vs the previous per-vertex layout (36 bytes per vertex × 4 verts =
28
+ * 144 bytes per particle), this is an 83% bandwidth reduction and
29
+ * roughly 80% fewer CPU writes per particle (one pack call vs four
30
+ * duplicated vertex writes plus per-corner UV coords).
31
+ *
32
+ * The system transform stays as a uniform — mixing systems in one
33
+ * batch would require either a per-instance transform matrix (more
34
+ * bandwidth) or per-particle texture-slot indexing (multi-texture
35
+ * support similar to the sprite renderer). Both are follow-ups; the
36
+ * current pattern keeps the renderer focused on the per-particle win.
37
+ */
38
+ const instanceStrideBytes = 24;
39
+ const wordsPerInstance = instanceStrideBytes / Uint32Array.BYTES_PER_ELEMENT;
40
+ const indicesPerQuad = 6;
41
+ const quadIndices = new Uint16Array([0, 1, 2, 0, 2, 3]);
42
+ class WebGl2ParticleRenderer extends AbstractWebGl2Renderer {
43
+ _shader;
44
+ _batchSize;
45
+ _instanceData;
46
+ _instanceFloat32;
47
+ _instanceUint32;
48
+ _instanceCount = 0;
49
+ _currentTexture = null;
50
+ _currentBlendMode = null;
51
+ _currentView = null;
52
+ _currentViewId = -1;
53
+ _instanceBuffer = null;
54
+ _indexBuffer = null;
55
+ _vao = null;
56
+ _connection = null;
7
57
  constructor(batchSize) {
8
- /**
9
- * 4 x 9 Attributes:
10
- * 2 = vertexPos (x, y) +
11
- * 1 = texCoord (packed uv) +
12
- * 2 = position (x, y) +
13
- * 2 = scale (x, y) +
14
- * 1 = rotation (x, y) +
15
- * 1 = color (ARGB int)
16
- */
17
- super(batchSize, 36, vertexSource, fragmentSource);
58
+ super();
59
+ this._batchSize = batchSize;
60
+ this._shader = new Shader(vertexSource, fragmentSource);
61
+ this._instanceData = new ArrayBuffer(batchSize * instanceStrideBytes);
62
+ this._instanceFloat32 = new Float32Array(this._instanceData);
63
+ this._instanceUint32 = new Uint32Array(this._instanceData);
18
64
  }
19
65
  render(system) {
20
- const { texture, vertices, texCoords, particles, blendMode } = system;
21
- const textureChanged = (texture !== this.currentTexture);
22
- const blendModeChanged = (blendMode !== this.currentBlendMode);
23
- const float32View = this.float32View;
24
- const uint32View = this.uint32View;
25
- const runtime = this.getRuntime();
26
- // System transform is a uniform, so mixing systems in one batch is invalid.
66
+ const backend = this.getBackend();
67
+ const { texture, particles, blendMode } = system;
68
+ const textureChanged = texture !== this._currentTexture;
69
+ const blendModeChanged = blendMode !== this._currentBlendMode;
70
+ // System transform / texture / UV / local-bounds are uniforms, so
71
+ // mixing systems in one batch is invalid. Flush any prior system
72
+ // before setting up this one.
27
73
  this.flush();
28
- if (textureChanged || blendModeChanged) {
29
- if (textureChanged) {
30
- this.currentTexture = texture;
31
- }
32
- if (blendModeChanged) {
33
- this.currentBlendMode = blendMode;
34
- runtime.setBlendMode(blendMode);
35
- }
36
- }
37
74
  if (textureChanged) {
38
- runtime.bindTexture(texture);
75
+ this._currentTexture = texture;
76
+ backend.bindTexture(texture);
77
+ }
78
+ if (blendModeChanged) {
79
+ this._currentBlendMode = blendMode;
80
+ backend.setBlendMode(blendMode);
39
81
  }
40
- this.shader
41
- .getUniform('u_translation')
82
+ // System-level uniforms are set before packing so the eventual
83
+ // flush() can sync them in one go.
84
+ const localBounds = system.vertices;
85
+ const uvBounds = this._unpackUvBounds(system);
86
+ this._shader
87
+ .getUniform('u_systemTransform')
42
88
  .setValue(system.getGlobalTransform().toArray(false));
43
- for (const particle of particles) {
44
- if (this.batchIndex >= this.batchSize) {
45
- this.flush();
46
- }
47
- const { position, scale, rotation, tint } = particle;
48
- const index = (this.batchIndex * this.attributeCount);
49
- float32View[index + 0] = float32View[index + 27] = vertices[0];
50
- float32View[index + 1] = float32View[index + 10] = vertices[1];
51
- float32View[index + 9] = float32View[index + 18] = vertices[2];
52
- float32View[index + 19] = float32View[index + 28] = vertices[3];
53
- uint32View[index + 2] = texCoords[0];
54
- uint32View[index + 11] = texCoords[1];
55
- uint32View[index + 20] = texCoords[2];
56
- uint32View[index + 29] = texCoords[3];
57
- float32View[index + 3]
58
- = float32View[index + 12]
59
- = float32View[index + 21]
60
- = float32View[index + 30]
61
- = position.x;
62
- float32View[index + 4]
63
- = float32View[index + 13]
64
- = float32View[index + 22]
65
- = float32View[index + 31]
66
- = position.y;
67
- float32View[index + 5]
68
- = float32View[index + 14]
69
- = float32View[index + 23]
70
- = float32View[index + 32]
71
- = scale.x;
72
- float32View[index + 6]
73
- = float32View[index + 15]
74
- = float32View[index + 24]
75
- = float32View[index + 33]
76
- = scale.y;
77
- float32View[index + 7]
78
- = float32View[index + 16]
79
- = float32View[index + 25]
80
- = float32View[index + 34]
81
- = rotation;
82
- uint32View[index + 8]
83
- = uint32View[index + 17]
84
- = uint32View[index + 26]
85
- = uint32View[index + 35]
86
- = tint.toRgba();
87
- this.batchIndex++;
89
+ this._shader
90
+ .getUniform('u_localBounds')
91
+ .setValue(localBounds);
92
+ this._shader
93
+ .getUniform('u_uvBounds')
94
+ .setValue(uvBounds);
95
+ const f32 = this._instanceFloat32;
96
+ const u32 = this._instanceUint32;
97
+ const limit = Math.min(particles.length, this._batchSize);
98
+ for (let i = 0; i < limit; i++) {
99
+ const particle = particles[i];
100
+ const offset = i * wordsPerInstance;
101
+ f32[offset + 0] = particle.position.x;
102
+ f32[offset + 1] = particle.position.y;
103
+ f32[offset + 2] = particle.scale.x;
104
+ f32[offset + 3] = particle.scale.y;
105
+ f32[offset + 4] = particle.rotation;
106
+ u32[offset + 5] = particle.tint.toRgba();
88
107
  }
108
+ this._instanceCount = limit;
89
109
  return this;
90
110
  }
91
- createVao(gl, indexBuffer, vertexBuffer) {
92
- return new WebGl2VertexArrayObject()
93
- .addIndex(indexBuffer)
94
- .addAttribute(vertexBuffer, this.shader.getAttribute('a_position'), gl.FLOAT, false, this.attributeCount, 0)
95
- .addAttribute(vertexBuffer, this.shader.getAttribute('a_texcoord'), gl.UNSIGNED_SHORT, true, this.attributeCount, 8)
96
- .addAttribute(vertexBuffer, this.shader.getAttribute('a_translation'), gl.FLOAT, false, this.attributeCount, 12)
97
- .addAttribute(vertexBuffer, this.shader.getAttribute('a_scale'), gl.FLOAT, false, this.attributeCount, 20)
98
- .addAttribute(vertexBuffer, this.shader.getAttribute('a_rotation'), gl.FLOAT, false, this.attributeCount, 28)
99
- .addAttribute(vertexBuffer, this.shader.getAttribute('a_color'), gl.UNSIGNED_BYTE, true, this.attributeCount, 32);
111
+ flush() {
112
+ const backend = this.getBackendOrNull();
113
+ const instanceBuffer = this._instanceBuffer;
114
+ const indexBuffer = this._indexBuffer;
115
+ const vao = this._vao;
116
+ if (this._instanceCount === 0 || backend === null || instanceBuffer === null || indexBuffer === null || vao === null) {
117
+ return;
118
+ }
119
+ const view = backend.view;
120
+ if (this._currentView !== view || this._currentViewId !== view.updateId) {
121
+ this._currentView = view;
122
+ this._currentViewId = view.updateId;
123
+ this._shader
124
+ .getUniform('u_projection')
125
+ .setValue(view.getTransform().toArray(false));
126
+ }
127
+ this._shader.sync();
128
+ backend.bindVertexArrayObject(vao);
129
+ instanceBuffer.upload(this._instanceFloat32.subarray(0, this._instanceCount * wordsPerInstance));
130
+ vao.drawInstanced(indicesPerQuad, 0, this._instanceCount, RenderingPrimitives.Triangles);
131
+ backend.stats.batches++;
132
+ backend.stats.drawCalls++;
133
+ this._instanceCount = 0;
100
134
  }
101
- updateView(view) {
102
- this.shader
103
- .getUniform('u_projection')
104
- .setValue(view.getTransform().toArray(false));
105
- return this;
135
+ onConnect(backend) {
136
+ const gl = backend.context;
137
+ this._shader.connect(createWebGl2ShaderProgram(gl));
138
+ this._connection = this._createConnection(gl);
139
+ this._indexBuffer = new WebGl2RenderBuffer(BufferTypes.ElementArrayBuffer, quadIndices, BufferUsage.StaticDraw)
140
+ .connect(this._createBufferRuntime(this._connection));
141
+ this._instanceBuffer = new WebGl2RenderBuffer(BufferTypes.ArrayBuffer, this._instanceData, BufferUsage.DynamicDraw)
142
+ .connect(this._createBufferRuntime(this._connection));
143
+ this._shader.sync();
144
+ this._vao = new WebGl2VertexArrayObject()
145
+ .addIndex(this._indexBuffer)
146
+ .addAttribute(this._instanceBuffer, this._shader.getAttribute('a_translation'), gl.FLOAT, false, instanceStrideBytes, 0, false, 1)
147
+ .addAttribute(this._instanceBuffer, this._shader.getAttribute('a_scale'), gl.FLOAT, false, instanceStrideBytes, 8, false, 1)
148
+ .addAttribute(this._instanceBuffer, this._shader.getAttribute('a_rotation'), gl.FLOAT, false, instanceStrideBytes, 16, false, 1)
149
+ .addAttribute(this._instanceBuffer, this._shader.getAttribute('a_color'), gl.UNSIGNED_BYTE, true, instanceStrideBytes, 20, false, 1)
150
+ .connect(this._createVaoRuntime(this._connection));
151
+ }
152
+ onDisconnect() {
153
+ this._shader.disconnect();
154
+ this._instanceBuffer?.destroy();
155
+ this._instanceBuffer = null;
156
+ this._indexBuffer?.destroy();
157
+ this._indexBuffer = null;
158
+ this._vao?.destroy();
159
+ this._vao = null;
160
+ this._connection = null;
161
+ this._currentTexture = null;
162
+ this._currentBlendMode = null;
163
+ this._currentView = null;
164
+ this._currentViewId = -1;
165
+ this._instanceCount = 0;
166
+ }
167
+ destroy() {
168
+ this.disconnect();
169
+ this._shader.destroy();
170
+ }
171
+ /**
172
+ * Convert the system's per-corner packed-u32 texCoords into the
173
+ * (uMin, vMin, uMax, vMax) bounds the new vertex shader expects.
174
+ * Already accounts for flipY (the system's `texCoords` baked it in).
175
+ *
176
+ * The four packed u32s, by corner index, are:
177
+ * [0] TL = (uMin/uMax | vMin/vMax depending on flipY)
178
+ * [1] TR
179
+ * [2] BR
180
+ * [3] BL
181
+ * We need (uMin, vMin, uMax, vMax) — the corner-extreme values.
182
+ */
183
+ _uvBoundsScratch = new Float32Array(4);
184
+ _unpackUvBounds(system) {
185
+ const texCoords = system.texCoords;
186
+ // Each packed u32: low 16 bits = U normalised to 0..65535,
187
+ // high 16 bits = V normalised.
188
+ const uTopLeft = (texCoords[0] & 0xFFFF) / 0xFFFF;
189
+ const vTopLeft = ((texCoords[0] >>> 16) & 0xFFFF) / 0xFFFF;
190
+ const uBottomRight = (texCoords[2] & 0xFFFF) / 0xFFFF;
191
+ const vBottomRight = ((texCoords[2] >>> 16) & 0xFFFF) / 0xFFFF;
192
+ // For flipY: TL.v becomes vMax (was minY → maxY). The shader picks
193
+ // (cornerY == 0 ? vMin : vMax); writing TL.v into vMin and BR.v into
194
+ // vMax matches the original per-corner ordering whether or not flipY
195
+ // was applied at texCoords pack time.
196
+ this._uvBoundsScratch[0] = uTopLeft;
197
+ this._uvBoundsScratch[1] = vTopLeft;
198
+ this._uvBoundsScratch[2] = uBottomRight;
199
+ this._uvBoundsScratch[3] = vBottomRight;
200
+ return this._uvBoundsScratch;
201
+ }
202
+ _createConnection(gl) {
203
+ const vaoHandle = gl.createVertexArray();
204
+ if (vaoHandle === null) {
205
+ throw new Error('WebGl2ParticleRenderer: could not create vertex array object.');
206
+ }
207
+ return {
208
+ gl,
209
+ buffers: new Map(),
210
+ vaoHandle,
211
+ };
212
+ }
213
+ _createBufferRuntime(connection) {
214
+ const handle = connection.gl.createBuffer();
215
+ if (handle === null) {
216
+ throw new Error('WebGl2ParticleRenderer: could not create render buffer.');
217
+ }
218
+ return {
219
+ bind: (buffer) => {
220
+ connection.gl.bindBuffer(buffer.type, handle);
221
+ },
222
+ upload: (buffer, offset) => {
223
+ const gl = connection.gl;
224
+ const data = buffer.data;
225
+ const state = connection.buffers.get(buffer);
226
+ gl.bindBuffer(buffer.type, handle);
227
+ if (state && state.dataByteLength >= data.byteLength) {
228
+ gl.bufferSubData(buffer.type, offset, data);
229
+ state.dataByteLength = data.byteLength;
230
+ }
231
+ else {
232
+ gl.bufferData(buffer.type, data, buffer.usage);
233
+ connection.buffers.set(buffer, { handle, dataByteLength: data.byteLength });
234
+ }
235
+ },
236
+ destroy: (buffer) => {
237
+ connection.gl.deleteBuffer(handle);
238
+ connection.buffers.delete(buffer);
239
+ buffer.disconnect();
240
+ },
241
+ };
242
+ }
243
+ _createVaoRuntime(connection) {
244
+ let appliedVersion = -1;
245
+ return {
246
+ bind: (vao) => {
247
+ const gl = connection.gl;
248
+ gl.bindVertexArray(connection.vaoHandle);
249
+ if (appliedVersion !== vao.version) {
250
+ let lastBuffer = null;
251
+ for (const attribute of vao.attributes) {
252
+ if (lastBuffer !== attribute.buffer) {
253
+ attribute.buffer.bind();
254
+ lastBuffer = attribute.buffer;
255
+ }
256
+ if (attribute.integer) {
257
+ gl.vertexAttribIPointer(attribute.location, attribute.size, attribute.type, attribute.stride, attribute.start);
258
+ }
259
+ else {
260
+ gl.vertexAttribPointer(attribute.location, attribute.size, attribute.type, attribute.normalized, attribute.stride, attribute.start);
261
+ }
262
+ gl.enableVertexAttribArray(attribute.location);
263
+ gl.vertexAttribDivisor(attribute.location, attribute.divisor);
264
+ }
265
+ if (vao.indexBuffer) {
266
+ vao.indexBuffer.bind();
267
+ }
268
+ appliedVersion = vao.version;
269
+ }
270
+ },
271
+ unbind: () => {
272
+ connection.gl.bindVertexArray(null);
273
+ },
274
+ draw: (vao, size, start, type) => {
275
+ const gl = connection.gl;
276
+ if (vao.indexBuffer) {
277
+ gl.drawElements(type, size, gl.UNSIGNED_SHORT, start);
278
+ }
279
+ else {
280
+ gl.drawArrays(type, start, size);
281
+ }
282
+ },
283
+ drawInstanced: (vao, count, start, instanceCount, type) => {
284
+ const gl = connection.gl;
285
+ if (vao.indexBuffer) {
286
+ gl.drawElementsInstanced(type, count, gl.UNSIGNED_SHORT, start, instanceCount);
287
+ }
288
+ else {
289
+ gl.drawArraysInstanced(type, start, count, instanceCount);
290
+ }
291
+ },
292
+ destroy: (vao) => {
293
+ connection.gl.deleteVertexArray(connection.vaoHandle);
294
+ vao.disconnect();
295
+ },
296
+ };
106
297
  }
107
298
  }
108
299