@rig-c/play 0.0.0-stage → 1.8.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/LICENSE +21 -0
- package/NOTICE.md +57 -0
- package/README.md +962 -2
- package/dist/CanvasRenderer.d.ts +154 -0
- package/dist/CanvasRenderer.d.ts.map +1 -0
- package/dist/DomRenderer.d.ts +349 -0
- package/dist/DomRenderer.d.ts.map +1 -0
- package/dist/DomTexture.d.ts +180 -0
- package/dist/DomTexture.d.ts.map +1 -0
- package/dist/MeshGlBlitter.d.ts +197 -0
- package/dist/MeshGlBlitter.d.ts.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8990 -0
- package/dist/index.js.map +1 -0
- package/dist/loadModel.d.ts +112 -0
- package/dist/loadModel.d.ts.map +1 -0
- package/dist/pixelRatio.d.ts +22 -0
- package/dist/pixelRatio.d.ts.map +1 -0
- package/dist/player.d.ts +453 -0
- package/dist/player.d.ts.map +1 -0
- package/dist/posedFrame.d.ts +99 -0
- package/dist/posedFrame.d.ts.map +1 -0
- package/package.json +62 -4
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { PosedFrame } from './posedFrame.js';
|
|
2
|
+
/** What actually drew the last frame of a `CanvasRenderer`. */
|
|
3
|
+
export type CanvasBackend = 'webgl' | 'canvas2d';
|
|
4
|
+
/** A rectangle of world space, Y up: `x`, `y` is its bottom-left corner. */
|
|
5
|
+
export interface CanvasViewport {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Renders a posed frame (`PosedFrame`) into **one `<canvas>`** — the canvas
|
|
13
|
+
* render mode, beside the DOM tier (`DomRenderer`) and with the same
|
|
14
|
+
* `render(frame)` interface.
|
|
15
|
+
*
|
|
16
|
+
* Every slot is drawn as textured triangles straight from its atlas page — a
|
|
17
|
+
* region as two triangles over its four corners and its page UVs, a mesh as its
|
|
18
|
+
* own triangles — through the **shared module-level WebGL context** of the mesh
|
|
19
|
+
* blitter (`MeshGlBlitter.ts`), into one rect of its offscreen buffer, and the
|
|
20
|
+
* rect is copied onto this renderer's visible canvas with one `drawImage`. No
|
|
21
|
+
* context is created per renderer, so any number of players draw under the
|
|
22
|
+
* browser's ~16-context cap, and the page textures are shared and
|
|
23
|
+
* reference-counted with every other renderer on the page.
|
|
24
|
+
*
|
|
25
|
+
* - **Tint** (slot × attachment × the player's colour) is a vertex colour,
|
|
26
|
+
* premultiplied by its alpha; the **dark** (two-colour) tint is a second
|
|
27
|
+
* vertex colour, carried only when some slot of the frame has one.
|
|
28
|
+
* - **Blend** is batch state: a run of triangles ends wherever the page or the
|
|
29
|
+
* blend mode changes (`drawCalls`, and `blendFlushes` for the blend changes).
|
|
30
|
+
* The blends are composed inside this canvas's own rect, which starts
|
|
31
|
+
* transparent — an additive or multiply slot meets the slots under it, not
|
|
32
|
+
* the page behind the canvas.
|
|
33
|
+
* - **Clipping** is the core's cut: every triangle a clip covers goes through
|
|
34
|
+
* `clipThrough` from `rig-c/core` (convex polygons by the runtime's measured
|
|
35
|
+
* rule, concave ones into convex pieces, inverse clips as the hull's
|
|
36
|
+
* outside). No stencil, no `clip-path`.
|
|
37
|
+
* - **Projection**: the canvas covers a rect of the root's own coordinates
|
|
38
|
+
* (world x, world −y — the DOM tier's frame), so a world point lands at
|
|
39
|
+
* `((x − left) · pixelRatio, (−y − top) · pixelRatio)` device px. That rect
|
|
40
|
+
* is the caller's `viewport` when set, and otherwise grows to cover what the
|
|
41
|
+
* frames draw (grow-only, so steady animation reallocates nothing).
|
|
42
|
+
* - **Dirty-skip**: a frame whose record equals the last one drawn — vertex
|
|
43
|
+
* arrays, tints, order, clips, pages — draws nothing (`framesReused`).
|
|
44
|
+
*
|
|
45
|
+
* When WebGL is unavailable or the shared context is lost, the frame is drawn
|
|
46
|
+
* on the same visible canvas with canvas2d: regions by `drawImage` under their
|
|
47
|
+
* affine, meshes and clipped pieces by the DOM tier's per-triangle mapping.
|
|
48
|
+
* That fallback multiplies **alpha only** — RGB and dark tints are not drawn
|
|
49
|
+
* there (`ctx.filter` is not available on Safari) — and it is a safety net,
|
|
50
|
+
* not the measured path.
|
|
51
|
+
*/
|
|
52
|
+
export declare class CanvasRenderer {
|
|
53
|
+
private readonly root;
|
|
54
|
+
private readonly pages;
|
|
55
|
+
/**
|
|
56
|
+
* Backing-store pixels per root CSS pixel; defaults to the device pixel
|
|
57
|
+
* ratio. A root under a CSS-scaled ancestor rasters at the wrong resolution
|
|
58
|
+
* until the scale is folded in — `syncPixelRatio()` measures it, as for the
|
|
59
|
+
* DOM tier. The next `render` sizes the backing store to the new ratio.
|
|
60
|
+
*/
|
|
61
|
+
pixelRatio: number;
|
|
62
|
+
/** Apply the clips the frame carries (default). False draws every slot unclipped and counts the clips in `clipSkipCount`. */
|
|
63
|
+
clipping: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* The world rect the canvas covers. Null (default) sizes it from the frames
|
|
66
|
+
* themselves: it grows to cover everything drawn and never shrinks. Set it to
|
|
67
|
+
* pin the canvas to a stage box; what falls outside is not drawn.
|
|
68
|
+
*/
|
|
69
|
+
viewport: CanvasViewport | null;
|
|
70
|
+
/** The backend to draw with: 'webgl' (default) through the shared context, or 'canvas2d' to force the fallback. */
|
|
71
|
+
backend: CanvasBackend;
|
|
72
|
+
/** What drew the last frame (or would have, had it not been reused). */
|
|
73
|
+
backendActive: CanvasBackend;
|
|
74
|
+
/** The visible canvas — the renderer's one element in the root. */
|
|
75
|
+
readonly canvas: HTMLCanvasElement;
|
|
76
|
+
/** Draw calls issued last frame — one per run of one page under one blend. */
|
|
77
|
+
drawCalls: number;
|
|
78
|
+
/** Of those runs, how many began because the blend mode changed. */
|
|
79
|
+
blendFlushes: number;
|
|
80
|
+
/** Triangles drawn last frame, clipped pieces counted as cut. */
|
|
81
|
+
trianglesDrawn: number;
|
|
82
|
+
/** 1 when the last render() found the record unchanged and drew nothing, else 0. */
|
|
83
|
+
framesReused: number;
|
|
84
|
+
/** Blits onto the visible canvas last frame (0 or 1). */
|
|
85
|
+
blits: number;
|
|
86
|
+
/** Visible-canvas backing reallocations last frame; settles at zero. */
|
|
87
|
+
canvasReallocCount: number;
|
|
88
|
+
/** Distinct clips applied last frame. */
|
|
89
|
+
clipCount: number;
|
|
90
|
+
/** Clips in the frame not applied last frame: `clipping` off, fewer than three points, or a polygon the core refuses to cut. */
|
|
91
|
+
clipSkipCount: number;
|
|
92
|
+
private readonly ctx;
|
|
93
|
+
/** The rect in root units the canvas covers: left, top, right, bottom (Y down). Empty until the first frame. */
|
|
94
|
+
private left;
|
|
95
|
+
private top;
|
|
96
|
+
private right;
|
|
97
|
+
private bottom;
|
|
98
|
+
private hasRect;
|
|
99
|
+
private backingW;
|
|
100
|
+
private backingH;
|
|
101
|
+
private backingRatio;
|
|
102
|
+
private data;
|
|
103
|
+
private readonly runs;
|
|
104
|
+
/** Pages retained from the shared blitter, one retain each, released by dispose(). */
|
|
105
|
+
private readonly glPages;
|
|
106
|
+
/** The dirty signature of the last frame drawn: its numbers, and the objects it compares by identity. */
|
|
107
|
+
private sig;
|
|
108
|
+
private sigLength;
|
|
109
|
+
private sigRefs;
|
|
110
|
+
private nextSig;
|
|
111
|
+
private nextRefs;
|
|
112
|
+
/**
|
|
113
|
+
* @param root Positioned element that becomes the skeleton origin, exactly as
|
|
114
|
+
* for `DomRenderer`: the canvas is appended to it and placed in its own
|
|
115
|
+
* coordinates; layout and scaling of the root belong to the caller.
|
|
116
|
+
* @param pages Page images keyed by page name — `PosedRegion.page` and
|
|
117
|
+
* `PosedMesh.page`. Read on every draw.
|
|
118
|
+
*/
|
|
119
|
+
constructor(root: HTMLElement, pages: ReadonlyMap<string, HTMLImageElement>);
|
|
120
|
+
/** Grows the automatic viewport to cover `box` (world units, Y up) ahead of the frames reaching it. */
|
|
121
|
+
/**
|
|
122
|
+
* Measures the root's effective on-screen scale and sets `pixelRatio` to
|
|
123
|
+
* `devicePixelRatio × scale`, returning the ratio now in effect — the same
|
|
124
|
+
* call, the same meaning and the same deadband as `DomRenderer`'s, so code
|
|
125
|
+
* written against `player.renderer` survives a `mode` change. One forced
|
|
126
|
+
* layout per call (a hidden box appended to the root, read, removed), which
|
|
127
|
+
* is why the renderer never calls it for you. The backing store follows on
|
|
128
|
+
* the next `render`; a root that is not laid out leaves the ratio alone.
|
|
129
|
+
*/
|
|
130
|
+
syncPixelRatio(): number;
|
|
131
|
+
reserve(box: CanvasViewport): void;
|
|
132
|
+
/** Draws one frame. */
|
|
133
|
+
render(frame: PosedFrame): void;
|
|
134
|
+
/** Removes the canvas from the root and hands the pages it uploaded back to the shared blitter. Idempotent. */
|
|
135
|
+
dispose(): void;
|
|
136
|
+
private growTo;
|
|
137
|
+
private setRect;
|
|
138
|
+
/** The backing store at `rect × pixelRatio`; reallocated only when that changes. */
|
|
139
|
+
private sizeBacking;
|
|
140
|
+
/** Writes this frame's signature and compares it with the last one drawn; keeps it when it differs. */
|
|
141
|
+
private signatureChanged;
|
|
142
|
+
/** The clip counters for a reused frame — what drawing it would have counted. */
|
|
143
|
+
private countClips;
|
|
144
|
+
private stride;
|
|
145
|
+
/**
|
|
146
|
+
* The slot's triangles in world space with their page UVs — cut by the clip
|
|
147
|
+
* over it when one applies — or null when the slot draws nothing.
|
|
148
|
+
*/
|
|
149
|
+
private piecesOf;
|
|
150
|
+
/** Fills `data` and `runs` for the GL path. */
|
|
151
|
+
private buildVertices;
|
|
152
|
+
private draw2d;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=CanvasRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CanvasRenderer.d.ts","sourceRoot":"","sources":["../src/CanvasRenderer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAwB,UAAU,EAAa,MAAM,iBAAiB,CAAC;AAEnF,+DAA+D;AAC/D,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,UAAU,CAAC;AAEjD,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,qBAAa,cAAc;IAqEvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK;IArExB;;;;;OAKG;IACH,UAAU,SAA+D;IACzE,6HAA6H;IAC7H,QAAQ,UAAQ;IAChB;;;;OAIG;IACH,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAQ;IACvC,mHAAmH;IACnH,OAAO,EAAE,aAAa,CAAW;IACjC,wEAAwE;IACxE,aAAa,EAAE,aAAa,CAAW;IACvC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAEnC,8EAA8E;IAC9E,SAAS,SAAK;IACd,oEAAoE;IACpE,YAAY,SAAK;IACjB,iEAAiE;IACjE,cAAc,SAAK;IACnB,oFAAoF;IACpF,YAAY,SAAK;IACjB,yDAAyD;IACzD,KAAK,SAAK;IACV,wEAAwE;IACxE,kBAAkB,SAAK;IACvB,yCAAyC;IACzC,SAAS,SAAK;IACd,gIAAgI;IAChI,aAAa,SAAK;IAElB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkC;IACtD,gHAAgH;IAChH,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAkB;IACvC,sFAAsF;IACtF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,yGAAyG;IACzG,OAAO,CAAC,GAAG,CAAyB;IACpC,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,QAAQ,CAAiB;IAEjC;;;;;;OAMG;gBAEgB,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAgB/D,uGAAuG;IACvG;;;;;;;;OAQG;IACH,cAAc,IAAI,MAAM;IAKxB,OAAO,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI;IAIlC,uBAAuB;IACvB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAuD/B,+GAA+G;IAC/G,OAAO,IAAI,IAAI;IAYf,OAAO,CAAC,MAAM;IAkBd,OAAO,CAAC,OAAO;IAUf,oFAAoF;IACpF,OAAO,CAAC,WAAW;IAkBnB,uGAAuG;IACvG,OAAO,CAAC,gBAAgB;IAoExB,iFAAiF;IACjF,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,MAAM,CAAa;IAE3B;;;OAGG;IACH,OAAO,CAAC,QAAQ;IA4BhB,+CAA+C;IAC/C,OAAO,CAAC,aAAa;IAmErB,OAAO,CAAC,MAAM;CAmEf"}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { type RegionImage } from './DomTexture.js';
|
|
2
|
+
import type { PosedFrame } from './posedFrame.js';
|
|
3
|
+
/** Rasterizer used for the mesh (deform) tier. */
|
|
4
|
+
export type MeshBackend = 'canvas2d' | 'webgl';
|
|
5
|
+
/**
|
|
6
|
+
* Renders a posed frame (`PosedFrame`) as DOM, split by slot kind:
|
|
7
|
+
*
|
|
8
|
+
* - Region slots (rigid parts) become one absolutely-positioned <img>
|
|
9
|
+
* posed with a single CSS matrix() write per frame — exact, since bone
|
|
10
|
+
* transforms are affine.
|
|
11
|
+
* - Mesh slots (deform parts) each get a small per-part <canvas> sized
|
|
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.
|
|
17
|
+
*
|
|
18
|
+
* Both element kinds share one stacking context, so draw order interleaves
|
|
19
|
+
* freely via z-index (rear hair canvas < torso img < front hair canvas).
|
|
20
|
+
* The RGB tint the frame carries (slot × attachment × the player's colour) is
|
|
21
|
+
* applied per element with an SVG feColorMatrix reference filter — exact
|
|
22
|
+
* channel multiply, works the same on <img> and <canvas>. Dark (two-color)
|
|
23
|
+
* tint is not expressible that way and is unsupported. A clip the frame
|
|
24
|
+
* carries becomes a CSS clip-path per element, in that element's own local
|
|
25
|
+
* frame (see `clipping`) — which slots a clip covers is decided by the poser's
|
|
26
|
+
* adapter (`frameOf`), not here.
|
|
27
|
+
*
|
|
28
|
+
* Coordinate mapping: the frame is world space, Y-up; CSS is Y-down — world Y
|
|
29
|
+
* is negated. The poser computes everything (bones, constraints, physics,
|
|
30
|
+
* deformed vertices) on the CPU; this class only draws.
|
|
31
|
+
*/
|
|
32
|
+
export declare class DomRenderer {
|
|
33
|
+
private readonly root;
|
|
34
|
+
private readonly regionImages;
|
|
35
|
+
private readonly pages;
|
|
36
|
+
/**
|
|
37
|
+
* Apply the clips the frame carries (default). Each element drawn inside a
|
|
38
|
+
* clip gets a CSS `clip-path` in its own local frame — an element-level
|
|
39
|
+
* feature, so both mesh backends see exactly the same thing and neither
|
|
40
|
+
* raster path knows clipping exists.
|
|
41
|
+
*
|
|
42
|
+
* Which slots a clip covers is the frame's, decided by the adapter on the
|
|
43
|
+
* runtime's semantics (`clipCover` in posedFrame.ts: one clip in force at a
|
|
44
|
+
* time, a second one met while one is in force ignored, a clip covering the
|
|
45
|
+
* slots after its own through its end slot inclusive, an end slot that is
|
|
46
|
+
* the clip's own slot — or one already passed — never ending it). A clip
|
|
47
|
+
* that covers every slot of the frame is the whole-skeleton clip, and it
|
|
48
|
+
* takes a fast path: one `clip-path` on the root instead of one per element.
|
|
49
|
+
* The root is the caller's element, so its inline `clip-path` is borrowed —
|
|
50
|
+
* saved on the first write and restored when the clip stops covering the
|
|
51
|
+
* frame, when `clipping` goes false, and by `dispose()`.
|
|
52
|
+
*
|
|
53
|
+
* False restores the pre-0.6 behaviour: clips are counted in clipSkipCount
|
|
54
|
+
* and nothing is clipped, with any clip-path this renderer wrote removed.
|
|
55
|
+
*/
|
|
56
|
+
clipping: boolean;
|
|
57
|
+
/** Clips applied last frame — distinct clip polygons over the drawn slots. */
|
|
58
|
+
clipCount: number;
|
|
59
|
+
/**
|
|
60
|
+
* Clips in the frame NOT applied last frame: `clipping` is false, or the
|
|
61
|
+
* polygon has fewer than three points. A clip the runtime itself ignores (a
|
|
62
|
+
* second one met while one is in force, one on an inactive bone) never
|
|
63
|
+
* reaches the frame, so it is not counted here.
|
|
64
|
+
*/
|
|
65
|
+
clipSkipCount: number;
|
|
66
|
+
/**
|
|
67
|
+
* `clip-path` style writes performed last frame, across slot elements and
|
|
68
|
+
* the root, clears included. A static polygon over static elements settles
|
|
69
|
+
* at zero: every clip-path is cached per element exactly as `transform` is.
|
|
70
|
+
*/
|
|
71
|
+
clipWriteCount: number;
|
|
72
|
+
/** Mesh canvases rasterized last frame. */
|
|
73
|
+
meshCount: number;
|
|
74
|
+
/** Mesh canvases that reused their previous raster last frame. */
|
|
75
|
+
meshReuseCount: number;
|
|
76
|
+
/**
|
|
77
|
+
* Mesh canvas backing stores (re)allocated last frame. Should drop to zero
|
|
78
|
+
* once an animation reaches its steady state; a persistent nonzero value
|
|
79
|
+
* means GPU surfaces are being recreated per frame (a Safari killer).
|
|
80
|
+
*/
|
|
81
|
+
canvasReallocCount: number;
|
|
82
|
+
/** Triangles rasterized last frame. */
|
|
83
|
+
triangleCount: number;
|
|
84
|
+
/**
|
|
85
|
+
* How far (px) each triangle's clip polygon is expanded from its centroid.
|
|
86
|
+
* Closes antialiased-clip cracks between adjacent triangles (Safari);
|
|
87
|
+
* set to 0 to see them.
|
|
88
|
+
*/
|
|
89
|
+
triangleExpand: number;
|
|
90
|
+
/**
|
|
91
|
+
* Mesh-canvas backing-store pixels per CSS pixel. Defaults to the device
|
|
92
|
+
* pixel ratio. If the caller scales the root element, fold that scale in
|
|
93
|
+
* (e.g. devicePixelRatio * rootScale) so the raster matches the on-screen
|
|
94
|
+
* resolution instead of over- or under-sampling — syncPixelRatio() measures
|
|
95
|
+
* that scale and folds it in for you.
|
|
96
|
+
*/
|
|
97
|
+
pixelRatio: number;
|
|
98
|
+
/**
|
|
99
|
+
* Rasterizer for the mesh (deform) tier. 'canvas2d' (default) maps each
|
|
100
|
+
* triangle with clip+transform+drawImage directly on the per-part canvas.
|
|
101
|
+
* 'webgl' rasterizes every dirty mesh into one shared offscreen WebGL
|
|
102
|
+
* canvas and rect-blits each mesh back onto its per-part canvas — the DOM
|
|
103
|
+
* structure and all element-level behavior (z-index interleave, tint
|
|
104
|
+
* filter, mix-blend-mode, dirty-skip) are identical, only the raster step
|
|
105
|
+
* changes. Motivation: Safari antialiases canvas2d clip paths, so the
|
|
106
|
+
* per-triangle clip mapping pays a per-triangle AA-mask cost in the GPU
|
|
107
|
+
* process that rAF-limits heavy scenes; GL rasterizes shared edges
|
|
108
|
+
* seamlessly (no clip, no crack overdraw) and the blit is an unclipped
|
|
109
|
+
* rect copy. Falls back to 'canvas2d' when WebGL is unavailable or the
|
|
110
|
+
* shared context is lost — see meshBackendActive.
|
|
111
|
+
*/
|
|
112
|
+
meshBackend: MeshBackend;
|
|
113
|
+
/** Backend that actually rasterized the mesh tier during the last render(). */
|
|
114
|
+
meshBackendActive: MeshBackend;
|
|
115
|
+
private readonly views;
|
|
116
|
+
/** Counts render() calls: the stamp a view drawn this frame carries (`SlotView.drawnIn`). */
|
|
117
|
+
private frameNumber;
|
|
118
|
+
private readonly pendingJobs;
|
|
119
|
+
private readonly pendingViews;
|
|
120
|
+
/**
|
|
121
|
+
* Atlas page images this renderer has retained from the shared blitter,
|
|
122
|
+
* one retain each. Allocated on the first webgl mesh job, so a renderer
|
|
123
|
+
* that never leaves canvas2d holds nothing and gives nothing back.
|
|
124
|
+
*/
|
|
125
|
+
private glPages;
|
|
126
|
+
/**
|
|
127
|
+
* World vertices copied out of the frame. Float32, as the rigid and mesh
|
|
128
|
+
* paths have always read them: the raster, the dirty signature and the
|
|
129
|
+
* clip-path quantization below are calibrated on single-precision
|
|
130
|
+
* coordinates, and the frame's doubles are rounded here once rather than
|
|
131
|
+
* changing what those paths see.
|
|
132
|
+
*/
|
|
133
|
+
private scratchVertices;
|
|
134
|
+
private tintDefs;
|
|
135
|
+
/** The clip whose polygon is loaded in `clipPolygon`, if any. */
|
|
136
|
+
private clipActive;
|
|
137
|
+
/** The clips counted so far this frame, applied or skipped — one count each. */
|
|
138
|
+
private readonly clipsSeen;
|
|
139
|
+
private clipInverse;
|
|
140
|
+
/** True while the active clip is served by one clip-path on the root. */
|
|
141
|
+
private clipOnRoot;
|
|
142
|
+
/** The active clip polygon in CSS space (Y already negated), flat x,y. */
|
|
143
|
+
private clipPolygon;
|
|
144
|
+
private clipPolygonLength;
|
|
145
|
+
/**
|
|
146
|
+
* The root's inline `clip-path` before this renderer borrowed it; null means
|
|
147
|
+
* not borrowed. The root belongs to the caller, so the fast path gives back
|
|
148
|
+
* exactly the string it found.
|
|
149
|
+
*/
|
|
150
|
+
private rootClipSaved;
|
|
151
|
+
/** Last clip-path string written to the root, to skip no-op style writes. */
|
|
152
|
+
private rootClipWritten;
|
|
153
|
+
/**
|
|
154
|
+
* @param root Positioned element (e.g. position:absolute) that becomes the
|
|
155
|
+
* skeleton origin. The renderer only appends slot elements to it — layout
|
|
156
|
+
* and scaling of the root belong to the caller.
|
|
157
|
+
* @param regionImages Unpacked per-region bitmaps (for the rigid tier),
|
|
158
|
+
* keyed by region name — `PosedRegion.region`.
|
|
159
|
+
* @param pages Page images (for the mesh tier), keyed by page name —
|
|
160
|
+
* `PosedMesh.page`. Read on every mesh draw, so a caller may fill it after
|
|
161
|
+
* construction.
|
|
162
|
+
*/
|
|
163
|
+
constructor(root: HTMLElement, regionImages: ReadonlyMap<string, RegionImage>, pages?: ReadonlyMap<string, HTMLImageElement>);
|
|
164
|
+
/**
|
|
165
|
+
* Backing-store pixels currently allocated across the mesh canvases
|
|
166
|
+
* (Σ width × height). Computed on demand from the views — nothing is
|
|
167
|
+
* tracked per frame — so reading it is cheap but not free; the demo reads it
|
|
168
|
+
* with the other counters, twice a second.
|
|
169
|
+
*
|
|
170
|
+
* This is the quantity `pixelRatio` moves quadratically, and the one that
|
|
171
|
+
* makes an oversampling stage visible before the frame rate does: a root
|
|
172
|
+
* scaled down by CSS without that scale folded into `pixelRatio` holds
|
|
173
|
+
* 1/scale² of the backing it needs. Hidden slots are included — their
|
|
174
|
+
* canvases keep their allocation.
|
|
175
|
+
*/
|
|
176
|
+
get meshBackingPixels(): number;
|
|
177
|
+
/**
|
|
178
|
+
* Re-derives `pixelRatio` from the root's effective on-screen scale, so the
|
|
179
|
+
* mesh tier rasters at the resolution the screen actually shows.
|
|
180
|
+
*
|
|
181
|
+
* Mesh canvases are sized in the root's own coordinates, and a CSS transform
|
|
182
|
+
* anywhere above the root — the natural way to build a pan/zoom stage is
|
|
183
|
+
* `transform: scale(zoom)` on an ancestor — rescales them on screen without
|
|
184
|
+
* the renderer seeing it. At zoom z the raster is then oversampled by 1/z per
|
|
185
|
+
* axis, 1/z² in backing pixels: GPU memory and fill rate, not correctness.
|
|
186
|
+
* This folds the measured scale in, `devicePixelRatio × scale`.
|
|
187
|
+
*
|
|
188
|
+
* One layout read per call. The root itself is usually 0×0 (slot elements are
|
|
189
|
+
* absolutely positioned and posed by transforms), so there is no box to
|
|
190
|
+
* measure: a hidden 100×100 px child is appended, measured once with
|
|
191
|
+
* getBoundingClientRect(), and removed again — nothing of it is left behind.
|
|
192
|
+
* It cannot disturb the slot elements either: they interleave by z-index,
|
|
193
|
+
* which applyCommon() writes explicitly on every visible one, so DOM order is
|
|
194
|
+
* not what orders them; and the probe is `visibility: hidden` and gone before
|
|
195
|
+
* anything can paint.
|
|
196
|
+
*
|
|
197
|
+
* The scale is the larger axis of the measured box. An ancestor rotation
|
|
198
|
+
* inflates that axis-aligned box, so the ratio errs high — the safe side:
|
|
199
|
+
* oversampling costs pixels, undersampling costs picture.
|
|
200
|
+
*
|
|
201
|
+
* A move smaller than 0.1% (see PIXEL_RATIO_DEADBAND) changes nothing, and a
|
|
202
|
+
* root that is not laid out (a `display: none` ancestor — the probe measures
|
|
203
|
+
* 0) changes nothing either. Both matter because writing `pixelRatio`
|
|
204
|
+
* reallocates every mesh canvas on the next frame.
|
|
205
|
+
*
|
|
206
|
+
* The renderer never calls this itself. It is a forced synchronous layout,
|
|
207
|
+
* which has no business in a render path — and there is no per-frame layout
|
|
208
|
+
* read anywhere in this class. Call it when a zoom or a layout settles: after
|
|
209
|
+
* a wheel/pinch gesture ends, on a debounced resize, not during the drag.
|
|
210
|
+
*
|
|
211
|
+
* @returns the pixel ratio now in effect (the current one if nothing moved).
|
|
212
|
+
*/
|
|
213
|
+
syncPixelRatio(): number;
|
|
214
|
+
/**
|
|
215
|
+
* Draws one frame. Slots the frame does not carry — no attachment, a
|
|
216
|
+
* clipping attachment, an inactive bone, a kind the DOM tier does not draw —
|
|
217
|
+
* are hidden, their elements kept for when they draw again.
|
|
218
|
+
*/
|
|
219
|
+
render(frame: PosedFrame): void;
|
|
220
|
+
/**
|
|
221
|
+
* Removes every element this renderer added to the root (slot elements and
|
|
222
|
+
* the tint filter defs), restores the root's own inline `clip-path` if the
|
|
223
|
+
* whole-skeleton clip path borrowed it, and hands the atlas pages it
|
|
224
|
+
* uploaded back to the shared GL blitter — the one resource here that outlives the instance, so
|
|
225
|
+
* the one that has to be given back explicitly (GPU memory, and no garbage
|
|
226
|
+
* collector feels pressure from it). The pages are reference-counted there:
|
|
227
|
+
* a page another live renderer still draws stays uploaded, and the texture
|
|
228
|
+
* is deleted only when the last user of it is disposed.
|
|
229
|
+
*
|
|
230
|
+
* The region bitmaps are deliberately untouched: the map is the caller's,
|
|
231
|
+
* and one map is normally shared by many renderers (disposing one instance
|
|
232
|
+
* must not blind the others). Free the unpacked blob URLs with
|
|
233
|
+
* revokeRegions() once no renderer needs them.
|
|
234
|
+
*
|
|
235
|
+
* Idempotent: the retained-page ledger is cleared here, so a second call
|
|
236
|
+
* releases nothing a second time.
|
|
237
|
+
*/
|
|
238
|
+
dispose(): void;
|
|
239
|
+
private renderRegion;
|
|
240
|
+
private renderMesh;
|
|
241
|
+
/**
|
|
242
|
+
* Declares this renderer a user of `page` with the shared blitter, once per
|
|
243
|
+
* page — the counterpart of the release in dispose(). The set is this
|
|
244
|
+
* renderer's own ledger, so sharing one page across renderers works by
|
|
245
|
+
* counting rather than by guessing. On the render path this costs one
|
|
246
|
+
* Set.has per queued mesh job and allocates nothing after the first page.
|
|
247
|
+
*/
|
|
248
|
+
private retainPage;
|
|
249
|
+
/** The canvas2d raster path: clear the backing, map each triangle. */
|
|
250
|
+
private rasterizeMesh2d;
|
|
251
|
+
/**
|
|
252
|
+
* Standard canvas triangle texture mapping (same math as the official
|
|
253
|
+
* spine-canvas renderer): derive the affine that sends the triangle's
|
|
254
|
+
* texture-space corners to its screen-space corners, clip, draw the page —
|
|
255
|
+
* but only the part of the page this triangle can show (see below).
|
|
256
|
+
*
|
|
257
|
+
* The clip polygon is expanded outward from the centroid by a fraction of
|
|
258
|
+
* a pixel. Browsers that antialias clip paths (Safari) otherwise leave
|
|
259
|
+
* hairline cracks between adjacent triangles; the expanded clips overlap
|
|
260
|
+
* into the neighbouring triangle, and since the texture is continuous
|
|
261
|
+
* across the shared edge the overlap draws the same pixels — cracks close
|
|
262
|
+
* with no visible cost. The texture-mapping affine itself stays exact.
|
|
263
|
+
*
|
|
264
|
+
* ## Why a source sub-rect instead of the whole page
|
|
265
|
+
*
|
|
266
|
+
* The draw is a 9-argument `drawImage` whose source rect covers exactly what
|
|
267
|
+
* the clip can reveal, placed at the same texture-space position, so the
|
|
268
|
+
* mapping is identical to drawing the whole page — the same affine, the same
|
|
269
|
+
* texels, only fewer of them offered to the rasterizer.
|
|
270
|
+
*
|
|
271
|
+
* That is not an optimization. Linux WebKit — the software rasterizer, the
|
|
272
|
+
* only engine this has been observed on; Chromium and macOS WebKit never show
|
|
273
|
+
* it — garbles individual triangles when the whole page is drawn under a
|
|
274
|
+
* steep per-triangle affine: read off the parity dump, head/goggles/foot
|
|
275
|
+
* triangles land with displaced texture while the webgl capture of the same
|
|
276
|
+
* pose stays clean. Switching this one call to a source sub-rect — the only
|
|
277
|
+
* variable changed — took that platform's bad-pixel counts from 1042 to 2
|
|
278
|
+
* (hoverboard), 231 to 4 (portal) and 803 to 3 (walk + tint), and left every
|
|
279
|
+
* Chromium capture byte-identical. [measured on the ubuntu CI runner through
|
|
280
|
+
* PARITY_DUMP: first with a fixed 2-texel pad (run 35209262693), then with
|
|
281
|
+
* the derived rect below — the same counts (run 35213286993).] WHY that
|
|
282
|
+
* rasterizer garbles the whole-page form is not identified; steep affines
|
|
283
|
+
* making whole-page coordinates large is a suspicion, not a measurement. The
|
|
284
|
+
* sub-rect sidesteps it. (Moving `clip()` before `transform()` was tried as a
|
|
285
|
+
* separate one-variable experiment: every number came back identical, so the
|
|
286
|
+
* clip path is not the cause.)
|
|
287
|
+
*
|
|
288
|
+
* ## How the sub-rect is derived
|
|
289
|
+
*
|
|
290
|
+
* It must provably cover everything the clip can reveal, which is the
|
|
291
|
+
* *expanded* polygon, not the triangle. So each clip corner's canvas-space
|
|
292
|
+
* displacement is pushed back through the inverse of the 2×2 affine into
|
|
293
|
+
* texture space, the bounding box is taken over the displaced corners, one
|
|
294
|
+
* texel of bilinear support is added, and the result is floor/ceil'd to texel
|
|
295
|
+
* boundaries (so the sub-rect itself resamples nothing) and clamped to the
|
|
296
|
+
* page. A fixed pad would not do: the texture-space reach of the expansion is
|
|
297
|
+
* `triangleExpand` times the local texels-per-canvas-unit, which on the
|
|
298
|
+
* demo's own meshes ranges from ~0.26 texels on a typical triangle to ~8.6 on
|
|
299
|
+
* a foreshortened one. Under-padding lets the rim of the overdraw sample
|
|
300
|
+
* nothing, which brings back the very seams the overdraw exists to close.
|
|
301
|
+
*
|
|
302
|
+
* No half-texel offset is applied to the incoming u/v: the `(i + 0.5) / size`
|
|
303
|
+
* texel-centre convention belongs to *sampling* (landing inside the intended
|
|
304
|
+
* texel instead of on a filter boundary). This solves for the affine that
|
|
305
|
+
* sends three source corners to three destination corners; the rasterizer
|
|
306
|
+
* then samples continuously along that map. Biasing all three corners by
|
|
307
|
+
* half a texel would translate the texture against the geometry — the very
|
|
308
|
+
* class of offset the `uv * size` addressing removes.
|
|
309
|
+
*/
|
|
310
|
+
private drawTriangle;
|
|
311
|
+
/**
|
|
312
|
+
* Makes `clip` the clip in force for the slot about to be drawn, loading its
|
|
313
|
+
* polygon when it is not the one already loaded. Each clip object is counted
|
|
314
|
+
* once per frame — in clipCount when applied, in clipSkipCount when
|
|
315
|
+
* `clipping` is off or the polygon has fewer than three points (no clip is
|
|
316
|
+
* in force then, so the slot draws unclipped, as the runtime would).
|
|
317
|
+
*
|
|
318
|
+
* The polygon is the poser's world-space one, and the only things done to it
|
|
319
|
+
* here are the single-precision read the vertex paths share and the Y
|
|
320
|
+
* negation the rest of the renderer already applies. It is NOT convexified
|
|
321
|
+
* or triangulated the way the CPU clipper does — CSS `polygon()` takes a
|
|
322
|
+
* concave polygon directly, so the decomposition that exists to feed a
|
|
323
|
+
* triangle rasterizer has no job here.
|
|
324
|
+
*/
|
|
325
|
+
private useClip;
|
|
326
|
+
/**
|
|
327
|
+
* Writes (or clears) this element's clip-path. `a…f` is the element's own
|
|
328
|
+
* CSS transform, and the world polygon is carried through its INVERSE:
|
|
329
|
+
* clip-path is resolved in the element's local frame and then transformed
|
|
330
|
+
* along with the element, so the polygon has to arrive there already.
|
|
331
|
+
*
|
|
332
|
+
* `transform-origin` is `0 0` on every slot element (see view()), so the
|
|
333
|
+
* matrix maps local (0,0) to (e,f) with no origin term to undo, and
|
|
334
|
+
* `left/top: 0` puts the border box — clip-path's reference box — on that
|
|
335
|
+
* same local origin.
|
|
336
|
+
*/
|
|
337
|
+
private applyClip;
|
|
338
|
+
private setClipPath;
|
|
339
|
+
/** The active polygon in the root's own frame — no element transform. */
|
|
340
|
+
private rootClipPath;
|
|
341
|
+
private writeRootClip;
|
|
342
|
+
private releaseRootClip;
|
|
343
|
+
private setTransform;
|
|
344
|
+
private applyCommon;
|
|
345
|
+
private tintMatrixFor;
|
|
346
|
+
private view;
|
|
347
|
+
private hide;
|
|
348
|
+
}
|
|
349
|
+
//# sourceMappingURL=DomRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DomRenderer.d.ts","sourceRoot":"","sources":["../src/DomRenderer.ts"],"names":[],"mappings":"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"}
|