spine-html 0.5.0 → 0.5.2

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 CHANGED
@@ -247,6 +247,37 @@ it — and calling it twice is a no-op. Load once for the page's lifetime and yo
247
247
  can ignore it; load and unload repeatedly without it and you leak an atlas per
248
248
  cycle.
249
249
 
250
+ ### Pages that ship at another resolution
251
+
252
+ A page image may ship at a resolution its atlas does not declare — a
253
+ half-resolution texture build, or an @2x variant, with the `size:` line left as
254
+ the packer wrote it. Nothing here needs a flag for that. A region's bounds are
255
+ read **relative to the declared page size** and scaled onto the image's natural
256
+ size, which is exactly how `spine-core` derives the UVs the mesh tier samples
257
+ with (`region.u = region.x / page.width`), so both tiers land on the same
258
+ pixels at any resolution:
259
+
260
+ ```
261
+ hero.png
262
+ size: 4096, 4096 # what the packer wrote
263
+ # hero.png itself ships 2048×2048
264
+ ```
265
+
266
+ Each unpacked bitmap comes out at the **native resolution of the pixels it was
267
+ cut from** — half-resolution pages cost a quarter of the cut pixels, and
268
+ nothing is upscaled back — while `RegionImage.width`/`height` stay in **atlas
269
+ units**. Those two numbers are the `<img>` layout box and the denominator of
270
+ its CSS matrix, so the skeleton poses identically and the browser scales the
271
+ smaller bitmap into the same box, the way a GPU samples a smaller texture
272
+ through the same UVs. Ship one atlas and swap the images per device if you
273
+ like.
274
+
275
+ The corollary: a `size:` line that is simply **wrong** is now wrong for the
276
+ whole renderer rather than for the mesh tier alone. Bounds are read against
277
+ what the atlas declares, so a page declared at a size its artwork was not
278
+ packed at reads every region from the wrong place in both tiers — correct the
279
+ `size:` line, which is what every other Spine runtime needs too.
280
+
250
281
  ### What unloading frees
251
282
 
252
283
  `revokeRegions()` — and `assets.dispose()`, which just calls it — frees the blob
@@ -297,12 +328,16 @@ bounds: 0, 0, 640, 480
297
328
  `spine-core` parses this as a normal multi-page atlas and nothing here needs a
298
329
  flag. Two things to know:
299
330
 
300
- - **`size:` must be the PNG's real pixel size.** UVs are derived from it
301
- (`region.x / page.width`), and a wrong value skews the mesh tier while the
302
- rigid tier still looks fine a confusing failure to chase.
331
+ - **`size:` is the frame every coordinate is read in**, so each page's
332
+ `bounds` must be written against the `size:` above them. The two need not be
333
+ the PNG's pixel sizethat is the point of the section above, and a
334
+ half-resolution `head.png` under `size: 512, 512` works — but a `size:` that
335
+ matches neither the bounds nor the artwork reads every region from the wrong
336
+ place, in both tiers.
303
337
  - Regions like these cover their whole page, so `unpackRegions` hands the page
304
- image straight through instead of cutting and re-encoding it. Load cost for
305
- this atlas shape is just the image loads.
338
+ image straight through instead of cutting and re-encoding it at any image
339
+ resolution, since covering the page is a statement about the declared size.
340
+ Load cost for this atlas shape is just the image loads.
306
341
 
307
342
  For a **rigid-only** skeleton you can skip atlas unpacking altogether and hand
308
343
  the renderer a map you build yourself — meshes cannot, because the deform tier
@@ -16,9 +16,18 @@ export interface RegionImage {
16
16
  * whole page, or anything the caller put there in a hand-built map.
17
17
  */
18
18
  url: string;
19
- /** Unpacked width in atlas pixels. */
19
+ /**
20
+ * Unpacked width in **atlas units** — the region's own `width`, not the
21
+ * bitmap's pixel width. The two differ whenever the page image ships at a
22
+ * resolution its atlas `size:` line does not mention (see cutRegion): a
23
+ * half-resolution page yields a bitmap half this wide. This is the layout
24
+ * box the rigid tier writes onto its `<img>` and the denominator of the CSS
25
+ * matrix it builds, so it stays in the frame the skeleton is authored in
26
+ * and the browser scales the bitmap into it — the same thing a GPU does
27
+ * when it samples normalized UVs from a texture of any resolution.
28
+ */
20
29
  width: number;
21
- /** Unpacked height in atlas pixels. */
30
+ /** Unpacked height in atlas units — see `width`. */
22
31
  height: number;
23
32
  }
