@codexo/exojs 0.6.4 → 0.6.6
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 +122 -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/input/InputManager.d.ts +8 -0
- package/dist/esm/input/InputManager.js +43 -5
- package/dist/esm/input/InputManager.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 +618 -1268
- 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);
|
|
@@ -8396,707 +8151,226 @@ class WebGl2Backend {
|
|
|
8396
8151
|
}
|
|
8397
8152
|
_flushActiveRenderer() {
|
|
8398
8153
|
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;
|
|
8975
|
-
}
|
|
8976
|
-
_getTopology(drawMode) {
|
|
8977
|
-
switch (drawMode) {
|
|
8978
|
-
case RenderingPrimitives.Points:
|
|
8979
|
-
return 'point-list';
|
|
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.`);
|
|
8154
|
+
this._bindRenderTarget(this._renderTarget);
|
|
8155
|
+
this._renderer.flush();
|
|
8992
8156
|
}
|
|
8993
8157
|
}
|
|
8994
|
-
|
|
8995
|
-
const
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
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;
|
|
8169
|
+
}
|
|
8999
8170
|
}
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
indices,
|
|
9017
|
-
indexCount,
|
|
9018
|
-
};
|
|
8171
|
+
return state;
|
|
8172
|
+
}
|
|
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);
|
|
9019
8187
|
}
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
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);
|
|
9027
8202
|
}
|
|
8203
|
+
state.version = version;
|
|
8204
|
+
state.width = texture.width;
|
|
8205
|
+
state.height = texture.height;
|
|
9028
8206
|
}
|
|
8207
|
+
return state;
|
|
9029
8208
|
}
|
|
9030
|
-
|
|
9031
|
-
const
|
|
9032
|
-
const
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
const
|
|
9037
|
-
const
|
|
9038
|
-
|
|
9039
|
-
|
|
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
|
-
this._sequentialIndexData = new Uint16Array(nextLength);
|
|
8244
|
+
_applyClipState() {
|
|
8245
|
+
const gl = this._context;
|
|
8246
|
+
if (this._clipPixelStack.length === 0) {
|
|
8247
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
8248
|
+
return;
|
|
9077
8249
|
}
|
|
9078
|
-
|
|
9079
|
-
|
|
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;
|
|
8275
|
+
}
|
|
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
|
-
while (nextLength < indexCount) {
|
|
9087
|
-
nextLength *= 2;
|
|
9088
|
-
}
|
|
9089
|
-
this._generatedIndexData = new Uint16Array(nextLength);
|
|
8283
|
+
disconnect() {
|
|
8284
|
+
if (this._backend === null) {
|
|
8285
|
+
return;
|
|
9090
8286
|
}
|
|
9091
|
-
|
|
8287
|
+
this.flush();
|
|
8288
|
+
this.onDisconnect();
|
|
8289
|
+
this._backend = null;
|
|
9092
8290
|
}
|
|
9093
|
-
|
|
9094
|
-
this.
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
this.
|
|
9098
|
-
|
|
9099
|
-
|
|
8291
|
+
getBackend() {
|
|
8292
|
+
if (this._backend === null) {
|
|
8293
|
+
throw new Error(`${this.constructor.name} is not connected to a backend.`);
|
|
8294
|
+
}
|
|
8295
|
+
return this._backend;
|
|
8296
|
+
}
|
|
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());
|
|
@@ -12445,16 +11717,30 @@ class InputManager {
|
|
|
12445
11717
|
this.onGamepadUpdated.destroy();
|
|
12446
11718
|
}
|
|
12447
11719
|
handleKeyDown(event) {
|
|
11720
|
+
// Game-engine convention: keys only register while the canvas
|
|
11721
|
+
// owns focus. Otherwise typing into adjacent <input> fields would
|
|
11722
|
+
// also drive game state, which is never what users want.
|
|
11723
|
+
if (!this.canvasFocusedValue) {
|
|
11724
|
+
return;
|
|
11725
|
+
}
|
|
12448
11726
|
const channel = ChannelOffset.Keyboard + event.keyCode;
|
|
12449
11727
|
this.channels[channel] = 1;
|
|
12450
11728
|
this.channelsPressed.push(channel);
|
|
12451
11729
|
this.flags.push(InputManagerFlag.KeyDown);
|
|
11730
|
+
// Consume the event: stop default browser actions (page scroll on
|
|
11731
|
+
// arrow/space, find-as-you-type on /, etc.) and stop propagation
|
|
11732
|
+
// so other listeners on the page don't double-handle.
|
|
11733
|
+
stopEvent(event);
|
|
12452
11734
|
}
|
|
12453
11735
|
handleKeyUp(event) {
|
|
11736
|
+
if (!this.canvasFocusedValue) {
|
|
11737
|
+
return;
|
|
11738
|
+
}
|
|
12454
11739
|
const channel = ChannelOffset.Keyboard + event.keyCode;
|
|
12455
11740
|
this.channels[channel] = 0;
|
|
12456
11741
|
this.channelsReleased.push(channel);
|
|
12457
11742
|
this.flags.push(InputManagerFlag.KeyUp);
|
|
11743
|
+
stopEvent(event);
|
|
12458
11744
|
}
|
|
12459
11745
|
handlePointerOver(event) {
|
|
12460
11746
|
this.pointers[event.pointerId] = new Pointer(event, this.canvas);
|
|
@@ -12469,7 +11755,11 @@ class InputManager {
|
|
|
12469
11755
|
this.canvasFocusedValue = true;
|
|
12470
11756
|
this.pointers[event.pointerId].handlePress(event);
|
|
12471
11757
|
this.flags.push(InputManagerFlag.PointerUpdate);
|
|
12472
|
-
|
|
11758
|
+
// preventDefault stops native drag / text-selection;
|
|
11759
|
+
// stopImmediatePropagation prevents bubbling to host-page click
|
|
11760
|
+
// handlers so an embedded canvas doesn't accidentally trigger UI
|
|
11761
|
+
// outside its bounds.
|
|
11762
|
+
stopEvent(event);
|
|
12473
11763
|
}
|
|
12474
11764
|
handlePointerMove(event) {
|
|
12475
11765
|
this.pointers[event.pointerId].handleMove(event);
|
|
@@ -12478,27 +11768,47 @@ class InputManager {
|
|
|
12478
11768
|
handlePointerUp(event) {
|
|
12479
11769
|
this.pointers[event.pointerId].handleRelease(event);
|
|
12480
11770
|
this.flags.push(InputManagerFlag.PointerUpdate);
|
|
12481
|
-
event
|
|
11771
|
+
stopEvent(event);
|
|
12482
11772
|
}
|
|
12483
11773
|
handlePointerCancel(event) {
|
|
12484
11774
|
this.pointers[event.pointerId].handleCancel(event);
|
|
12485
11775
|
this.flags.push(InputManagerFlag.PointerUpdate);
|
|
12486
11776
|
}
|
|
12487
11777
|
handleMouseWheel(event) {
|
|
11778
|
+
if (!this.canvasFocusedValue) {
|
|
11779
|
+
return;
|
|
11780
|
+
}
|
|
12488
11781
|
this.wheelOffset.set(event.deltaX, event.deltaY);
|
|
12489
11782
|
this.flags.push(InputManagerFlag.MouseWheel);
|
|
12490
|
-
|
|
12491
|
-
event.preventDefault();
|
|
12492
|
-
}
|
|
11783
|
+
stopEvent(event);
|
|
12493
11784
|
}
|
|
12494
11785
|
handleCanvasFocus() {
|
|
12495
11786
|
this.canvasFocusedValue = true;
|
|
12496
11787
|
}
|
|
12497
11788
|
handleCanvasBlur() {
|
|
12498
11789
|
this.canvasFocusedValue = false;
|
|
11790
|
+
this.releaseAllKeyboardChannels();
|
|
12499
11791
|
}
|
|
12500
11792
|
handleWindowBlur() {
|
|
12501
11793
|
this.canvasFocusedValue = false;
|
|
11794
|
+
this.releaseAllKeyboardChannels();
|
|
11795
|
+
}
|
|
11796
|
+
/**
|
|
11797
|
+
* Force every currently-held keyboard channel back to zero and emit
|
|
11798
|
+
* onKeyUp for each. Called on canvas/window blur so keys held when
|
|
11799
|
+
* focus leaves don't stay stuck "down" forever — without this, a user
|
|
11800
|
+
* who alt-tabs while pressing W would have W register as held until
|
|
11801
|
+
* they manually release while focus is back.
|
|
11802
|
+
*/
|
|
11803
|
+
releaseAllKeyboardChannels() {
|
|
11804
|
+
for (let offset = 0; offset < ChannelSize.Category; offset++) {
|
|
11805
|
+
const channel = ChannelOffset.Keyboard + offset;
|
|
11806
|
+
if (this.channels[channel] !== 0) {
|
|
11807
|
+
this.channels[channel] = 0;
|
|
11808
|
+
this.channelsReleased.push(channel);
|
|
11809
|
+
this.flags.push(InputManagerFlag.KeyUp);
|
|
11810
|
+
}
|
|
11811
|
+
}
|
|
12502
11812
|
}
|
|
12503
11813
|
addEventListeners() {
|
|
12504
11814
|
const activeWindow = window;
|
|
@@ -14815,7 +14125,6 @@ const defaultAppSettings = {
|
|
|
14815
14125
|
debug: false,
|
|
14816
14126
|
spriteRendererBatchSize: 4096, // ~ 262kb
|
|
14817
14127
|
particleRendererBatchSize: 8192, // ~ 1.18mb
|
|
14818
|
-
primitiveRendererBatchSize: 65536, // ~ 786kb
|
|
14819
14128
|
gamepadDefinitions: [],
|
|
14820
14129
|
pointerDistanceThreshold: 10,
|
|
14821
14130
|
webglAttributes: {
|
|
@@ -16347,24 +15656,30 @@ function signedArea(data, start, end, dim) {
|
|
|
16347
15656
|
return sum;
|
|
16348
15657
|
}
|
|
16349
15658
|
|
|
16350
|
-
const buildLine = (startX, startY, endX, endY, width
|
|
15659
|
+
const buildLine = (startX, startY, endX, endY, width) => {
|
|
16351
15660
|
const points = [startX, startY, endX, endY];
|
|
16352
15661
|
const distance = width / 2;
|
|
16353
|
-
const index = vertices.length / 6;
|
|
16354
15662
|
const perpA = new Vector(startX - endX, startY - endY).perp().normalize().multiply(distance);
|
|
16355
15663
|
const perpB = new Vector(endX - startX, endY - startY).perp().normalize().multiply(distance);
|
|
16356
|
-
vertices
|
|
16357
|
-
|
|
16358
|
-
|
|
16359
|
-
|
|
16360
|
-
|
|
16361
|
-
|
|
15664
|
+
const vertices = new Float32Array([
|
|
15665
|
+
startX - perpA.x, startY - perpA.y, // 0: start-left
|
|
15666
|
+
startX + perpA.x, startY + perpA.y, // 1: start-right
|
|
15667
|
+
endX - perpB.x, endY - perpB.y, // 2: end-left
|
|
15668
|
+
endX + perpB.x, endY + perpB.y, // 3: end-right
|
|
15669
|
+
]);
|
|
15670
|
+
perpA.destroy();
|
|
15671
|
+
perpB.destroy();
|
|
15672
|
+
const indices = new Uint16Array([0, 1, 3, 0, 3, 2]);
|
|
15673
|
+
return { vertices, indices, points };
|
|
16362
15674
|
};
|
|
16363
|
-
const buildPath = (points, width
|
|
15675
|
+
const buildPath = (points, width) => {
|
|
16364
15676
|
if (points.length < 4) {
|
|
16365
15677
|
throw new Error('At least two X/Y pairs are required to build a line.');
|
|
16366
15678
|
}
|
|
16367
|
-
const lineWidth = width / 2
|
|
15679
|
+
const lineWidth = width / 2;
|
|
15680
|
+
const firstPoint = new Vector(points[0], points[1]);
|
|
15681
|
+
const lastPoint = new Vector(points[points.length - 2], points[points.length - 1]);
|
|
15682
|
+
const outlinePoints = points;
|
|
16368
15683
|
if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) {
|
|
16369
15684
|
points = points.slice();
|
|
16370
15685
|
points.pop();
|
|
@@ -16375,9 +15690,10 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16375
15690
|
points.unshift(midPointX, midPointY);
|
|
16376
15691
|
points.push(midPointX, midPointY);
|
|
16377
15692
|
}
|
|
15693
|
+
firstPoint.destroy();
|
|
15694
|
+
lastPoint.destroy();
|
|
16378
15695
|
const length = points.length / 2;
|
|
16379
|
-
|
|
16380
|
-
let indexStart = vertices.length / 6;
|
|
15696
|
+
const stripVertices = [];
|
|
16381
15697
|
let p1x = points[0];
|
|
16382
15698
|
let p1y = points[1];
|
|
16383
15699
|
let p2x = points[2];
|
|
@@ -16395,8 +15711,8 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16395
15711
|
perpy /= dist;
|
|
16396
15712
|
perpx *= lineWidth;
|
|
16397
15713
|
perpy *= lineWidth;
|
|
16398
|
-
|
|
16399
|
-
|
|
15714
|
+
stripVertices.push(p1x - perpx, p1y - perpy);
|
|
15715
|
+
stripVertices.push(p1x + perpx, p1y + perpy);
|
|
16400
15716
|
for (let i = 1; i < length - 1; i++) {
|
|
16401
15717
|
p1x = points[(i - 1) * 2];
|
|
16402
15718
|
p1y = points[((i - 1) * 2) + 1];
|
|
@@ -16427,8 +15743,8 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16427
15743
|
let denom = (a1 * b2) - (a2 * b1);
|
|
16428
15744
|
if (Math.abs(denom) < 0.1) {
|
|
16429
15745
|
denom += 10.1;
|
|
16430
|
-
|
|
16431
|
-
|
|
15746
|
+
stripVertices.push(p2x - perpx, p2y - perpy);
|
|
15747
|
+
stripVertices.push(p2x + perpx, p2y + perpy);
|
|
16432
15748
|
continue;
|
|
16433
15749
|
}
|
|
16434
15750
|
const px = ((b1 * c2) - (b2 * c1)) / denom;
|
|
@@ -16442,14 +15758,13 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16442
15758
|
perp3y /= dist;
|
|
16443
15759
|
perp3x *= lineWidth;
|
|
16444
15760
|
perp3y *= lineWidth;
|
|
16445
|
-
|
|
16446
|
-
|
|
16447
|
-
|
|
16448
|
-
indexCount++;
|
|
15761
|
+
stripVertices.push(p2x - perp3x, p2y - perp3y);
|
|
15762
|
+
stripVertices.push(p2x + perp3x, p2y + perp3y);
|
|
15763
|
+
stripVertices.push(p2x - perp3x, p2y - perp3y);
|
|
16449
15764
|
}
|
|
16450
15765
|
else {
|
|
16451
|
-
|
|
16452
|
-
|
|
15766
|
+
stripVertices.push(px, py);
|
|
15767
|
+
stripVertices.push(p2x - (px - p2x), p2y - (py - p2y));
|
|
16453
15768
|
}
|
|
16454
15769
|
}
|
|
16455
15770
|
p1x = points[(length - 2) * 2];
|
|
@@ -16463,70 +15778,112 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16463
15778
|
perpy /= dist;
|
|
16464
15779
|
perpx *= lineWidth;
|
|
16465
15780
|
perpy *= lineWidth;
|
|
16466
|
-
|
|
16467
|
-
|
|
16468
|
-
indices.
|
|
16469
|
-
|
|
16470
|
-
|
|
16471
|
-
|
|
16472
|
-
|
|
16473
|
-
|
|
15781
|
+
stripVertices.push(p2x - perpx, p2y - perpy);
|
|
15782
|
+
stripVertices.push(p2x + perpx, p2y + perpy);
|
|
15783
|
+
// Convert strip-style vertex sequence to triangle-list indices.
|
|
15784
|
+
// For N strip vertices (N = stripVertices.length / 2), each i in [0, N-3]
|
|
15785
|
+
// produces a triangle. Even i: (i, i+1, i+2). Odd i: (i+1, i, i+2).
|
|
15786
|
+
// This preserves the same winding the original triangle-strip pipeline saw.
|
|
15787
|
+
const stripVertexCount = stripVertices.length / 2;
|
|
15788
|
+
const vertices = new Float32Array(stripVertices);
|
|
15789
|
+
const triangleCount = stripVertexCount >= 3 ? stripVertexCount - 2 : 0;
|
|
15790
|
+
const indices = new Uint16Array(triangleCount * 3);
|
|
15791
|
+
for (let i = 0; i < triangleCount; i++) {
|
|
15792
|
+
const base = i * 3;
|
|
15793
|
+
if ((i & 1) === 0) {
|
|
15794
|
+
indices[base] = i;
|
|
15795
|
+
indices[base + 1] = i + 1;
|
|
15796
|
+
indices[base + 2] = i + 2;
|
|
15797
|
+
}
|
|
15798
|
+
else {
|
|
15799
|
+
indices[base] = i + 1;
|
|
15800
|
+
indices[base + 1] = i;
|
|
15801
|
+
indices[base + 2] = i + 2;
|
|
15802
|
+
}
|
|
15803
|
+
}
|
|
15804
|
+
return { vertices, indices, points: outlinePoints };
|
|
16474
15805
|
};
|
|
16475
|
-
const buildCircle = (centerX, centerY, radius
|
|
16476
|
-
const length = Math.floor(15 * Math.sqrt(radius + radius))
|
|
16477
|
-
|
|
16478
|
-
|
|
16479
|
-
|
|
16480
|
-
|
|
15806
|
+
const buildCircle = (centerX, centerY, radius) => {
|
|
15807
|
+
const length = Math.floor(15 * Math.sqrt(radius + radius));
|
|
15808
|
+
const segment = (Math.PI * 2) / length;
|
|
15809
|
+
const points = [];
|
|
15810
|
+
// 1 center vertex + N perimeter vertices.
|
|
15811
|
+
const vertices = new Float32Array((length + 1) * 2);
|
|
15812
|
+
vertices[0] = centerX;
|
|
15813
|
+
vertices[1] = centerY;
|
|
15814
|
+
for (let i = 0; i < length; i++) {
|
|
15815
|
+
const segmentX = centerX + (Math.sin(segment * i) * radius);
|
|
15816
|
+
const segmentY = centerY + (Math.cos(segment * i) * radius);
|
|
16481
15817
|
points.push(segmentX, segmentY);
|
|
16482
|
-
|
|
16483
|
-
vertices
|
|
16484
|
-
|
|
15818
|
+
const offset = (i + 1) * 2;
|
|
15819
|
+
vertices[offset] = segmentX;
|
|
15820
|
+
vertices[offset + 1] = segmentY;
|
|
15821
|
+
}
|
|
15822
|
+
const indices = new Uint16Array(length * 3);
|
|
15823
|
+
for (let i = 0; i < length; i++) {
|
|
15824
|
+
const base = i * 3;
|
|
15825
|
+
indices[base] = 0;
|
|
15826
|
+
indices[base + 1] = i + 1;
|
|
15827
|
+
indices[base + 2] = i + 2 > length ? 1 : i + 2;
|
|
16485
15828
|
}
|
|
16486
|
-
indices
|
|
16487
|
-
return new Geometry({ vertices, indices, points });
|
|
15829
|
+
return { vertices, indices, points };
|
|
16488
15830
|
};
|
|
16489
|
-
const buildEllipse = (centerX, centerY, radiusX, radiusY
|
|
16490
|
-
const length = Math.floor(15 * Math.sqrt(radiusX + radiusY))
|
|
16491
|
-
|
|
16492
|
-
|
|
16493
|
-
|
|
16494
|
-
|
|
15831
|
+
const buildEllipse = (centerX, centerY, radiusX, radiusY) => {
|
|
15832
|
+
const length = Math.floor(15 * Math.sqrt(radiusX + radiusY));
|
|
15833
|
+
const segment = (Math.PI * 2) / length;
|
|
15834
|
+
const points = [];
|
|
15835
|
+
const vertices = new Float32Array((length + 1) * 2);
|
|
15836
|
+
vertices[0] = centerX;
|
|
15837
|
+
vertices[1] = centerY;
|
|
15838
|
+
for (let i = 0; i < length; i++) {
|
|
15839
|
+
const segmentX = centerX + (Math.sin(segment * i) * radiusX);
|
|
15840
|
+
const segmentY = centerY + (Math.cos(segment * i) * radiusY);
|
|
16495
15841
|
points.push(segmentX, segmentY);
|
|
16496
|
-
|
|
16497
|
-
vertices
|
|
16498
|
-
|
|
15842
|
+
const offset = (i + 1) * 2;
|
|
15843
|
+
vertices[offset] = segmentX;
|
|
15844
|
+
vertices[offset + 1] = segmentY;
|
|
15845
|
+
}
|
|
15846
|
+
const indices = new Uint16Array(length * 3);
|
|
15847
|
+
for (let i = 0; i < length; i++) {
|
|
15848
|
+
const base = i * 3;
|
|
15849
|
+
indices[base] = 0;
|
|
15850
|
+
indices[base + 1] = i + 1;
|
|
15851
|
+
indices[base + 2] = i + 2 > length ? 1 : i + 2;
|
|
16499
15852
|
}
|
|
16500
|
-
indices
|
|
16501
|
-
return new Geometry({ vertices, indices, points });
|
|
15853
|
+
return { vertices, indices, points };
|
|
16502
15854
|
};
|
|
16503
|
-
const buildPolygon = (points
|
|
15855
|
+
const buildPolygon = (points) => {
|
|
16504
15856
|
if (points.length < 6) {
|
|
16505
15857
|
throw new Error('At least three X/Y pairs are required to build a polygon.');
|
|
16506
15858
|
}
|
|
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
|
-
}
|
|
15859
|
+
const length = points.length / 2;
|
|
15860
|
+
const triangles = earcut(points, [], 2);
|
|
15861
|
+
const vertices = new Float32Array(points.length);
|
|
15862
|
+
for (let i = 0; i < length; i++) {
|
|
15863
|
+
vertices[i * 2] = points[i * 2];
|
|
15864
|
+
vertices[(i * 2) + 1] = points[(i * 2) + 1];
|
|
16519
15865
|
}
|
|
16520
|
-
|
|
15866
|
+
const indices = triangles ? new Uint16Array(triangles) : new Uint16Array(0);
|
|
15867
|
+
return { vertices, indices, points };
|
|
16521
15868
|
};
|
|
16522
|
-
const buildRectangle = (x, y, width, height
|
|
16523
|
-
|
|
16524
|
-
vertices
|
|
16525
|
-
|
|
16526
|
-
|
|
15869
|
+
const buildRectangle = (x, y, width, height) => {
|
|
15870
|
+
// 4 vertices: TL, TR, BL, BR. Triangles [0, 1, 2, 1, 3, 2] (clockwise).
|
|
15871
|
+
const vertices = new Float32Array([
|
|
15872
|
+
x, y, // 0 TL
|
|
15873
|
+
x + width, y, // 1 TR
|
|
15874
|
+
x, y + height, // 2 BL
|
|
15875
|
+
x + width, y + height, // 3 BR
|
|
15876
|
+
]);
|
|
15877
|
+
const indices = new Uint16Array([0, 1, 2, 1, 3, 2]);
|
|
15878
|
+
// Outline points walk the perimeter (TL -> TR -> BR -> BL).
|
|
15879
|
+
const points = [x, y, x + width, y, x + width, y + height, x, y + height];
|
|
15880
|
+
return { vertices, indices, points };
|
|
16527
15881
|
};
|
|
16528
15882
|
const buildStar = (centerX, centerY, points, radius, innerRadius = radius / 2, rotation = 0) => {
|
|
16529
|
-
const startAngle = (Math.PI / -2) + rotation
|
|
15883
|
+
const startAngle = (Math.PI / -2) + rotation;
|
|
15884
|
+
const length = points * 2;
|
|
15885
|
+
const delta = tau / length;
|
|
15886
|
+
const path = [];
|
|
16530
15887
|
for (let i = 0; i < length; i++) {
|
|
16531
15888
|
const angle = startAngle + (i * delta);
|
|
16532
15889
|
const rad = i % 2 ? innerRadius : radius;
|
|
@@ -17369,23 +16726,6 @@ class UniversalEmitter {
|
|
|
17369
16726
|
}
|
|
17370
16727
|
}
|
|
17371
16728
|
|
|
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
16729
|
class Graphics extends Container {
|
|
17390
16730
|
_lineWidth = 0;
|
|
17391
16731
|
_lineColor = new Color();
|
|
@@ -17416,14 +16756,14 @@ class Graphics extends Container {
|
|
|
17416
16756
|
return super.getChildAt(index);
|
|
17417
16757
|
}
|
|
17418
16758
|
addChild(child) {
|
|
17419
|
-
if (!(child instanceof
|
|
17420
|
-
throw new Error('Graphics can only contain
|
|
16759
|
+
if (!(child instanceof Mesh)) {
|
|
16760
|
+
throw new Error('Graphics can only contain Mesh children.');
|
|
17421
16761
|
}
|
|
17422
16762
|
return super.addChild(child);
|
|
17423
16763
|
}
|
|
17424
16764
|
addChildAt(child, index) {
|
|
17425
|
-
if (!(child instanceof
|
|
17426
|
-
throw new Error('Graphics can only contain
|
|
16765
|
+
if (!(child instanceof Mesh)) {
|
|
16766
|
+
throw new Error('Graphics can only contain Mesh children.');
|
|
17427
16767
|
}
|
|
17428
16768
|
return super.addChildAt(child, index);
|
|
17429
16769
|
}
|
|
@@ -17519,50 +16859,52 @@ class Graphics extends Container {
|
|
|
17519
16859
|
return this;
|
|
17520
16860
|
}
|
|
17521
16861
|
drawLine(startX, startY, endX, endY) {
|
|
17522
|
-
|
|
16862
|
+
const data = buildLine(startX, startY, endX, endY, this._lineWidth);
|
|
16863
|
+
this.addChild(this._createMesh(data, this._lineColor));
|
|
17523
16864
|
return this;
|
|
17524
16865
|
}
|
|
17525
16866
|
drawPath(path) {
|
|
17526
|
-
|
|
16867
|
+
const data = buildPath(path, this._lineWidth);
|
|
16868
|
+
this.addChild(this._createMesh(data, this._lineColor));
|
|
17527
16869
|
return this;
|
|
17528
16870
|
}
|
|
17529
16871
|
drawPolygon(path) {
|
|
17530
|
-
const
|
|
17531
|
-
this.addChild(
|
|
16872
|
+
const data = buildPolygon(path);
|
|
16873
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17532
16874
|
if (this._lineWidth > 0) {
|
|
17533
|
-
this.drawPath(
|
|
16875
|
+
this.drawPath(data.points);
|
|
17534
16876
|
}
|
|
17535
16877
|
return this;
|
|
17536
16878
|
}
|
|
17537
16879
|
drawCircle(centerX, centerY, radius) {
|
|
17538
|
-
const
|
|
17539
|
-
this.addChild(
|
|
16880
|
+
const data = buildCircle(centerX, centerY, radius);
|
|
16881
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17540
16882
|
if (this._lineWidth > 0) {
|
|
17541
|
-
this.drawPath(
|
|
16883
|
+
this.drawPath(data.points);
|
|
17542
16884
|
}
|
|
17543
16885
|
return this;
|
|
17544
16886
|
}
|
|
17545
16887
|
drawEllipse(centerX, centerY, radiusX, radiusY) {
|
|
17546
|
-
const
|
|
17547
|
-
this.addChild(
|
|
16888
|
+
const data = buildEllipse(centerX, centerY, radiusX, radiusY);
|
|
16889
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17548
16890
|
if (this._lineWidth > 0) {
|
|
17549
|
-
this.drawPath(
|
|
16891
|
+
this.drawPath(data.points);
|
|
17550
16892
|
}
|
|
17551
16893
|
return this;
|
|
17552
16894
|
}
|
|
17553
16895
|
drawRectangle(x, y, width, height) {
|
|
17554
|
-
const
|
|
17555
|
-
this.addChild(
|
|
16896
|
+
const data = buildRectangle(x, y, width, height);
|
|
16897
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17556
16898
|
if (this._lineWidth > 0) {
|
|
17557
|
-
this.drawPath(
|
|
16899
|
+
this.drawPath(data.points);
|
|
17558
16900
|
}
|
|
17559
16901
|
return this;
|
|
17560
16902
|
}
|
|
17561
16903
|
drawStar(centerX, centerY, points, radius, innerRadius = radius / 2, rotation = 0) {
|
|
17562
|
-
const
|
|
17563
|
-
this.addChild(
|
|
16904
|
+
const data = buildStar(centerX, centerY, points, radius, innerRadius, rotation);
|
|
16905
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17564
16906
|
if (this._lineWidth > 0) {
|
|
17565
|
-
this.drawPath(
|
|
16907
|
+
this.drawPath(data.points);
|
|
17566
16908
|
}
|
|
17567
16909
|
return this;
|
|
17568
16910
|
}
|
|
@@ -17581,6 +16923,14 @@ class Graphics extends Container {
|
|
|
17581
16923
|
this._fillColor.destroy();
|
|
17582
16924
|
this._currentPoint.destroy();
|
|
17583
16925
|
}
|
|
16926
|
+
_createMesh(data, color) {
|
|
16927
|
+
const mesh = new Mesh({
|
|
16928
|
+
vertices: data.vertices,
|
|
16929
|
+
indices: data.indices,
|
|
16930
|
+
});
|
|
16931
|
+
mesh.tint = color;
|
|
16932
|
+
return mesh;
|
|
16933
|
+
}
|
|
17584
16934
|
}
|
|
17585
16935
|
|
|
17586
16936
|
class Spritesheet {
|
|
@@ -19197,5 +18547,5 @@ const createRapierPhysicsWorld = async (options = {}) => {
|
|
|
19197
18547
|
return await RapierPhysicsWorld.create(options);
|
|
19198
18548
|
};
|
|
19199
18549
|
|
|
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,
|
|
18550
|
+
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
18551
|
//# sourceMappingURL=exo.esm.js.map
|