spine-html 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +197 -39
- package/dist/DomTexture.d.ts +31 -2
- package/dist/DomTexture.d.ts.map +1 -1
- package/dist/DomTexture.js +104 -27
- package/dist/DomTexture.js.map +1 -1
- package/dist/MeshGlBlitter.d.ts +69 -0
- package/dist/MeshGlBlitter.d.ts.map +1 -0
- package/dist/MeshGlBlitter.js +265 -0
- package/dist/MeshGlBlitter.js.map +1 -0
- package/dist/SpineHtmlRenderer.d.ts +41 -3
- package/dist/SpineHtmlRenderer.d.ts.map +1 -1
- package/dist/SpineHtmlRenderer.js +120 -25
- package/dist/SpineHtmlRenderer.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/loadSkeletonAssets.d.ts +69 -0
- package/dist/loadSkeletonAssets.d.ts.map +1 -0
- package/dist/loadSkeletonAssets.js +80 -0
- package/dist/loadSkeletonAssets.js.map +1 -0
- package/package.json +4 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared offscreen WebGL rasterizer for the mesh (deform) tier.
|
|
3
|
+
*
|
|
4
|
+
* One module-level context serves every renderer instance: browsers cap live
|
|
5
|
+
* WebGL contexts (16 on Chrome/Safari) and a page can hold many renderers, so
|
|
6
|
+
* per-renderer contexts would evict each other. Per flush, every dirty mesh is
|
|
7
|
+
* shelf-packed into a rect of the offscreen canvas, its triangles are drawn
|
|
8
|
+
* textured (atlas page texture, premultiplied alpha), and each rect is blitted
|
|
9
|
+
* onto the mesh's per-part 2d canvas with an unclipped drawImage rect copy.
|
|
10
|
+
*
|
|
11
|
+
* Why this exists: Safari antialiases canvas2d clip paths, so the standard
|
|
12
|
+
* per-triangle clip+transform+drawImage mapping pays a per-triangle AA-mask
|
|
13
|
+
* cost in the GPU process — invisible to in-callback JS timing, but it
|
|
14
|
+
* rAF-limits heavy scenes. GL rasterizes shared triangle edges seamlessly
|
|
15
|
+
* (no clip, no crack overdraw needed), and the remaining canvas2d work is a
|
|
16
|
+
* plain rect blit.
|
|
17
|
+
*
|
|
18
|
+
* The offscreen buffer is scratch space: rects are scissor-cleared and
|
|
19
|
+
* redrawn every flush and read back in the same task, so neither
|
|
20
|
+
* preserveDrawingBuffer nor cross-frame content is relied on. The buffer
|
|
21
|
+
* grows quantized and never shrinks, mirroring the per-part backing policy
|
|
22
|
+
* (reallocating GPU surfaces per frame is a Safari killer).
|
|
23
|
+
*/
|
|
24
|
+
const VERTEX_SHADER = `
|
|
25
|
+
attribute vec2 aPos;
|
|
26
|
+
attribute vec2 aUV;
|
|
27
|
+
uniform vec2 uResolution;
|
|
28
|
+
varying vec2 vUV;
|
|
29
|
+
void main() {
|
|
30
|
+
// aPos is in offscreen-canvas device px, top-left origin (matching the
|
|
31
|
+
// top-left-origin drawImage read of the same rect).
|
|
32
|
+
vec2 clip = aPos / uResolution * 2.0 - 1.0;
|
|
33
|
+
gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
|
|
34
|
+
vUV = aUV;
|
|
35
|
+
}`;
|
|
36
|
+
const FRAGMENT_SHADER = `
|
|
37
|
+
precision mediump float;
|
|
38
|
+
uniform sampler2D uTex;
|
|
39
|
+
varying vec2 vUV;
|
|
40
|
+
void main() {
|
|
41
|
+
gl_FragColor = texture2D(uTex, vUV);
|
|
42
|
+
}`;
|
|
43
|
+
/** Gap between packed rects; blits are 1:1 unfiltered, so 1px is plenty. */
|
|
44
|
+
const GUTTER = 1;
|
|
45
|
+
/** Drawing-buffer growth quantum (device px). */
|
|
46
|
+
const GROW_STEP = 256;
|
|
47
|
+
class MeshGlBlitter {
|
|
48
|
+
lost = false;
|
|
49
|
+
canvas = document.createElement('canvas');
|
|
50
|
+
gl;
|
|
51
|
+
uResolution;
|
|
52
|
+
textures = new Map();
|
|
53
|
+
maxSize;
|
|
54
|
+
vertexData = new Float32Array(8192);
|
|
55
|
+
/** Per-job packed rect origins, filled by flush(). */
|
|
56
|
+
packX = [];
|
|
57
|
+
packY = [];
|
|
58
|
+
constructor() {
|
|
59
|
+
const gl = this.canvas.getContext('webgl', {
|
|
60
|
+
alpha: true,
|
|
61
|
+
premultipliedAlpha: true,
|
|
62
|
+
antialias: false,
|
|
63
|
+
depth: false,
|
|
64
|
+
stencil: false,
|
|
65
|
+
preserveDrawingBuffer: false,
|
|
66
|
+
});
|
|
67
|
+
if (!gl)
|
|
68
|
+
throw new Error('WebGL unavailable');
|
|
69
|
+
this.gl = gl;
|
|
70
|
+
// No restore attempt: on loss the renderer falls back to canvas2d and the
|
|
71
|
+
// backend signature re-dirties every mesh, so frames stay complete.
|
|
72
|
+
this.canvas.addEventListener('webglcontextlost', () => {
|
|
73
|
+
this.lost = true;
|
|
74
|
+
});
|
|
75
|
+
const program = gl.createProgram();
|
|
76
|
+
if (!program)
|
|
77
|
+
throw new Error('createProgram failed');
|
|
78
|
+
gl.attachShader(program, this.compile(gl.VERTEX_SHADER, VERTEX_SHADER));
|
|
79
|
+
gl.attachShader(program, this.compile(gl.FRAGMENT_SHADER, FRAGMENT_SHADER));
|
|
80
|
+
gl.linkProgram(program);
|
|
81
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
82
|
+
throw new Error(`program link failed: ${gl.getProgramInfoLog(program)}`);
|
|
83
|
+
}
|
|
84
|
+
gl.useProgram(program);
|
|
85
|
+
const uResolution = gl.getUniformLocation(program, 'uResolution');
|
|
86
|
+
if (!uResolution)
|
|
87
|
+
throw new Error('uResolution not found');
|
|
88
|
+
this.uResolution = uResolution;
|
|
89
|
+
gl.uniform1i(gl.getUniformLocation(program, 'uTex'), 0);
|
|
90
|
+
// Static state: this context does exactly one thing, set it up once.
|
|
91
|
+
// Interleaved [x, y, u, v] vertices in a single dynamic buffer.
|
|
92
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
|
|
93
|
+
const aPos = gl.getAttribLocation(program, 'aPos');
|
|
94
|
+
const aUV = gl.getAttribLocation(program, 'aUV');
|
|
95
|
+
gl.enableVertexAttribArray(aPos);
|
|
96
|
+
gl.enableVertexAttribArray(aUV);
|
|
97
|
+
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 16, 0);
|
|
98
|
+
gl.vertexAttribPointer(aUV, 2, gl.FLOAT, false, 16, 8);
|
|
99
|
+
// Premultiplied source-over, matching canvas2d triangle compositing.
|
|
100
|
+
gl.enable(gl.BLEND);
|
|
101
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
102
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
103
|
+
gl.clearColor(0, 0, 0, 0);
|
|
104
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
105
|
+
this.maxSize = Math.min(gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) || 4096, 8192);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Rasterize every job into the offscreen buffer, then blit each rect onto
|
|
109
|
+
* its per-part canvas. Returns false when the context is lost (or the jobs
|
|
110
|
+
* cannot fit) so the caller can rasterize the batch on the canvas2d path.
|
|
111
|
+
*/
|
|
112
|
+
flush(jobs) {
|
|
113
|
+
const gl = this.gl;
|
|
114
|
+
if (this.lost || gl.isContextLost())
|
|
115
|
+
return false;
|
|
116
|
+
// Shelf-pack, tallest first (indices keep job order for the draw pass).
|
|
117
|
+
let width = Math.max(512, this.canvas.width);
|
|
118
|
+
for (const job of jobs)
|
|
119
|
+
width = Math.max(width, job.width + GUTTER * 2);
|
|
120
|
+
if (width > this.maxSize)
|
|
121
|
+
return false;
|
|
122
|
+
const order = jobs.map((_, i) => i).sort((a, b) => jobs[b].height - jobs[a].height);
|
|
123
|
+
const packX = (this.packX = new Array(jobs.length));
|
|
124
|
+
const packY = (this.packY = new Array(jobs.length));
|
|
125
|
+
let shelfX = GUTTER;
|
|
126
|
+
let shelfY = GUTTER;
|
|
127
|
+
let shelfH = 0;
|
|
128
|
+
let height = GUTTER;
|
|
129
|
+
for (const i of order) {
|
|
130
|
+
const job = jobs[i];
|
|
131
|
+
if (shelfX + job.width + GUTTER > width) {
|
|
132
|
+
shelfY += shelfH + GUTTER;
|
|
133
|
+
shelfX = GUTTER;
|
|
134
|
+
shelfH = 0;
|
|
135
|
+
}
|
|
136
|
+
packX[i] = shelfX;
|
|
137
|
+
packY[i] = shelfY;
|
|
138
|
+
shelfX += job.width + GUTTER;
|
|
139
|
+
if (job.height > shelfH)
|
|
140
|
+
shelfH = job.height;
|
|
141
|
+
if (shelfY + job.height + GUTTER > height)
|
|
142
|
+
height = shelfY + job.height + GUTTER;
|
|
143
|
+
}
|
|
144
|
+
if (height > this.maxSize)
|
|
145
|
+
return false;
|
|
146
|
+
// Grow-only quantized drawing buffer. Resizing clears it, which is fine:
|
|
147
|
+
// every rect below is cleared and redrawn anyway.
|
|
148
|
+
if (width > this.canvas.width || height > this.canvas.height) {
|
|
149
|
+
this.canvas.width = Math.min(this.maxSize, Math.ceil(width / GROW_STEP) * GROW_STEP);
|
|
150
|
+
this.canvas.height = Math.min(this.maxSize, Math.ceil(Math.max(height, this.canvas.height) / GROW_STEP) * GROW_STEP);
|
|
151
|
+
}
|
|
152
|
+
const bufW = this.canvas.width;
|
|
153
|
+
const bufH = this.canvas.height;
|
|
154
|
+
gl.viewport(0, 0, bufW, bufH);
|
|
155
|
+
gl.uniform2f(this.uResolution, bufW, bufH);
|
|
156
|
+
// Build one interleaved vertex array for the whole batch (unindexed —
|
|
157
|
+
// meshes are a few hundred triangles, expansion is cheaper than managing
|
|
158
|
+
// index buffers).
|
|
159
|
+
let floats = 0;
|
|
160
|
+
for (const job of jobs)
|
|
161
|
+
floats += job.triangles.length * 4;
|
|
162
|
+
if (this.vertexData.length < floats) {
|
|
163
|
+
this.vertexData = new Float32Array(1 << Math.ceil(Math.log2(floats)));
|
|
164
|
+
}
|
|
165
|
+
const data = this.vertexData;
|
|
166
|
+
let f = 0;
|
|
167
|
+
for (let i = 0; i < jobs.length; i++) {
|
|
168
|
+
const { vertices, uvs, triangles, ratio } = jobs[i];
|
|
169
|
+
const ox = packX[i];
|
|
170
|
+
const oy = packY[i];
|
|
171
|
+
for (let t = 0; t < triangles.length; t++) {
|
|
172
|
+
const vi = triangles[t] * 2;
|
|
173
|
+
data[f++] = ox + vertices[vi] * ratio;
|
|
174
|
+
data[f++] = oy + vertices[vi + 1] * ratio;
|
|
175
|
+
data[f++] = uvs[vi];
|
|
176
|
+
data[f++] = uvs[vi + 1];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
gl.bufferData(gl.ARRAY_BUFFER, data.subarray(0, floats), gl.DYNAMIC_DRAW);
|
|
180
|
+
let boundPage = null;
|
|
181
|
+
let first = 0;
|
|
182
|
+
for (let i = 0; i < jobs.length; i++) {
|
|
183
|
+
const job = jobs[i];
|
|
184
|
+
if (job.page !== boundPage) {
|
|
185
|
+
gl.bindTexture(gl.TEXTURE_2D, this.textureFor(job.page));
|
|
186
|
+
boundPage = job.page;
|
|
187
|
+
}
|
|
188
|
+
// Scissor is bottom-left origin; pack coords are top-left origin.
|
|
189
|
+
gl.scissor(packX[i], bufH - packY[i] - job.height, job.width, job.height);
|
|
190
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
191
|
+
gl.drawArrays(gl.TRIANGLES, first, job.triangles.length);
|
|
192
|
+
first += job.triangles.length;
|
|
193
|
+
}
|
|
194
|
+
// If the context died mid-batch the draws above were no-ops; report it
|
|
195
|
+
// before blitting stale/blank rects onto the part canvases.
|
|
196
|
+
if (gl.isContextLost()) {
|
|
197
|
+
this.lost = true;
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
// Blit each rect onto its per-part canvas: same-task read (no
|
|
201
|
+
// preserveDrawingBuffer needed), 1:1 device px (no filtering).
|
|
202
|
+
for (let i = 0; i < jobs.length; i++) {
|
|
203
|
+
const job = jobs[i];
|
|
204
|
+
const ctx = job.canvas.getContext('2d');
|
|
205
|
+
if (!ctx)
|
|
206
|
+
continue;
|
|
207
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
208
|
+
// Clear the full backing: the previous raster may have been larger.
|
|
209
|
+
ctx.clearRect(0, 0, job.canvas.width, job.canvas.height);
|
|
210
|
+
ctx.drawImage(this.canvas, packX[i], packY[i], job.width, job.height, 0, 0, job.width, job.height);
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
compile(type, source) {
|
|
215
|
+
const gl = this.gl;
|
|
216
|
+
const shader = gl.createShader(type);
|
|
217
|
+
if (!shader)
|
|
218
|
+
throw new Error('createShader failed');
|
|
219
|
+
gl.shaderSource(shader, source);
|
|
220
|
+
gl.compileShader(shader);
|
|
221
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
222
|
+
throw new Error(`shader compile failed: ${gl.getShaderInfoLog(shader)}`);
|
|
223
|
+
}
|
|
224
|
+
return shader;
|
|
225
|
+
}
|
|
226
|
+
textureFor(page) {
|
|
227
|
+
let texture = this.textures.get(page);
|
|
228
|
+
if (texture)
|
|
229
|
+
return texture;
|
|
230
|
+
const gl = this.gl;
|
|
231
|
+
texture = gl.createTexture();
|
|
232
|
+
if (!texture)
|
|
233
|
+
throw new Error('createTexture failed');
|
|
234
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
235
|
+
// Premultiply at upload so blending and the premultiplied canvas agree.
|
|
236
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
|
|
237
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);
|
|
238
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, page);
|
|
239
|
+
// Linear, no mips, clamped — NPOT-safe in WebGL1.
|
|
240
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
241
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
242
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
243
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
244
|
+
this.textures.set(page, texture);
|
|
245
|
+
return texture;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
let shared;
|
|
249
|
+
/**
|
|
250
|
+
* The module-level blitter, created on first use. Returns null when WebGL is
|
|
251
|
+
* unavailable or the shared context has been lost — callers then stay on the
|
|
252
|
+
* canvas2d path.
|
|
253
|
+
*/
|
|
254
|
+
export function getMeshGlBlitter() {
|
|
255
|
+
if (shared === undefined) {
|
|
256
|
+
try {
|
|
257
|
+
shared = new MeshGlBlitter();
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
shared = null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return shared && !shared.lost ? shared : null;
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=MeshGlBlitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MeshGlBlitter.js","sourceRoot":"","sources":["../src/MeshGlBlitter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type Skeleton } from '@esotericsoftware/spine-core';
|
|
2
2
|
import type { RegionImage } from './DomTexture';
|
|
3
|
+
/** Rasterizer used for the mesh (deform) tier. */
|
|
4
|
+
export type MeshBackend = 'canvas2d' | 'webgl';
|
|
3
5
|
/**
|
|
4
6
|
* Renders a spine-core Skeleton as DOM, split by slot type:
|
|
5
7
|
*
|
|
@@ -7,9 +9,11 @@ import type { RegionImage } from './DomTexture';
|
|
|
7
9
|
* posed with a single CSS matrix() write per frame — exact, since bone
|
|
8
10
|
* transforms are affine.
|
|
9
11
|
* - Mesh attachments (deform parts) each get a small per-part <canvas> sized
|
|
10
|
-
* to the mesh's world bounding box, redrawn per frame with the
|
|
11
|
-
* per-triangle clip+transform+drawImage mapping
|
|
12
|
-
* canvas
|
|
12
|
+
* to the mesh's world bounding box, redrawn per frame — either with the
|
|
13
|
+
* standard per-triangle clip+transform+drawImage mapping (default), or via
|
|
14
|
+
* a shared offscreen WebGL canvas that rect-blits into the same per-part
|
|
15
|
+
* canvases (meshBackend = 'webgl'). Frames where the canvas-space vertices
|
|
16
|
+
* are unchanged reuse the previous raster on both backends.
|
|
13
17
|
*
|
|
14
18
|
* Both element kinds share one stacking context, so draw order interleaves
|
|
15
19
|
* freely via z-index (rear hair canvas < torso img < front hair canvas).
|
|
@@ -33,6 +37,12 @@ export declare class SpineHtmlRenderer {
|
|
|
33
37
|
meshCount: number;
|
|
34
38
|
/** Mesh canvases that reused their previous raster last frame. */
|
|
35
39
|
meshReuseCount: number;
|
|
40
|
+
/**
|
|
41
|
+
* Mesh canvas backing stores (re)allocated last frame. Should drop to zero
|
|
42
|
+
* once an animation reaches its steady state; a persistent nonzero value
|
|
43
|
+
* means GPU surfaces are being recreated per frame (a Safari killer).
|
|
44
|
+
*/
|
|
45
|
+
canvasReallocCount: number;
|
|
36
46
|
/** Triangles rasterized last frame. */
|
|
37
47
|
triangleCount: number;
|
|
38
48
|
/**
|
|
@@ -48,7 +58,26 @@ export declare class SpineHtmlRenderer {
|
|
|
48
58
|
* resolution instead of over- or under-sampling.
|
|
49
59
|
*/
|
|
50
60
|
pixelRatio: number;
|
|
61
|
+
/**
|
|
62
|
+
* Rasterizer for the mesh (deform) tier. 'canvas2d' (default) maps each
|
|
63
|
+
* triangle with clip+transform+drawImage directly on the per-part canvas.
|
|
64
|
+
* 'webgl' rasterizes every dirty mesh into one shared offscreen WebGL
|
|
65
|
+
* canvas and rect-blits each mesh back onto its per-part canvas — the DOM
|
|
66
|
+
* structure and all element-level behavior (z-index interleave, tint
|
|
67
|
+
* filter, mix-blend-mode, dirty-skip) are identical, only the raster step
|
|
68
|
+
* changes. Motivation: Safari antialiases canvas2d clip paths, so the
|
|
69
|
+
* per-triangle clip mapping pays a per-triangle AA-mask cost in the GPU
|
|
70
|
+
* process that rAF-limits heavy scenes; GL rasterizes shared edges
|
|
71
|
+
* seamlessly (no clip, no crack overdraw) and the blit is an unclipped
|
|
72
|
+
* rect copy. Falls back to 'canvas2d' when WebGL is unavailable or the
|
|
73
|
+
* shared context is lost — see meshBackendActive.
|
|
74
|
+
*/
|
|
75
|
+
meshBackend: MeshBackend;
|
|
76
|
+
/** Backend that actually rasterized the mesh tier during the last render(). */
|
|
77
|
+
meshBackendActive: MeshBackend;
|
|
51
78
|
private readonly views;
|
|
79
|
+
private readonly pendingJobs;
|
|
80
|
+
private readonly pendingViews;
|
|
52
81
|
private scratchVertices;
|
|
53
82
|
private tintDefs;
|
|
54
83
|
/**
|
|
@@ -59,9 +88,18 @@ export declare class SpineHtmlRenderer {
|
|
|
59
88
|
*/
|
|
60
89
|
constructor(root: HTMLElement, regionImages: Map<string, RegionImage>);
|
|
61
90
|
render(skeleton: Skeleton): void;
|
|
91
|
+
/**
|
|
92
|
+
* Removes every element this renderer added to the root (slot elements and
|
|
93
|
+
* the tint filter defs). The region bitmaps are deliberately untouched: the
|
|
94
|
+
* map is the caller's, and one map is normally shared by many renderers
|
|
95
|
+
* (disposing one instance must not blind the others). Free the unpacked
|
|
96
|
+
* blob URLs with revokeRegions() once no renderer needs them.
|
|
97
|
+
*/
|
|
62
98
|
dispose(): void;
|
|
63
99
|
private renderRegion;
|
|
64
100
|
private renderMesh;
|
|
101
|
+
/** The canvas2d raster path: clear the backing, map each triangle. */
|
|
102
|
+
private rasterizeMesh2d;
|
|
65
103
|
/**
|
|
66
104
|
* Standard canvas triangle texture mapping (same math as the official
|
|
67
105
|
* spine-canvas renderer): derive the affine that sends the triangle's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpineHtmlRenderer.d.ts","sourceRoot":"","sources":["../src/SpineHtmlRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,QAAQ,EAId,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"SpineHtmlRenderer.d.ts","sourceRoot":"","sources":["../src/SpineHtmlRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,QAAQ,EAId,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,kDAAkD;AAClD,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC;AAiE/C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,iBAAiB;IA2D1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY;IA3D/B,kEAAkE;IAClE,aAAa,SAAK;IAClB,2CAA2C;IAC3C,SAAS,SAAK;IACd,kEAAkE;IAClE,cAAc,SAAK;IACnB;;;;OAIG;IACH,kBAAkB,SAAK;IACvB,uCAAuC;IACvC,aAAa,SAAK;IAClB;;;;OAIG;IACH,cAAc,SAAO;IACrB;;;;;OAKG;IACH,UAAU,SAA+D;IACzE;;;;;;;;;;;;;OAaG;IACH,WAAW,EAAE,WAAW,CAAc;IACtC,+EAA+E;IAC/E,iBAAiB,EAAE,WAAW,CAAc;IAE5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;IACnD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkB;IAC/C,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,QAAQ,CAA8B;IAE9C;;;;;OAKG;gBAEgB,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC;IAGzD,MAAM,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IA4ChC;;;;;;OAMG;IACH,OAAO,IAAI,IAAI;IASf,OAAO,CAAC,YAAY;IA8CpB,OAAO,CAAC,UAAU;IAsIlB,sEAAsE;IACtE,OAAO,CAAC,eAAe;IA+BvB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,YAAY;IAwCpB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,WAAW;IAmDnB,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,IAAI;IA+CZ,OAAO,CAAC,IAAI;CAOb"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BlendMode, ClippingAttachment, MeshAttachment, RegionAttachment, } from '@esotericsoftware/spine-core';
|
|
2
|
+
import { getMeshGlBlitter } from './MeshGlBlitter';
|
|
2
3
|
const regionVertices = new Float32Array(8);
|
|
3
4
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
4
5
|
/** Unique tint-filter ids across renderer instances (ids are document-global). */
|
|
@@ -26,9 +27,11 @@ const BLEND_CSS = {
|
|
|
26
27
|
* posed with a single CSS matrix() write per frame — exact, since bone
|
|
27
28
|
* transforms are affine.
|
|
28
29
|
* - Mesh attachments (deform parts) each get a small per-part <canvas> sized
|
|
29
|
-
* to the mesh's world bounding box, redrawn per frame with the
|
|
30
|
-
* per-triangle clip+transform+drawImage mapping
|
|
31
|
-
* canvas
|
|
30
|
+
* to the mesh's world bounding box, redrawn per frame — either with the
|
|
31
|
+
* standard per-triangle clip+transform+drawImage mapping (default), or via
|
|
32
|
+
* a shared offscreen WebGL canvas that rect-blits into the same per-part
|
|
33
|
+
* canvases (meshBackend = 'webgl'). Frames where the canvas-space vertices
|
|
34
|
+
* are unchanged reuse the previous raster on both backends.
|
|
32
35
|
*
|
|
33
36
|
* Both element kinds share one stacking context, so draw order interleaves
|
|
34
37
|
* freely via z-index (rear hair canvas < torso img < front hair canvas).
|
|
@@ -52,6 +55,12 @@ export class SpineHtmlRenderer {
|
|
|
52
55
|
meshCount = 0;
|
|
53
56
|
/** Mesh canvases that reused their previous raster last frame. */
|
|
54
57
|
meshReuseCount = 0;
|
|
58
|
+
/**
|
|
59
|
+
* Mesh canvas backing stores (re)allocated last frame. Should drop to zero
|
|
60
|
+
* once an animation reaches its steady state; a persistent nonzero value
|
|
61
|
+
* means GPU surfaces are being recreated per frame (a Safari killer).
|
|
62
|
+
*/
|
|
63
|
+
canvasReallocCount = 0;
|
|
55
64
|
/** Triangles rasterized last frame. */
|
|
56
65
|
triangleCount = 0;
|
|
57
66
|
/**
|
|
@@ -67,7 +76,26 @@ export class SpineHtmlRenderer {
|
|
|
67
76
|
* resolution instead of over- or under-sampling.
|
|
68
77
|
*/
|
|
69
78
|
pixelRatio = typeof devicePixelRatio === 'number' ? devicePixelRatio : 1;
|
|
79
|
+
/**
|
|
80
|
+
* Rasterizer for the mesh (deform) tier. 'canvas2d' (default) maps each
|
|
81
|
+
* triangle with clip+transform+drawImage directly on the per-part canvas.
|
|
82
|
+
* 'webgl' rasterizes every dirty mesh into one shared offscreen WebGL
|
|
83
|
+
* canvas and rect-blits each mesh back onto its per-part canvas — the DOM
|
|
84
|
+
* structure and all element-level behavior (z-index interleave, tint
|
|
85
|
+
* filter, mix-blend-mode, dirty-skip) are identical, only the raster step
|
|
86
|
+
* changes. Motivation: Safari antialiases canvas2d clip paths, so the
|
|
87
|
+
* per-triangle clip mapping pays a per-triangle AA-mask cost in the GPU
|
|
88
|
+
* process that rAF-limits heavy scenes; GL rasterizes shared edges
|
|
89
|
+
* seamlessly (no clip, no crack overdraw) and the blit is an unclipped
|
|
90
|
+
* rect copy. Falls back to 'canvas2d' when WebGL is unavailable or the
|
|
91
|
+
* shared context is lost — see meshBackendActive.
|
|
92
|
+
*/
|
|
93
|
+
meshBackend = 'canvas2d';
|
|
94
|
+
/** Backend that actually rasterized the mesh tier during the last render(). */
|
|
95
|
+
meshBackendActive = 'canvas2d';
|
|
70
96
|
views = new Map();
|
|
97
|
+
pendingJobs = [];
|
|
98
|
+
pendingViews = [];
|
|
71
99
|
scratchVertices = new Float32Array(256);
|
|
72
100
|
tintDefs = null;
|
|
73
101
|
/**
|
|
@@ -84,7 +112,10 @@ export class SpineHtmlRenderer {
|
|
|
84
112
|
this.clipSkipCount = 0;
|
|
85
113
|
this.meshCount = 0;
|
|
86
114
|
this.meshReuseCount = 0;
|
|
115
|
+
this.canvasReallocCount = 0;
|
|
87
116
|
this.triangleCount = 0;
|
|
117
|
+
const blitter = this.meshBackend === 'webgl' ? getMeshGlBlitter() : null;
|
|
118
|
+
this.meshBackendActive = blitter ? 'webgl' : 'canvas2d';
|
|
88
119
|
const drawOrder = skeleton.drawOrder.appliedPose;
|
|
89
120
|
for (let i = 0, n = drawOrder.length; i < n; i++) {
|
|
90
121
|
const slot = drawOrder[i];
|
|
@@ -106,7 +137,27 @@ export class SpineHtmlRenderer {
|
|
|
106
137
|
this.hide(slot);
|
|
107
138
|
}
|
|
108
139
|
}
|
|
140
|
+
if (this.pendingJobs.length) {
|
|
141
|
+
if (!blitter || !blitter.flush(this.pendingJobs)) {
|
|
142
|
+
// Context lost mid-frame: rasterize this batch on the 2d path so the
|
|
143
|
+
// frame stays complete; the next render() re-selects the backend.
|
|
144
|
+
for (let i = 0; i < this.pendingJobs.length; i++) {
|
|
145
|
+
const job = this.pendingJobs[i];
|
|
146
|
+
this.pendingViews[i].meshBackendDrawn = 'canvas2d';
|
|
147
|
+
this.rasterizeMesh2d(job.canvas, job.page, job.vertices, job.uvs, job.triangles, job.ratio);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
this.pendingJobs.length = 0;
|
|
151
|
+
this.pendingViews.length = 0;
|
|
152
|
+
}
|
|
109
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Removes every element this renderer added to the root (slot elements and
|
|
156
|
+
* the tint filter defs). The region bitmaps are deliberately untouched: the
|
|
157
|
+
* map is the caller's, and one map is normally shared by many renderers
|
|
158
|
+
* (disposing one instance must not blind the others). Free the unpacked
|
|
159
|
+
* blob URLs with revokeRegions() once no renderer needs them.
|
|
160
|
+
*/
|
|
110
161
|
dispose() {
|
|
111
162
|
for (const view of this.views.values())
|
|
112
163
|
view.el.remove();
|
|
@@ -190,19 +241,35 @@ export class SpineHtmlRenderer {
|
|
|
190
241
|
const view = this.view(slot, 'canvas');
|
|
191
242
|
const canvas = view.el;
|
|
192
243
|
const ratio = this.pixelRatio;
|
|
193
|
-
const backingW = Math.max(1, Math.round(w * ratio));
|
|
194
|
-
const backingH = Math.max(1, Math.round(h * ratio));
|
|
195
244
|
let dirty = view.meshAttachment !== attachment ||
|
|
196
245
|
view.meshSequenceIndex !== sequenceIndex ||
|
|
197
246
|
view.meshExpand !== this.triangleExpand ||
|
|
198
|
-
view.meshVertexCount !== count
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
247
|
+
view.meshVertexCount !== count ||
|
|
248
|
+
view.meshBackendDrawn !== this.meshBackendActive;
|
|
249
|
+
// The backing store only grows, in 32-device-px steps. Setting
|
|
250
|
+
// canvas.width recreates the GPU surface, and a deforming mesh changes
|
|
251
|
+
// its bbox every frame — reallocating every mesh canvas per frame
|
|
252
|
+
// stalled real Safari to ~3 fps while the JS split showed ~4 ms (the
|
|
253
|
+
// cost lives in the compositor, invisible to in-callback timing). With
|
|
254
|
+
// grow-only quantized backing, steady-state animation reallocates
|
|
255
|
+
// nothing. The CSS size mirrors the whole backing so the pixel mapping
|
|
256
|
+
// stays 1:1; the mesh draws into the top-left w×h logical region and
|
|
257
|
+
// the rest stays transparent.
|
|
258
|
+
const needW = Math.max(1, Math.round(w * ratio));
|
|
259
|
+
const needH = Math.max(1, Math.round(h * ratio));
|
|
260
|
+
if (needW > view.canvasW || needH > view.canvasH || view.meshRatio !== ratio) {
|
|
261
|
+
// 25% slack: at low fps the animation is sampled sparsely, so new bbox
|
|
262
|
+
// maxima keep being discovered for many seconds — allocate ahead of the
|
|
263
|
+
// curve instead of chasing it.
|
|
264
|
+
const step = 32;
|
|
265
|
+
view.canvasW = Math.ceil(Math.max(needW * 1.25, view.canvasW) / step) * step;
|
|
266
|
+
view.canvasH = Math.ceil(Math.max(needH * 1.25, view.canvasH) / step) * step;
|
|
267
|
+
view.meshRatio = ratio;
|
|
268
|
+
canvas.width = view.canvasW;
|
|
269
|
+
canvas.height = view.canvasH;
|
|
270
|
+
canvas.style.width = `${view.canvasW / ratio}px`;
|
|
271
|
+
canvas.style.height = `${view.canvasH / ratio}px`;
|
|
272
|
+
this.canvasReallocCount++;
|
|
206
273
|
dirty = true;
|
|
207
274
|
}
|
|
208
275
|
// Canvas-space vertices: identical values mean last frame's raster is
|
|
@@ -232,26 +299,52 @@ export class SpineHtmlRenderer {
|
|
|
232
299
|
view.meshSequenceIndex = sequenceIndex;
|
|
233
300
|
view.meshExpand = this.triangleExpand;
|
|
234
301
|
view.meshVertexCount = count;
|
|
235
|
-
|
|
236
|
-
if (!ctx)
|
|
237
|
-
return;
|
|
238
|
-
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
|
|
239
|
-
ctx.clearRect(0, 0, w, h);
|
|
302
|
+
view.meshBackendDrawn = this.meshBackendActive;
|
|
240
303
|
const uvs = sequence.getUVs(sequenceIndex);
|
|
241
304
|
const triangles = attachment.triangles;
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
305
|
+
if (this.meshBackendActive === 'webgl') {
|
|
306
|
+
// Queued, not drawn: render() flushes the whole batch through the
|
|
307
|
+
// shared GL context once the slot loop is done. `rel` is this view's
|
|
308
|
+
// own signature array — nothing mutates it before the flush.
|
|
309
|
+
this.pendingJobs.push({
|
|
310
|
+
canvas,
|
|
311
|
+
page,
|
|
312
|
+
vertices: rel,
|
|
313
|
+
uvs,
|
|
314
|
+
triangles,
|
|
315
|
+
ratio,
|
|
316
|
+
width: Math.min(view.canvasW, Math.ceil(w * ratio)),
|
|
317
|
+
height: Math.min(view.canvasH, Math.ceil(h * ratio)),
|
|
318
|
+
});
|
|
319
|
+
this.pendingViews.push(view);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
this.rasterizeMesh2d(canvas, page, rel, uvs, triangles, ratio);
|
|
249
323
|
}
|
|
250
324
|
this.meshCount++;
|
|
251
325
|
this.triangleCount += triangles.length / 3;
|
|
252
326
|
}
|
|
253
327
|
this.applyCommon(view, slot, pose, attachment.color, skeleton, zIndex);
|
|
254
328
|
}
|
|
329
|
+
/** The canvas2d raster path: clear the backing, map each triangle. */
|
|
330
|
+
rasterizeMesh2d(canvas, page, vertices, uvs, triangles, ratio) {
|
|
331
|
+
const ctx = canvas.getContext('2d');
|
|
332
|
+
if (!ctx)
|
|
333
|
+
return;
|
|
334
|
+
// Clear the full backing: the previous frame's bbox (and so its drawn
|
|
335
|
+
// region) may have been larger than today's.
|
|
336
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
337
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
338
|
+
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
|
|
339
|
+
const uw = page.width - 1;
|
|
340
|
+
const uh = page.height - 1;
|
|
341
|
+
for (let t = 0; t < triangles.length; t += 3) {
|
|
342
|
+
const i0 = triangles[t] * 2;
|
|
343
|
+
const i1 = triangles[t + 1] * 2;
|
|
344
|
+
const i2 = triangles[t + 2] * 2;
|
|
345
|
+
this.drawTriangle(ctx, page, vertices[i0], vertices[i0 + 1], uvs[i0] * uw, uvs[i0 + 1] * uh, vertices[i1], vertices[i1 + 1], uvs[i1] * uw, uvs[i1 + 1] * uh, vertices[i2], vertices[i2 + 1], uvs[i2] * uw, uvs[i2 + 1] * uh);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
255
348
|
/**
|
|
256
349
|
* Standard canvas triangle texture mapping (same math as the official
|
|
257
350
|
* spine-canvas renderer): derive the affine that sends the triangle's
|
|
@@ -404,10 +497,12 @@ export class SpineHtmlRenderer {
|
|
|
404
497
|
tintMatrix: null,
|
|
405
498
|
canvasW: 0,
|
|
406
499
|
canvasH: 0,
|
|
500
|
+
meshRatio: -1,
|
|
407
501
|
meshAttachment: null,
|
|
408
502
|
meshSequenceIndex: -1,
|
|
409
503
|
meshExpand: -1,
|
|
410
504
|
meshVertexCount: -1,
|
|
505
|
+
meshBackendDrawn: '',
|
|
411
506
|
meshVertices: new Float64Array(0),
|
|
412
507
|
};
|
|
413
508
|
this.views.set(slot, view);
|