@oh-just-another/renderer-canvas 0.59.0 → 0.61.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/CHANGELOG.md +108 -0
- package/README.md +10 -7
- package/dist/.tsbuildinfo +1 -1
- package/dist/canvas-target.d.ts +6 -1
- package/dist/canvas-target.d.ts.map +1 -1
- package/dist/canvas-target.js +12 -2
- package/dist/canvas-target.js.map +1 -1
- package/dist/constants.d.ts +49 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +49 -0
- package/dist/constants.js.map +1 -1
- package/dist/hi-dpi.d.ts +10 -0
- package/dist/hi-dpi.d.ts.map +1 -1
- package/dist/hi-dpi.js +12 -1
- package/dist/hi-dpi.js.map +1 -1
- package/dist/image-source.d.ts +11 -0
- package/dist/image-source.d.ts.map +1 -1
- package/dist/image-source.js +20 -0
- package/dist/image-source.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/layered-canvas.d.ts +7 -1
- package/dist/layered-canvas.d.ts.map +1 -1
- package/dist/layered-canvas.js +6 -3
- package/dist/layered-canvas.js.map +1 -1
- package/dist/layered-surface.d.ts +16 -0
- package/dist/layered-surface.d.ts.map +1 -1
- package/dist/layered-surface.js +59 -14
- package/dist/layered-surface.js.map +1 -1
- package/dist/recording-target.d.ts +56 -13
- package/dist/recording-target.d.ts.map +1 -1
- package/dist/recording-target.js +229 -38
- package/dist/recording-target.js.map +1 -1
- package/dist/render-worker.js +19 -7
- package/dist/render-worker.js.map +1 -1
- package/dist/replay-codec.d.ts +56 -0
- package/dist/replay-codec.d.ts.map +1 -0
- package/dist/replay-codec.js +543 -0
- package/dist/replay-codec.js.map +1 -0
- package/dist/tile-compositor.d.ts +52 -1
- package/dist/tile-compositor.d.ts.map +1 -1
- package/dist/tile-compositor.js +82 -5
- package/dist/tile-compositor.js.map +1 -1
- package/dist/webgl2-curve.d.ts +12 -2
- package/dist/webgl2-curve.d.ts.map +1 -1
- package/dist/webgl2-curve.js +168 -40
- package/dist/webgl2-curve.js.map +1 -1
- package/dist/webgl2-ellipse.d.ts +1 -29
- package/dist/webgl2-ellipse.d.ts.map +1 -1
- package/dist/webgl2-ellipse.js +35 -22
- package/dist/webgl2-ellipse.js.map +1 -1
- package/dist/webgl2-msdf-text.d.ts +15 -2
- package/dist/webgl2-msdf-text.d.ts.map +1 -1
- package/dist/webgl2-msdf-text.js +101 -9
- package/dist/webgl2-msdf-text.js.map +1 -1
- package/dist/webgl2-rect-batch.d.ts +81 -0
- package/dist/webgl2-rect-batch.d.ts.map +1 -0
- package/dist/webgl2-rect-batch.js +201 -0
- package/dist/webgl2-rect-batch.js.map +1 -0
- package/dist/webgl2-stroke.d.ts +14 -35
- package/dist/webgl2-stroke.d.ts.map +1 -1
- package/dist/webgl2-stroke.js +27 -30
- package/dist/webgl2-stroke.js.map +1 -1
- package/dist/webgl2-target.d.ts +95 -13
- package/dist/webgl2-target.d.ts.map +1 -1
- package/dist/webgl2-target.js +423 -230
- package/dist/webgl2-target.js.map +1 -1
- package/package.json +4 -3
package/dist/render-worker.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { LruCache, installBuiltinRenderers, renderScene } from "@oh-just-another/renderer-core";
|
|
3
3
|
import { registerBundledFonts } from "@oh-just-another/fonts";
|
|
4
4
|
import { Canvas2DTarget } from "./canvas-target.js";
|
|
5
|
-
import {
|
|
5
|
+
import { replayPackedFrame } from "./replay-codec.js";
|
|
6
6
|
import { OFFSCREEN_IMAGE_CACHE_CAP } from "./constants.js";
|
|
7
7
|
const state = {
|
|
8
8
|
canvas: null,
|
|
@@ -77,17 +77,29 @@ const snapshot = (scene) => {
|
|
|
77
77
|
post({ type: "frame-done", bitmap }, [bitmap]);
|
|
78
78
|
};
|
|
79
79
|
/**
|
|
80
|
-
* Replay a
|
|
80
|
+
* Replay a packed RecordingTarget command stream onto the owned
|
|
81
81
|
* OffscreenCanvas. Used by the LayeredSurface "offscreen" backend: the
|
|
82
|
-
* main thread captures every RenderTarget call into a buffer
|
|
83
|
-
* it here per frame
|
|
82
|
+
* main thread captures every RenderTarget call into a buffer, packs it
|
|
83
|
+
* via `packReplayFrame`, and ships it here per frame (numeric stream in
|
|
84
|
+
* the transfer list, bitmaps cloned alongside); the worker replays.
|
|
84
85
|
*/
|
|
85
|
-
const replay = (
|
|
86
|
+
const replay = (msg) => {
|
|
86
87
|
if (!state.target) {
|
|
87
88
|
post({ type: "error", message: "Worker not initialised" });
|
|
88
89
|
return;
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
+
// Register this frame's bitmaps BEFORE replaying so the stream's
|
|
92
|
+
// drawImage id references resolve. These are worker-owned clones —
|
|
93
|
+
// the LRU's evict hook closes them.
|
|
94
|
+
for (const { id, bitmap } of msg.bitmaps) {
|
|
95
|
+
// A re-defined id (re-captured video frame) replaces the stored clone;
|
|
96
|
+
// close the old one — LruCache.set does not fire onEvict on overwrite.
|
|
97
|
+
const prev = state.images.get(id);
|
|
98
|
+
if (prev && prev !== bitmap)
|
|
99
|
+
prev.close();
|
|
100
|
+
state.images.set(id, bitmap);
|
|
101
|
+
}
|
|
102
|
+
replayPackedFrame(state.target, msg.buffer, msg.strings, state.images);
|
|
91
103
|
};
|
|
92
104
|
self.addEventListener("message", (ev) => {
|
|
93
105
|
const msg = ev.data;
|
|
@@ -105,7 +117,7 @@ self.addEventListener("message", (ev) => {
|
|
|
105
117
|
snapshot(msg.scene);
|
|
106
118
|
break;
|
|
107
119
|
case "replay":
|
|
108
|
-
replay(msg
|
|
120
|
+
replay(msg);
|
|
109
121
|
break;
|
|
110
122
|
case "frame":
|
|
111
123
|
// Patch-stream frames are not implemented; the protocol is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-worker.js","sourceRoot":"","sources":["../src/render-worker.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,OAAO,EAAE,QAAQ,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAGhG,OAAO,EAAE,oBAAoB,EAAkB,MAAM,wBAAwB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"render-worker.js","sourceRoot":"","sources":["../src/render-worker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { LruCache, type RenderTarget } from "@oh-just-another/renderer-core";
|
|
2
|
+
import type { RenderCommand } from "./recording-target.js";
|
|
3
|
+
/** One `defineImage` payload carried alongside the numeric stream. */
|
|
4
|
+
export interface PackedFrameBitmap {
|
|
5
|
+
readonly id: number;
|
|
6
|
+
readonly bitmap: ImageBitmap;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Result of {@link packReplayFrame}: `buffer` is the transferable numeric
|
|
10
|
+
* stream, `strings` the per-frame deduplicated string table it indexes
|
|
11
|
+
* into, `bitmaps` the `defineImage` payloads (worker registers them BEFORE
|
|
12
|
+
* replaying the stream).
|
|
13
|
+
*/
|
|
14
|
+
export interface PackedReplayFrame {
|
|
15
|
+
readonly buffer: ArrayBuffer;
|
|
16
|
+
readonly strings: readonly string[];
|
|
17
|
+
readonly bitmaps: readonly PackedFrameBitmap[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* postMessage shape the offscreen surface posts per changed layer and the
|
|
21
|
+
* render worker consumes. `buffer` goes in the transfer list; `strings`
|
|
22
|
+
* are cloned (cheap — deduplicated); `bitmaps` are CLONED, never
|
|
23
|
+
* transferred — see {@link packReplayFrame}.
|
|
24
|
+
*/
|
|
25
|
+
export interface PackedReplayMessage {
|
|
26
|
+
readonly type: "replay";
|
|
27
|
+
readonly buffer: ArrayBuffer;
|
|
28
|
+
readonly strings: readonly string[];
|
|
29
|
+
readonly bitmaps: readonly PackedFrameBitmap[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Flatten a flushed {@link RenderCommand} buffer into a transferable packed
|
|
33
|
+
* frame: one Float64 word per opcode / argument, strings deduplicated into
|
|
34
|
+
* a side table, enums and presence flags as small ints.
|
|
35
|
+
*
|
|
36
|
+
* `defineImage` commands emit nothing into the numeric stream; their
|
|
37
|
+
* `{ id, bitmap }` payloads are collected into `bitmaps` instead. The
|
|
38
|
+
* caller must post them WITHOUT a transfer-list entry so `postMessage`
|
|
39
|
+
* clones the pixels: the recorder's intern LRU still owns the source
|
|
40
|
+
* bitmap and will keep drawing it on later frames (GIF / video), so
|
|
41
|
+
* transferring (detaching) it would break the main thread's copy.
|
|
42
|
+
*/
|
|
43
|
+
export declare const packReplayFrame: (commands: readonly RenderCommand[]) => PackedReplayFrame;
|
|
44
|
+
/**
|
|
45
|
+
* Decode a packed frame and dispatch each command straight onto `target`
|
|
46
|
+
* in one pass — no intermediate {@link RenderCommand} objects.
|
|
47
|
+
*
|
|
48
|
+
* `images` is the worker's persistent id → bitmap LRU (mirrors the
|
|
49
|
+
* recorder's same-capacity intern LRU): the caller must have registered
|
|
50
|
+
* the frame's side bitmaps into it BEFORE calling. Semantics match
|
|
51
|
+
* {@link replayCommands}: `resize` is a no-op (the worker owns the canvas
|
|
52
|
+
* size via its own `resize` message) and a `drawImage` whose id misses the
|
|
53
|
+
* cache is skipped rather than thrown.
|
|
54
|
+
*/
|
|
55
|
+
export declare const replayPackedFrame: (target: RenderTarget, buffer: ArrayBuffer, strings: readonly string[], images?: LruCache<number, ImageBitmap>) => void;
|
|
56
|
+
//# sourceMappingURL=replay-codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-codec.d.ts","sourceRoot":"","sources":["../src/replay-codec.ts"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EAIR,KAAK,YAAY,EAGlB,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AA0G3D,sEAAsE;AACtE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAChD;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAChD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,SAAS,aAAa,EAAE,KAAG,iBAgOpE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,YAAY,EACpB,QAAQ,WAAW,EACnB,SAAS,SAAS,MAAM,EAAE,EAC1B,SAAQ,QAAQ,CAAC,MAAM,EAAE,WAAW,CAA2C,KAC9E,IAuLF,CAAC"}
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import { LruCache, } from "@oh-just-another/renderer-core";
|
|
2
|
+
import { OFFSCREEN_IMAGE_CACHE_CAP } from "./constants.js";
|
|
3
|
+
/**
|
|
4
|
+
* Packed-frame codec for the offscreen backend's per-frame worker hop.
|
|
5
|
+
*
|
|
6
|
+
* `structuredClone`-ing an array of {@link RenderCommand} objects costs
|
|
7
|
+
* ~1.6 ms for a ~4.5k-command frame (see `tests/offscreen-transfer.bench.ts`)
|
|
8
|
+
* — every object, key, and string is walked and copied. This codec flattens
|
|
9
|
+
* the stream into one transferable `ArrayBuffer` of Float64 words (opcode +
|
|
10
|
+
* args per command) plus a per-frame deduplicated string table, so
|
|
11
|
+
* `postMessage` transfers the numeric bulk for free and only clones a small
|
|
12
|
+
* string array. `ImageBitmap` payloads (`defineImage`) travel in a side
|
|
13
|
+
* array — see {@link packReplayFrame}.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Opcodes for the packed numeric stream — one per {@link RenderCommand}
|
|
17
|
+
* variant except `defineImage`, which travels in the side bitmap array and
|
|
18
|
+
* emits nothing here. Wire format only: values are arbitrary but must stay
|
|
19
|
+
* in sync between {@link packReplayFrame} and {@link replayPackedFrame}
|
|
20
|
+
* (same-package protocol; both sides always ship together).
|
|
21
|
+
*/
|
|
22
|
+
const OP_SET_FILL = 0;
|
|
23
|
+
const OP_SET_STROKE = 1;
|
|
24
|
+
const OP_SET_STROKE_WIDTH = 2;
|
|
25
|
+
const OP_SET_OPACITY = 3;
|
|
26
|
+
const OP_SET_LINE_CAP = 4;
|
|
27
|
+
const OP_SET_LINE_JOIN = 5;
|
|
28
|
+
const OP_SET_DASH_ARRAY = 6;
|
|
29
|
+
const OP_SET_FONT = 7;
|
|
30
|
+
const OP_SET_TEXT_ALIGN = 8;
|
|
31
|
+
const OP_SET_TEXT_BASELINE = 9;
|
|
32
|
+
const OP_SAVE = 10;
|
|
33
|
+
const OP_RESTORE = 11;
|
|
34
|
+
const OP_TRANSLATE = 12;
|
|
35
|
+
const OP_ROTATE = 13;
|
|
36
|
+
const OP_SCALE = 14;
|
|
37
|
+
const OP_SET_TRANSFORM = 15;
|
|
38
|
+
const OP_RESET_TRANSFORM = 16;
|
|
39
|
+
const OP_BEGIN_PATH = 17;
|
|
40
|
+
const OP_CLOSE_PATH = 18;
|
|
41
|
+
const OP_MOVE_TO = 19;
|
|
42
|
+
const OP_LINE_TO = 20;
|
|
43
|
+
const OP_QUADRATIC_CURVE_TO = 21;
|
|
44
|
+
const OP_BEZIER_CURVE_TO = 22;
|
|
45
|
+
const OP_RECT = 23;
|
|
46
|
+
const OP_ELLIPSE = 24;
|
|
47
|
+
const OP_FILL = 25;
|
|
48
|
+
const OP_STROKE = 26;
|
|
49
|
+
const OP_FILL_TEXT = 27;
|
|
50
|
+
const OP_CLEAR = 28;
|
|
51
|
+
const OP_MARK_DIRTY = 29;
|
|
52
|
+
const OP_RESIZE = 30;
|
|
53
|
+
const OP_DRAW_IMAGE = 31;
|
|
54
|
+
/**
|
|
55
|
+
* String-table index sentinel for a `null` color (`setFill` / `setStroke`
|
|
56
|
+
* accept `Color | null`). Real indices are >= 0.
|
|
57
|
+
*/
|
|
58
|
+
const NULL_STRING_INDEX = -1;
|
|
59
|
+
/** `setDashArray(null)` marker written in place of the dash length. */
|
|
60
|
+
const NULL_DASH_LENGTH = -1;
|
|
61
|
+
/**
|
|
62
|
+
* Enum wire codes. Encode side uses the `*_CODE` records, decode side the
|
|
63
|
+
* positional arrays — index === code. Order is wire format; append only.
|
|
64
|
+
*/
|
|
65
|
+
const LINE_CAPS = ["butt", "round", "square"];
|
|
66
|
+
const LINE_CAP_CODE = { butt: 0, round: 1, square: 2 };
|
|
67
|
+
const LINE_JOINS = ["miter", "round", "bevel"];
|
|
68
|
+
const LINE_JOIN_CODE = { miter: 0, round: 1, bevel: 2 };
|
|
69
|
+
const TEXT_ALIGNS = ["left", "center", "right"];
|
|
70
|
+
const TEXT_ALIGN_CODE = { left: 0, center: 1, right: 2 };
|
|
71
|
+
const TEXT_BASELINES = ["top", "middle", "bottom"];
|
|
72
|
+
const TEXT_BASELINE_CODE = { top: 0, middle: 1, bottom: 2 };
|
|
73
|
+
/**
|
|
74
|
+
* `fill(rule?)` wire codes: 0 = no rule argument, 1 = "nonzero",
|
|
75
|
+
* 2 = "evenodd".
|
|
76
|
+
*/
|
|
77
|
+
const FILL_RULE_CODE = { nonzero: 1, evenodd: 2 };
|
|
78
|
+
/**
|
|
79
|
+
* `setFont` options wire codes. 0 = key absent from the options object;
|
|
80
|
+
* 1 / 2 = the two allowed values. A separate leading flag word (0 / 1)
|
|
81
|
+
* distinguishes "no options argument at all" from an empty options object.
|
|
82
|
+
*/
|
|
83
|
+
const FONT_WEIGHT_CODE = { normal: 1, bold: 2 };
|
|
84
|
+
const FONT_STYLE_CODE = { normal: 1, italic: 2 };
|
|
85
|
+
/** Optional-argument presence flags (`fillText` maxWidth, `clear` bounds). */
|
|
86
|
+
const ABSENT = 0;
|
|
87
|
+
const PRESENT = 1;
|
|
88
|
+
/**
|
|
89
|
+
* Initial capacity (in Float64 words) of the packed stream, as a multiple
|
|
90
|
+
* of the command count. Most commands fit in opcode + ≤6 args; the writer
|
|
91
|
+
* doubles on overflow, so this only tunes how often the first frames
|
|
92
|
+
* reallocate.
|
|
93
|
+
*/
|
|
94
|
+
const PACK_WORDS_PER_COMMAND = 4;
|
|
95
|
+
/** Floor for the writer's initial capacity so tiny frames don't thrash. */
|
|
96
|
+
const PACK_MIN_CAPACITY = 64;
|
|
97
|
+
/**
|
|
98
|
+
* Flatten a flushed {@link RenderCommand} buffer into a transferable packed
|
|
99
|
+
* frame: one Float64 word per opcode / argument, strings deduplicated into
|
|
100
|
+
* a side table, enums and presence flags as small ints.
|
|
101
|
+
*
|
|
102
|
+
* `defineImage` commands emit nothing into the numeric stream; their
|
|
103
|
+
* `{ id, bitmap }` payloads are collected into `bitmaps` instead. The
|
|
104
|
+
* caller must post them WITHOUT a transfer-list entry so `postMessage`
|
|
105
|
+
* clones the pixels: the recorder's intern LRU still owns the source
|
|
106
|
+
* bitmap and will keep drawing it on later frames (GIF / video), so
|
|
107
|
+
* transferring (detaching) it would break the main thread's copy.
|
|
108
|
+
*/
|
|
109
|
+
export const packReplayFrame = (commands) => {
|
|
110
|
+
let words = new Float64Array(Math.max(PACK_MIN_CAPACITY, commands.length * PACK_WORDS_PER_COMMAND));
|
|
111
|
+
let used = 0;
|
|
112
|
+
const push = (v) => {
|
|
113
|
+
if (used === words.length) {
|
|
114
|
+
const grown = new Float64Array(words.length * 2);
|
|
115
|
+
grown.set(words);
|
|
116
|
+
words = grown;
|
|
117
|
+
}
|
|
118
|
+
words[used++] = v;
|
|
119
|
+
};
|
|
120
|
+
const strings = [];
|
|
121
|
+
const stringIndex = new Map();
|
|
122
|
+
/** Dedup a string through the per-frame table, returning its index. */
|
|
123
|
+
const intern = (s) => {
|
|
124
|
+
let idx = stringIndex.get(s);
|
|
125
|
+
if (idx === undefined) {
|
|
126
|
+
idx = strings.length;
|
|
127
|
+
strings.push(s);
|
|
128
|
+
stringIndex.set(s, idx);
|
|
129
|
+
}
|
|
130
|
+
return idx;
|
|
131
|
+
};
|
|
132
|
+
const bitmaps = [];
|
|
133
|
+
for (const cmd of commands) {
|
|
134
|
+
switch (cmd.k) {
|
|
135
|
+
case "setFill":
|
|
136
|
+
push(OP_SET_FILL);
|
|
137
|
+
push(cmd.color === null ? NULL_STRING_INDEX : intern(cmd.color));
|
|
138
|
+
break;
|
|
139
|
+
case "setStroke":
|
|
140
|
+
push(OP_SET_STROKE);
|
|
141
|
+
push(cmd.color === null ? NULL_STRING_INDEX : intern(cmd.color));
|
|
142
|
+
break;
|
|
143
|
+
case "setStrokeWidth":
|
|
144
|
+
push(OP_SET_STROKE_WIDTH);
|
|
145
|
+
push(cmd.w);
|
|
146
|
+
break;
|
|
147
|
+
case "setOpacity":
|
|
148
|
+
push(OP_SET_OPACITY);
|
|
149
|
+
push(cmd.a);
|
|
150
|
+
break;
|
|
151
|
+
case "setLineCap":
|
|
152
|
+
push(OP_SET_LINE_CAP);
|
|
153
|
+
push(LINE_CAP_CODE[cmd.cap]);
|
|
154
|
+
break;
|
|
155
|
+
case "setLineJoin":
|
|
156
|
+
push(OP_SET_LINE_JOIN);
|
|
157
|
+
push(LINE_JOIN_CODE[cmd.join]);
|
|
158
|
+
break;
|
|
159
|
+
case "setDashArray":
|
|
160
|
+
push(OP_SET_DASH_ARRAY);
|
|
161
|
+
if (cmd.dash === null) {
|
|
162
|
+
push(NULL_DASH_LENGTH);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
push(cmd.dash.length);
|
|
166
|
+
for (const d of cmd.dash)
|
|
167
|
+
push(d);
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
case "setFont":
|
|
171
|
+
push(OP_SET_FONT);
|
|
172
|
+
push(intern(cmd.family));
|
|
173
|
+
push(cmd.size);
|
|
174
|
+
if (cmd.options === undefined) {
|
|
175
|
+
push(ABSENT);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
push(PRESENT);
|
|
179
|
+
push(cmd.options.weight === undefined ? 0 : FONT_WEIGHT_CODE[cmd.options.weight]);
|
|
180
|
+
push(cmd.options.style === undefined ? 0 : FONT_STYLE_CODE[cmd.options.style]);
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
case "setTextAlign":
|
|
184
|
+
push(OP_SET_TEXT_ALIGN);
|
|
185
|
+
push(TEXT_ALIGN_CODE[cmd.align]);
|
|
186
|
+
break;
|
|
187
|
+
case "setTextBaseline":
|
|
188
|
+
push(OP_SET_TEXT_BASELINE);
|
|
189
|
+
push(TEXT_BASELINE_CODE[cmd.baseline]);
|
|
190
|
+
break;
|
|
191
|
+
case "save":
|
|
192
|
+
push(OP_SAVE);
|
|
193
|
+
break;
|
|
194
|
+
case "restore":
|
|
195
|
+
push(OP_RESTORE);
|
|
196
|
+
break;
|
|
197
|
+
case "translate":
|
|
198
|
+
push(OP_TRANSLATE);
|
|
199
|
+
push(cmd.x);
|
|
200
|
+
push(cmd.y);
|
|
201
|
+
break;
|
|
202
|
+
case "rotate":
|
|
203
|
+
push(OP_ROTATE);
|
|
204
|
+
push(cmd.r);
|
|
205
|
+
break;
|
|
206
|
+
case "scale":
|
|
207
|
+
push(OP_SCALE);
|
|
208
|
+
push(cmd.sx);
|
|
209
|
+
push(cmd.sy);
|
|
210
|
+
break;
|
|
211
|
+
case "setTransform":
|
|
212
|
+
push(OP_SET_TRANSFORM);
|
|
213
|
+
push(cmd.t.a);
|
|
214
|
+
push(cmd.t.b);
|
|
215
|
+
push(cmd.t.c);
|
|
216
|
+
push(cmd.t.d);
|
|
217
|
+
push(cmd.t.e);
|
|
218
|
+
push(cmd.t.f);
|
|
219
|
+
break;
|
|
220
|
+
case "resetTransform":
|
|
221
|
+
push(OP_RESET_TRANSFORM);
|
|
222
|
+
break;
|
|
223
|
+
case "beginPath":
|
|
224
|
+
push(OP_BEGIN_PATH);
|
|
225
|
+
break;
|
|
226
|
+
case "closePath":
|
|
227
|
+
push(OP_CLOSE_PATH);
|
|
228
|
+
break;
|
|
229
|
+
case "moveTo":
|
|
230
|
+
push(OP_MOVE_TO);
|
|
231
|
+
push(cmd.x);
|
|
232
|
+
push(cmd.y);
|
|
233
|
+
break;
|
|
234
|
+
case "lineTo":
|
|
235
|
+
push(OP_LINE_TO);
|
|
236
|
+
push(cmd.x);
|
|
237
|
+
push(cmd.y);
|
|
238
|
+
break;
|
|
239
|
+
case "quadraticCurveTo":
|
|
240
|
+
push(OP_QUADRATIC_CURVE_TO);
|
|
241
|
+
push(cmd.cx);
|
|
242
|
+
push(cmd.cy);
|
|
243
|
+
push(cmd.x);
|
|
244
|
+
push(cmd.y);
|
|
245
|
+
break;
|
|
246
|
+
case "bezierCurveTo":
|
|
247
|
+
push(OP_BEZIER_CURVE_TO);
|
|
248
|
+
push(cmd.c1x);
|
|
249
|
+
push(cmd.c1y);
|
|
250
|
+
push(cmd.c2x);
|
|
251
|
+
push(cmd.c2y);
|
|
252
|
+
push(cmd.x);
|
|
253
|
+
push(cmd.y);
|
|
254
|
+
break;
|
|
255
|
+
case "rect":
|
|
256
|
+
push(OP_RECT);
|
|
257
|
+
push(cmd.x);
|
|
258
|
+
push(cmd.y);
|
|
259
|
+
push(cmd.w);
|
|
260
|
+
push(cmd.h);
|
|
261
|
+
break;
|
|
262
|
+
case "ellipse":
|
|
263
|
+
push(OP_ELLIPSE);
|
|
264
|
+
push(cmd.cx);
|
|
265
|
+
push(cmd.cy);
|
|
266
|
+
push(cmd.rx);
|
|
267
|
+
push(cmd.ry);
|
|
268
|
+
break;
|
|
269
|
+
case "fill":
|
|
270
|
+
push(OP_FILL);
|
|
271
|
+
push(cmd.rule === undefined ? ABSENT : FILL_RULE_CODE[cmd.rule]);
|
|
272
|
+
break;
|
|
273
|
+
case "stroke":
|
|
274
|
+
push(OP_STROKE);
|
|
275
|
+
break;
|
|
276
|
+
case "fillText":
|
|
277
|
+
push(OP_FILL_TEXT);
|
|
278
|
+
push(intern(cmd.text));
|
|
279
|
+
push(cmd.x);
|
|
280
|
+
push(cmd.y);
|
|
281
|
+
if (cmd.maxWidth === undefined) {
|
|
282
|
+
push(ABSENT);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
push(PRESENT);
|
|
286
|
+
push(cmd.maxWidth);
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
case "clear":
|
|
290
|
+
push(OP_CLEAR);
|
|
291
|
+
if (cmd.bounds === undefined) {
|
|
292
|
+
push(ABSENT);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
push(PRESENT);
|
|
296
|
+
push(cmd.bounds.x);
|
|
297
|
+
push(cmd.bounds.y);
|
|
298
|
+
push(cmd.bounds.width);
|
|
299
|
+
push(cmd.bounds.height);
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
case "markDirty":
|
|
303
|
+
push(OP_MARK_DIRTY);
|
|
304
|
+
push(cmd.bounds.x);
|
|
305
|
+
push(cmd.bounds.y);
|
|
306
|
+
push(cmd.bounds.width);
|
|
307
|
+
push(cmd.bounds.height);
|
|
308
|
+
break;
|
|
309
|
+
case "resize":
|
|
310
|
+
push(OP_RESIZE);
|
|
311
|
+
push(cmd.w);
|
|
312
|
+
push(cmd.h);
|
|
313
|
+
break;
|
|
314
|
+
case "defineImage":
|
|
315
|
+
// Not packed: the bitmap travels beside the numeric stream. The
|
|
316
|
+
// worker registers all side bitmaps before replaying, so the
|
|
317
|
+
// stream's `drawImage` id references always resolve.
|
|
318
|
+
bitmaps.push({ id: cmd.id, bitmap: cmd.bitmap });
|
|
319
|
+
break;
|
|
320
|
+
case "drawImage":
|
|
321
|
+
push(OP_DRAW_IMAGE);
|
|
322
|
+
push(cmd.id);
|
|
323
|
+
push(cmd.dx);
|
|
324
|
+
push(cmd.dy);
|
|
325
|
+
push(cmd.dw);
|
|
326
|
+
push(cmd.dh);
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Exact-size copy so the transferred buffer carries no slack capacity.
|
|
331
|
+
return { buffer: words.slice(0, used).buffer, strings, bitmaps };
|
|
332
|
+
};
|
|
333
|
+
/**
|
|
334
|
+
* Decode a packed frame and dispatch each command straight onto `target`
|
|
335
|
+
* in one pass — no intermediate {@link RenderCommand} objects.
|
|
336
|
+
*
|
|
337
|
+
* `images` is the worker's persistent id → bitmap LRU (mirrors the
|
|
338
|
+
* recorder's same-capacity intern LRU): the caller must have registered
|
|
339
|
+
* the frame's side bitmaps into it BEFORE calling. Semantics match
|
|
340
|
+
* {@link replayCommands}: `resize` is a no-op (the worker owns the canvas
|
|
341
|
+
* size via its own `resize` message) and a `drawImage` whose id misses the
|
|
342
|
+
* cache is skipped rather than thrown.
|
|
343
|
+
*/
|
|
344
|
+
export const replayPackedFrame = (target, buffer, strings, images = new LruCache(OFFSCREEN_IMAGE_CACHE_CAP)) => {
|
|
345
|
+
const words = new Float64Array(buffer);
|
|
346
|
+
let i = 0;
|
|
347
|
+
const next = () => {
|
|
348
|
+
const v = words[i++];
|
|
349
|
+
if (v === undefined)
|
|
350
|
+
throw new Error("replayPackedFrame: truncated stream");
|
|
351
|
+
return v;
|
|
352
|
+
};
|
|
353
|
+
const str = (idx) => {
|
|
354
|
+
const s = strings[idx];
|
|
355
|
+
if (s === undefined)
|
|
356
|
+
throw new Error(`replayPackedFrame: bad string index ${String(idx)}`);
|
|
357
|
+
return s;
|
|
358
|
+
};
|
|
359
|
+
const at = (table, code) => {
|
|
360
|
+
const v = table[code];
|
|
361
|
+
if (v === undefined)
|
|
362
|
+
throw new Error(`replayPackedFrame: bad enum code ${String(code)}`);
|
|
363
|
+
return v;
|
|
364
|
+
};
|
|
365
|
+
while (i < words.length) {
|
|
366
|
+
const op = next();
|
|
367
|
+
switch (op) {
|
|
368
|
+
case OP_SET_FILL: {
|
|
369
|
+
const idx = next();
|
|
370
|
+
target.setFill(idx === NULL_STRING_INDEX ? null : str(idx));
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
case OP_SET_STROKE: {
|
|
374
|
+
const idx = next();
|
|
375
|
+
target.setStroke(idx === NULL_STRING_INDEX ? null : str(idx));
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
case OP_SET_STROKE_WIDTH:
|
|
379
|
+
target.setStrokeWidth(next());
|
|
380
|
+
break;
|
|
381
|
+
case OP_SET_OPACITY:
|
|
382
|
+
target.setOpacity(next());
|
|
383
|
+
break;
|
|
384
|
+
case OP_SET_LINE_CAP:
|
|
385
|
+
target.setLineCap(at(LINE_CAPS, next()));
|
|
386
|
+
break;
|
|
387
|
+
case OP_SET_LINE_JOIN:
|
|
388
|
+
target.setLineJoin(at(LINE_JOINS, next()));
|
|
389
|
+
break;
|
|
390
|
+
case OP_SET_DASH_ARRAY: {
|
|
391
|
+
const n = next();
|
|
392
|
+
if (n === NULL_DASH_LENGTH) {
|
|
393
|
+
target.setDashArray(null);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
const dash = [];
|
|
397
|
+
for (let d = 0; d < n; d++)
|
|
398
|
+
dash.push(next());
|
|
399
|
+
target.setDashArray(dash);
|
|
400
|
+
}
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
case OP_SET_FONT: {
|
|
404
|
+
const family = str(next());
|
|
405
|
+
const size = next();
|
|
406
|
+
if (next() === ABSENT) {
|
|
407
|
+
target.setFont(family, size);
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
const weight = next();
|
|
411
|
+
const style = next();
|
|
412
|
+
const options = {};
|
|
413
|
+
if (weight === FONT_WEIGHT_CODE.normal)
|
|
414
|
+
options.weight = "normal";
|
|
415
|
+
else if (weight === FONT_WEIGHT_CODE.bold)
|
|
416
|
+
options.weight = "bold";
|
|
417
|
+
if (style === FONT_STYLE_CODE.normal)
|
|
418
|
+
options.style = "normal";
|
|
419
|
+
else if (style === FONT_STYLE_CODE.italic)
|
|
420
|
+
options.style = "italic";
|
|
421
|
+
target.setFont(family, size, options);
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
case OP_SET_TEXT_ALIGN:
|
|
425
|
+
target.setTextAlign(at(TEXT_ALIGNS, next()));
|
|
426
|
+
break;
|
|
427
|
+
case OP_SET_TEXT_BASELINE:
|
|
428
|
+
target.setTextBaseline(at(TEXT_BASELINES, next()));
|
|
429
|
+
break;
|
|
430
|
+
case OP_SAVE:
|
|
431
|
+
target.save();
|
|
432
|
+
break;
|
|
433
|
+
case OP_RESTORE:
|
|
434
|
+
target.restore();
|
|
435
|
+
break;
|
|
436
|
+
case OP_TRANSLATE:
|
|
437
|
+
target.translate(next(), next());
|
|
438
|
+
break;
|
|
439
|
+
case OP_ROTATE:
|
|
440
|
+
target.rotate(next());
|
|
441
|
+
break;
|
|
442
|
+
case OP_SCALE:
|
|
443
|
+
target.scale(next(), next());
|
|
444
|
+
break;
|
|
445
|
+
case OP_SET_TRANSFORM:
|
|
446
|
+
target.setTransform({
|
|
447
|
+
a: next(),
|
|
448
|
+
b: next(),
|
|
449
|
+
c: next(),
|
|
450
|
+
d: next(),
|
|
451
|
+
e: next(),
|
|
452
|
+
f: next(),
|
|
453
|
+
});
|
|
454
|
+
break;
|
|
455
|
+
case OP_RESET_TRANSFORM:
|
|
456
|
+
target.resetTransform();
|
|
457
|
+
break;
|
|
458
|
+
case OP_BEGIN_PATH:
|
|
459
|
+
target.beginPath();
|
|
460
|
+
break;
|
|
461
|
+
case OP_CLOSE_PATH:
|
|
462
|
+
target.closePath();
|
|
463
|
+
break;
|
|
464
|
+
case OP_MOVE_TO:
|
|
465
|
+
target.moveTo(next(), next());
|
|
466
|
+
break;
|
|
467
|
+
case OP_LINE_TO:
|
|
468
|
+
target.lineTo(next(), next());
|
|
469
|
+
break;
|
|
470
|
+
case OP_QUADRATIC_CURVE_TO:
|
|
471
|
+
target.quadraticCurveTo(next(), next(), next(), next());
|
|
472
|
+
break;
|
|
473
|
+
case OP_BEZIER_CURVE_TO:
|
|
474
|
+
target.bezierCurveTo(next(), next(), next(), next(), next(), next());
|
|
475
|
+
break;
|
|
476
|
+
case OP_RECT:
|
|
477
|
+
target.rect(next(), next(), next(), next());
|
|
478
|
+
break;
|
|
479
|
+
case OP_ELLIPSE:
|
|
480
|
+
target.ellipse(next(), next(), next(), next());
|
|
481
|
+
break;
|
|
482
|
+
case OP_FILL: {
|
|
483
|
+
const code = next();
|
|
484
|
+
if (code === FILL_RULE_CODE.nonzero)
|
|
485
|
+
target.fill("nonzero");
|
|
486
|
+
else if (code === FILL_RULE_CODE.evenodd)
|
|
487
|
+
target.fill("evenodd");
|
|
488
|
+
else
|
|
489
|
+
target.fill();
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
case OP_STROKE:
|
|
493
|
+
target.stroke();
|
|
494
|
+
break;
|
|
495
|
+
case OP_FILL_TEXT: {
|
|
496
|
+
const text = str(next());
|
|
497
|
+
const x = next();
|
|
498
|
+
const y = next();
|
|
499
|
+
if (next() === PRESENT)
|
|
500
|
+
target.fillText(text, x, y, next());
|
|
501
|
+
else
|
|
502
|
+
target.fillText(text, x, y);
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
case OP_CLEAR:
|
|
506
|
+
if (next() === PRESENT) {
|
|
507
|
+
const bounds = { x: next(), y: next(), width: next(), height: next() };
|
|
508
|
+
target.clear(bounds);
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
target.clear();
|
|
512
|
+
}
|
|
513
|
+
break;
|
|
514
|
+
case OP_MARK_DIRTY:
|
|
515
|
+
target.markDirty?.({ x: next(), y: next(), width: next(), height: next() });
|
|
516
|
+
break;
|
|
517
|
+
case OP_RESIZE:
|
|
518
|
+
// No-op for replay — the worker owns the canvas size and resizes
|
|
519
|
+
// via its own `resize` message, not via the command stream. Still
|
|
520
|
+
// consume the args to stay in sync with the stream.
|
|
521
|
+
next();
|
|
522
|
+
next();
|
|
523
|
+
break;
|
|
524
|
+
case OP_DRAW_IMAGE: {
|
|
525
|
+
// `get` bumps recency so the worker LRU evicts in lockstep with
|
|
526
|
+
// the recorder's. A miss means an out-of-sync stream — skip
|
|
527
|
+
// rather than throw (matches the non-drawable skip on record).
|
|
528
|
+
const id = next();
|
|
529
|
+
const dx = next();
|
|
530
|
+
const dy = next();
|
|
531
|
+
const dw = next();
|
|
532
|
+
const dh = next();
|
|
533
|
+
const bitmap = images.get(id);
|
|
534
|
+
if (bitmap)
|
|
535
|
+
target.drawImage(bitmap, dx, dy, dw, dh);
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
default:
|
|
539
|
+
throw new Error(`replayPackedFrame: unknown opcode ${String(op)}`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
//# sourceMappingURL=replay-codec.js.map
|