24
33
  /**
@@ -30,14 +39,53 @@ export interface RegionImage {
30
39
  * rendering is pure DOM (one <img> per slot, one CSS matrix write per frame).
31
40
  *
32
41
  * A region that covers its whole page unrotated skips the cut and reuses the
33
- * page image URL — see the pass-through below.
42
+ * page image URL — see the pass-through in planCut.
43
+ *
44
+ * **A page may ship at a resolution its atlas does not declare.** Half-size
45
+ * texture builds and @2x variants are normal, and no option is needed for
46
+ * them: a region's bounds are read relative to the declared page size and
47
+ * scaled onto the image's natural size, which is exactly how spine-core
48
+ * derives the UVs the mesh tier already samples with. Each bitmap comes out at
49
+ * the native resolution of the pixels it was cut from, and the `RegionImage`
50
+ * sizes beside it stay in atlas units, so nothing downstream has to know.
51
+ *
52
+ * *Behaviour change (0.5.1 → next).* An atlas whose `size:` line is simply
53
+ * wrong — bounds written in image pixels under a page declared some other size
54
+ * — used to be cut "as the coordinates say", because the cut read the image
55
+ * directly. It is now read the way every other Spine runtime reads it:
56
+ * relative to the declared size. Such an atlas rendered with a skewed mesh
57
+ * tier and an intact rigid tier before; now both tiers agree, and the fix is
58
+ * to correct the `size:` line.
59
+ *
60
+ * **The cuts run concurrently.** PNG encoding is asynchronous and the browser
61
+ * does it off the main thread, so the encodes are started together and awaited
62
+ * together instead of one `toBlob` per iteration: awaited in series a load
63
+ * costs the *sum* of every encode, which a throttled document turns into one
64
+ * full throttle period per region (measured: a single 8×8 `toBlob` took 7.5 s
65
+ * to call back in a hidden desktop Chromium pane). How many run at once is
66
+ * bounded by CUT_BACKING_BUDGET_PX above, not by an option.
67
+ *
68
+ * Concurrency changes nothing a caller can observe:
69
+ *
70
+ * - **Order.** The returned map is keyed in `atlas.regions` order whatever
71
+ * order the blobs arrive in — the map is built after the cuts settle, from
72
+ * the region list, not from the arrivals.
73
+ * - **Duplicate names.** The last region of a given name in atlas order wins,
74
+ * and the shadowed URL is revoked (and dropped from the ledger) rather than
75
+ * stranded.
76
+ * - **Failure.** Every URL this call minted is revoked before the error
77
+ * propagates, *including cuts that were still in flight when the first
78
+ * failure happened* — their blobs arrive later, and a load that walked away
79
+ * from them would mint owned URLs nobody could ever revoke. So a failure
80
+ * stops new cuts from starting but still waits for the started ones, and the
81
+ * error it raises is the one from the earliest region in atlas order (what
82
+ * the serial loop used to throw). Nothing here rejects unobserved.
34
83
  *
35
84
  * The blob URLs stay alive until revokeRegions() frees them — a document-wide
36
85
  * allocation the GC cannot reclaim on its own. Callers that load and unload
37
86
  * skeletons repeatedly (cutscenes, level transitions) must pair every
38
87
  * unpackRegions() with a revokeRegions(); a caller that loads once for the
39
- * page lifetime can ignore it. If this function throws part-way through,
40
- * everything it already minted is revoked before the error propagates.
88
+ * page lifetime can ignore it.
41
89
  */
42
90
  export declare function unpackRegions(atlas: TextureAtlas, pageImages: Map<string, HTMLImageElement>): Promise<Map<string, RegionImage>>;
