@codexo/exojs 0.6.4 → 0.6.5
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 +87 -0
- package/dist/esm/core/Application.d.ts +0 -1
- package/dist/esm/core/Application.js +0 -1
- package/dist/esm/core/Application.js.map +1 -1
- package/dist/esm/core/SceneManager.js +11 -12
- package/dist/esm/core/SceneManager.js.map +1 -1
- package/dist/esm/index.js +0 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/math/geometry.d.ts +12 -8
- package/dist/esm/math/geometry.js +119 -72
- package/dist/esm/math/geometry.js.map +1 -1
- package/dist/esm/rendering/index.d.ts +0 -5
- package/dist/esm/rendering/primitives/Graphics.d.ts +3 -2
- package/dist/esm/rendering/primitives/Graphics.js +33 -25
- package/dist/esm/rendering/primitives/Graphics.js.map +1 -1
- package/dist/esm/rendering/webgl2/WebGl2Backend.js +1 -4
- package/dist/esm/rendering/webgl2/WebGl2Backend.js.map +1 -1
- package/dist/esm/rendering/webgpu/WebGpuBackend.js +0 -3
- package/dist/esm/rendering/webgpu/WebGpuBackend.js.map +1 -1
- package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js +1 -2
- package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js.map +1 -1
- package/dist/exo.esm.js +586 -1274
- package/dist/exo.esm.js.map +1 -1
- package/package.json +1 -1
- package/dist/esm/rendering/primitives/CircleGeometry.d.ts +0 -4
- package/dist/esm/rendering/primitives/CircleGeometry.js +0 -21
- package/dist/esm/rendering/primitives/CircleGeometry.js.map +0 -1
- package/dist/esm/rendering/primitives/DrawableShape.d.ts +0 -11
- package/dist/esm/rendering/primitives/DrawableShape.js +0 -21
- package/dist/esm/rendering/primitives/DrawableShape.js.map +0 -1
- package/dist/esm/rendering/primitives/Geometry.d.ts +0 -13
- package/dist/esm/rendering/primitives/Geometry.js +0 -16
- package/dist/esm/rendering/primitives/Geometry.js.map +0 -1
- package/dist/esm/rendering/webgl2/WebGl2PrimitiveRenderer.d.ts +0 -26
- package/dist/esm/rendering/webgl2/WebGl2PrimitiveRenderer.js +0 -222
- package/dist/esm/rendering/webgl2/WebGl2PrimitiveRenderer.js.map +0 -1
- package/dist/esm/rendering/webgl2/glsl/primitive.frag.js +0 -4
- package/dist/esm/rendering/webgl2/glsl/primitive.frag.js.map +0 -1
- package/dist/esm/rendering/webgl2/glsl/primitive.vert.js +0 -4
- package/dist/esm/rendering/webgl2/glsl/primitive.vert.js.map +0 -1
- package/dist/esm/rendering/webgpu/WebGpuPrimitiveRenderer.d.ts +0 -38
- package/dist/esm/rendering/webgpu/WebGpuPrimitiveRenderer.js +0 -488
- package/dist/esm/rendering/webgpu/WebGpuPrimitiveRenderer.js.map +0 -1
package/dist/exo.esm.js
CHANGED
|
@@ -3642,37 +3642,117 @@ class Drawable extends RenderNode {
|
|
|
3642
3642
|
}
|
|
3643
3643
|
}
|
|
3644
3644
|
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3645
|
+
/**
|
|
3646
|
+
* Arbitrary 2D triangle-mesh primitive.
|
|
3647
|
+
*
|
|
3648
|
+
* `Mesh` lives alongside {@link Sprite} as a public Drawable: it has the
|
|
3649
|
+
* same transform (position/rotation/scale/origin), tint, blendMode,
|
|
3650
|
+
* filters, masks, and cacheAsBitmap — but the geometry it renders is
|
|
3651
|
+
* user-supplied rather than implied by a texture frame. The intended use
|
|
3652
|
+
* cases are:
|
|
3653
|
+
*
|
|
3654
|
+
* - Custom-shape sprites whose silhouette isn't a quad (badges, speech
|
|
3655
|
+
* bubbles, region overlays).
|
|
3656
|
+
* - Deformable visuals (rope/ribbon, banner, water surface): mutate the
|
|
3657
|
+
* vertex array between frames and the GPU re-tessellates nothing —
|
|
3658
|
+
* only the transform changes per frame.
|
|
3659
|
+
* - Particles or trails with custom geometry per emitter.
|
|
3660
|
+
*
|
|
3661
|
+
* The mesh data is **immutable after construction** in v1: vertex /
|
|
3662
|
+
* index / UV / color arrays are exposed as readonly references. Mutate
|
|
3663
|
+
* the underlying typed arrays in-place if you need per-frame updates,
|
|
3664
|
+
* but the array lengths and topology cannot change. Texture is the only
|
|
3665
|
+
* post-construction mutable property.
|
|
3666
|
+
*
|
|
3667
|
+
* The vertex stream is a flat `Float32Array` of (x, y) pairs in local
|
|
3668
|
+
* space. The mesh's local bounds are computed once at construction from
|
|
3669
|
+
* the AABB of those vertices and used by the cull pass. Re-computing
|
|
3670
|
+
* after in-place mutation is the caller's responsibility (call
|
|
3671
|
+
* `recomputeLocalBounds()`).
|
|
3672
|
+
*/
|
|
3673
|
+
class Mesh extends Drawable {
|
|
3674
|
+
vertices;
|
|
3675
|
+
indices;
|
|
3676
|
+
uvs;
|
|
3677
|
+
colors;
|
|
3678
|
+
_texture;
|
|
3679
|
+
constructor(options) {
|
|
3650
3680
|
super();
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3681
|
+
const { vertices, indices = null, uvs = null, colors = null, texture = null } = options;
|
|
3682
|
+
if (vertices.length === 0 || vertices.length % 2 !== 0) {
|
|
3683
|
+
throw new Error(`Mesh vertices must be a non-empty flat array of (x,y) pairs (got length ${vertices.length}).`);
|
|
3684
|
+
}
|
|
3685
|
+
const vertexCount = vertices.length / 2;
|
|
3686
|
+
if (vertexCount < 3) {
|
|
3687
|
+
throw new Error(`Mesh requires at least 3 vertices (got ${vertexCount}).`);
|
|
3688
|
+
}
|
|
3689
|
+
if (uvs !== null && uvs.length !== vertices.length) {
|
|
3690
|
+
throw new Error(`Mesh uvs length ${uvs.length} must equal vertices length ${vertices.length}.`);
|
|
3691
|
+
}
|
|
3692
|
+
if (colors !== null && colors.length !== vertexCount) {
|
|
3693
|
+
throw new Error(`Mesh colors length ${colors.length} must equal vertex count ${vertexCount}.`);
|
|
3694
|
+
}
|
|
3695
|
+
if (indices !== null) {
|
|
3696
|
+
if (indices.length === 0 || indices.length % 3 !== 0) {
|
|
3697
|
+
throw new Error(`Mesh indices must be a non-empty multiple of 3 (got length ${indices.length}).`);
|
|
3698
|
+
}
|
|
3699
|
+
for (let i = 0; i < indices.length; i++) {
|
|
3700
|
+
if (indices[i] >= vertexCount) {
|
|
3701
|
+
throw new Error(`Mesh index ${indices[i]} at position ${i} is out of range for vertex count ${vertexCount}.`);
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
3705
|
+
else if (vertexCount % 3 !== 0) {
|
|
3706
|
+
throw new Error(`Non-indexed Mesh requires a vertex count that is a multiple of 3 (got ${vertexCount}).`);
|
|
3707
|
+
}
|
|
3708
|
+
this.vertices = vertices;
|
|
3709
|
+
this.indices = indices;
|
|
3710
|
+
this.uvs = uvs;
|
|
3711
|
+
this.colors = colors;
|
|
3712
|
+
this._texture = texture;
|
|
3713
|
+
this.recomputeLocalBounds();
|
|
3654
3714
|
}
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
this.color.destroy();
|
|
3715
|
+
get vertexCount() {
|
|
3716
|
+
return this.vertices.length / 2;
|
|
3658
3717
|
}
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
class Geometry {
|
|
3662
|
-
vertices;
|
|
3663
|
-
indices;
|
|
3664
|
-
points;
|
|
3665
|
-
constructor({ vertices = [], indices = [], points = [], } = {}) {
|
|
3666
|
-
this.vertices = new Float32Array(vertices);
|
|
3667
|
-
this.indices = new Uint16Array(indices);
|
|
3668
|
-
this.points = points;
|
|
3718
|
+
get indexCount() {
|
|
3719
|
+
return this.indices?.length ?? this.vertexCount;
|
|
3669
3720
|
}
|
|
3670
|
-
|
|
3671
|
-
|
|
3721
|
+
get texture() {
|
|
3722
|
+
return this._texture;
|
|
3723
|
+
}
|
|
3724
|
+
set texture(texture) {
|
|
3725
|
+
this._texture = texture;
|
|
3726
|
+
this.invalidateCache();
|
|
3727
|
+
}
|
|
3728
|
+
/**
|
|
3729
|
+
* Recompute the local AABB from the current vertex array. Call after
|
|
3730
|
+
* mutating `vertices` in place to keep culling correct; otherwise the
|
|
3731
|
+
* bounds the cull pass sees will be the AABB at construction time.
|
|
3732
|
+
*/
|
|
3733
|
+
recomputeLocalBounds() {
|
|
3734
|
+
let minX = Infinity;
|
|
3735
|
+
let minY = Infinity;
|
|
3736
|
+
let maxX = -Infinity;
|
|
3737
|
+
let maxY = -Infinity;
|
|
3738
|
+
for (let i = 0; i < this.vertices.length; i += 2) {
|
|
3739
|
+
const x = this.vertices[i];
|
|
3740
|
+
const y = this.vertices[i + 1];
|
|
3741
|
+
if (x < minX)
|
|
3742
|
+
minX = x;
|
|
3743
|
+
if (x > maxX)
|
|
3744
|
+
maxX = x;
|
|
3745
|
+
if (y < minY)
|
|
3746
|
+
minY = y;
|
|
3747
|
+
if (y > maxY)
|
|
3748
|
+
maxY = y;
|
|
3749
|
+
}
|
|
3750
|
+
this.localBounds.set(minX, minY, maxX - minX, maxY - minY);
|
|
3751
|
+
return this;
|
|
3672
3752
|
}
|
|
3673
3753
|
}
|
|
3674
3754
|
|
|
3675
|
-
class
|
|
3755
|
+
class TransitionOverlayMesh extends Mesh {
|
|
3676
3756
|
render(backend) {
|
|
3677
3757
|
if (this.visible) {
|
|
3678
3758
|
backend.draw(this);
|
|
@@ -3680,16 +3760,16 @@ class TransitionOverlayShape extends DrawableShape {
|
|
|
3680
3760
|
return this;
|
|
3681
3761
|
}
|
|
3682
3762
|
}
|
|
3683
|
-
const
|
|
3684
|
-
vertices
|
|
3685
|
-
|
|
3686
|
-
|
|
3763
|
+
const createOverlayMesh = () => new TransitionOverlayMesh({
|
|
3764
|
+
// 4 vertices (TL, TR, BL, BR) with 2 indexed triangles forming a screen quad.
|
|
3765
|
+
vertices: new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]),
|
|
3766
|
+
indices: new Uint16Array([0, 1, 2, 1, 3, 2]),
|
|
3687
3767
|
});
|
|
3688
3768
|
const defaultFadeTransitionDuration = 220;
|
|
3689
3769
|
class SceneManager {
|
|
3690
3770
|
_app;
|
|
3691
3771
|
_stack = [];
|
|
3692
|
-
_transitionOverlay =
|
|
3772
|
+
_transitionOverlay = createOverlayMesh();
|
|
3693
3773
|
_transition = null;
|
|
3694
3774
|
onChangeScene = new Signal();
|
|
3695
3775
|
onStartScene = new Signal();
|
|
@@ -4052,7 +4132,8 @@ class SceneManager {
|
|
|
4052
4132
|
const overlayColor = transition ? transition.color : Color.black;
|
|
4053
4133
|
const backend = this._app.backend;
|
|
4054
4134
|
const bounds = backend.view.getBounds();
|
|
4055
|
-
const
|
|
4135
|
+
const overlay = this._transitionOverlay;
|
|
4136
|
+
const vertices = overlay.vertices;
|
|
4056
4137
|
vertices[0] = bounds.left;
|
|
4057
4138
|
vertices[1] = bounds.top;
|
|
4058
4139
|
vertices[2] = bounds.right;
|
|
@@ -4061,8 +4142,8 @@ class SceneManager {
|
|
|
4061
4142
|
vertices[5] = bounds.bottom;
|
|
4062
4143
|
vertices[6] = bounds.right;
|
|
4063
4144
|
vertices[7] = bounds.bottom;
|
|
4064
|
-
|
|
4065
|
-
|
|
4145
|
+
overlay.tint.set(overlayColor.r, overlayColor.g, overlayColor.b, Math.max(0, Math.min(1, alpha)));
|
|
4146
|
+
overlay.render(backend);
|
|
4066
4147
|
}
|
|
4067
4148
|
}
|
|
4068
4149
|
|
|
@@ -5834,9 +5915,9 @@ class WebGl2VertexArrayObject {
|
|
|
5834
5915
|
}
|
|
5835
5916
|
}
|
|
5836
5917
|
|
|
5837
|
-
var vertexSource$
|
|
5918
|
+
var vertexSource$3 = "#version 300 es\nprecision lowp float;\nprecision lowp int;\n\n// Per-instance attributes (divisor = 1). Each Sprite contributes one entry\n// to the per-instance buffer; gl_VertexID 0..3 selects which corner of the\n// quad this invocation is computing.\nlayout(location = 0) in vec4 a_localBounds; // left, top, right, bottom (local space)\nlayout(location = 1) in vec3 a_transformAB; // a, b, x — first row of 2D affine\nlayout(location = 2) in vec3 a_transformCD; // c, d, y — second row\nlayout(location = 3) in vec4 a_uvBounds; // uMin, vMin, uMax, vMax (normalised, already flipY-swapped)\nlayout(location = 4) in vec4 a_color; // RGBA tint\nlayout(location = 5) in uint a_textureSlot;\n\nuniform mat3 u_projection;\n\nout vec2 v_texcoord;\nout vec4 v_color;\nflat out uint v_textureSlot;\n\nvoid main(void) {\n // gl_VertexID 0..3 → corner: 0=TL, 1=TR, 2=BL, 3=BR (TRIANGLE_STRIP order)\n int vid = gl_VertexID;\n int cornerX = vid & 1;\n int cornerY = (vid >> 1) & 1;\n\n // Local-space corner: pick from the bounds rectangle.\n float localX = (cornerX == 0) ? a_localBounds.x : a_localBounds.z;\n float localY = (cornerY == 0) ? a_localBounds.y : a_localBounds.w;\n\n // Apply the per-instance affine transform: world = M * (localX, localY, 1)\n float worldX = (a_transformAB.x * localX) + (a_transformAB.y * localY) + a_transformAB.z;\n float worldY = (a_transformCD.x * localX) + (a_transformCD.y * localY) + a_transformCD.z;\n\n gl_Position = vec4((u_projection * vec3(worldX, worldY, 1.0)).xy, 0.0, 1.0);\n\n // UV: pick from the bounds rectangle. The CPU pre-swaps Y bounds when\n // the texture is flipY, so the shader doesn't have to know.\n float u = (cornerX == 0) ? a_uvBounds.x : a_uvBounds.z;\n float v = (cornerY == 0) ? a_uvBounds.y : a_uvBounds.w;\n v_texcoord = vec2(u, v);\n\n v_color = vec4(a_color.rgb * a_color.a, a_color.a);\n v_textureSlot = a_textureSlot;\n}\n";
|
|
5838
5919
|
|
|
5839
|
-
var fragmentSource$
|
|
5920
|
+
var fragmentSource$3 = "#version 300 es\nprecision lowp float;\nprecision lowp int;\n\n// Multi-texture sprite batching: up to 8 textures bound per draw call,\n// each fragment selects its source via a flat-interpolated slot index.\n//\n// GLSL ES 3.0 forbids non-constant array-of-sampler indexing unless the\n// expression is dynamically uniform — which a per-vertex slot is not\n// once different triangles in the same batch carry different slots. The\n// if/else chain below dispatches statically and dodges that constraint.\n\nuniform sampler2D u_texture0;\nuniform sampler2D u_texture1;\nuniform sampler2D u_texture2;\nuniform sampler2D u_texture3;\nuniform sampler2D u_texture4;\nuniform sampler2D u_texture5;\nuniform sampler2D u_texture6;\nuniform sampler2D u_texture7;\n\nin vec2 v_texcoord;\nin vec4 v_color;\nflat in uint v_textureSlot;\n\nlayout(location = 0) out vec4 fragColor;\n\nvoid main(void) {\n vec4 sampleColor;\n\n if (v_textureSlot == 0u) {\n sampleColor = texture(u_texture0, v_texcoord);\n } else if (v_textureSlot == 1u) {\n sampleColor = texture(u_texture1, v_texcoord);\n } else if (v_textureSlot == 2u) {\n sampleColor = texture(u_texture2, v_texcoord);\n } else if (v_textureSlot == 3u) {\n sampleColor = texture(u_texture3, v_texcoord);\n } else if (v_textureSlot == 4u) {\n sampleColor = texture(u_texture4, v_texcoord);\n } else if (v_textureSlot == 5u) {\n sampleColor = texture(u_texture5, v_texcoord);\n } else if (v_textureSlot == 6u) {\n sampleColor = texture(u_texture6, v_texcoord);\n } else {\n sampleColor = texture(u_texture7, v_texcoord);\n }\n\n fragColor = sampleColor * v_color;\n}\n";
|
|
5840
5921
|
|
|
5841
5922
|
/**
|
|
5842
5923
|
* Instanced sprite renderer for WebGL2.
|
|
@@ -5884,7 +5965,7 @@ class WebGl2SpriteRenderer extends AbstractWebGl2Renderer {
|
|
|
5884
5965
|
constructor(batchSize) {
|
|
5885
5966
|
super();
|
|
5886
5967
|
this._batchSize = batchSize;
|
|
5887
|
-
this._shader = new Shader(vertexSource$
|
|
5968
|
+
this._shader = new Shader(vertexSource$3, fragmentSource$3);
|
|
5888
5969
|
this._instanceData = new ArrayBuffer(batchSize * instanceStrideBytes$3);
|
|
5889
5970
|
this._instanceFloat32 = new Float32Array(this._instanceData);
|
|
5890
5971
|
this._instanceUint32 = new Uint32Array(this._instanceData);
|
|
@@ -6309,26 +6390,26 @@ class Texture {
|
|
|
6309
6390
|
}
|
|
6310
6391
|
}
|
|
6311
6392
|
|
|
6312
|
-
var vertexSource$
|
|
6393
|
+
var vertexSource$2 = "#version 300 es\nprecision lowp float;\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\nlayout(location = 2) in vec4 a_color;\n\nuniform mat3 u_projection;\nuniform mat3 u_translation;\n\nout vec2 v_texcoord;\nout vec4 v_color;\n\nvoid main(void) {\n gl_Position = vec4((u_projection * u_translation * vec3(a_position, 1.0)).xy, 0.0, 1.0);\n v_texcoord = a_texcoord;\n v_color = a_color;\n}\n";
|
|
6313
6394
|
|
|
6314
|
-
var fragmentSource$
|
|
6395
|
+
var fragmentSource$2 = "#version 300 es\nprecision lowp float;\n\nuniform sampler2D u_texture;\nuniform vec4 u_tint;\n\nin vec2 v_texcoord;\nin vec4 v_color;\n\nlayout(location = 0) out vec4 fragColor;\n\nvoid main(void) {\n vec4 base = texture(u_texture, v_texcoord) * v_color * u_tint;\n fragColor = vec4(base.rgb * base.a, base.a);\n}\n";
|
|
6315
6396
|
|
|
6316
6397
|
// Per-vertex layout (20 bytes):
|
|
6317
6398
|
// position: vec2 f32 (offset 0, 8 bytes)
|
|
6318
6399
|
// texcoord: vec2 f32 (offset 8, 8 bytes)
|
|
6319
6400
|
// color: u8x4 norm (offset 16, 4 bytes)
|
|
6320
|
-
const vertexStrideBytes$
|
|
6321
|
-
const vertexStrideWords
|
|
6401
|
+
const vertexStrideBytes$3 = 20;
|
|
6402
|
+
const vertexStrideWords = vertexStrideBytes$3 / 4;
|
|
6322
6403
|
const initialVertexCapacity = 64;
|
|
6323
6404
|
const initialIndexCapacity = 192;
|
|
6324
6405
|
const defaultVertexColor = 0xFFFFFFFF; // white, full alpha
|
|
6325
6406
|
class WebGl2MeshRenderer extends AbstractWebGl2Renderer {
|
|
6326
|
-
_shader = new Shader(vertexSource$
|
|
6407
|
+
_shader = new Shader(vertexSource$2, fragmentSource$2);
|
|
6327
6408
|
_tintScratch = new Float32Array(4);
|
|
6328
6409
|
_textureUnitScratch = new Int32Array([0]);
|
|
6329
6410
|
_vertexCapacity = initialVertexCapacity;
|
|
6330
6411
|
_indexCapacity = initialIndexCapacity;
|
|
6331
|
-
_vertexData = new ArrayBuffer(initialVertexCapacity * vertexStrideBytes$
|
|
6412
|
+
_vertexData = new ArrayBuffer(initialVertexCapacity * vertexStrideBytes$3);
|
|
6332
6413
|
_float32View = new Float32Array(this._vertexData);
|
|
6333
6414
|
_uint32View = new Uint32Array(this._vertexData);
|
|
6334
6415
|
_indexData = new Uint16Array(initialIndexCapacity);
|
|
@@ -6375,7 +6456,7 @@ class WebGl2MeshRenderer extends AbstractWebGl2Renderer {
|
|
|
6375
6456
|
const uvs = mesh.uvs;
|
|
6376
6457
|
const colors = mesh.colors;
|
|
6377
6458
|
for (let i = 0; i < vertexCount; i++) {
|
|
6378
|
-
const word = i * vertexStrideWords
|
|
6459
|
+
const word = i * vertexStrideWords;
|
|
6379
6460
|
const pair = i * 2;
|
|
6380
6461
|
this._float32View[word] = positions[pair];
|
|
6381
6462
|
this._float32View[word + 1] = positions[pair + 1];
|
|
@@ -6401,7 +6482,7 @@ class WebGl2MeshRenderer extends AbstractWebGl2Renderer {
|
|
|
6401
6482
|
}
|
|
6402
6483
|
this._shader.sync();
|
|
6403
6484
|
backend.bindVertexArrayObject(connection.vao);
|
|
6404
|
-
connection.vertexBuffer.upload(this._float32View.subarray(0, vertexCount * vertexStrideWords
|
|
6485
|
+
connection.vertexBuffer.upload(this._float32View.subarray(0, vertexCount * vertexStrideWords));
|
|
6405
6486
|
connection.indexBuffer.upload(this._indexData.subarray(0, indexCount));
|
|
6406
6487
|
connection.vao.draw(indexCount, 0, RenderingPrimitives.Triangles);
|
|
6407
6488
|
backend.stats.batches++;
|
|
@@ -6434,9 +6515,9 @@ class WebGl2MeshRenderer extends AbstractWebGl2Renderer {
|
|
|
6434
6515
|
this._shader.sync();
|
|
6435
6516
|
const vao = new WebGl2VertexArrayObject()
|
|
6436
6517
|
.addIndex(indexBuffer)
|
|
6437
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('a_position'), gl.FLOAT, false, vertexStrideBytes$
|
|
6438
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('a_texcoord'), gl.FLOAT, false, vertexStrideBytes$
|
|
6439
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('a_color'), gl.UNSIGNED_BYTE, true, vertexStrideBytes$
|
|
6518
|
+
.addAttribute(vertexBuffer, this._shader.getAttribute('a_position'), gl.FLOAT, false, vertexStrideBytes$3, 0)
|
|
6519
|
+
.addAttribute(vertexBuffer, this._shader.getAttribute('a_texcoord'), gl.FLOAT, false, vertexStrideBytes$3, 8)
|
|
6520
|
+
.addAttribute(vertexBuffer, this._shader.getAttribute('a_color'), gl.UNSIGNED_BYTE, true, vertexStrideBytes$3, 16)
|
|
6440
6521
|
.connect(this._createVaoRuntime(gl, vaoHandle));
|
|
6441
6522
|
this._connection = { gl, vao, vertexBuffer, indexBuffer };
|
|
6442
6523
|
}
|
|
@@ -6461,7 +6542,7 @@ class WebGl2MeshRenderer extends AbstractWebGl2Renderer {
|
|
|
6461
6542
|
while (this._vertexCapacity < vertexCount) {
|
|
6462
6543
|
this._vertexCapacity *= 2;
|
|
6463
6544
|
}
|
|
6464
|
-
this._vertexData = new ArrayBuffer(this._vertexCapacity * vertexStrideBytes$
|
|
6545
|
+
this._vertexData = new ArrayBuffer(this._vertexCapacity * vertexStrideBytes$3);
|
|
6465
6546
|
this._float32View = new Float32Array(this._vertexData);
|
|
6466
6547
|
this._uint32View = new Uint32Array(this._vertexData);
|
|
6467
6548
|
}
|
|
@@ -6543,9 +6624,9 @@ class WebGl2MeshRenderer extends AbstractWebGl2Renderer {
|
|
|
6543
6624
|
}
|
|
6544
6625
|
}
|
|
6545
6626
|
|
|
6546
|
-
var vertexSource$
|
|
6627
|
+
var vertexSource$1 = "#version 300 es\nprecision lowp float;\nprecision lowp int;\n\n// Per-instance attributes (one entry per particle, 24 bytes total).\nlayout(location = 0) in vec2 a_translation; // particle position in system-local space\nlayout(location = 1) in vec2 a_scale; // particle scale\nlayout(location = 2) in float a_rotation; // particle rotation in degrees\nlayout(location = 3) in vec4 a_color; // RGBA tint\n\nuniform mat3 u_projection;\nuniform mat3 u_systemTransform;\nuniform vec4 u_localBounds; // left, top, right, bottom (system.vertices)\nuniform vec4 u_uvBounds; // uMin, vMin, uMax, vMax (flipY-swapped)\n\nout vec2 v_texcoord;\nout vec4 v_color;\n\nvoid main(void) {\n // Static index buffer is [0,1,2,0,2,3] (triangle-list), so gl_VertexID 0..3\n // maps to TL, TR, BR, BL via the same bit math the sprite renderer uses.\n int vid = gl_VertexID;\n int cornerX = ((vid + 1) >> 1) & 1;\n int cornerY = vid >> 1;\n\n float localX = (cornerX == 0) ? u_localBounds.x : u_localBounds.z;\n float localY = (cornerY == 0) ? u_localBounds.y : u_localBounds.w;\n\n // Per-particle scale + rotation.\n vec2 rotation = vec2(sin(radians(a_rotation)), cos(radians(a_rotation)));\n vec2 transformed = vec2(\n (localX * (a_scale.x * rotation.y)) + (localY * (a_scale.y * rotation.x)),\n (localX * (a_scale.x * -rotation.x)) + (localY * (a_scale.y * rotation.y))\n );\n\n vec3 worldPos = vec3(transformed + a_translation, 1.0);\n\n gl_Position = vec4((u_projection * u_systemTransform * worldPos).xy, 0.0, 1.0);\n\n float u = (cornerX == 0) ? u_uvBounds.x : u_uvBounds.z;\n float v = (cornerY == 0) ? u_uvBounds.y : u_uvBounds.w;\n v_texcoord = vec2(u, v);\n\n v_color = vec4(a_color.rgb * a_color.a, a_color.a);\n}\n";
|
|
6547
6628
|
|
|
6548
|
-
var fragmentSource$
|
|
6629
|
+
var fragmentSource$1 = "#version 300 es\nprecision lowp float;\n\nuniform sampler2D u_texture;\n\nin vec2 v_texcoord;\nin vec4 v_color;\n\nlayout(location = 0) out vec4 fragColor;\n\nvoid main(void) {\n fragColor = texture(u_texture, v_texcoord) * v_color;\n}\n";
|
|
6549
6630
|
|
|
6550
6631
|
/**
|
|
6551
6632
|
* Instanced particle renderer for WebGL2.
|
|
@@ -6597,7 +6678,7 @@ class WebGl2ParticleRenderer extends AbstractWebGl2Renderer {
|
|
|
6597
6678
|
constructor(batchSize) {
|
|
6598
6679
|
super();
|
|
6599
6680
|
this._batchSize = batchSize;
|
|
6600
|
-
this._shader = new Shader(vertexSource$
|
|
6681
|
+
this._shader = new Shader(vertexSource$1, fragmentSource$1);
|
|
6601
6682
|
this._instanceData = new ArrayBuffer(batchSize * instanceStrideBytes$2);
|
|
6602
6683
|
this._instanceFloat32 = new Float32Array(this._instanceData);
|
|
6603
6684
|
this._instanceUint32 = new Uint32Array(this._instanceData);
|
|
@@ -6837,315 +6918,100 @@ class WebGl2ParticleRenderer extends AbstractWebGl2Renderer {
|
|
|
6837
6918
|
}
|
|
6838
6919
|
}
|
|
6839
6920
|
|
|
6840
|
-
var vertexSource
|
|
6841
|
-
|
|
6842
|
-
var fragmentSource
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
const vertexStrideBytes$
|
|
6846
|
-
const
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6921
|
+
var vertexSource = "#version 300 es\nprecision lowp float;\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nuniform mat3 u_projection;\n\nout vec2 v_texcoord;\n\nvoid main(void) {\n gl_Position = vec4((u_projection * vec3(a_position, 1.0)).xy, 0.0, 1.0);\n v_texcoord = a_texcoord;\n}\n";
|
|
6922
|
+
|
|
6923
|
+
var fragmentSource = "#version 300 es\nprecision lowp float;\n\nuniform sampler2D u_content;\nuniform sampler2D u_mask;\n\nin vec2 v_texcoord;\n\nlayout(location = 0) out vec4 fragColor;\n\nvoid main(void) {\n vec4 contentColor = texture(u_content, v_texcoord);\n float maskAlpha = texture(u_mask, v_texcoord).a;\n\n fragColor = vec4(contentColor.rgb * maskAlpha, contentColor.a * maskAlpha);\n}\n";
|
|
6924
|
+
|
|
6925
|
+
// 4 floats per vertex: position(x, y) + texcoord(u, v).
|
|
6926
|
+
const vertexStrideBytes$2 = 16;
|
|
6927
|
+
const quadIndices$1 = new Uint16Array([0, 1, 2, 0, 2, 3]);
|
|
6928
|
+
/**
|
|
6929
|
+
* Single-quad two-texture compositor used by `WebGl2Backend.composeWithAlphaMask`.
|
|
6930
|
+
*
|
|
6931
|
+
* Renders the content texture onto the active render target with each
|
|
6932
|
+
* output texel's alpha multiplied by the mask texture's alpha at the
|
|
6933
|
+
* same UV. Both textures are sampled with stretched-fit UVs over the
|
|
6934
|
+
* destination rectangle.
|
|
6935
|
+
*
|
|
6936
|
+
* Intentionally not a {@link AbstractWebGl2Renderer} subclass: this
|
|
6937
|
+
* compositor is invoked directly by the manager for non-Drawable
|
|
6938
|
+
* compositing operations and never participates in the renderer
|
|
6939
|
+
* registry dispatch path.
|
|
6940
|
+
*/
|
|
6941
|
+
class WebGl2MaskCompositor {
|
|
6942
|
+
_shader = new Shader(vertexSource, fragmentSource);
|
|
6943
|
+
_vertexData = new ArrayBuffer(4 * vertexStrideBytes$2);
|
|
6944
|
+
_float32View = new Float32Array(this._vertexData);
|
|
6945
|
+
_contentSamplerSlot = new Int32Array([0]);
|
|
6946
|
+
_maskSamplerSlot = new Int32Array([1]);
|
|
6855
6947
|
_connection = null;
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
_viewId = -1;
|
|
6859
|
-
constructor(batchSize) {
|
|
6860
|
-
super();
|
|
6861
|
-
this._vertexCapacity = Math.max(minBatchVertexSize, batchSize);
|
|
6862
|
-
this._indexCapacity = Math.max(6, this._vertexCapacity * 3);
|
|
6863
|
-
this._vertexData = new ArrayBuffer(this._vertexCapacity * vertexStrideBytes$4);
|
|
6864
|
-
this._indexData = new Uint16Array(this._indexCapacity);
|
|
6865
|
-
this._float32View = new Float32Array(this._vertexData);
|
|
6866
|
-
this._uint32View = new Uint32Array(this._vertexData);
|
|
6867
|
-
}
|
|
6868
|
-
render(shape) {
|
|
6869
|
-
const connection = this._connection;
|
|
6870
|
-
if (!connection) {
|
|
6871
|
-
throw new Error('Renderer not connected');
|
|
6872
|
-
}
|
|
6873
|
-
const backend = this.getBackend();
|
|
6874
|
-
const { geometry, drawMode, color, blendMode } = shape;
|
|
6875
|
-
const vertices = geometry.vertices;
|
|
6876
|
-
const sourceIndices = geometry.indices;
|
|
6877
|
-
const vertexCount = vertices.length / 2;
|
|
6878
|
-
const indexCount = sourceIndices.length > 0 ? sourceIndices.length : vertexCount;
|
|
6879
|
-
if (vertexCount === 0 || indexCount === 0) {
|
|
6948
|
+
connect(backend) {
|
|
6949
|
+
if (this._connection !== null) {
|
|
6880
6950
|
return;
|
|
6881
6951
|
}
|
|
6882
|
-
this._ensureVertexCapacity(vertexCount);
|
|
6883
|
-
this._ensureIndexCapacity(indexCount);
|
|
6884
|
-
if (blendMode !== this._currentBlendMode) {
|
|
6885
|
-
this._currentBlendMode = blendMode;
|
|
6886
|
-
backend.setBlendMode(blendMode);
|
|
6887
|
-
}
|
|
6888
|
-
const view = backend.view;
|
|
6889
|
-
if (this._currentView !== view || this._viewId !== view.updateId) {
|
|
6890
|
-
this._currentView = view;
|
|
6891
|
-
this._viewId = view.updateId;
|
|
6892
|
-
this._shader.getUniform('u_projection').setValue(view.getTransform().toArray(false));
|
|
6893
|
-
}
|
|
6894
|
-
this._shader.getUniform('u_translation').setValue(shape.getGlobalTransform().toArray(false));
|
|
6895
|
-
const packedColor = color.toRgba();
|
|
6896
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
6897
|
-
const sourceIndex = i * 2;
|
|
6898
|
-
const targetIndex = i * vertexStrideWords;
|
|
6899
|
-
this._float32View[targetIndex] = vertices[sourceIndex];
|
|
6900
|
-
this._float32View[targetIndex + 1] = vertices[sourceIndex + 1];
|
|
6901
|
-
this._uint32View[targetIndex + 2] = packedColor;
|
|
6902
|
-
}
|
|
6903
|
-
if (sourceIndices.length > 0) {
|
|
6904
|
-
this._indexData.set(sourceIndices, 0);
|
|
6905
|
-
}
|
|
6906
|
-
else {
|
|
6907
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
6908
|
-
this._indexData[i] = i;
|
|
6909
|
-
}
|
|
6910
|
-
}
|
|
6911
|
-
this._shader.sync();
|
|
6912
|
-
backend.bindVertexArrayObject(connection.vao);
|
|
6913
|
-
connection.vertexBuffer.upload(this._float32View.subarray(0, vertexCount * vertexStrideWords));
|
|
6914
|
-
connection.indexBuffer.upload(this._indexData.subarray(0, indexCount));
|
|
6915
|
-
connection.vao.draw(indexCount, 0, drawMode);
|
|
6916
|
-
backend.stats.batches++;
|
|
6917
|
-
backend.stats.drawCalls++;
|
|
6918
|
-
}
|
|
6919
|
-
flush() {
|
|
6920
|
-
// Primitive rendering is immediate per shape in this bridge stage.
|
|
6921
|
-
}
|
|
6922
|
-
destroy() {
|
|
6923
|
-
this.disconnect();
|
|
6924
|
-
this._shader.destroy();
|
|
6925
|
-
this._currentBlendMode = null;
|
|
6926
|
-
this._currentView = null;
|
|
6927
|
-
}
|
|
6928
|
-
onConnect(backend) {
|
|
6929
6952
|
const gl = backend.context;
|
|
6930
6953
|
const vaoHandle = gl.createVertexArray();
|
|
6931
|
-
this._shader.connect(createWebGl2ShaderProgram(gl));
|
|
6932
6954
|
if (vaoHandle === null) {
|
|
6933
|
-
throw new Error('
|
|
6955
|
+
throw new Error('WebGl2MaskCompositor: could not create vertex array object.');
|
|
6934
6956
|
}
|
|
6935
|
-
|
|
6936
|
-
const
|
|
6937
|
-
|
|
6957
|
+
this._shader.connect(createWebGl2ShaderProgram(gl));
|
|
6958
|
+
const bufferHandles = new Map();
|
|
6959
|
+
const indexBuffer = new WebGl2RenderBuffer(BufferTypes.ElementArrayBuffer, quadIndices$1, BufferUsage.StaticDraw)
|
|
6960
|
+
.connect(this._createBufferRuntime(gl, bufferHandles));
|
|
6938
6961
|
const vertexBuffer = new WebGl2RenderBuffer(BufferTypes.ArrayBuffer, this._vertexData, BufferUsage.DynamicDraw)
|
|
6939
|
-
.connect(this._createBufferRuntime(gl,
|
|
6940
|
-
// Force shader finalize so
|
|
6941
|
-
// async-compile path defers
|
|
6942
|
-
//
|
|
6943
|
-
// throw "Attribute 'a_position' is not available".
|
|
6962
|
+
.connect(this._createBufferRuntime(gl, bufferHandles));
|
|
6963
|
+
// Force shader finalize so getAttribute() below sees a populated
|
|
6964
|
+
// attribute table; the async-compile path defers extraction until
|
|
6965
|
+
// the first sync() call.
|
|
6944
6966
|
this._shader.sync();
|
|
6945
6967
|
const vao = new WebGl2VertexArrayObject()
|
|
6946
6968
|
.addIndex(indexBuffer)
|
|
6947
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('a_position'), gl.FLOAT, false, vertexStrideBytes$
|
|
6948
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('
|
|
6969
|
+
.addAttribute(vertexBuffer, this._shader.getAttribute('a_position'), gl.FLOAT, false, vertexStrideBytes$2, 0)
|
|
6970
|
+
.addAttribute(vertexBuffer, this._shader.getAttribute('a_texcoord'), gl.FLOAT, false, vertexStrideBytes$2, 8)
|
|
6949
6971
|
.connect(this._createVaoRuntime(gl, vaoHandle));
|
|
6950
|
-
this._connection = { gl,
|
|
6972
|
+
this._connection = { gl, vaoHandle, vao, indexBuffer, vertexBuffer, bufferHandles };
|
|
6951
6973
|
}
|
|
6952
|
-
|
|
6974
|
+
disconnect() {
|
|
6953
6975
|
const connection = this._connection;
|
|
6954
|
-
if (
|
|
6976
|
+
if (connection === null) {
|
|
6955
6977
|
return;
|
|
6956
6978
|
}
|
|
6957
|
-
this._shader.disconnect();
|
|
6958
6979
|
connection.indexBuffer.destroy();
|
|
6959
6980
|
connection.vertexBuffer.destroy();
|
|
6960
6981
|
connection.vao.destroy();
|
|
6982
|
+
this._shader.disconnect();
|
|
6961
6983
|
this._connection = null;
|
|
6962
|
-
this._currentBlendMode = null;
|
|
6963
|
-
this._currentView = null;
|
|
6964
|
-
this._viewId = -1;
|
|
6965
6984
|
}
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
while (this._vertexCapacity < vertexCount) {
|
|
6971
|
-
this._vertexCapacity *= 2;
|
|
6985
|
+
compose(backend, content, mask, x, y, width, height, blendMode) {
|
|
6986
|
+
const connection = this._connection;
|
|
6987
|
+
if (connection === null) {
|
|
6988
|
+
throw new Error('WebGl2MaskCompositor: not connected.');
|
|
6972
6989
|
}
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
this.
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
gl.bindBuffer(buffer.type, handle);
|
|
6999
|
-
if (state && state.dataByteLength >= data.byteLength) {
|
|
7000
|
-
gl.bufferSubData(buffer.type, offset, data);
|
|
7001
|
-
state.dataByteLength = data.byteLength;
|
|
7002
|
-
}
|
|
7003
|
-
else {
|
|
7004
|
-
gl.bufferData(buffer.type, data, buffer.usage);
|
|
7005
|
-
buffers.set(buffer, { handle, dataByteLength: data.byteLength });
|
|
7006
|
-
}
|
|
7007
|
-
},
|
|
7008
|
-
destroy: (buffer) => {
|
|
7009
|
-
gl.deleteBuffer(handle);
|
|
7010
|
-
buffers.delete(buffer);
|
|
7011
|
-
buffer.disconnect();
|
|
7012
|
-
},
|
|
7013
|
-
};
|
|
7014
|
-
}
|
|
7015
|
-
_createVaoRuntime(gl, vaoHandle) {
|
|
7016
|
-
let appliedVersion = -1;
|
|
7017
|
-
return {
|
|
7018
|
-
bind: (vao) => {
|
|
7019
|
-
gl.bindVertexArray(vaoHandle);
|
|
7020
|
-
if (appliedVersion !== vao.version) {
|
|
7021
|
-
let lastBuffer = null;
|
|
7022
|
-
for (const attribute of vao.attributes) {
|
|
7023
|
-
if (lastBuffer !== attribute.buffer) {
|
|
7024
|
-
attribute.buffer.bind();
|
|
7025
|
-
lastBuffer = attribute.buffer;
|
|
7026
|
-
}
|
|
7027
|
-
gl.vertexAttribPointer(attribute.location, attribute.size, attribute.type, attribute.normalized, attribute.stride, attribute.start);
|
|
7028
|
-
gl.enableVertexAttribArray(attribute.location);
|
|
7029
|
-
}
|
|
7030
|
-
if (vao.indexBuffer) {
|
|
7031
|
-
vao.indexBuffer.bind();
|
|
7032
|
-
}
|
|
7033
|
-
appliedVersion = vao.version;
|
|
7034
|
-
}
|
|
7035
|
-
},
|
|
7036
|
-
unbind: () => {
|
|
7037
|
-
gl.bindVertexArray(null);
|
|
7038
|
-
},
|
|
7039
|
-
draw: (vao, size, start, type) => {
|
|
7040
|
-
if (vao.indexBuffer) {
|
|
7041
|
-
gl.drawElements(type, size, gl.UNSIGNED_SHORT, start);
|
|
7042
|
-
}
|
|
7043
|
-
else {
|
|
7044
|
-
gl.drawArrays(type, start, size);
|
|
7045
|
-
}
|
|
7046
|
-
},
|
|
7047
|
-
destroy: (vao) => {
|
|
7048
|
-
gl.deleteVertexArray(vaoHandle);
|
|
7049
|
-
vao.disconnect();
|
|
7050
|
-
},
|
|
7051
|
-
};
|
|
7052
|
-
}
|
|
7053
|
-
}
|
|
7054
|
-
|
|
7055
|
-
var vertexSource = "#version 300 es\nprecision lowp float;\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nuniform mat3 u_projection;\n\nout vec2 v_texcoord;\n\nvoid main(void) {\n gl_Position = vec4((u_projection * vec3(a_position, 1.0)).xy, 0.0, 1.0);\n v_texcoord = a_texcoord;\n}\n";
|
|
7056
|
-
|
|
7057
|
-
var fragmentSource = "#version 300 es\nprecision lowp float;\n\nuniform sampler2D u_content;\nuniform sampler2D u_mask;\n\nin vec2 v_texcoord;\n\nlayout(location = 0) out vec4 fragColor;\n\nvoid main(void) {\n vec4 contentColor = texture(u_content, v_texcoord);\n float maskAlpha = texture(u_mask, v_texcoord).a;\n\n fragColor = vec4(contentColor.rgb * maskAlpha, contentColor.a * maskAlpha);\n}\n";
|
|
7058
|
-
|
|
7059
|
-
// 4 floats per vertex: position(x, y) + texcoord(u, v).
|
|
7060
|
-
const vertexStrideBytes$3 = 16;
|
|
7061
|
-
const quadIndices$1 = new Uint16Array([0, 1, 2, 0, 2, 3]);
|
|
7062
|
-
/**
|
|
7063
|
-
* Single-quad two-texture compositor used by `WebGl2Backend.composeWithAlphaMask`.
|
|
7064
|
-
*
|
|
7065
|
-
* Renders the content texture onto the active render target with each
|
|
7066
|
-
* output texel's alpha multiplied by the mask texture's alpha at the
|
|
7067
|
-
* same UV. Both textures are sampled with stretched-fit UVs over the
|
|
7068
|
-
* destination rectangle.
|
|
7069
|
-
*
|
|
7070
|
-
* Intentionally not a {@link AbstractWebGl2Renderer} subclass: this
|
|
7071
|
-
* compositor is invoked directly by the manager for non-Drawable
|
|
7072
|
-
* compositing operations and never participates in the renderer
|
|
7073
|
-
* registry dispatch path.
|
|
7074
|
-
*/
|
|
7075
|
-
class WebGl2MaskCompositor {
|
|
7076
|
-
_shader = new Shader(vertexSource, fragmentSource);
|
|
7077
|
-
_vertexData = new ArrayBuffer(4 * vertexStrideBytes$3);
|
|
7078
|
-
_float32View = new Float32Array(this._vertexData);
|
|
7079
|
-
_contentSamplerSlot = new Int32Array([0]);
|
|
7080
|
-
_maskSamplerSlot = new Int32Array([1]);
|
|
7081
|
-
_connection = null;
|
|
7082
|
-
connect(backend) {
|
|
7083
|
-
if (this._connection !== null) {
|
|
7084
|
-
return;
|
|
7085
|
-
}
|
|
7086
|
-
const gl = backend.context;
|
|
7087
|
-
const vaoHandle = gl.createVertexArray();
|
|
7088
|
-
if (vaoHandle === null) {
|
|
7089
|
-
throw new Error('WebGl2MaskCompositor: could not create vertex array object.');
|
|
7090
|
-
}
|
|
7091
|
-
this._shader.connect(createWebGl2ShaderProgram(gl));
|
|
7092
|
-
const bufferHandles = new Map();
|
|
7093
|
-
const indexBuffer = new WebGl2RenderBuffer(BufferTypes.ElementArrayBuffer, quadIndices$1, BufferUsage.StaticDraw)
|
|
7094
|
-
.connect(this._createBufferRuntime(gl, bufferHandles));
|
|
7095
|
-
const vertexBuffer = new WebGl2RenderBuffer(BufferTypes.ArrayBuffer, this._vertexData, BufferUsage.DynamicDraw)
|
|
7096
|
-
.connect(this._createBufferRuntime(gl, bufferHandles));
|
|
7097
|
-
// Force shader finalize so getAttribute() below sees a populated
|
|
7098
|
-
// attribute table; the async-compile path defers extraction until
|
|
7099
|
-
// the first sync() call.
|
|
7100
|
-
this._shader.sync();
|
|
7101
|
-
const vao = new WebGl2VertexArrayObject()
|
|
7102
|
-
.addIndex(indexBuffer)
|
|
7103
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('a_position'), gl.FLOAT, false, vertexStrideBytes$3, 0)
|
|
7104
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('a_texcoord'), gl.FLOAT, false, vertexStrideBytes$3, 8)
|
|
7105
|
-
.connect(this._createVaoRuntime(gl, vaoHandle));
|
|
7106
|
-
this._connection = { gl, vaoHandle, vao, indexBuffer, vertexBuffer, bufferHandles };
|
|
7107
|
-
}
|
|
7108
|
-
disconnect() {
|
|
7109
|
-
const connection = this._connection;
|
|
7110
|
-
if (connection === null) {
|
|
7111
|
-
return;
|
|
7112
|
-
}
|
|
7113
|
-
connection.indexBuffer.destroy();
|
|
7114
|
-
connection.vertexBuffer.destroy();
|
|
7115
|
-
connection.vao.destroy();
|
|
7116
|
-
this._shader.disconnect();
|
|
7117
|
-
this._connection = null;
|
|
7118
|
-
}
|
|
7119
|
-
compose(backend, content, mask, x, y, width, height, blendMode) {
|
|
7120
|
-
const connection = this._connection;
|
|
7121
|
-
if (connection === null) {
|
|
7122
|
-
throw new Error('WebGl2MaskCompositor: not connected.');
|
|
7123
|
-
}
|
|
7124
|
-
// Update the quad vertices for this destination rect. UVs are 0..1
|
|
7125
|
-
// mapped over (left, top) → (right, bottom) with an explicit Y-flip
|
|
7126
|
-
// for render-texture sampling: the mask path samples textures
|
|
7127
|
-
// already authored as RGBA in render-texture orientation.
|
|
7128
|
-
this._writeQuadVertices(x, y, x + width, y + height);
|
|
7129
|
-
// Bind the compositor program. Setting projection + sampler uniforms
|
|
7130
|
-
// each call because they need to match the current render target.
|
|
7131
|
-
backend.bindShader(this._shader);
|
|
7132
|
-
const view = backend.view;
|
|
7133
|
-
const projection = view.getTransform().toArray(false);
|
|
7134
|
-
this._shader.getUniform('u_projection').setValue(projection);
|
|
7135
|
-
this._shader.getUniform('u_content').setValue(this._contentSamplerSlot);
|
|
7136
|
-
this._shader.getUniform('u_mask').setValue(this._maskSamplerSlot);
|
|
7137
|
-
this._shader.sync();
|
|
7138
|
-
backend.bindTexture(content, 0);
|
|
7139
|
-
backend.bindTexture(mask, 1);
|
|
7140
|
-
backend.setBlendMode(blendMode);
|
|
7141
|
-
backend.bindVertexArrayObject(connection.vao);
|
|
7142
|
-
connection.vertexBuffer.upload(this._float32View);
|
|
7143
|
-
connection.vao.draw(6, 0);
|
|
7144
|
-
backend.stats.batches++;
|
|
7145
|
-
backend.stats.drawCalls++;
|
|
7146
|
-
// Reset the active texture unit to 0 to avoid leaking unit 1 into
|
|
7147
|
-
// subsequent renderer state.
|
|
7148
|
-
backend.bindTexture(null, 1);
|
|
6990
|
+
// Update the quad vertices for this destination rect. UVs are 0..1
|
|
6991
|
+
// mapped over (left, top) → (right, bottom) with an explicit Y-flip
|
|
6992
|
+
// for render-texture sampling: the mask path samples textures
|
|
6993
|
+
// already authored as RGBA in render-texture orientation.
|
|
6994
|
+
this._writeQuadVertices(x, y, x + width, y + height);
|
|
6995
|
+
// Bind the compositor program. Setting projection + sampler uniforms
|
|
6996
|
+
// each call because they need to match the current render target.
|
|
6997
|
+
backend.bindShader(this._shader);
|
|
6998
|
+
const view = backend.view;
|
|
6999
|
+
const projection = view.getTransform().toArray(false);
|
|
7000
|
+
this._shader.getUniform('u_projection').setValue(projection);
|
|
7001
|
+
this._shader.getUniform('u_content').setValue(this._contentSamplerSlot);
|
|
7002
|
+
this._shader.getUniform('u_mask').setValue(this._maskSamplerSlot);
|
|
7003
|
+
this._shader.sync();
|
|
7004
|
+
backend.bindTexture(content, 0);
|
|
7005
|
+
backend.bindTexture(mask, 1);
|
|
7006
|
+
backend.setBlendMode(blendMode);
|
|
7007
|
+
backend.bindVertexArrayObject(connection.vao);
|
|
7008
|
+
connection.vertexBuffer.upload(this._float32View);
|
|
7009
|
+
connection.vao.draw(6, 0);
|
|
7010
|
+
backend.stats.batches++;
|
|
7011
|
+
backend.stats.drawCalls++;
|
|
7012
|
+
// Reset the active texture unit to 0 to avoid leaking unit 1 into
|
|
7013
|
+
// subsequent renderer state.
|
|
7014
|
+
backend.bindTexture(null, 1);
|
|
7149
7015
|
}
|
|
7150
7016
|
_writeQuadVertices(left, top, right, bottom) {
|
|
7151
7017
|
const view = this._float32View;
|
|
@@ -7393,134 +7259,24 @@ class Sprite extends Drawable {
|
|
|
7393
7259
|
}
|
|
7394
7260
|
RenderNode.setInternalSpriteFactory(() => new Sprite(null));
|
|
7395
7261
|
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
* vertex array between frames and the GPU re-tessellates nothing —
|
|
7409
|
-
* only the transform changes per frame.
|
|
7410
|
-
* - Particles or trails with custom geometry per emitter.
|
|
7411
|
-
*
|
|
7412
|
-
* The mesh data is **immutable after construction** in v1: vertex /
|
|
7413
|
-
* index / UV / color arrays are exposed as readonly references. Mutate
|
|
7414
|
-
* the underlying typed arrays in-place if you need per-frame updates,
|
|
7415
|
-
* but the array lengths and topology cannot change. Texture is the only
|
|
7416
|
-
* post-construction mutable property.
|
|
7417
|
-
*
|
|
7418
|
-
* The vertex stream is a flat `Float32Array` of (x, y) pairs in local
|
|
7419
|
-
* space. The mesh's local bounds are computed once at construction from
|
|
7420
|
-
* the AABB of those vertices and used by the cull pass. Re-computing
|
|
7421
|
-
* after in-place mutation is the caller's responsibility (call
|
|
7422
|
-
* `recomputeLocalBounds()`).
|
|
7423
|
-
*/
|
|
7424
|
-
class Mesh extends Drawable {
|
|
7425
|
-
vertices;
|
|
7426
|
-
indices;
|
|
7427
|
-
uvs;
|
|
7428
|
-
colors;
|
|
7429
|
-
_texture;
|
|
7430
|
-
constructor(options) {
|
|
7431
|
-
super();
|
|
7432
|
-
const { vertices, indices = null, uvs = null, colors = null, texture = null } = options;
|
|
7433
|
-
if (vertices.length === 0 || vertices.length % 2 !== 0) {
|
|
7434
|
-
throw new Error(`Mesh vertices must be a non-empty flat array of (x,y) pairs (got length ${vertices.length}).`);
|
|
7435
|
-
}
|
|
7436
|
-
const vertexCount = vertices.length / 2;
|
|
7437
|
-
if (vertexCount < 3) {
|
|
7438
|
-
throw new Error(`Mesh requires at least 3 vertices (got ${vertexCount}).`);
|
|
7439
|
-
}
|
|
7440
|
-
if (uvs !== null && uvs.length !== vertices.length) {
|
|
7441
|
-
throw new Error(`Mesh uvs length ${uvs.length} must equal vertices length ${vertices.length}.`);
|
|
7442
|
-
}
|
|
7443
|
-
if (colors !== null && colors.length !== vertexCount) {
|
|
7444
|
-
throw new Error(`Mesh colors length ${colors.length} must equal vertex count ${vertexCount}.`);
|
|
7445
|
-
}
|
|
7446
|
-
if (indices !== null) {
|
|
7447
|
-
if (indices.length === 0 || indices.length % 3 !== 0) {
|
|
7448
|
-
throw new Error(`Mesh indices must be a non-empty multiple of 3 (got length ${indices.length}).`);
|
|
7449
|
-
}
|
|
7450
|
-
for (let i = 0; i < indices.length; i++) {
|
|
7451
|
-
if (indices[i] >= vertexCount) {
|
|
7452
|
-
throw new Error(`Mesh index ${indices[i]} at position ${i} is out of range for vertex count ${vertexCount}.`);
|
|
7453
|
-
}
|
|
7454
|
-
}
|
|
7455
|
-
}
|
|
7456
|
-
else if (vertexCount % 3 !== 0) {
|
|
7457
|
-
throw new Error(`Non-indexed Mesh requires a vertex count that is a multiple of 3 (got ${vertexCount}).`);
|
|
7458
|
-
}
|
|
7459
|
-
this.vertices = vertices;
|
|
7460
|
-
this.indices = indices;
|
|
7461
|
-
this.uvs = uvs;
|
|
7462
|
-
this.colors = colors;
|
|
7463
|
-
this._texture = texture;
|
|
7464
|
-
this.recomputeLocalBounds();
|
|
7262
|
+
class Particle {
|
|
7263
|
+
_totalLifetime = Time.oneSecond.clone();
|
|
7264
|
+
_elapsedLifetime = Time.zero.clone();
|
|
7265
|
+
_position = Vector.zero.clone();
|
|
7266
|
+
_velocity = Vector.zero.clone();
|
|
7267
|
+
_scale = Vector.one.clone();
|
|
7268
|
+
_rotation = 0;
|
|
7269
|
+
_rotationSpeed = 0;
|
|
7270
|
+
_textureIndex = 0;
|
|
7271
|
+
_tint = Color.white.clone();
|
|
7272
|
+
get totalLifetime() {
|
|
7273
|
+
return this._totalLifetime;
|
|
7465
7274
|
}
|
|
7466
|
-
|
|
7467
|
-
|
|
7275
|
+
set totalLifetime(totalLifetime) {
|
|
7276
|
+
this._totalLifetime.copy(totalLifetime);
|
|
7468
7277
|
}
|
|
7469
|
-
get
|
|
7470
|
-
return this.
|
|
7471
|
-
}
|
|
7472
|
-
get texture() {
|
|
7473
|
-
return this._texture;
|
|
7474
|
-
}
|
|
7475
|
-
set texture(texture) {
|
|
7476
|
-
this._texture = texture;
|
|
7477
|
-
this.invalidateCache();
|
|
7478
|
-
}
|
|
7479
|
-
/**
|
|
7480
|
-
* Recompute the local AABB from the current vertex array. Call after
|
|
7481
|
-
* mutating `vertices` in place to keep culling correct; otherwise the
|
|
7482
|
-
* bounds the cull pass sees will be the AABB at construction time.
|
|
7483
|
-
*/
|
|
7484
|
-
recomputeLocalBounds() {
|
|
7485
|
-
let minX = Infinity;
|
|
7486
|
-
let minY = Infinity;
|
|
7487
|
-
let maxX = -Infinity;
|
|
7488
|
-
let maxY = -Infinity;
|
|
7489
|
-
for (let i = 0; i < this.vertices.length; i += 2) {
|
|
7490
|
-
const x = this.vertices[i];
|
|
7491
|
-
const y = this.vertices[i + 1];
|
|
7492
|
-
if (x < minX)
|
|
7493
|
-
minX = x;
|
|
7494
|
-
if (x > maxX)
|
|
7495
|
-
maxX = x;
|
|
7496
|
-
if (y < minY)
|
|
7497
|
-
minY = y;
|
|
7498
|
-
if (y > maxY)
|
|
7499
|
-
maxY = y;
|
|
7500
|
-
}
|
|
7501
|
-
this.localBounds.set(minX, minY, maxX - minX, maxY - minY);
|
|
7502
|
-
return this;
|
|
7503
|
-
}
|
|
7504
|
-
}
|
|
7505
|
-
|
|
7506
|
-
class Particle {
|
|
7507
|
-
_totalLifetime = Time.oneSecond.clone();
|
|
7508
|
-
_elapsedLifetime = Time.zero.clone();
|
|
7509
|
-
_position = Vector.zero.clone();
|
|
7510
|
-
_velocity = Vector.zero.clone();
|
|
7511
|
-
_scale = Vector.one.clone();
|
|
7512
|
-
_rotation = 0;
|
|
7513
|
-
_rotationSpeed = 0;
|
|
7514
|
-
_textureIndex = 0;
|
|
7515
|
-
_tint = Color.white.clone();
|
|
7516
|
-
get totalLifetime() {
|
|
7517
|
-
return this._totalLifetime;
|
|
7518
|
-
}
|
|
7519
|
-
set totalLifetime(totalLifetime) {
|
|
7520
|
-
this._totalLifetime.copy(totalLifetime);
|
|
7521
|
-
}
|
|
7522
|
-
get elapsedLifetime() {
|
|
7523
|
-
return this._elapsedLifetime;
|
|
7278
|
+
get elapsedLifetime() {
|
|
7279
|
+
return this._elapsedLifetime;
|
|
7524
7280
|
}
|
|
7525
7281
|
set elapsedLifetime(elapsedLifetime) {
|
|
7526
7282
|
this._elapsedLifetime.copy(elapsedLifetime);
|
|
@@ -7945,7 +7701,7 @@ class WebGl2Backend {
|
|
|
7945
7701
|
_boundFramebuffer = null;
|
|
7946
7702
|
_stats = createRenderStats();
|
|
7947
7703
|
constructor(app) {
|
|
7948
|
-
const { width, height, clearColor, webglAttributes, debug, spriteRendererBatchSize, particleRendererBatchSize,
|
|
7704
|
+
const { width, height, clearColor, webglAttributes, debug, spriteRendererBatchSize, particleRendererBatchSize, } = app.options;
|
|
7949
7705
|
this._canvas = app.canvas;
|
|
7950
7706
|
const gl = this._createContext(webglAttributes);
|
|
7951
7707
|
if (!gl) {
|
|
@@ -7969,7 +7725,6 @@ class WebGl2Backend {
|
|
|
7969
7725
|
this.rendererRegistry.registerRenderer(Sprite, new WebGl2SpriteRenderer(spriteRendererBatchSize));
|
|
7970
7726
|
this.rendererRegistry.registerRenderer(Mesh, new WebGl2MeshRenderer());
|
|
7971
7727
|
this.rendererRegistry.registerRenderer(ParticleSystem, new WebGl2ParticleRenderer(particleRendererBatchSize));
|
|
7972
|
-
this.rendererRegistry.registerRenderer(DrawableShape, new WebGl2PrimitiveRenderer(primitiveRendererBatchSize));
|
|
7973
7728
|
this.rendererRegistry.connect(this);
|
|
7974
7729
|
this._bindRenderTarget(this._renderTarget);
|
|
7975
7730
|
this.setBlendMode(BlendModes.Normal);
|
|
@@ -8384,719 +8139,238 @@ class WebGl2Backend {
|
|
|
8384
8139
|
this._boundFramebuffer = state.framebuffer;
|
|
8385
8140
|
state.version = target.version;
|
|
8386
8141
|
}
|
|
8387
|
-
if (this._clipPixelStack.length > 0) {
|
|
8388
|
-
this._applyClipState();
|
|
8389
|
-
}
|
|
8390
|
-
}
|
|
8391
|
-
_setActiveRenderer(renderer) {
|
|
8392
|
-
if (this._renderer !== renderer) {
|
|
8393
|
-
this._flushActiveRenderer();
|
|
8394
|
-
this._renderer = renderer;
|
|
8395
|
-
}
|
|
8396
|
-
}
|
|
8397
|
-
_flushActiveRenderer() {
|
|
8398
|
-
if (this._renderer && !this._contextLost) {
|
|
8399
|
-
this._bindRenderTarget(this._renderTarget);
|
|
8400
|
-
this._renderer.flush();
|
|
8401
|
-
}
|
|
8402
|
-
}
|
|
8403
|
-
_prepareRenderTarget(target) {
|
|
8404
|
-
const state = this._getRenderTargetState(target);
|
|
8405
|
-
if (target instanceof RenderTexture && state.framebuffer) {
|
|
8406
|
-
const previousFramebuffer = this._boundFramebuffer;
|
|
8407
|
-
const textureState = this._syncTexture(target);
|
|
8408
|
-
if (state.attachedTexture !== textureState.handle) {
|
|
8409
|
-
const gl = this._context;
|
|
8410
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
|
|
8411
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureState.handle, 0);
|
|
8412
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, previousFramebuffer);
|
|
8413
|
-
state.attachedTexture = textureState.handle;
|
|
8414
|
-
}
|
|
8415
|
-
}
|
|
8416
|
-
return state;
|
|
8417
|
-
}
|
|
8418
|
-
_syncTexture(texture) {
|
|
8419
|
-
const gl = this._context;
|
|
8420
|
-
const state = this._getTextureState(texture);
|
|
8421
|
-
const version = texture instanceof RenderTexture ? texture.textureVersion : texture.version;
|
|
8422
|
-
gl.bindTexture(gl.TEXTURE_2D, state.handle);
|
|
8423
|
-
if (state.version !== version) {
|
|
8424
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode);
|
|
8425
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode);
|
|
8426
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, texture.wrapMode);
|
|
8427
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, texture.wrapMode);
|
|
8428
|
-
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha);
|
|
8429
|
-
if (texture instanceof RenderTexture) {
|
|
8430
|
-
if (state.version === -1 || state.width !== texture.width || state.height !== texture.height || texture.source === null) {
|
|
8431
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texture.width, texture.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, texture.source);
|
|
8432
|
-
}
|
|
8433
|
-
else {
|
|
8434
|
-
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, texture.width, texture.height, gl.RGBA, gl.UNSIGNED_BYTE, texture.source);
|
|
8435
|
-
}
|
|
8436
|
-
}
|
|
8437
|
-
else if (texture.source) {
|
|
8438
|
-
if (state.version === -1 || state.width !== texture.width || state.height !== texture.height) {
|
|
8439
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source);
|
|
8440
|
-
}
|
|
8441
|
-
else {
|
|
8442
|
-
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, texture.source);
|
|
8443
|
-
}
|
|
8444
|
-
}
|
|
8445
|
-
if (texture.generateMipMap && (texture instanceof RenderTexture || texture.source !== null)) {
|
|
8446
|
-
gl.generateMipmap(gl.TEXTURE_2D);
|
|
8447
|
-
}
|
|
8448
|
-
state.version = version;
|
|
8449
|
-
state.width = texture.width;
|
|
8450
|
-
state.height = texture.height;
|
|
8451
|
-
}
|
|
8452
|
-
return state;
|
|
8453
|
-
}
|
|
8454
|
-
_toClipPixels(bounds) {
|
|
8455
|
-
const topLeft = this._renderTarget.mapCoordsToPixel(this._clipPointA.set(bounds.left, bounds.top));
|
|
8456
|
-
const bottomRight = this._renderTarget.mapCoordsToPixel(this._clipPointB.set(bounds.right, bounds.bottom));
|
|
8457
|
-
const minX = Math.min(topLeft.x, bottomRight.x);
|
|
8458
|
-
const maxX = Math.max(topLeft.x, bottomRight.x);
|
|
8459
|
-
const minY = Math.min(topLeft.y, bottomRight.y);
|
|
8460
|
-
const maxY = Math.max(topLeft.y, bottomRight.y);
|
|
8461
|
-
const targetWidth = this._renderTarget.width;
|
|
8462
|
-
const targetHeight = this._renderTarget.height;
|
|
8463
|
-
const x = Math.max(0, Math.min(targetWidth, Math.floor(minX)));
|
|
8464
|
-
const right = Math.max(0, Math.min(targetWidth, Math.ceil(maxX)));
|
|
8465
|
-
const yTop = Math.max(0, Math.min(targetHeight, Math.floor(minY)));
|
|
8466
|
-
const bottom = Math.max(0, Math.min(targetHeight, Math.ceil(maxY)));
|
|
8467
|
-
const width = Math.max(0, right - x);
|
|
8468
|
-
const height = Math.max(0, bottom - yTop);
|
|
8469
|
-
const y = Math.max(0, targetHeight - bottom);
|
|
8470
|
-
return {
|
|
8471
|
-
x,
|
|
8472
|
-
y,
|
|
8473
|
-
width,
|
|
8474
|
-
height,
|
|
8475
|
-
};
|
|
8476
|
-
}
|
|
8477
|
-
_intersectClips(first, second) {
|
|
8478
|
-
const left = Math.max(first.x, second.x);
|
|
8479
|
-
const bottom = Math.max(first.y, second.y);
|
|
8480
|
-
const right = Math.min(first.x + first.width, second.x + second.width);
|
|
8481
|
-
const top = Math.min(first.y + first.height, second.y + second.height);
|
|
8482
|
-
return {
|
|
8483
|
-
x: left,
|
|
8484
|
-
y: bottom,
|
|
8485
|
-
width: Math.max(0, right - left),
|
|
8486
|
-
height: Math.max(0, top - bottom),
|
|
8487
|
-
};
|
|
8488
|
-
}
|
|
8489
|
-
_applyClipState() {
|
|
8490
|
-
const gl = this._context;
|
|
8491
|
-
if (this._clipPixelStack.length === 0) {
|
|
8492
|
-
gl.disable(gl.SCISSOR_TEST);
|
|
8493
|
-
return;
|
|
8494
|
-
}
|
|
8495
|
-
const clip = this._clipPixelStack[this._clipPixelStack.length - 1];
|
|
8496
|
-
gl.enable(gl.SCISSOR_TEST);
|
|
8497
|
-
gl.scissor(clip.x, clip.y, clip.width, clip.height);
|
|
8498
|
-
}
|
|
8499
|
-
}
|
|
8500
|
-
|
|
8501
|
-
/// <reference types="@webgpu/types" />
|
|
8502
|
-
/**
|
|
8503
|
-
* Base class for WebGPU renderers.
|
|
8504
|
-
*
|
|
8505
|
-
* Manages the connect/disconnect lifecycle and provides a safe
|
|
8506
|
-
* `getBackend()` accessor that throws if the renderer is not connected.
|
|
8507
|
-
*
|
|
8508
|
-
* Subclasses must implement:
|
|
8509
|
-
* - onConnect(backend): set up GPU resources (shader modules, pipelines, buffers)
|
|
8510
|
-
* - onDisconnect(): tear down GPU resources
|
|
8511
|
-
* - render(drawable): collect draw call data for the given drawable
|
|
8512
|
-
* - flush(): encode and submit command buffers for all collected draw calls
|
|
8513
|
-
*/
|
|
8514
|
-
class AbstractWebGpuRenderer {
|
|
8515
|
-
backendType = RenderBackendType.WebGpu;
|
|
8516
|
-
_backend = null;
|
|
8517
|
-
connect(backend) {
|
|
8518
|
-
if (this._backend !== null) {
|
|
8519
|
-
return;
|
|
8520
|
-
}
|
|
8521
|
-
if (backend.backendType !== RenderBackendType.WebGpu) {
|
|
8522
|
-
throw new Error(`${this.constructor.name} requires a WebGPU backend, `
|
|
8523
|
-
+ `but received backendType ${String(backend.backendType)}.`);
|
|
8524
|
-
}
|
|
8525
|
-
this._backend = backend;
|
|
8526
|
-
this.onConnect(backend);
|
|
8527
|
-
}
|
|
8528
|
-
disconnect() {
|
|
8529
|
-
if (this._backend === null) {
|
|
8530
|
-
return;
|
|
8531
|
-
}
|
|
8532
|
-
this.flush();
|
|
8533
|
-
this.onDisconnect();
|
|
8534
|
-
this._backend = null;
|
|
8535
|
-
}
|
|
8536
|
-
getBackend() {
|
|
8537
|
-
if (this._backend === null) {
|
|
8538
|
-
throw new Error(`${this.constructor.name} is not connected to a backend.`);
|
|
8539
|
-
}
|
|
8540
|
-
return this._backend;
|
|
8541
|
-
}
|
|
8542
|
-
getBackendOrNull() {
|
|
8543
|
-
return this._backend;
|
|
8544
|
-
}
|
|
8545
|
-
}
|
|
8546
|
-
|
|
8547
|
-
/// <reference types="@webgpu/types" />
|
|
8548
|
-
/**
|
|
8549
|
-
* Returns the GPUBlendState for a given ExoJS blend mode.
|
|
8550
|
-
* Shared by all WebGPU renderers to avoid duplication.
|
|
8551
|
-
*/
|
|
8552
|
-
function getWebGpuBlendState(blendMode) {
|
|
8553
|
-
switch (blendMode) {
|
|
8554
|
-
case BlendModes.Additive:
|
|
8555
|
-
return {
|
|
8556
|
-
color: {
|
|
8557
|
-
operation: 'add',
|
|
8558
|
-
srcFactor: 'one',
|
|
8559
|
-
dstFactor: 'one',
|
|
8560
|
-
},
|
|
8561
|
-
alpha: {
|
|
8562
|
-
operation: 'add',
|
|
8563
|
-
srcFactor: 'one',
|
|
8564
|
-
dstFactor: 'one',
|
|
8565
|
-
},
|
|
8566
|
-
};
|
|
8567
|
-
case BlendModes.Subtract:
|
|
8568
|
-
return {
|
|
8569
|
-
color: {
|
|
8570
|
-
operation: 'add',
|
|
8571
|
-
srcFactor: 'zero',
|
|
8572
|
-
dstFactor: 'one-minus-src',
|
|
8573
|
-
},
|
|
8574
|
-
alpha: {
|
|
8575
|
-
operation: 'add',
|
|
8576
|
-
srcFactor: 'zero',
|
|
8577
|
-
dstFactor: 'one-minus-src-alpha',
|
|
8578
|
-
},
|
|
8579
|
-
};
|
|
8580
|
-
case BlendModes.Multiply:
|
|
8581
|
-
return {
|
|
8582
|
-
color: {
|
|
8583
|
-
operation: 'add',
|
|
8584
|
-
srcFactor: 'dst',
|
|
8585
|
-
dstFactor: 'one-minus-src-alpha',
|
|
8586
|
-
},
|
|
8587
|
-
alpha: {
|
|
8588
|
-
operation: 'add',
|
|
8589
|
-
srcFactor: 'dst-alpha',
|
|
8590
|
-
dstFactor: 'one-minus-src-alpha',
|
|
8591
|
-
},
|
|
8592
|
-
};
|
|
8593
|
-
case BlendModes.Screen:
|
|
8594
|
-
return {
|
|
8595
|
-
color: {
|
|
8596
|
-
operation: 'add',
|
|
8597
|
-
srcFactor: 'one',
|
|
8598
|
-
dstFactor: 'one-minus-src',
|
|
8599
|
-
},
|
|
8600
|
-
alpha: {
|
|
8601
|
-
operation: 'add',
|
|
8602
|
-
srcFactor: 'one',
|
|
8603
|
-
dstFactor: 'one-minus-src-alpha',
|
|
8604
|
-
},
|
|
8605
|
-
};
|
|
8606
|
-
default:
|
|
8607
|
-
return {
|
|
8608
|
-
color: {
|
|
8609
|
-
operation: 'add',
|
|
8610
|
-
srcFactor: 'one',
|
|
8611
|
-
dstFactor: 'one-minus-src-alpha',
|
|
8612
|
-
},
|
|
8613
|
-
alpha: {
|
|
8614
|
-
operation: 'add',
|
|
8615
|
-
srcFactor: 'one',
|
|
8616
|
-
dstFactor: 'one-minus-src-alpha',
|
|
8617
|
-
},
|
|
8618
|
-
};
|
|
8619
|
-
}
|
|
8620
|
-
}
|
|
8621
|
-
|
|
8622
|
-
/// <reference types="@webgpu/types" />
|
|
8623
|
-
const primitiveShaderSource = `
|
|
8624
|
-
struct VertexInput {
|
|
8625
|
-
@location(0) position: vec4<f32>,
|
|
8626
|
-
@location(1) color: vec4<f32>,
|
|
8627
|
-
};
|
|
8628
|
-
|
|
8629
|
-
struct VertexOutput {
|
|
8630
|
-
@builtin(position) position: vec4<f32>,
|
|
8631
|
-
@location(0) color: vec4<f32>,
|
|
8632
|
-
};
|
|
8633
|
-
|
|
8634
|
-
@vertex
|
|
8635
|
-
fn vertexMain(input: VertexInput) -> VertexOutput {
|
|
8636
|
-
var output: VertexOutput;
|
|
8637
|
-
|
|
8638
|
-
output.position = input.position;
|
|
8639
|
-
output.color = vec4<f32>(input.color.rgb * input.color.a, input.color.a);
|
|
8640
|
-
|
|
8641
|
-
return output;
|
|
8642
|
-
}
|
|
8643
|
-
|
|
8644
|
-
@fragment
|
|
8645
|
-
fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
8646
|
-
return input.color;
|
|
8647
|
-
}
|
|
8648
|
-
`;
|
|
8649
|
-
// 4 floats (pre-transformed clip-space position) + 1 u32 (color) = 20 bytes.
|
|
8650
|
-
// The CPU applies (view * shape.globalTransform) to each vertex before writing
|
|
8651
|
-
// it into the vertex buffer, so the shader outputs the position as-is. This
|
|
8652
|
-
// matches the sprite renderer's approach and eliminates the need for a per-
|
|
8653
|
-
// drawcall uniform binding.
|
|
8654
|
-
const vertexStrideBytes$2 = 20;
|
|
8655
|
-
const wordsPerVertex$1 = vertexStrideBytes$2 / Uint32Array.BYTES_PER_ELEMENT;
|
|
8656
|
-
class WebGpuPrimitiveRenderer extends AbstractWebGpuRenderer {
|
|
8657
|
-
_combinedTransform = new Matrix();
|
|
8658
|
-
_drawCalls = [];
|
|
8659
|
-
_drawCallCount = 0;
|
|
8660
|
-
_pipelines = new Map();
|
|
8661
|
-
_device = null;
|
|
8662
|
-
_shaderModule = null;
|
|
8663
|
-
_pipelineLayout = null;
|
|
8664
|
-
_vertexBuffer = null;
|
|
8665
|
-
_indexBuffer = null;
|
|
8666
|
-
_vertexBufferCapacity = 0;
|
|
8667
|
-
_indexBufferCapacity = 0;
|
|
8668
|
-
_vertexData = new ArrayBuffer(0);
|
|
8669
|
-
_float32View = new Float32Array(this._vertexData);
|
|
8670
|
-
_uint32View = new Uint32Array(this._vertexData);
|
|
8671
|
-
_packedIndexData = new Uint16Array(0);
|
|
8672
|
-
_generatedIndexData = new Uint16Array(0);
|
|
8673
|
-
_sequentialIndexData = new Uint16Array(0);
|
|
8674
|
-
render(shape) {
|
|
8675
|
-
const backend = this._backend;
|
|
8676
|
-
if (backend === null) {
|
|
8677
|
-
throw new Error('Renderer not connected');
|
|
8678
|
-
}
|
|
8679
|
-
if (shape.drawMode !== RenderingPrimitives.Points
|
|
8680
|
-
&& shape.drawMode !== RenderingPrimitives.Lines
|
|
8681
|
-
&& shape.drawMode !== RenderingPrimitives.LineLoop
|
|
8682
|
-
&& shape.drawMode !== RenderingPrimitives.LineStrip
|
|
8683
|
-
&& shape.drawMode !== RenderingPrimitives.Triangles
|
|
8684
|
-
&& shape.drawMode !== RenderingPrimitives.TriangleFan
|
|
8685
|
-
&& shape.drawMode !== RenderingPrimitives.TriangleStrip) {
|
|
8686
|
-
throw new Error(`WebGPU primitive renderer does not support draw mode "${shape.drawMode}" yet.`);
|
|
8687
|
-
}
|
|
8688
|
-
backend.setBlendMode(shape.blendMode);
|
|
8689
|
-
if (shape.geometry.vertices.length === 0) {
|
|
8690
|
-
return;
|
|
8691
|
-
}
|
|
8692
|
-
const drawCallIndex = this._drawCallCount++;
|
|
8693
|
-
const drawCall = this._drawCalls[drawCallIndex];
|
|
8694
|
-
if (drawCall) {
|
|
8695
|
-
drawCall.shape = shape;
|
|
8696
|
-
drawCall.blendMode = shape.blendMode;
|
|
8697
|
-
}
|
|
8698
|
-
else {
|
|
8699
|
-
this._drawCalls.push({
|
|
8700
|
-
shape,
|
|
8701
|
-
blendMode: shape.blendMode,
|
|
8702
|
-
});
|
|
8703
|
-
}
|
|
8704
|
-
}
|
|
8705
|
-
flush() {
|
|
8706
|
-
const backend = this._backend;
|
|
8707
|
-
const device = this._device;
|
|
8708
|
-
if (!backend || !device) {
|
|
8709
|
-
return;
|
|
8710
|
-
}
|
|
8711
|
-
if (this._drawCallCount === 0 && !backend.clearRequested) {
|
|
8712
|
-
return;
|
|
8713
|
-
}
|
|
8714
|
-
const scissor = backend.getScissorRect();
|
|
8715
|
-
const maskClipsAll = scissor !== null && (scissor.width <= 0 || scissor.height <= 0);
|
|
8716
|
-
// Phase 1: resolve drawcalls and record each one's offsets into the
|
|
8717
|
-
// shared packed buffers. Transform gets baked into the vertex data
|
|
8718
|
-
// during phase 2 so no per-drawcall uniform binding is needed.
|
|
8719
|
-
const plan = [];
|
|
8720
|
-
const resolvedDrawCalls = [];
|
|
8721
|
-
let totalVertices = 0;
|
|
8722
|
-
let totalIndices = 0;
|
|
8723
|
-
if (this._drawCallCount > 0 && !maskClipsAll) {
|
|
8724
|
-
for (let drawCallIndex = 0; drawCallIndex < this._drawCallCount; drawCallIndex++) {
|
|
8725
|
-
const drawCall = this._drawCalls[drawCallIndex];
|
|
8726
|
-
const shape = drawCall.shape;
|
|
8727
|
-
const resolved = this._resolveDrawCall(shape);
|
|
8728
|
-
resolvedDrawCalls.push(resolved);
|
|
8729
|
-
if (resolved === null) {
|
|
8730
|
-
continue;
|
|
8731
|
-
}
|
|
8732
|
-
const pipeline = this._getPipeline({
|
|
8733
|
-
topology: resolved.topology,
|
|
8734
|
-
usesStripIndex: resolved.usesStripIndex,
|
|
8735
|
-
blendMode: drawCall.blendMode,
|
|
8736
|
-
format: backend.renderTargetFormat,
|
|
8737
|
-
});
|
|
8738
|
-
plan.push({
|
|
8739
|
-
pipeline,
|
|
8740
|
-
vertexByteOffset: totalVertices * vertexStrideBytes$2,
|
|
8741
|
-
vertexCount: resolved.vertexCount,
|
|
8742
|
-
indexByteOffset: totalIndices * Uint16Array.BYTES_PER_ELEMENT,
|
|
8743
|
-
indexCount: resolved.indexCount,
|
|
8744
|
-
});
|
|
8745
|
-
totalVertices += resolved.vertexCount;
|
|
8746
|
-
totalIndices += resolved.indexCount;
|
|
8747
|
-
}
|
|
8748
|
-
}
|
|
8749
|
-
// If nothing will actually render, still honor a pending clear with
|
|
8750
|
-
// a single empty pass so createColorAttachment consumes the clear
|
|
8751
|
-
// state exactly once.
|
|
8752
|
-
if (plan.length === 0) {
|
|
8753
|
-
if (backend.clearRequested) {
|
|
8754
|
-
const encoder = device.createCommandEncoder();
|
|
8755
|
-
const pass = encoder.beginRenderPass({
|
|
8756
|
-
colorAttachments: [backend.createColorAttachment()],
|
|
8757
|
-
});
|
|
8758
|
-
backend.stats.renderPasses++;
|
|
8759
|
-
pass.end();
|
|
8760
|
-
backend.submit(encoder.finish());
|
|
8761
|
-
}
|
|
8762
|
-
this._drawCallCount = 0;
|
|
8763
|
-
return;
|
|
8764
|
-
}
|
|
8765
|
-
// Phase 2: size GPU buffers for the whole-frame totals, then pack
|
|
8766
|
-
// every drawcall's CPU-side data. _writeShapeVertices applies
|
|
8767
|
-
// (view * shape.globalTransform) per-vertex so the shader simply
|
|
8768
|
-
// outputs input.position unchanged.
|
|
8769
|
-
this._ensureVertexCapacity(totalVertices);
|
|
8770
|
-
if (totalIndices > 0) {
|
|
8771
|
-
this._ensureIndexCapacity(totalIndices);
|
|
8772
|
-
if (this._packedIndexData.length < totalIndices) {
|
|
8773
|
-
this._packedIndexData = new Uint16Array(Math.max(totalIndices, this._packedIndexData.length === 0 ? 1 : this._packedIndexData.length * 2));
|
|
8774
|
-
}
|
|
8775
|
-
}
|
|
8776
|
-
{
|
|
8777
|
-
let vOffset = 0;
|
|
8778
|
-
let iOffset = 0;
|
|
8779
|
-
for (let i = 0; i < this._drawCallCount; i++) {
|
|
8780
|
-
const resolved = resolvedDrawCalls[i];
|
|
8781
|
-
if (resolved === null) {
|
|
8782
|
-
continue;
|
|
8783
|
-
}
|
|
8784
|
-
const drawCall = this._drawCalls[i];
|
|
8785
|
-
const shape = drawCall.shape;
|
|
8786
|
-
this._writeShapeVertices(backend, shape, vOffset);
|
|
8787
|
-
if (resolved.indices !== null && resolved.indexCount > 0) {
|
|
8788
|
-
this._packedIndexData.set(resolved.indices.subarray(0, resolved.indexCount), iOffset);
|
|
8789
|
-
iOffset += resolved.indexCount;
|
|
8790
|
-
}
|
|
8791
|
-
vOffset += resolved.vertexCount;
|
|
8792
|
-
}
|
|
8793
|
-
}
|
|
8794
|
-
// Phase 3: single writeBuffer per GPU buffer covers the whole frame.
|
|
8795
|
-
device.queue.writeBuffer(this._vertexBuffer, 0, this._vertexData, 0, totalVertices * vertexStrideBytes$2);
|
|
8796
|
-
if (totalIndices > 0) {
|
|
8797
|
-
device.queue.writeBuffer(this._indexBuffer, 0, this._packedIndexData.buffer, this._packedIndexData.byteOffset, totalIndices * Uint16Array.BYTES_PER_ELEMENT);
|
|
8798
|
-
}
|
|
8799
|
-
// Phase 4: single render pass. Per-draw state is just pipeline and
|
|
8800
|
-
// vertex/index subrange offsets — the transform has already been
|
|
8801
|
-
// baked into the vertex data.
|
|
8802
|
-
const encoder = device.createCommandEncoder();
|
|
8803
|
-
const pass = encoder.beginRenderPass({
|
|
8804
|
-
colorAttachments: [backend.createColorAttachment()],
|
|
8805
|
-
});
|
|
8806
|
-
backend.stats.renderPasses++;
|
|
8807
|
-
if (scissor !== null) {
|
|
8808
|
-
pass.setScissorRect(scissor.x, scissor.y, scissor.width, scissor.height);
|
|
8809
|
-
}
|
|
8810
|
-
for (const planned of plan) {
|
|
8811
|
-
pass.setPipeline(planned.pipeline);
|
|
8812
|
-
pass.setVertexBuffer(0, this._vertexBuffer, planned.vertexByteOffset);
|
|
8813
|
-
if (planned.indexCount > 0) {
|
|
8814
|
-
pass.setIndexBuffer(this._indexBuffer, 'uint16', planned.indexByteOffset);
|
|
8815
|
-
pass.drawIndexed(planned.indexCount);
|
|
8816
|
-
}
|
|
8817
|
-
else {
|
|
8818
|
-
pass.draw(planned.vertexCount);
|
|
8819
|
-
}
|
|
8820
|
-
backend.stats.batches++;
|
|
8821
|
-
backend.stats.drawCalls++;
|
|
8822
|
-
}
|
|
8823
|
-
pass.end();
|
|
8824
|
-
backend.submit(encoder.finish());
|
|
8825
|
-
this._drawCallCount = 0;
|
|
8826
|
-
}
|
|
8827
|
-
destroy() {
|
|
8828
|
-
this.disconnect();
|
|
8829
|
-
this._combinedTransform.destroy();
|
|
8830
|
-
}
|
|
8831
|
-
onConnect(backend) {
|
|
8832
|
-
this._backend = backend;
|
|
8833
|
-
this._device = this._backend.device;
|
|
8834
|
-
this._shaderModule = this._device.createShaderModule({ code: primitiveShaderSource });
|
|
8835
|
-
// Transform is applied per-vertex on the CPU, so no uniform binding
|
|
8836
|
-
// is needed — the shader outputs input.position directly.
|
|
8837
|
-
this._pipelineLayout = this._device.createPipelineLayout({
|
|
8838
|
-
bindGroupLayouts: [],
|
|
8839
|
-
});
|
|
8840
|
-
}
|
|
8841
|
-
onDisconnect() {
|
|
8842
|
-
this.flush();
|
|
8843
|
-
this._destroyBuffers();
|
|
8844
|
-
this._pipelines.clear();
|
|
8845
|
-
this._pipelineLayout = null;
|
|
8846
|
-
this._shaderModule = null;
|
|
8847
|
-
this._device = null;
|
|
8848
|
-
this._backend = null;
|
|
8849
|
-
this._drawCallCount = 0;
|
|
8850
|
-
}
|
|
8851
|
-
_writeShapeVertices(backend, shape, vertexStart) {
|
|
8852
|
-
// Matrix.combine is `other * this` (see Matrix.rotate and
|
|
8853
|
-
// SceneNode.getGlobalTransform, both of which chain via
|
|
8854
|
-
// local.combine(parent.global) to yield parent.global * local).
|
|
8855
|
-
//
|
|
8856
|
-
// We need view * global applied to a local vertex, so start with
|
|
8857
|
-
// global and combine with view — that gives
|
|
8858
|
-
// _combinedTransform = view * global.
|
|
8859
|
-
const matrix = this._combinedTransform
|
|
8860
|
-
.copy(shape.getGlobalTransform())
|
|
8861
|
-
.combine(backend.view.getTransform());
|
|
8862
|
-
// Match the original uniform-based WGSL layout exactly.
|
|
8863
|
-
//
|
|
8864
|
-
// The shader packs the Matrix's 9 fields into a 4x4 mat (column-major
|
|
8865
|
-
// in WGSL):
|
|
8866
|
-
// col 0 = [a, c, 0, 0]
|
|
8867
|
-
// col 1 = [b, d, 0, 0]
|
|
8868
|
-
// col 2 = [0, 0, 1, 0]
|
|
8869
|
-
// col 3 = [x, y, 0, z]
|
|
8870
|
-
//
|
|
8871
|
-
// Multiplied by vec4(px, py, 0, 1):
|
|
8872
|
-
// out = col0*px + col1*py + col2*0 + col3*1
|
|
8873
|
-
// out.x = a*px + b*py + x
|
|
8874
|
-
// out.y = c*px + d*py + y
|
|
8875
|
-
// out.z = 0
|
|
8876
|
-
// out.w = z
|
|
8877
|
-
//
|
|
8878
|
-
// The Matrix class represents the affine matrix in the order
|
|
8879
|
-
// [a b x]
|
|
8880
|
-
// [c d y]
|
|
8881
|
-
// [e f z]
|
|
8882
|
-
// so a/b/c/d are rotation+scale (note: b on the TOP row, c on the
|
|
8883
|
-
// LEFT column, not the other way around) and x/y/z the translation /
|
|
8884
|
-
// w component. Matrix.toArray(false) confirms this layout.
|
|
8885
|
-
const a = matrix.a;
|
|
8886
|
-
const b = matrix.b;
|
|
8887
|
-
const c = matrix.c;
|
|
8888
|
-
const d = matrix.d;
|
|
8889
|
-
const tx = matrix.x;
|
|
8890
|
-
const ty = matrix.y;
|
|
8891
|
-
const tw = matrix.z;
|
|
8892
|
-
const color = shape.color.toRgba();
|
|
8893
|
-
const vertices = shape.geometry.vertices;
|
|
8894
|
-
const vertexCount = vertices.length / 2;
|
|
8895
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
8896
|
-
const sourceIndex = i * 2;
|
|
8897
|
-
const targetIndex = (vertexStart + i) * wordsPerVertex$1;
|
|
8898
|
-
const px = vertices[sourceIndex];
|
|
8899
|
-
const py = vertices[sourceIndex + 1];
|
|
8900
|
-
this._float32View[targetIndex + 0] = a * px + b * py + tx;
|
|
8901
|
-
this._float32View[targetIndex + 1] = c * px + d * py + ty;
|
|
8902
|
-
this._float32View[targetIndex + 2] = 0;
|
|
8903
|
-
this._float32View[targetIndex + 3] = tw;
|
|
8904
|
-
this._uint32View[targetIndex + 4] = color;
|
|
8905
|
-
}
|
|
8906
|
-
}
|
|
8907
|
-
_ensureVertexCapacity(vertexCount) {
|
|
8908
|
-
const requiredBytes = vertexCount * vertexStrideBytes$2;
|
|
8909
|
-
if (requiredBytes > this._vertexData.byteLength) {
|
|
8910
|
-
const byteLength = Math.max(requiredBytes, this._vertexData.byteLength === 0 ? vertexStrideBytes$2 : this._vertexData.byteLength * 2);
|
|
8911
|
-
this._vertexData = new ArrayBuffer(byteLength);
|
|
8912
|
-
this._float32View = new Float32Array(this._vertexData);
|
|
8913
|
-
this._uint32View = new Uint32Array(this._vertexData);
|
|
8914
|
-
}
|
|
8915
|
-
if (requiredBytes > this._vertexBufferCapacity) {
|
|
8916
|
-
this._vertexBuffer?.destroy();
|
|
8917
|
-
this._vertexBufferCapacity = Math.max(requiredBytes, this._vertexBufferCapacity === 0 ? vertexStrideBytes$2 : this._vertexBufferCapacity * 2);
|
|
8918
|
-
this._vertexBuffer = this._device.createBuffer({
|
|
8919
|
-
size: this._vertexBufferCapacity,
|
|
8920
|
-
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
8921
|
-
});
|
|
8922
|
-
}
|
|
8923
|
-
}
|
|
8924
|
-
_ensureIndexCapacity(indexCount) {
|
|
8925
|
-
const requiredBytes = indexCount * Uint16Array.BYTES_PER_ELEMENT;
|
|
8926
|
-
if (requiredBytes > this._indexBufferCapacity) {
|
|
8927
|
-
this._indexBuffer?.destroy();
|
|
8928
|
-
this._indexBufferCapacity = Math.max(requiredBytes, this._indexBufferCapacity === 0 ? Uint16Array.BYTES_PER_ELEMENT : this._indexBufferCapacity * 2);
|
|
8929
|
-
this._indexBuffer = this._device.createBuffer({
|
|
8930
|
-
size: this._indexBufferCapacity,
|
|
8931
|
-
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
8932
|
-
});
|
|
8933
|
-
}
|
|
8934
|
-
}
|
|
8935
|
-
_getPipeline(key) {
|
|
8936
|
-
const pipelineKey = `${key.topology}:${key.usesStripIndex ? 1 : 0}:${key.blendMode}:${key.format}`;
|
|
8937
|
-
const existingPipeline = this._pipelines.get(pipelineKey);
|
|
8938
|
-
if (existingPipeline) {
|
|
8939
|
-
return existingPipeline;
|
|
8940
|
-
}
|
|
8941
|
-
const pipeline = this._device.createRenderPipeline({
|
|
8942
|
-
layout: this._pipelineLayout,
|
|
8943
|
-
vertex: {
|
|
8944
|
-
module: this._shaderModule,
|
|
8945
|
-
entryPoint: 'vertexMain',
|
|
8946
|
-
buffers: [{
|
|
8947
|
-
arrayStride: vertexStrideBytes$2,
|
|
8948
|
-
attributes: [{
|
|
8949
|
-
shaderLocation: 0,
|
|
8950
|
-
offset: 0,
|
|
8951
|
-
format: 'float32x4',
|
|
8952
|
-
}, {
|
|
8953
|
-
shaderLocation: 1,
|
|
8954
|
-
offset: 16,
|
|
8955
|
-
format: 'unorm8x4',
|
|
8956
|
-
}],
|
|
8957
|
-
}],
|
|
8958
|
-
},
|
|
8959
|
-
fragment: {
|
|
8960
|
-
module: this._shaderModule,
|
|
8961
|
-
entryPoint: 'fragmentMain',
|
|
8962
|
-
targets: [{
|
|
8963
|
-
format: key.format,
|
|
8964
|
-
blend: getWebGpuBlendState(key.blendMode),
|
|
8965
|
-
writeMask: GPUColorWrite.ALL,
|
|
8966
|
-
}],
|
|
8967
|
-
},
|
|
8968
|
-
primitive: {
|
|
8969
|
-
topology: key.topology,
|
|
8970
|
-
stripIndexFormat: key.usesStripIndex ? 'uint16' : undefined,
|
|
8971
|
-
},
|
|
8972
|
-
});
|
|
8973
|
-
this._pipelines.set(pipelineKey, pipeline);
|
|
8974
|
-
return pipeline;
|
|
8142
|
+
if (this._clipPixelStack.length > 0) {
|
|
8143
|
+
this._applyClipState();
|
|
8144
|
+
}
|
|
8975
8145
|
}
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
case RenderingPrimitives.Lines:
|
|
8981
|
-
return 'line-list';
|
|
8982
|
-
case RenderingPrimitives.LineLoop:
|
|
8983
|
-
case RenderingPrimitives.LineStrip:
|
|
8984
|
-
return 'line-strip';
|
|
8985
|
-
case RenderingPrimitives.Triangles:
|
|
8986
|
-
case RenderingPrimitives.TriangleFan:
|
|
8987
|
-
return 'triangle-list';
|
|
8988
|
-
case RenderingPrimitives.TriangleStrip:
|
|
8989
|
-
return 'triangle-strip';
|
|
8990
|
-
default:
|
|
8991
|
-
throw new Error(`WebGPU primitive renderer does not support draw mode "${drawMode}" yet.`);
|
|
8146
|
+
_setActiveRenderer(renderer) {
|
|
8147
|
+
if (this._renderer !== renderer) {
|
|
8148
|
+
this._flushActiveRenderer();
|
|
8149
|
+
this._renderer = renderer;
|
|
8992
8150
|
}
|
|
8993
8151
|
}
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
return null;
|
|
8152
|
+
_flushActiveRenderer() {
|
|
8153
|
+
if (this._renderer && !this._contextLost) {
|
|
8154
|
+
this._bindRenderTarget(this._renderTarget);
|
|
8155
|
+
this._renderer.flush();
|
|
8999
8156
|
}
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
const
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
return {
|
|
9013
|
-
topology,
|
|
9014
|
-
usesStripIndex,
|
|
9015
|
-
vertexCount,
|
|
9016
|
-
indices,
|
|
9017
|
-
indexCount,
|
|
9018
|
-
};
|
|
9019
|
-
}
|
|
9020
|
-
return {
|
|
9021
|
-
topology,
|
|
9022
|
-
usesStripIndex,
|
|
9023
|
-
vertexCount,
|
|
9024
|
-
indices: null,
|
|
9025
|
-
indexCount: 0,
|
|
9026
|
-
};
|
|
8157
|
+
}
|
|
8158
|
+
_prepareRenderTarget(target) {
|
|
8159
|
+
const state = this._getRenderTargetState(target);
|
|
8160
|
+
if (target instanceof RenderTexture && state.framebuffer) {
|
|
8161
|
+
const previousFramebuffer = this._boundFramebuffer;
|
|
8162
|
+
const textureState = this._syncTexture(target);
|
|
8163
|
+
if (state.attachedTexture !== textureState.handle) {
|
|
8164
|
+
const gl = this._context;
|
|
8165
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, state.framebuffer);
|
|
8166
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureState.handle, 0);
|
|
8167
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, previousFramebuffer);
|
|
8168
|
+
state.attachedTexture = textureState.handle;
|
|
9027
8169
|
}
|
|
9028
8170
|
}
|
|
8171
|
+
return state;
|
|
9029
8172
|
}
|
|
9030
|
-
|
|
9031
|
-
const
|
|
9032
|
-
const
|
|
9033
|
-
|
|
9034
|
-
|
|
8173
|
+
_syncTexture(texture) {
|
|
8174
|
+
const gl = this._context;
|
|
8175
|
+
const state = this._getTextureState(texture);
|
|
8176
|
+
const version = texture instanceof RenderTexture ? texture.textureVersion : texture.version;
|
|
8177
|
+
gl.bindTexture(gl.TEXTURE_2D, state.handle);
|
|
8178
|
+
if (state.version !== version) {
|
|
8179
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode);
|
|
8180
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode);
|
|
8181
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, texture.wrapMode);
|
|
8182
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, texture.wrapMode);
|
|
8183
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha);
|
|
8184
|
+
if (texture instanceof RenderTexture) {
|
|
8185
|
+
if (state.version === -1 || state.width !== texture.width || state.height !== texture.height || texture.source === null) {
|
|
8186
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texture.width, texture.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, texture.source);
|
|
8187
|
+
}
|
|
8188
|
+
else {
|
|
8189
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, texture.width, texture.height, gl.RGBA, gl.UNSIGNED_BYTE, texture.source);
|
|
8190
|
+
}
|
|
8191
|
+
}
|
|
8192
|
+
else if (texture.source) {
|
|
8193
|
+
if (state.version === -1 || state.width !== texture.width || state.height !== texture.height) {
|
|
8194
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source);
|
|
8195
|
+
}
|
|
8196
|
+
else {
|
|
8197
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, texture.source);
|
|
8198
|
+
}
|
|
8199
|
+
}
|
|
8200
|
+
if (texture.generateMipMap && (texture instanceof RenderTexture || texture.source !== null)) {
|
|
8201
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
8202
|
+
}
|
|
8203
|
+
state.version = version;
|
|
8204
|
+
state.width = texture.width;
|
|
8205
|
+
state.height = texture.height;
|
|
9035
8206
|
}
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
8207
|
+
return state;
|
|
8208
|
+
}
|
|
8209
|
+
_toClipPixels(bounds) {
|
|
8210
|
+
const topLeft = this._renderTarget.mapCoordsToPixel(this._clipPointA.set(bounds.left, bounds.top));
|
|
8211
|
+
const bottomRight = this._renderTarget.mapCoordsToPixel(this._clipPointB.set(bounds.right, bounds.bottom));
|
|
8212
|
+
const minX = Math.min(topLeft.x, bottomRight.x);
|
|
8213
|
+
const maxX = Math.max(topLeft.x, bottomRight.x);
|
|
8214
|
+
const minY = Math.min(topLeft.y, bottomRight.y);
|
|
8215
|
+
const maxY = Math.max(topLeft.y, bottomRight.y);
|
|
8216
|
+
const targetWidth = this._renderTarget.width;
|
|
8217
|
+
const targetHeight = this._renderTarget.height;
|
|
8218
|
+
const x = Math.max(0, Math.min(targetWidth, Math.floor(minX)));
|
|
8219
|
+
const right = Math.max(0, Math.min(targetWidth, Math.ceil(maxX)));
|
|
8220
|
+
const yTop = Math.max(0, Math.min(targetHeight, Math.floor(minY)));
|
|
8221
|
+
const bottom = Math.max(0, Math.min(targetHeight, Math.ceil(maxY)));
|
|
8222
|
+
const width = Math.max(0, right - x);
|
|
8223
|
+
const height = Math.max(0, bottom - yTop);
|
|
8224
|
+
const y = Math.max(0, targetHeight - bottom);
|
|
9040
8225
|
return {
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
indexCount: loopIndexCount,
|
|
8226
|
+
x,
|
|
8227
|
+
y,
|
|
8228
|
+
width,
|
|
8229
|
+
height,
|
|
9046
8230
|
};
|
|
9047
8231
|
}
|
|
9048
|
-
|
|
9049
|
-
const
|
|
9050
|
-
const
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
}
|
|
9054
|
-
const indexCount = (sourceCount - 2) * 3;
|
|
9055
|
-
const generatedIndices = this._ensureGeneratedIndexCapacity(indexCount);
|
|
9056
|
-
let targetIndex = 0;
|
|
9057
|
-
for (let index = 1; index < sourceCount - 1; index++) {
|
|
9058
|
-
generatedIndices[targetIndex++] = sourceIndices[0];
|
|
9059
|
-
generatedIndices[targetIndex++] = sourceIndices[index];
|
|
9060
|
-
generatedIndices[targetIndex++] = sourceIndices[index + 1];
|
|
9061
|
-
}
|
|
8232
|
+
_intersectClips(first, second) {
|
|
8233
|
+
const left = Math.max(first.x, second.x);
|
|
8234
|
+
const bottom = Math.max(first.y, second.y);
|
|
8235
|
+
const right = Math.min(first.x + first.width, second.x + second.width);
|
|
8236
|
+
const top = Math.min(first.y + first.height, second.y + second.height);
|
|
9062
8237
|
return {
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
indexCount,
|
|
8238
|
+
x: left,
|
|
8239
|
+
y: bottom,
|
|
8240
|
+
width: Math.max(0, right - left),
|
|
8241
|
+
height: Math.max(0, top - bottom),
|
|
9068
8242
|
};
|
|
9069
8243
|
}
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
8244
|
+
_applyClipState() {
|
|
8245
|
+
const gl = this._context;
|
|
8246
|
+
if (this._clipPixelStack.length === 0) {
|
|
8247
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
8248
|
+
return;
|
|
8249
|
+
}
|
|
8250
|
+
const clip = this._clipPixelStack[this._clipPixelStack.length - 1];
|
|
8251
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
8252
|
+
gl.scissor(clip.x, clip.y, clip.width, clip.height);
|
|
8253
|
+
}
|
|
8254
|
+
}
|
|
8255
|
+
|
|
8256
|
+
/// <reference types="@webgpu/types" />
|
|
8257
|
+
/**
|
|
8258
|
+
* Base class for WebGPU renderers.
|
|
8259
|
+
*
|
|
8260
|
+
* Manages the connect/disconnect lifecycle and provides a safe
|
|
8261
|
+
* `getBackend()` accessor that throws if the renderer is not connected.
|
|
8262
|
+
*
|
|
8263
|
+
* Subclasses must implement:
|
|
8264
|
+
* - onConnect(backend): set up GPU resources (shader modules, pipelines, buffers)
|
|
8265
|
+
* - onDisconnect(): tear down GPU resources
|
|
8266
|
+
* - render(drawable): collect draw call data for the given drawable
|
|
8267
|
+
* - flush(): encode and submit command buffers for all collected draw calls
|
|
8268
|
+
*/
|
|
8269
|
+
class AbstractWebGpuRenderer {
|
|
8270
|
+
backendType = RenderBackendType.WebGpu;
|
|
8271
|
+
_backend = null;
|
|
8272
|
+
connect(backend) {
|
|
8273
|
+
if (this._backend !== null) {
|
|
8274
|
+
return;
|
|
9077
8275
|
}
|
|
9078
|
-
|
|
9079
|
-
this.
|
|
8276
|
+
if (backend.backendType !== RenderBackendType.WebGpu) {
|
|
8277
|
+
throw new Error(`${this.constructor.name} requires a WebGPU backend, `
|
|
8278
|
+
+ `but received backendType ${String(backend.backendType)}.`);
|
|
9080
8279
|
}
|
|
9081
|
-
|
|
8280
|
+
this._backend = backend;
|
|
8281
|
+
this.onConnect(backend);
|
|
9082
8282
|
}
|
|
9083
|
-
|
|
9084
|
-
if (
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
8283
|
+
disconnect() {
|
|
8284
|
+
if (this._backend === null) {
|
|
8285
|
+
return;
|
|
8286
|
+
}
|
|
8287
|
+
this.flush();
|
|
8288
|
+
this.onDisconnect();
|
|
8289
|
+
this._backend = null;
|
|
8290
|
+
}
|
|
8291
|
+
getBackend() {
|
|
8292
|
+
if (this._backend === null) {
|
|
8293
|
+
throw new Error(`${this.constructor.name} is not connected to a backend.`);
|
|
9090
8294
|
}
|
|
9091
|
-
return this.
|
|
8295
|
+
return this._backend;
|
|
9092
8296
|
}
|
|
9093
|
-
|
|
9094
|
-
this.
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
8297
|
+
getBackendOrNull() {
|
|
8298
|
+
return this._backend;
|
|
8299
|
+
}
|
|
8300
|
+
}
|
|
8301
|
+
|
|
8302
|
+
/// <reference types="@webgpu/types" />
|
|
8303
|
+
/**
|
|
8304
|
+
* Returns the GPUBlendState for a given ExoJS blend mode.
|
|
8305
|
+
* Shared by all WebGPU renderers to avoid duplication.
|
|
8306
|
+
*/
|
|
8307
|
+
function getWebGpuBlendState(blendMode) {
|
|
8308
|
+
switch (blendMode) {
|
|
8309
|
+
case BlendModes.Additive:
|
|
8310
|
+
return {
|
|
8311
|
+
color: {
|
|
8312
|
+
operation: 'add',
|
|
8313
|
+
srcFactor: 'one',
|
|
8314
|
+
dstFactor: 'one',
|
|
8315
|
+
},
|
|
8316
|
+
alpha: {
|
|
8317
|
+
operation: 'add',
|
|
8318
|
+
srcFactor: 'one',
|
|
8319
|
+
dstFactor: 'one',
|
|
8320
|
+
},
|
|
8321
|
+
};
|
|
8322
|
+
case BlendModes.Subtract:
|
|
8323
|
+
return {
|
|
8324
|
+
color: {
|
|
8325
|
+
operation: 'add',
|
|
8326
|
+
srcFactor: 'zero',
|
|
8327
|
+
dstFactor: 'one-minus-src',
|
|
8328
|
+
},
|
|
8329
|
+
alpha: {
|
|
8330
|
+
operation: 'add',
|
|
8331
|
+
srcFactor: 'zero',
|
|
8332
|
+
dstFactor: 'one-minus-src-alpha',
|
|
8333
|
+
},
|
|
8334
|
+
};
|
|
8335
|
+
case BlendModes.Multiply:
|
|
8336
|
+
return {
|
|
8337
|
+
color: {
|
|
8338
|
+
operation: 'add',
|
|
8339
|
+
srcFactor: 'dst',
|
|
8340
|
+
dstFactor: 'one-minus-src-alpha',
|
|
8341
|
+
},
|
|
8342
|
+
alpha: {
|
|
8343
|
+
operation: 'add',
|
|
8344
|
+
srcFactor: 'dst-alpha',
|
|
8345
|
+
dstFactor: 'one-minus-src-alpha',
|
|
8346
|
+
},
|
|
8347
|
+
};
|
|
8348
|
+
case BlendModes.Screen:
|
|
8349
|
+
return {
|
|
8350
|
+
color: {
|
|
8351
|
+
operation: 'add',
|
|
8352
|
+
srcFactor: 'one',
|
|
8353
|
+
dstFactor: 'one-minus-src',
|
|
8354
|
+
},
|
|
8355
|
+
alpha: {
|
|
8356
|
+
operation: 'add',
|
|
8357
|
+
srcFactor: 'one',
|
|
8358
|
+
dstFactor: 'one-minus-src-alpha',
|
|
8359
|
+
},
|
|
8360
|
+
};
|
|
8361
|
+
default:
|
|
8362
|
+
return {
|
|
8363
|
+
color: {
|
|
8364
|
+
operation: 'add',
|
|
8365
|
+
srcFactor: 'one',
|
|
8366
|
+
dstFactor: 'one-minus-src-alpha',
|
|
8367
|
+
},
|
|
8368
|
+
alpha: {
|
|
8369
|
+
operation: 'add',
|
|
8370
|
+
srcFactor: 'one',
|
|
8371
|
+
dstFactor: 'one-minus-src-alpha',
|
|
8372
|
+
},
|
|
8373
|
+
};
|
|
9100
8374
|
}
|
|
9101
8375
|
}
|
|
9102
8376
|
|
|
@@ -10746,8 +10020,7 @@ class WebGpuMaskCompositor {
|
|
|
10746
10020
|
}
|
|
10747
10021
|
_writeProjectionMatrix(viewMatrix) {
|
|
10748
10022
|
// Pack the 3x3 affine view matrix into a 4x4 column-major mat4x4
|
|
10749
|
-
// for WGSL
|
|
10750
|
-
// (see `_writeShapeVertices` in that file for the rationale).
|
|
10023
|
+
// for WGSL.
|
|
10751
10024
|
const m = this._projectionMatrix.copy(viewMatrix);
|
|
10752
10025
|
const data = this._projectionData;
|
|
10753
10026
|
// col 0
|
|
@@ -10814,7 +10087,6 @@ class WebGpuBackend {
|
|
|
10814
10087
|
if (clearColor) {
|
|
10815
10088
|
this._clearColor.copy(clearColor);
|
|
10816
10089
|
}
|
|
10817
|
-
this.rendererRegistry.registerRenderer(DrawableShape, new WebGpuPrimitiveRenderer());
|
|
10818
10090
|
this.rendererRegistry.registerRenderer(Sprite, new WebGpuSpriteRenderer());
|
|
10819
10091
|
this.rendererRegistry.registerRenderer(Mesh, new WebGpuMeshRenderer());
|
|
10820
10092
|
this.rendererRegistry.registerRenderer(ParticleSystem, new WebGpuParticleRenderer());
|
|
@@ -14815,7 +14087,6 @@ const defaultAppSettings = {
|
|
|
14815
14087
|
debug: false,
|
|
14816
14088
|
spriteRendererBatchSize: 4096, // ~ 262kb
|
|
14817
14089
|
particleRendererBatchSize: 8192, // ~ 1.18mb
|
|
14818
|
-
primitiveRendererBatchSize: 65536, // ~ 786kb
|
|
14819
14090
|
gamepadDefinitions: [],
|
|
14820
14091
|
pointerDistanceThreshold: 10,
|
|
14821
14092
|
webglAttributes: {
|
|
@@ -16347,24 +15618,30 @@ function signedArea(data, start, end, dim) {
|
|
|
16347
15618
|
return sum;
|
|
16348
15619
|
}
|
|
16349
15620
|
|
|
16350
|
-
const buildLine = (startX, startY, endX, endY, width
|
|
15621
|
+
const buildLine = (startX, startY, endX, endY, width) => {
|
|
16351
15622
|
const points = [startX, startY, endX, endY];
|
|
16352
15623
|
const distance = width / 2;
|
|
16353
|
-
const index = vertices.length / 6;
|
|
16354
15624
|
const perpA = new Vector(startX - endX, startY - endY).perp().normalize().multiply(distance);
|
|
16355
15625
|
const perpB = new Vector(endX - startX, endY - startY).perp().normalize().multiply(distance);
|
|
16356
|
-
vertices
|
|
16357
|
-
|
|
16358
|
-
|
|
16359
|
-
|
|
16360
|
-
|
|
16361
|
-
|
|
15626
|
+
const vertices = new Float32Array([
|
|
15627
|
+
startX - perpA.x, startY - perpA.y, // 0: start-left
|
|
15628
|
+
startX + perpA.x, startY + perpA.y, // 1: start-right
|
|
15629
|
+
endX - perpB.x, endY - perpB.y, // 2: end-left
|
|
15630
|
+
endX + perpB.x, endY + perpB.y, // 3: end-right
|
|
15631
|
+
]);
|
|
15632
|
+
perpA.destroy();
|
|
15633
|
+
perpB.destroy();
|
|
15634
|
+
const indices = new Uint16Array([0, 1, 3, 0, 3, 2]);
|
|
15635
|
+
return { vertices, indices, points };
|
|
16362
15636
|
};
|
|
16363
|
-
const buildPath = (points, width
|
|
15637
|
+
const buildPath = (points, width) => {
|
|
16364
15638
|
if (points.length < 4) {
|
|
16365
15639
|
throw new Error('At least two X/Y pairs are required to build a line.');
|
|
16366
15640
|
}
|
|
16367
|
-
const lineWidth = width / 2
|
|
15641
|
+
const lineWidth = width / 2;
|
|
15642
|
+
const firstPoint = new Vector(points[0], points[1]);
|
|
15643
|
+
const lastPoint = new Vector(points[points.length - 2], points[points.length - 1]);
|
|
15644
|
+
const outlinePoints = points;
|
|
16368
15645
|
if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) {
|
|
16369
15646
|
points = points.slice();
|
|
16370
15647
|
points.pop();
|
|
@@ -16375,9 +15652,10 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16375
15652
|
points.unshift(midPointX, midPointY);
|
|
16376
15653
|
points.push(midPointX, midPointY);
|
|
16377
15654
|
}
|
|
15655
|
+
firstPoint.destroy();
|
|
15656
|
+
lastPoint.destroy();
|
|
16378
15657
|
const length = points.length / 2;
|
|
16379
|
-
|
|
16380
|
-
let indexStart = vertices.length / 6;
|
|
15658
|
+
const stripVertices = [];
|
|
16381
15659
|
let p1x = points[0];
|
|
16382
15660
|
let p1y = points[1];
|
|
16383
15661
|
let p2x = points[2];
|
|
@@ -16395,8 +15673,8 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16395
15673
|
perpy /= dist;
|
|
16396
15674
|
perpx *= lineWidth;
|
|
16397
15675
|
perpy *= lineWidth;
|
|
16398
|
-
|
|
16399
|
-
|
|
15676
|
+
stripVertices.push(p1x - perpx, p1y - perpy);
|
|
15677
|
+
stripVertices.push(p1x + perpx, p1y + perpy);
|
|
16400
15678
|
for (let i = 1; i < length - 1; i++) {
|
|
16401
15679
|
p1x = points[(i - 1) * 2];
|
|
16402
15680
|
p1y = points[((i - 1) * 2) + 1];
|
|
@@ -16427,8 +15705,8 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16427
15705
|
let denom = (a1 * b2) - (a2 * b1);
|
|
16428
15706
|
if (Math.abs(denom) < 0.1) {
|
|
16429
15707
|
denom += 10.1;
|
|
16430
|
-
|
|
16431
|
-
|
|
15708
|
+
stripVertices.push(p2x - perpx, p2y - perpy);
|
|
15709
|
+
stripVertices.push(p2x + perpx, p2y + perpy);
|
|
16432
15710
|
continue;
|
|
16433
15711
|
}
|
|
16434
15712
|
const px = ((b1 * c2) - (b2 * c1)) / denom;
|
|
@@ -16442,14 +15720,13 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16442
15720
|
perp3y /= dist;
|
|
16443
15721
|
perp3x *= lineWidth;
|
|
16444
15722
|
perp3y *= lineWidth;
|
|
16445
|
-
|
|
16446
|
-
|
|
16447
|
-
|
|
16448
|
-
indexCount++;
|
|
15723
|
+
stripVertices.push(p2x - perp3x, p2y - perp3y);
|
|
15724
|
+
stripVertices.push(p2x + perp3x, p2y + perp3y);
|
|
15725
|
+
stripVertices.push(p2x - perp3x, p2y - perp3y);
|
|
16449
15726
|
}
|
|
16450
15727
|
else {
|
|
16451
|
-
|
|
16452
|
-
|
|
15728
|
+
stripVertices.push(px, py);
|
|
15729
|
+
stripVertices.push(p2x - (px - p2x), p2y - (py - p2y));
|
|
16453
15730
|
}
|
|
16454
15731
|
}
|
|
16455
15732
|
p1x = points[(length - 2) * 2];
|
|
@@ -16463,70 +15740,112 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16463
15740
|
perpy /= dist;
|
|
16464
15741
|
perpx *= lineWidth;
|
|
16465
15742
|
perpy *= lineWidth;
|
|
16466
|
-
|
|
16467
|
-
|
|
16468
|
-
indices.
|
|
16469
|
-
|
|
16470
|
-
|
|
16471
|
-
|
|
16472
|
-
|
|
16473
|
-
|
|
15743
|
+
stripVertices.push(p2x - perpx, p2y - perpy);
|
|
15744
|
+
stripVertices.push(p2x + perpx, p2y + perpy);
|
|
15745
|
+
// Convert strip-style vertex sequence to triangle-list indices.
|
|
15746
|
+
// For N strip vertices (N = stripVertices.length / 2), each i in [0, N-3]
|
|
15747
|
+
// produces a triangle. Even i: (i, i+1, i+2). Odd i: (i+1, i, i+2).
|
|
15748
|
+
// This preserves the same winding the original triangle-strip pipeline saw.
|
|
15749
|
+
const stripVertexCount = stripVertices.length / 2;
|
|
15750
|
+
const vertices = new Float32Array(stripVertices);
|
|
15751
|
+
const triangleCount = stripVertexCount >= 3 ? stripVertexCount - 2 : 0;
|
|
15752
|
+
const indices = new Uint16Array(triangleCount * 3);
|
|
15753
|
+
for (let i = 0; i < triangleCount; i++) {
|
|
15754
|
+
const base = i * 3;
|
|
15755
|
+
if ((i & 1) === 0) {
|
|
15756
|
+
indices[base] = i;
|
|
15757
|
+
indices[base + 1] = i + 1;
|
|
15758
|
+
indices[base + 2] = i + 2;
|
|
15759
|
+
}
|
|
15760
|
+
else {
|
|
15761
|
+
indices[base] = i + 1;
|
|
15762
|
+
indices[base + 1] = i;
|
|
15763
|
+
indices[base + 2] = i + 2;
|
|
15764
|
+
}
|
|
15765
|
+
}
|
|
15766
|
+
return { vertices, indices, points: outlinePoints };
|
|
16474
15767
|
};
|
|
16475
|
-
const buildCircle = (centerX, centerY, radius
|
|
16476
|
-
const length = Math.floor(15 * Math.sqrt(radius + radius))
|
|
16477
|
-
|
|
16478
|
-
|
|
16479
|
-
|
|
16480
|
-
|
|
15768
|
+
const buildCircle = (centerX, centerY, radius) => {
|
|
15769
|
+
const length = Math.floor(15 * Math.sqrt(radius + radius));
|
|
15770
|
+
const segment = (Math.PI * 2) / length;
|
|
15771
|
+
const points = [];
|
|
15772
|
+
// 1 center vertex + N perimeter vertices.
|
|
15773
|
+
const vertices = new Float32Array((length + 1) * 2);
|
|
15774
|
+
vertices[0] = centerX;
|
|
15775
|
+
vertices[1] = centerY;
|
|
15776
|
+
for (let i = 0; i < length; i++) {
|
|
15777
|
+
const segmentX = centerX + (Math.sin(segment * i) * radius);
|
|
15778
|
+
const segmentY = centerY + (Math.cos(segment * i) * radius);
|
|
16481
15779
|
points.push(segmentX, segmentY);
|
|
16482
|
-
|
|
16483
|
-
vertices
|
|
16484
|
-
|
|
15780
|
+
const offset = (i + 1) * 2;
|
|
15781
|
+
vertices[offset] = segmentX;
|
|
15782
|
+
vertices[offset + 1] = segmentY;
|
|
15783
|
+
}
|
|
15784
|
+
const indices = new Uint16Array(length * 3);
|
|
15785
|
+
for (let i = 0; i < length; i++) {
|
|
15786
|
+
const base = i * 3;
|
|
15787
|
+
indices[base] = 0;
|
|
15788
|
+
indices[base + 1] = i + 1;
|
|
15789
|
+
indices[base + 2] = i + 2 > length ? 1 : i + 2;
|
|
16485
15790
|
}
|
|
16486
|
-
indices
|
|
16487
|
-
return new Geometry({ vertices, indices, points });
|
|
15791
|
+
return { vertices, indices, points };
|
|
16488
15792
|
};
|
|
16489
|
-
const buildEllipse = (centerX, centerY, radiusX, radiusY
|
|
16490
|
-
const length = Math.floor(15 * Math.sqrt(radiusX + radiusY))
|
|
16491
|
-
|
|
16492
|
-
|
|
16493
|
-
|
|
16494
|
-
|
|
15793
|
+
const buildEllipse = (centerX, centerY, radiusX, radiusY) => {
|
|
15794
|
+
const length = Math.floor(15 * Math.sqrt(radiusX + radiusY));
|
|
15795
|
+
const segment = (Math.PI * 2) / length;
|
|
15796
|
+
const points = [];
|
|
15797
|
+
const vertices = new Float32Array((length + 1) * 2);
|
|
15798
|
+
vertices[0] = centerX;
|
|
15799
|
+
vertices[1] = centerY;
|
|
15800
|
+
for (let i = 0; i < length; i++) {
|
|
15801
|
+
const segmentX = centerX + (Math.sin(segment * i) * radiusX);
|
|
15802
|
+
const segmentY = centerY + (Math.cos(segment * i) * radiusY);
|
|
16495
15803
|
points.push(segmentX, segmentY);
|
|
16496
|
-
|
|
16497
|
-
vertices
|
|
16498
|
-
|
|
15804
|
+
const offset = (i + 1) * 2;
|
|
15805
|
+
vertices[offset] = segmentX;
|
|
15806
|
+
vertices[offset + 1] = segmentY;
|
|
15807
|
+
}
|
|
15808
|
+
const indices = new Uint16Array(length * 3);
|
|
15809
|
+
for (let i = 0; i < length; i++) {
|
|
15810
|
+
const base = i * 3;
|
|
15811
|
+
indices[base] = 0;
|
|
15812
|
+
indices[base + 1] = i + 1;
|
|
15813
|
+
indices[base + 2] = i + 2 > length ? 1 : i + 2;
|
|
16499
15814
|
}
|
|
16500
|
-
indices
|
|
16501
|
-
return new Geometry({ vertices, indices, points });
|
|
15815
|
+
return { vertices, indices, points };
|
|
16502
15816
|
};
|
|
16503
|
-
const buildPolygon = (points
|
|
15817
|
+
const buildPolygon = (points) => {
|
|
16504
15818
|
if (points.length < 6) {
|
|
16505
15819
|
throw new Error('At least three X/Y pairs are required to build a polygon.');
|
|
16506
15820
|
}
|
|
16507
|
-
const
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
indices.push(triangles[i + 2] + index);
|
|
16514
|
-
indices.push(triangles[i + 2] + index);
|
|
16515
|
-
}
|
|
16516
|
-
for (let i = 0; i < length; i++) {
|
|
16517
|
-
vertices.push(points[i * 2], points[(i * 2) + 1]);
|
|
16518
|
-
}
|
|
15821
|
+
const length = points.length / 2;
|
|
15822
|
+
const triangles = earcut(points, [], 2);
|
|
15823
|
+
const vertices = new Float32Array(points.length);
|
|
15824
|
+
for (let i = 0; i < length; i++) {
|
|
15825
|
+
vertices[i * 2] = points[i * 2];
|
|
15826
|
+
vertices[(i * 2) + 1] = points[(i * 2) + 1];
|
|
16519
15827
|
}
|
|
16520
|
-
|
|
15828
|
+
const indices = triangles ? new Uint16Array(triangles) : new Uint16Array(0);
|
|
15829
|
+
return { vertices, indices, points };
|
|
16521
15830
|
};
|
|
16522
|
-
const buildRectangle = (x, y, width, height
|
|
16523
|
-
|
|
16524
|
-
vertices
|
|
16525
|
-
|
|
16526
|
-
|
|
15831
|
+
const buildRectangle = (x, y, width, height) => {
|
|
15832
|
+
// 4 vertices: TL, TR, BL, BR. Triangles [0, 1, 2, 1, 3, 2] (clockwise).
|
|
15833
|
+
const vertices = new Float32Array([
|
|
15834
|
+
x, y, // 0 TL
|
|
15835
|
+
x + width, y, // 1 TR
|
|
15836
|
+
x, y + height, // 2 BL
|
|
15837
|
+
x + width, y + height, // 3 BR
|
|
15838
|
+
]);
|
|
15839
|
+
const indices = new Uint16Array([0, 1, 2, 1, 3, 2]);
|
|
15840
|
+
// Outline points walk the perimeter (TL -> TR -> BR -> BL).
|
|
15841
|
+
const points = [x, y, x + width, y, x + width, y + height, x, y + height];
|
|
15842
|
+
return { vertices, indices, points };
|
|
16527
15843
|
};
|
|
16528
15844
|
const buildStar = (centerX, centerY, points, radius, innerRadius = radius / 2, rotation = 0) => {
|
|
16529
|
-
const startAngle = (Math.PI / -2) + rotation
|
|
15845
|
+
const startAngle = (Math.PI / -2) + rotation;
|
|
15846
|
+
const length = points * 2;
|
|
15847
|
+
const delta = tau / length;
|
|
15848
|
+
const path = [];
|
|
16530
15849
|
for (let i = 0; i < length; i++) {
|
|
16531
15850
|
const angle = startAngle + (i * delta);
|
|
16532
15851
|
const rad = i % 2 ? innerRadius : radius;
|
|
@@ -17369,23 +16688,6 @@ class UniversalEmitter {
|
|
|
17369
16688
|
}
|
|
17370
16689
|
}
|
|
17371
16690
|
|
|
17372
|
-
class CircleGeometry extends Geometry {
|
|
17373
|
-
constructor(centerX, centerY, radius) {
|
|
17374
|
-
const length = Math.floor(15 * Math.sqrt(radius + radius)), segment = (Math.PI * 2) / length, vertices = [], indices = [], points = [];
|
|
17375
|
-
let index = vertices.length / 6;
|
|
17376
|
-
indices.push(index);
|
|
17377
|
-
for (let i = 0; i < length + 1; i++) {
|
|
17378
|
-
const segmentX = centerX + (Math.sin(segment * i) * radius), segmentY = centerY + (Math.cos(segment * i) * radius);
|
|
17379
|
-
points.push(segmentX, segmentY);
|
|
17380
|
-
vertices.push(centerX, centerY);
|
|
17381
|
-
vertices.push(segmentX, segmentY);
|
|
17382
|
-
indices.push(index++, index++);
|
|
17383
|
-
}
|
|
17384
|
-
indices.push(index - 1);
|
|
17385
|
-
super({ vertices, indices, points });
|
|
17386
|
-
}
|
|
17387
|
-
}
|
|
17388
|
-
|
|
17389
16691
|
class Graphics extends Container {
|
|
17390
16692
|
_lineWidth = 0;
|
|
17391
16693
|
_lineColor = new Color();
|
|
@@ -17416,14 +16718,14 @@ class Graphics extends Container {
|
|
|
17416
16718
|
return super.getChildAt(index);
|
|
17417
16719
|
}
|
|
17418
16720
|
addChild(child) {
|
|
17419
|
-
if (!(child instanceof
|
|
17420
|
-
throw new Error('Graphics can only contain
|
|
16721
|
+
if (!(child instanceof Mesh)) {
|
|
16722
|
+
throw new Error('Graphics can only contain Mesh children.');
|
|
17421
16723
|
}
|
|
17422
16724
|
return super.addChild(child);
|
|
17423
16725
|
}
|
|
17424
16726
|
addChildAt(child, index) {
|
|
17425
|
-
if (!(child instanceof
|
|
17426
|
-
throw new Error('Graphics can only contain
|
|
16727
|
+
if (!(child instanceof Mesh)) {
|
|
16728
|
+
throw new Error('Graphics can only contain Mesh children.');
|
|
17427
16729
|
}
|
|
17428
16730
|
return super.addChildAt(child, index);
|
|
17429
16731
|
}
|
|
@@ -17519,50 +16821,52 @@ class Graphics extends Container {
|
|
|
17519
16821
|
return this;
|
|
17520
16822
|
}
|
|
17521
16823
|
drawLine(startX, startY, endX, endY) {
|
|
17522
|
-
|
|
16824
|
+
const data = buildLine(startX, startY, endX, endY, this._lineWidth);
|
|
16825
|
+
this.addChild(this._createMesh(data, this._lineColor));
|
|
17523
16826
|
return this;
|
|
17524
16827
|
}
|
|
17525
16828
|
drawPath(path) {
|
|
17526
|
-
|
|
16829
|
+
const data = buildPath(path, this._lineWidth);
|
|
16830
|
+
this.addChild(this._createMesh(data, this._lineColor));
|
|
17527
16831
|
return this;
|
|
17528
16832
|
}
|
|
17529
16833
|
drawPolygon(path) {
|
|
17530
|
-
const
|
|
17531
|
-
this.addChild(
|
|
16834
|
+
const data = buildPolygon(path);
|
|
16835
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17532
16836
|
if (this._lineWidth > 0) {
|
|
17533
|
-
this.drawPath(
|
|
16837
|
+
this.drawPath(data.points);
|
|
17534
16838
|
}
|
|
17535
16839
|
return this;
|
|
17536
16840
|
}
|
|
17537
16841
|
drawCircle(centerX, centerY, radius) {
|
|
17538
|
-
const
|
|
17539
|
-
this.addChild(
|
|
16842
|
+
const data = buildCircle(centerX, centerY, radius);
|
|
16843
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17540
16844
|
if (this._lineWidth > 0) {
|
|
17541
|
-
this.drawPath(
|
|
16845
|
+
this.drawPath(data.points);
|
|
17542
16846
|
}
|
|
17543
16847
|
return this;
|
|
17544
16848
|
}
|
|
17545
16849
|
drawEllipse(centerX, centerY, radiusX, radiusY) {
|
|
17546
|
-
const
|
|
17547
|
-
this.addChild(
|
|
16850
|
+
const data = buildEllipse(centerX, centerY, radiusX, radiusY);
|
|
16851
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17548
16852
|
if (this._lineWidth > 0) {
|
|
17549
|
-
this.drawPath(
|
|
16853
|
+
this.drawPath(data.points);
|
|
17550
16854
|
}
|
|
17551
16855
|
return this;
|
|
17552
16856
|
}
|
|
17553
16857
|
drawRectangle(x, y, width, height) {
|
|
17554
|
-
const
|
|
17555
|
-
this.addChild(
|
|
16858
|
+
const data = buildRectangle(x, y, width, height);
|
|
16859
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17556
16860
|
if (this._lineWidth > 0) {
|
|
17557
|
-
this.drawPath(
|
|
16861
|
+
this.drawPath(data.points);
|
|
17558
16862
|
}
|
|
17559
16863
|
return this;
|
|
17560
16864
|
}
|
|
17561
16865
|
drawStar(centerX, centerY, points, radius, innerRadius = radius / 2, rotation = 0) {
|
|
17562
|
-
const
|
|
17563
|
-
this.addChild(
|
|
16866
|
+
const data = buildStar(centerX, centerY, points, radius, innerRadius, rotation);
|
|
16867
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17564
16868
|
if (this._lineWidth > 0) {
|
|
17565
|
-
this.drawPath(
|
|
16869
|
+
this.drawPath(data.points);
|
|
17566
16870
|
}
|
|
17567
16871
|
return this;
|
|
17568
16872
|
}
|
|
@@ -17581,6 +16885,14 @@ class Graphics extends Container {
|
|
|
17581
16885
|
this._fillColor.destroy();
|
|
17582
16886
|
this._currentPoint.destroy();
|
|
17583
16887
|
}
|
|
16888
|
+
_createMesh(data, color) {
|
|
16889
|
+
const mesh = new Mesh({
|
|
16890
|
+
vertices: data.vertices,
|
|
16891
|
+
indices: data.indices,
|
|
16892
|
+
});
|
|
16893
|
+
mesh.tint = color;
|
|
16894
|
+
return mesh;
|
|
16895
|
+
}
|
|
17584
16896
|
}
|
|
17585
16897
|
|
|
17586
16898
|
class Spritesheet {
|
|
@@ -19197,5 +18509,5 @@ const createRapierPhysicsWorld = async (options = {}) => {
|
|
|
19197
18509
|
return await RapierPhysicsWorld.create(options);
|
|
19198
18510
|
};
|
|
19199
18511
|
|
|
19200
|
-
export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, Capabilities, ChannelOffset, ChannelSize, Circle,
|
|
18512
|
+
export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, Capabilities, ChannelOffset, ChannelSize, Circle, Clock, CollisionType, Color, ColorAffector, ColorFilter, Container, Drawable, Ellipse, FactoryRegistry, Filter, Flags, FontFactory, ForceAffector, GameCubeGamepadMapping, Gamepad, GamepadChannel, GamepadControl, GamepadMapping, GamepadMappingFamily, GamepadPromptLayouts, GenericDualAnalogGamepadMapping, Graphics, ImageFactory, IndexedDbDatabase, IndexedDbStore, Input, InputManager, Interval, JoyConLeftGamepadMapping, JoyConRightGamepadMapping, Json, JsonFactory, Keyboard, Line, Loader, Matrix, Mesh, Music, MusicFactory, NetworkOnlyStrategy, ObservableSize, ObservableVector, Particle, ParticleOptions, ParticleSystem, PlayStationGamepadMapping, Pointer, PointerState, PointerStateFlag, PolarVector, Polygon, Quadtree, Random, RapierPhysicsBinding, RapierPhysicsWorld, Rectangle, RenderBackendType, RenderNode, RenderTarget, RenderTargetPass, RenderTexture, RendererRegistry, RenderingPrimitives, Sampler, ScaleAffector, ScaleModes, Scene, SceneManager, SceneNode, Segment, Shader, ShaderAttribute, ShaderPrimitives, ShaderUniform, Signal, Size, Sound, SoundFactory, Sprite, SpriteFlags, Spritesheet, SteamControllerGamepadMapping, SvgAsset, SvgFactory, SwitchProGamepadMapping, Text, TextAsset, TextFactory, TextStyle, Texture, TextureFactory, Time, Timer, TorqueAffector, UniversalEmitter, Vector, Video, VideoFactory, View, ViewFlags, VoronoiRegion, VttAsset, VttFactory, WasmFactory, WebGl2Backend, WebGl2MeshRenderer, WebGl2ParticleRenderer, WebGl2RenderBuffer, WebGl2ShaderBlock, WebGl2SpriteRenderer, WebGl2VertexArrayObject, WebGpuBackend, WebGpuMeshRenderer, WebGpuParticleRenderer, WebGpuSpriteRenderer, WrapModes, XboxGamepadMapping, bezierCurveTo, buildCircle, buildEllipse, buildLine, buildPath, buildPolygon, buildRectangle, buildStar, builtInGamepadDefinitions, canvasSourceToDataUrl, clamp, createRapierPhysicsWorld, createRenderStats, createWebGl2ShaderProgram, decodeAudioData, defineAssetManifest, degreesPerRadian, degreesToRadians, determineMimeType, emptyArrayBuffer, getAudioContext, getCanvasSourceSize, getCollisionCircleCircle, getCollisionCircleRectangle, getCollisionPolygonCircle, getCollisionRectangleRectangle, getCollisionSat, getDistance, getOfflineAudioContext, getPreciseTime, getTextureSourceSize, getVoronoiRegion$1 as getVoronoiRegion, getWebGpuBlendState, hours, inRange, intersectionCircleCircle, intersectionCircleEllipse, intersectionCirclePoly, intersectionEllipseEllipse, intersectionEllipsePoly, intersectionLineCircle, intersectionLineEllipse, intersectionLineLine, intersectionLinePoly, intersectionLineRect, intersectionPointCircle, intersectionPointEllipse, intersectionPointLine, intersectionPointPoint, intersectionPointPoly, intersectionPointRect, intersectionPolyPoly, intersectionRectCircle, intersectionRectEllipse, intersectionRectPoly, intersectionRectRect, intersectionSat, isAudioContextReady, isPowerOfTwo, lerp, matchesIds, milliseconds, minutes, noop$1 as noop, normalizeIds, onAudioContextReady, parseGamepadDescriptor, quadraticCurveTo, radiansPerDegree, radiansToDegrees, rand, removeArrayItems, resetRenderStats, resolveDefinition, resolveGamepadDefinition, seconds, sign$1 as sign, stopEvent, supportsCodec, supportsEventOptions, supportsIndexedDb, supportsPointerEvents, supportsTouchEvents, supportsWebAudio, tau, trimRotation, webGl2PrimitiveArrayConstructors, webGl2PrimitiveByteSizeMapping, webGl2PrimitiveTypeNames };
|
|
19201
18513
|
//# sourceMappingURL=exo.esm.js.map
|