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 +40 -5
- package/dist/DomTexture.d.ts +53 -5
- package/dist/DomTexture.d.ts.map +1 -1
- package/dist/DomTexture.js +257 -61
- package/dist/DomTexture.js.map +1 -1
- package/package.json +1 -1
- package/dist/main.d.ts +0 -2
- package/dist/main.d.ts.map +0 -1
- package/dist/main.js +0 -243
- package/dist/main.js.map +0 -1
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:`
|
|
301
|
-
|
|
302
|
-
|
|
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 size — that 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
|
|
305
|
-
|
|
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
|
package/dist/DomTexture.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
|
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
|
|
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.
|
|
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
|
/**
|
package/dist/DomTexture.d.ts.map
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/DomTexture.js
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
95
|
-
|
|
275
|
+
finally {
|
|
276
|
+
inFlightPixels -= pixels;
|
|
277
|
+
job.done = true;
|
|
96
278
|
}
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
}
|
package/dist/DomTexture.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DomTexture.js","sourceRoot":"","sources":["../src/DomTexture.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,GAKR,MAAM,8BAA8B,CAAC;AAEtC;;;;GAIG;AACH,MAAM,OAAO,UAAW,SAAQ,OAAO;IACrC,UAAU,CAAC,UAAyB,EAAE,UAAyB,IAAS,CAAC;IACzE,QAAQ,CAAC,MAAmB,EAAE,MAAmB,IAAS,CAAC;IAC3D,OAAO,KAAU,CAAC;CACnB;
|
|
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.
|
|
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
package/dist/main.d.ts.map
DELETED
|
@@ -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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"}
|