43
91
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"DomTexture.d.ts","sourceRoot":"","sources":["../src/DomTexture.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,YAAY,EAEZ,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AAEtC;;;;GAIG;AACH,qBAAa,UAAW,SAAQ,OAAO;IACrC,UAAU,CAAC,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,GAAG,IAAI;IACtE,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IACxD,OAAO,IAAI,IAAI;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB;AAkBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,GACxC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAqEnC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,IAAI,CAEpE"}
1
+ {"version":3,"file":"DomTexture.d.ts","sourceRoot":"","sources":["../src/DomTexture.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,YAAY,EAEZ,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AAEtC;;;;GAIG;AACH,qBAAa,UAAW,SAAQ,OAAO;IACrC,UAAU,CAAC,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,GAAG,IAAI;IACtE,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IACxD,OAAO,IAAI,IAAI;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;;OASG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;CAChB;AAsMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,GACxC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CA0EnC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,IAAI,CAEpE"}
@@ -24,6 +24,158 @@ function revokeOwned(url) {
24
24
  return;
25
25
  URL.revokeObjectURL(url);
26
26
  }
27
+ /**
28
+ * Backing pixels the cuts still waiting for their PNG encode may hold at once.
29
+ *
30
+ * A started cut keeps its canvas alive until `toBlob` calls back, so starting
31
+ * every region at once peaks at Σ(cut area) of canvas backing — on top of the
32
+ * decoded pages, which stay resident throughout. A cut is allocated at the
33
+ * page image's own resolution, so what is charged here is that area, not the
34
+ * region's atlas-unit area: a half-resolution page costs a quarter of it, an
35
+ * @2x page four times it. Measured by counting the simultaneously-alive cut
36
+ * canvases and their backing at `toBlob` (chromium, 1:1 pages):
37
+ * the demo's 40-region 1024×256 atlas peaks at all 40 cuts and 194,742 px
38
+ * (0.19 Mpx ≈ 0.74 MiB), which is nothing; a synthetic 256-region atlas on
39
+ * four 2048×2048 pages peaks at 16.78 Mpx ≈ 64 MiB, which doubles that
40
+ * atlas's resident cost for the length of the load.
41
+ *
42
+ * So the cuts are started against a budget rather than all at once. 4 Mpx is
43
+ * one 2048×2048 page's worth (≈ 16 MiB at 4 bytes/px): small atlases still run
44
+ * every region concurrently (spineboy: all 40, budget never engaged), and the
45
+ * synthetic one runs 64 cuts at a time — 4 waves instead of 256 serial
46
+ * encodes, for a quarter of the peak. A single region bigger than the budget
47
+ * is started anyway when nothing else is in flight, so the budget can never
48
+ * starve a cut.
49
+ */
50
+ const CUT_BACKING_BUDGET_PX = 4 * 1024 * 1024;
51
+ /**
52
+ * Locates a region on its page image.
53
+ *
54
+ * **The rect is read in the image's own frame, not in declared atlas pixels.**
55
+ * An atlas `size:` line and the image that ships for the page need not agree:
56
+ * projects export textures at half resolution (or @2x) and leave the atlas
57
+ * alone. Every official Spine runtime absorbs that for free, because
58
+ * spine-core turns a region's bounds into *normalized* UVs against the
59
+ * declared page size (`region.u = region.x / page.width`) and a GPU samples
60
+ * normalized coordinates at whatever resolution the texture happens to have.
61
+ * So the rect here is the region's bounds relative to the declared page size,
62
+ * times the image's natural size — the continuous `uv * size` frame both mesh
63
+ * backends already address texels in. No `size - 1`, no half-texel term: this
64
+ * maps a rect, it does not sample.
65
+ *
66
+ * With a page that does ship at its declared size the scale is exactly 1 and
67
+ * every number below is the region's own integer bounds, unchanged.
68
+ *
69
+ * Rotation: a region packed at 90° lies on its side, so it occupies a
70
+ * `height × width` rect on the page — which is how spine-core reads it too
71
+ * (`u2 = (x + height) / page.width` when `degrees === 90`). The cut canvas is
72
+ * the rect turned back upright, so the bitmap comes out in artwork
73
+ * orientation.
74
+ *
75
+ * Rounding: each *edge* is rounded to the nearest image pixel, rather than the
76
+ * width being rounded on its own. Neighbouring regions share an edge, so
77
+ * rounding the edge keeps their cuts tiling the image exactly — no gap, no
78
+ * overlap, and at most half a pixel of either region's own content given up on
79
+ * a boundary that falls mid-pixel. Rounding outward would instead pull a whole
80
+ * neighbouring pixel into every cut on every side, which is bleed the caller
81
+ * can see. A rect that scales down below one pixel still gets a 1×1 bitmap,
82
+ * since a zero-sized canvas is not one.
83
+ */
84
+ function planCut(region, image) {
85
+ const iw = image.naturalWidth;
86
+ const ih = image.naturalHeight;
87
+ // A page with no `size:` line (or a malformed one) declares nothing, so the
88
+ // image is taken to be its own declared size — which is what this did for
89
+ // every page before the frame above existed.
90
+ const pageW = region.page.width > 0 ? region.page.width : iw;
91
+ const pageH = region.page.height > 0 ? region.page.height : ih;
92
+ const rotated = region.degrees === 90;
93
+ const packedW = rotated ? region.height : region.width;
94
+ const packedH = rotated ? region.width : region.height;
95
+ // Whole-page pass-through: an unrotated region covering its entire page
96
+ // would be cut into a pixel-for-pixel copy of the page image, so reuse the
97
+ // page URL instead — no canvas, no PNG re-encode, no second decoded copy in
98
+ // memory, and nothing to revoke afterwards. Atlases written as one part per
99
+ // page (the loose-part-PNG workflow, where every part is declared its own
100
+ // page) hit this for every single region.
101
+ //
102
+ // The test is against the declared page size, at any image resolution: a
103
+ // whole page is a whole page whether it ships at 1×, half or double, and the
104
+ // cut would copy the image either way. (It used to be tested against the
105
+ // image, which was the same rule as long as the two always agreed.)
106
+ if (!rotated && region.x === 0 && region.y === 0 && packedW === pageW && packedH === pageH) {
107
+ return { passThrough: true, sx: 0, sy: 0, sw: iw, sh: ih, cw: iw, ch: ih };
108
+ }
109
+ const scaleX = iw / pageW;
110
+ const scaleY = ih / pageH;
111
+ const x0 = Math.round(region.x * scaleX);
112
+ const y0 = Math.round(region.y * scaleY);
113
+ const sw = Math.max(1, Math.round((region.x + packedW) * scaleX) - x0);
114
+ const sh = Math.max(1, Math.round((region.y + packedH) * scaleY) - y0);
115
+ return {
116
+ passThrough: false,
117
+ sx: x0,
118
+ sy: y0,
119
+ sw,
120
+ sh,
121
+ cw: rotated ? sh : sw,
122
+ ch: rotated ? sw : sh,
123
+ };
124
+ }
125
+ /** Backing pixels one cut allocates — the charge against the budget above. */
126
+ function cutBackingPixels(region, pageImages) {
127
+ const image = pageImages.get(region.page.name);
128
+ // No image: this region is about to fail in the cut. Charging its atlas
129
+ // units keeps the walk moving until it does.
130
+ if (!image)
131
+ return region.width * region.height;
132
+ const plan = planCut(region, image);
133
+ return plan.cw * plan.ch;
134
+ }
135
+ /**
136
+ * One region's bitmap: the whole-page pass-through, or a cut canvas encoded to
137
+ * a blob URL this module then owns.
138
+ *
139
+ * The cut is a **1:1 copy of the source rect** planCut located — never a
140
+ * resample — so the bitmap has the native resolution of those pixels, while
141
+ * the `RegionImage` sizes reported alongside it stay in atlas units. A page
142
+ * shipped at half resolution therefore costs a quarter of the cut pixels and
143
+ * still poses identically; upscaling the cut back to declared size would only
144
+ * quadruple the pixels and add a resample.
145
+ *
146
+ * Everything before the `toBlob` await runs synchronously, so calling this is
147
+ * what "starts" a cut: the canvas is painted and the encode is handed to the
148
+ * browser before control comes back.
149
+ */
150
+ async function cutRegion(region, pageImages) {
151
+ const image = pageImages.get(region.page.name);
152
+ if (!image)
153
+ throw new Error(`Missing page image: ${region.page.name}`);
154
+ const w = region.width;
155
+ const h = region.height;
156
+ const plan = planCut(region, image);
157
+ if (plan.passThrough)
158
+ return { url: image.src, width: w, height: h };
159
+ const canvas = document.createElement('canvas');
160
+ canvas.width = plan.cw;
161
+ canvas.height = plan.ch;
162
+ const ctx = canvas.getContext('2d');
163
+ if (!ctx)
164
+ throw new Error('2d context unavailable');
165
+ if (region.degrees === 90) {
166
+ // The region is packed rotated. Turn the destination frame back so the
167
+ // source rect, drawn at its own size, lands upright and fills the canvas.
168
+ ctx.translate(0, plan.ch);
169
+ ctx.rotate(-Math.PI / 2);
170
+ }
171
+ ctx.drawImage(image, plan.sx, plan.sy, plan.sw, plan.sh, 0, 0, plan.sw, plan.sh);
172
+ const blob = await new Promise((resolve, reject) => {
173
+ canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('toBlob failed'))), 'image/png');
174
+ });
175
+ const url = URL.createObjectURL(blob);
176
+ ownedUrls.add(url);
177
+ return { url, width: w, height: h };
178
+ }
27
179
  /**
28
180
  * Cuts every atlas region out of the page image into its own bitmap once at
29
181
  * load time, restoring 90° packing rotation, so the per-frame path never
@@ -33,78 +185,122 @@ function revokeOwned(url) {
33
185
  * rendering is pure DOM (one <img> per slot, one CSS matrix write per frame).
34
186
  *
35
187
  * A region that covers its whole page unrotated skips the cut and reuses the
36
- * page image URL — see the pass-through below.
188
+ * page image URL — see the pass-through in planCut.
189
+ *
190
+ * **A page may ship at a resolution its atlas does not declare.** Half-size
191
+ * texture builds and @2x variants are normal, and no option is needed for
192
+ * them: a region's bounds are read relative to the declared page size and
193
+ * scaled onto the image's natural size, which is exactly how spine-core
194
+ * derives the UVs the mesh tier already samples with. Each bitmap comes out at
195
+ * the native resolution of the pixels it was cut from, and the `RegionImage`
196
+ * sizes beside it stay in atlas units, so nothing downstream has to know.
197
+ *
198
+ * *Behaviour change (0.5.1 → next).* An atlas whose `size:` line is simply
199
+ * wrong — bounds written in image pixels under a page declared some other size
200
+ * — used to be cut "as the coordinates say", because the cut read the image
201
+ * directly. It is now read the way every other Spine runtime reads it:
202
+ * relative to the declared size. Such an atlas rendered with a skewed mesh
203
+ * tier and an intact rigid tier before; now both tiers agree, and the fix is
204
+ * to correct the `size:` line.
205
+ *
206
+ * **The cuts run concurrently.** PNG encoding is asynchronous and the browser
207
+ * does it off the main thread, so the encodes are started together and awaited
208
+ * together instead of one `toBlob` per iteration: awaited in series a load
209
+ * costs the *sum* of every encode, which a throttled document turns into one
210
+ * full throttle period per region (measured: a single 8×8 `toBlob` took 7.5 s
211
+ * to call back in a hidden desktop Chromium pane). How many run at once is
212
+ * bounded by CUT_BACKING_BUDGET_PX above, not by an option.
213
+ *
214
+ * Concurrency changes nothing a caller can observe:
215
+ *
216
+ * - **Order.** The returned map is keyed in `atlas.regions` order whatever
217
+ * order the blobs arrive in — the map is built after the cuts settle, from
218
+ * the region list, not from the arrivals.
219
+ * - **Duplicate names.** The last region of a given name in atlas order wins,
220
+ * and the shadowed URL is revoked (and dropped from the ledger) rather than
221
+ * stranded.
222
+ * - **Failure.** Every URL this call minted is revoked before the error
223
+ * propagates, *including cuts that were still in flight when the first
224
+ * failure happened* — their blobs arrive later, and a load that walked away
225
+ * from them would mint owned URLs nobody could ever revoke. So a failure
226
+ * stops new cuts from starting but still waits for the started ones, and the
227
+ * error it raises is the one from the earliest region in atlas order (what
228
+ * the serial loop used to throw). Nothing here rejects unobserved.
37
229
  *
38
230
  * The blob URLs stay alive until revokeRegions() frees them — a document-wide
39
231
  * allocation the GC cannot reclaim on its own. Callers that load and unload
40
232
  * skeletons repeatedly (cutscenes, level transitions) must pair every
41
233
  * unpackRegions() with a revokeRegions(); a caller that loads once for the
42
- * page lifetime can ignore it. If this function throws part-way through,
43
- * everything it already minted is revoked before the error propagates.
234
+ * page lifetime can ignore it.
44
235
  */
45
236
  export async function unpackRegions(atlas, pageImages) {
46
- const result = new Map();
47
- // Duplicate region names would otherwise strand the shadowed URL in the
48
- // ledger with nothing left pointing at it.
49
- const put = (name, entry) => {
50
- const shadowed = result.get(name);
51
- if (shadowed)
52
- revokeOwned(shadowed.url);
53
- result.set(name, entry);
54
- };
55
- try {
56
- for (const region of atlas.regions) {
57
- const atlasRegion = region;
58
- const image = pageImages.get(atlasRegion.page.name);
59
- if (!image)
60
- throw new Error(`Missing page image: ${atlasRegion.page.name}`);
61
- const w = region.width;
62
- const h = region.height;
63
- // Whole-page pass-through: an unrotated region covering its entire page
64
- // would be cut into a pixel-for-pixel copy of the page image, so reuse
65
- // the page URL instead — no canvas, no PNG re-encode, no second decoded
66
- // copy in memory, and nothing to revoke afterwards. Atlases written as
67
- // one part per page (the loose-part-PNG workflow, where every part is
68
- // declared its own page) hit this for every single region.
69
- //
70
- // The test is against the image, not the atlas `size:` line: a page
71
- // declared at the wrong size still goes through the cut, since the cut
72
- // is what the region's coordinates actually describe.
73
- if (region.degrees === 0 &&
74
- atlasRegion.x === 0 &&
75
- atlasRegion.y === 0 &&
76
- w === image.naturalWidth &&
77
- h === image.naturalHeight) {
78
- put(atlasRegion.name, { url: image.src, width: w, height: h });
79
- continue;
237
+ const regions = atlas.regions;
238
+ const cuts = new Array(regions.length);
239
+ // The earliest-in-atlas-order failure is the one that propagates, so that a
240
+ // broken atlas reports the same region whichever encode happens to land
241
+ // first.
242
+ let failedIndex = -1;
243
+ let failure;
244
+ let inFlight = [];
245
+ let inFlightPixels = 0;
246
+ for (let index = 0; index < regions.length && failedIndex === -1; index++) {
247
+ const region = regions[index];
248
+ if (!region)
249
+ continue;
250
+ // A pass-through allocates no canvas, but it also resolves without waiting
251
+ // for anything, so charging it the page's area costs at most one drain.
252
+ const pixels = cutBackingPixels(region, pageImages);
253
+ while (inFlight.length > 0 && inFlightPixels + pixels > CUT_BACKING_BUDGET_PX) {
254
+ await Promise.race(inFlight.map((job) => job.promise));
255
+ inFlight = inFlight.filter((job) => !job.done);
256
+ if (failedIndex !== -1)
257
+ break;
258
+ }
259
+ if (failedIndex !== -1)
260
+ break;
261
+ inFlightPixels += pixels;
262
+ const job = { done: false, promise: Promise.resolve() };
263
+ // Jobs settle, they never reject: the failure is recorded here so that
264
+ // nothing is left for an unhandled-rejection handler to find.
265
+ job.promise = (async () => {
266
+ try {
267
+ cuts[index] = await cutRegion(region, pageImages);
80
268
  }
81
- const canvas = document.createElement('canvas');
82
- canvas.width = w;
83
- canvas.height = h;
84
- const ctx = canvas.getContext('2d');
85
- if (!ctx)
86
- throw new Error('2d context unavailable');
87
- if (region.degrees === 90) {
88
- // The region is packed rotated: it occupies an h×w rect in the page.
89
- // Rotate it back so the bitmap is in artwork orientation.
90
- ctx.translate(0, h);
91
- ctx.rotate(-Math.PI / 2);
92
- ctx.drawImage(image, atlasRegion.x, atlasRegion.y, h, w, 0, 0, h, w);
269
+ catch (error) {
270
+ if (failedIndex === -1 || index < failedIndex) {
271
+ failedIndex = index;
272
+ failure = error;
273
+ }
93
274
  }
94
- else {
95
- ctx.drawImage(image, atlasRegion.x, atlasRegion.y, w, h, 0, 0, w, h);
275
+ finally {
276
+ inFlightPixels -= pixels;
277
+ job.done = true;
96
278
  }
97
- const blob = await new Promise((resolve, reject) => {
98
- canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('toBlob failed'))), 'image/png');
99
- });
100
- const url = URL.createObjectURL(blob);
101
- ownedUrls.add(url);
102
- put(atlasRegion.name, { url, width: w, height: h });
103
- }
279
+ })();
280
+ inFlight.push(job);
104
281
  }
105
- catch (error) {
106
- revokeRegions(result);
107
- throw error;
282
+ // Every cut this call started is settled after this, so the cleanup below
283
+ // reaches the blobs that arrived after the first failure too.
284
+ await Promise.all(inFlight.map((job) => job.promise));
285
+ if (failedIndex !== -1) {
286
+ for (const cut of cuts)
287
+ if (cut)
288
+ revokeOwned(cut.url);
289
+ throw failure;
290
+ }
291
+ const result = new Map();
292
+ for (let index = 0; index < regions.length; index++) {
293
+ const region = regions[index];
294
+ const cut = cuts[index];
295
+ if (!region || !cut)
296
+ continue;
297
+ // Duplicate region names would otherwise strand the shadowed URL in the
298
+ // ledger with nothing left pointing at it. Map.set keeps the key where it
299
+ // first appeared, so atlas order survives the overwrite.
300
+ const shadowed = result.get(region.name);
301
+ if (shadowed)
302
+ revokeOwned(shadowed.url);
303
+ result.set(region.name, cut);
108
304
  }
109
305
  return result;
110
306
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DomTexture.js","sourceRoot":"","sources":["../src/DomTexture.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DomTexture.js","sourceRoot":"","sources":["../src/DomTexture.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-html",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Render Spine skeletal animations as plain DOM — rigid slots as <img> posed by CSS matrix, deform meshes on small per-part canvases, interleaved in one stacking context.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/dist/main.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=main.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
package/dist/main.js DELETED
@@ -1,243 +0,0 @@
1
- import { AnimationState, AnimationStateData, AtlasAttachmentLoader, Physics, Skeleton, SkeletonJson, TextureAtlas, } from '@esotericsoftware/spine-core';
2
- import { DomTexture, unpackRegions } from './DomTexture.js';
3
- import { SpineHtmlRenderer } from './SpineHtmlRenderer.js';
4
- const ASSET_BASE = '/spineboy';
5
- const SKELETONS = {
6
- pro: 'spineboy-pro.json', // meshes (deform tier → per-part canvases)
7
- ess: 'spineboy-ess.json', // region attachments only (rigid tier → pure DOM)
8
- };
9
- const STAGE_SCALE = 0.4;
10
- // Debug knobs: ?skel=pro|ess &anim=walk &count=10 pick the scene;
11
- // ?expand=0 shows the mesh cracks; ?tint=ff8080 tints the whole skeleton;
12
- // ?dpr=2 overrides the mesh-canvas backing ratio; ?timescale=0 freezes the
13
- // animation (isolates the dirty-skip path: every mesh should report reused);
14
- // ?backend=webgl rasterizes meshes through the shared WebGL blitter;
15
- // ?time=1.2 seeks every instance to the same pose (deterministic screenshots).
16
- const params = new URLSearchParams(location.search);
17
- function parseTint() {
18
- const hex = params.get('tint');
19
- if (!hex || !/^[0-9a-fA-F]{6}$/.test(hex))
20
- return null;
21
- return {
22
- r: parseInt(hex.slice(0, 2), 16) / 255,
23
- g: parseInt(hex.slice(2, 4), 16) / 255,
24
- b: parseInt(hex.slice(4, 6), 16) / 255,
25
- };
26
- }
27
- async function loadImage(url) {
28
- return new Promise((resolve, reject) => {
29
- const img = new Image();
30
- img.onload = () => resolve(img);
31
- img.onerror = () => reject(new Error(`Failed to load ${url}`));
32
- img.src = url;
33
- });
34
- }
35
- async function load() {
36
- const [atlasText, proText, essText] = await Promise.all([
37
- fetch(`${ASSET_BASE}/spineboy.atlas`).then((r) => r.text()),
38
- fetch(`${ASSET_BASE}/${SKELETONS.pro}`).then((r) => r.text()),
39
- fetch(`${ASSET_BASE}/${SKELETONS.ess}`).then((r) => r.text()),
40
- ]);
41
- const atlas = new TextureAtlas(atlasText);
42
- const pageImages = new Map();
43
- for (const page of atlas.pages) {
44
- const image = await loadImage(`${ASSET_BASE}/${page.name}`);
45
- page.setTexture(new DomTexture(image));
46
- pageImages.set(page.name, image);
47
- }
48
- const regionImages = await unpackRegions(atlas, pageImages);
49
- const loader = new AtlasAttachmentLoader(atlas);
50
- return {
51
- data: {
52
- pro: new SkeletonJson(loader).readSkeletonData(proText),
53
- ess: new SkeletonJson(loader).readSkeletonData(essText),
54
- },
55
- regionImages,
56
- };
57
- }
58
- function main(data, regionImages) {
59
- const stage = document.getElementById('stage');
60
- const skelSelect = document.getElementById('skel');
61
- const animSelect = document.getElementById('anim');
62
- const countInput = document.getElementById('count');
63
- const backendSelect = document.getElementById('backend');
64
- const stats = document.getElementById('stats');
65
- backendSelect.value = params.get('backend') === 'webgl' ? 'webgl' : 'canvas2d';
66
- const skelParam = params.get('skel');
67
- let variant = skelParam === 'ess' ? 'ess' : 'pro';
68
- skelSelect.value = variant;
69
- let stateData = new AnimationStateData(data[variant]);
70
- let instances = [];
71
- const tint = parseTint();
72
- function fillAnimations() {
73
- animSelect.innerHTML = '';
74
- for (const anim of data[variant].animations) {
75
- const option = document.createElement('option');
76
- option.value = anim.name;
77
- option.textContent = anim.name;
78
- animSelect.appendChild(option);
79
- }
80
- const names = data[variant].animations.map((a) => a.name);
81
- animSelect.value = names.includes('walk') ? 'walk' : names[0];
82
- }
83
- function rebuild(count) {
84
- for (const inst of instances) {
85
- inst.renderer.dispose();
86
- inst.container.remove();
87
- }
88
- instances = [];
89
- for (let i = 0; i < count; i++) {
90
- const container = document.createElement('div');
91
- container.className = 'skeleton-root';
92
- // Spread instances horizontally; each root is the skeleton origin
93
- // (feet), so park it near the stage bottom.
94
- const x = count === 1 ? 50 : 10 + (80 * i) / (count - 1);
95
- container.style.left = `${x}%`;
96
- container.style.transform = `scale(${STAGE_SCALE})`;
97
- stage.appendChild(container);
98
- const skeleton = new Skeleton(data[variant]);
99
- if (tint)
100
- skeleton.color.set(tint.r, tint.g, tint.b, 1);
101
- const state = new AnimationState(stateData);
102
- state.setAnimation(0, animSelect.value, true);
103
- // Desync walk cycles so N instances don't look like one stamped sprite —
104
- // unless ?time seeks them all to one pose (for deterministic captures).
105
- const timeParam = params.get('time');
106
- state.update(timeParam !== null ? Number(timeParam) || 0 : (i * 0.37) % 2);
107
- const timescaleParam = params.get('timescale');
108
- if (timescaleParam !== null)
109
- state.timeScale = Number(timescaleParam) || 0;
110
- const renderer = new SpineHtmlRenderer(container, regionImages);
111
- renderer.meshBackend = backendSelect.value;
112
- const expandParam = params.get('expand');
113
- if (expandParam !== null)
114
- renderer.triangleExpand = Number(expandParam) || 0;
115
- // Mesh canvases raster at the effective on-screen resolution: device
116
- // pixels × the stage downscale (a plain devicePixelRatio would
117
- // oversample by 1/STAGE_SCALE).
118
- const dprParam = params.get('dpr');
119
- renderer.pixelRatio =
120
- dprParam !== null ? Number(dprParam) || 1 : (window.devicePixelRatio || 1) * STAGE_SCALE;
121
- instances.push({ skeleton, state, renderer, container });
122
- }
123
- }
124
- skelSelect.addEventListener('change', () => {
125
- variant = skelSelect.value;
126
- stateData = new AnimationStateData(data[variant]);
127
- stateData.defaultMix = 0.2;
128
- fillAnimations();
129
- rebuild(instances.length || 1);
130
- });
131
- animSelect.addEventListener('change', () => {
132
- for (const inst of instances)
133
- inst.state.setAnimation(0, animSelect.value, true);
134
- });
135
- countInput.addEventListener('change', () => {
136
- const count = Math.min(50, Math.max(1, Number(countInput.value) || 1));
137
- countInput.value = String(count);
138
- rebuild(count);
139
- });
140
- backendSelect.addEventListener('change', () => {
141
- // Live switch: the backend signature re-dirties every mesh on its own.
142
- for (const inst of instances)
143
- inst.renderer.meshBackend = backendSelect.value;
144
- });
145
- stateData.defaultMix = 0.2;
146
- fillAnimations();
147
- const animParam = params.get('anim');
148
- if (animParam && data[variant].animations.some((a) => a.name === animParam)) {
149
- animSelect.value = animParam;
150
- }
151
- const initialCount = Math.min(50, Math.max(1, Number(params.get('count')) || 1));
152
- countInput.value = String(initialCount);
153
- rebuild(initialCount);
154
- // Click the stats line to copy it (hand-selecting it mid-animation is fiddly).
155
- stats.title = 'click to copy';
156
- stats.addEventListener('click', async () => {
157
- try {
158
- await navigator.clipboard.writeText(stats.textContent ?? '');
159
- stats.style.color = '#ffd166';
160
- setTimeout(() => (stats.style.color = ''), 300);
161
- }
162
- catch {
163
- // Clipboard unavailable (non-secure context) — selecting stays possible.
164
- }
165
- });
166
- // --- frame loop with split timings (skeleton math vs DOM writes) ---
167
- let last = performance.now();
168
- let updateMs = 0;
169
- let renderMs = 0;
170
- let frames = 0;
171
- let statsAt = last;
172
- function frame(now) {
173
- const delta = Math.min((now - last) / 1000, 1 / 15);
174
- last = now;
175
- const t0 = performance.now();
176
- for (const inst of instances) {
177
- inst.state.update(delta);
178
- inst.state.apply(inst.skeleton);
179
- inst.skeleton.update(delta);
180
- inst.skeleton.updateWorldTransform(Physics.update);
181
- }
182
- const t1 = performance.now();
183
- for (const inst of instances)
184
- inst.renderer.render(inst.skeleton);
185
- const t2 = performance.now();
186
- updateMs += t1 - t0;
187
- renderMs += t2 - t1;
188
- frames++;
189
- if (now - statsAt >= 500 && frames > 0) {
190
- let meshes = 0;
191
- let reused = 0;
192
- let reallocs = 0;
193
- let triangles = 0;
194
- let clips = 0;
195
- let backing = 0;
196
- for (const inst of instances) {
197
- meshes += inst.renderer.meshCount;
198
- reused += inst.renderer.meshReuseCount;
199
- reallocs += inst.renderer.canvasReallocCount;
200
- triangles += inst.renderer.triangleCount;
201
- clips += inst.renderer.clipSkipCount;
202
- backing += inst.renderer.meshBackingPixels;
203
- }
204
- const reallocNote = reallocs ? ` / ${reallocs} realloc'd` : '';
205
- // Allocated mesh-canvas pixels: what an oversampling stage (a scaled
206
- // root whose scale is not folded into pixelRatio) shows first.
207
- const backingNote = backing ? ` · backing ${(backing / 1e6).toFixed(1)} Mpx` : '';
208
- const meshNote = meshes + reused
209
- ? ` · mesh canvases ${meshes} drawn (${triangles} tris) / ${reused} reused${reallocNote}${backingNote}`
210
- : '';
211
- const first = instances[0]?.renderer;
212
- // Surface the backend that actually ran, so a perf reading proves its path.
213
- const backendNote = first && first.meshBackend === 'webgl'
214
- ? first.meshBackendActive === 'webgl'
215
- ? ' · webgl'
216
- : ' · webgl unavailable → canvas2d'
217
- : '';
218
- const clipNote = clips ? ` · ${clips} clips skipped` : '';
219
- // Real fps from rAF cadence: catches bottlenecks that live outside the
220
- // frame callback (compositing, page throttling) which the ms split
221
- // cannot see.
222
- const fps = (frames * 1000) / (now - statsAt);
223
- stats.textContent =
224
- `${instances.length} skeleton(s) · ${fps.toFixed(0)} fps · ` +
225
- `skeleton ${(updateMs / frames).toFixed(2)}ms · ` +
226
- `render ${(renderMs / frames).toFixed(2)}ms / frame${meshNote}${backendNote}${clipNote}`;
227
- updateMs = renderMs = 0;
228
- frames = 0;
229
- statsAt = now;
230
- }
231
- requestAnimationFrame(frame);
232
- }
233
- requestAnimationFrame(frame);
234
- }
235
- load()
236
- .then(({ data, regionImages }) => main(data, regionImages))
237
- .catch((err) => {
238
- const stats = document.getElementById('stats');
239
- if (stats)
240
- stats.textContent = `Load failed: ${err.message} — did you run bun run fetch-assets?`;
241
- console.error(err);
242
- });
243
- //# sourceMappingURL=main.js.map
package/dist/main.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}