@solidtv/renderer 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/core/CoreNode.d.ts +0 -10
- package/dist/src/core/CoreNode.js +7 -14
- package/dist/src/core/CoreNode.js.map +1 -1
- package/dist/src/core/Stage.d.ts +7 -15
- package/dist/src/core/Stage.js +23 -41
- package/dist/src/core/Stage.js.map +1 -1
- package/dist/src/core/TextureMemoryManager.js +4 -3
- package/dist/src/core/TextureMemoryManager.js.map +1 -1
- package/dist/src/core/lib/ImageWorker.d.ts +16 -1
- package/dist/src/core/lib/ImageWorker.js +52 -17
- package/dist/src/core/lib/ImageWorker.js.map +1 -1
- package/dist/src/core/lib/WebGlContextWrapper.d.ts +2 -32
- package/dist/src/core/lib/WebGlContextWrapper.js +26 -81
- package/dist/src/core/lib/WebGlContextWrapper.js.map +1 -1
- package/dist/src/core/lib/utils.d.ts +0 -1
- package/dist/src/core/lib/utils.js +0 -3
- package/dist/src/core/lib/utils.js.map +1 -1
- package/dist/src/core/platforms/web/WebPlatform.js +1 -0
- package/dist/src/core/platforms/web/WebPlatform.js.map +1 -1
- package/dist/src/core/renderers/CoreRenderer.d.ts +0 -1
- package/dist/src/core/renderers/CoreRenderer.js.map +1 -1
- package/dist/src/core/renderers/CoreShaderNode.d.ts +16 -0
- package/dist/src/core/renderers/CoreShaderNode.js +22 -0
- package/dist/src/core/renderers/CoreShaderNode.js.map +1 -1
- package/dist/src/core/renderers/canvas/CanvasRenderer.js +23 -3
- package/dist/src/core/renderers/canvas/CanvasRenderer.js.map +1 -1
- package/dist/src/core/renderers/canvas/CanvasShaderNode.d.ts +0 -1
- package/dist/src/core/renderers/canvas/CanvasShaderNode.js +0 -1
- package/dist/src/core/renderers/canvas/CanvasShaderNode.js.map +1 -1
- package/dist/src/core/renderers/canvas/CanvasTexture.d.ts +1 -1
- package/dist/src/core/renderers/canvas/CanvasTexture.js +25 -6
- package/dist/src/core/renderers/canvas/CanvasTexture.js.map +1 -1
- package/dist/src/core/renderers/webgl/WebGlRenderer.d.ts +1 -7
- package/dist/src/core/renderers/webgl/WebGlRenderer.js +9 -13
- package/dist/src/core/renderers/webgl/WebGlRenderer.js.map +1 -1
- package/dist/src/core/renderers/webgl/WebGlShaderNode.d.ts +2 -4
- package/dist/src/core/renderers/webgl/WebGlShaderNode.js +0 -1
- package/dist/src/core/renderers/webgl/WebGlShaderNode.js.map +1 -1
- package/dist/src/core/renderers/webgl/WebGlShaderProgram.d.ts +0 -7
- package/dist/src/core/renderers/webgl/WebGlShaderProgram.js +3 -19
- package/dist/src/core/renderers/webgl/WebGlShaderProgram.js.map +1 -1
- package/dist/src/core/renderers/webgl/internal/RendererUtils.d.ts +0 -33
- package/dist/src/core/renderers/webgl/internal/RendererUtils.js +0 -50
- package/dist/src/core/renderers/webgl/internal/RendererUtils.js.map +1 -1
- package/dist/src/core/renderers/webgl/internal/ShaderUtils.d.ts +0 -1
- package/dist/src/core/renderers/webgl/internal/ShaderUtils.js.map +1 -1
- package/dist/src/core/text-rendering/CanvasFontHandler.js +0 -5
- package/dist/src/core/text-rendering/CanvasFontHandler.js.map +1 -1
- package/dist/src/core/text-rendering/CanvasTextRenderer.d.ts +1 -0
- package/dist/src/core/text-rendering/CanvasTextRenderer.js +23 -0
- package/dist/src/core/text-rendering/CanvasTextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/SdfTextRenderer.d.ts +1 -0
- package/dist/src/core/text-rendering/SdfTextRenderer.js +50 -4
- package/dist/src/core/text-rendering/SdfTextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/TextRenderer.d.ts +7 -0
- package/dist/src/core/textures/ImageTexture.js +11 -0
- package/dist/src/core/textures/ImageTexture.js.map +1 -1
- package/dist/src/core/textures/SubTexture.d.ts +1 -0
- package/dist/src/core/textures/SubTexture.js +12 -0
- package/dist/src/core/textures/SubTexture.js.map +1 -1
- package/dist/src/core/textures/Texture.js +10 -0
- package/dist/src/core/textures/Texture.js.map +1 -1
- package/dist/src/main-api/Inspector.d.ts +2 -1
- package/dist/src/main-api/Inspector.js +43 -15
- package/dist/src/main-api/Inspector.js.map +1 -1
- package/dist/src/main-api/Renderer.d.ts +16 -10
- package/dist/src/main-api/Renderer.js +3 -3
- package/dist/src/main-api/Renderer.js.map +1 -1
- package/dist/src/utils.d.ts +1 -1
- package/dist/src/utils.js +2 -2
- package/dist/src/utils.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/CoreNode.ts +8 -26
- package/src/core/Stage.ts +24 -49
- package/src/core/TextureMemoryManager.ts +4 -3
- package/src/core/lib/ImageWorker.test.ts +106 -0
- package/src/core/lib/ImageWorker.ts +60 -22
- package/src/core/lib/WebGlContextWrapper.ts +32 -101
- package/src/core/lib/utils.ts +0 -4
- package/src/core/platforms/web/WebPlatform.outOfMemory.test.ts +1 -0
- package/src/core/platforms/web/WebPlatform.ts +1 -0
- package/src/core/renderers/CoreRenderer.ts +0 -1
- package/src/core/renderers/CoreShaderNode.test.ts +53 -0
- package/src/core/renderers/CoreShaderNode.ts +23 -0
- package/src/core/renderers/canvas/CanvasRenderer.test.ts +34 -0
- package/src/core/renderers/canvas/CanvasRenderer.ts +33 -4
- package/src/core/renderers/canvas/CanvasShaderNode.ts +0 -1
- package/src/core/renderers/canvas/CanvasTexture.test.ts +31 -0
- package/src/core/renderers/canvas/CanvasTexture.ts +35 -7
- package/src/core/renderers/webgl/WebGlRenderer.ts +9 -26
- package/src/core/renderers/webgl/WebGlShaderNode.ts +2 -4
- package/src/core/renderers/webgl/WebGlShaderProgram.ts +3 -20
- package/src/core/renderers/webgl/internal/RendererUtils.ts +0 -87
- package/src/core/renderers/webgl/internal/ShaderUtils.ts +0 -1
- package/src/core/text-rendering/CanvasFontHandler.ts +0 -6
- package/src/core/text-rendering/CanvasTextRenderer.ts +26 -0
- package/src/core/text-rendering/SdfTextRenderer.test.ts +150 -0
- package/src/core/text-rendering/SdfTextRenderer.ts +54 -4
- package/src/core/text-rendering/TextRenderer.ts +7 -0
- package/src/core/textures/ImageTexture.ts +18 -0
- package/src/core/textures/SubTexture.test.ts +45 -0
- package/src/core/textures/SubTexture.ts +13 -0
- package/src/core/textures/Texture.ts +12 -0
- package/src/main-api/Inspector.ts +46 -16
- package/src/main-api/Renderer.ts +20 -14
- package/src/utils.ts +1 -2
- package/src/core/renderers/webgl/internal/WebGlUtils.ts +0 -16
package/src/core/Stage.ts
CHANGED
|
@@ -37,7 +37,6 @@ import {
|
|
|
37
37
|
import { CoreRenderer } from './renderers/CoreRenderer.js';
|
|
38
38
|
import { CoreTextNode, type CoreTextNodeProps } from './CoreTextNode.js';
|
|
39
39
|
import { santizeCustomDataMap } from '../main-api/utils.js';
|
|
40
|
-
import { pointInBound } from './lib/utils.js';
|
|
41
40
|
import type { CoreShaderNode } from './renderers/CoreShaderNode.js';
|
|
42
41
|
import { Matrix3d } from './lib/Matrix3d.js';
|
|
43
42
|
import { createBound, createPreloadBounds, type Bound } from './lib/utils.js';
|
|
@@ -71,11 +70,6 @@ export type StageFrameTickHandler = (
|
|
|
71
70
|
frameTickData: FrameTickPayload,
|
|
72
71
|
) => void;
|
|
73
72
|
|
|
74
|
-
export interface Point {
|
|
75
|
-
x: number;
|
|
76
|
-
y: number;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
73
|
const autoStart = true;
|
|
80
74
|
|
|
81
75
|
export class Stage {
|
|
@@ -88,7 +82,6 @@ export class Stage {
|
|
|
88
82
|
public readonly shManager: CoreShaderManager;
|
|
89
83
|
public readonly renderer: CoreRenderer;
|
|
90
84
|
public readonly root: CoreNode;
|
|
91
|
-
public readonly interactiveNodes: Set<CoreNode> = new Set();
|
|
92
85
|
public boundsMargin: [number, number, number, number];
|
|
93
86
|
public readonly defShaderNode: CoreShaderNode | null = null;
|
|
94
87
|
public strictBound: Bound;
|
|
@@ -173,7 +166,6 @@ export class Stage {
|
|
|
173
166
|
appHeight,
|
|
174
167
|
boundsMargin,
|
|
175
168
|
enableContextSpy,
|
|
176
|
-
forceWebGL2,
|
|
177
169
|
numImageWorkers,
|
|
178
170
|
textureMemory,
|
|
179
171
|
renderEngine,
|
|
@@ -244,7 +236,6 @@ export class Stage {
|
|
|
244
236
|
stage: this,
|
|
245
237
|
canvas,
|
|
246
238
|
contextSpy: this.contextSpy,
|
|
247
|
-
forceWebGL2,
|
|
248
239
|
});
|
|
249
240
|
|
|
250
241
|
this.shManager = new CoreShaderManager(this);
|
|
@@ -254,6 +245,17 @@ export class Stage {
|
|
|
254
245
|
|
|
255
246
|
const renderMode = this.renderer.mode || 'webgl';
|
|
256
247
|
|
|
248
|
+
// Canvas2D textures are plain JS heap objects managed by the browser GC.
|
|
249
|
+
// Threshold-based upload blocking makes no sense for JS heap — disable it
|
|
250
|
+
// by setting criticalThreshold to 0 while keeping the eviction machinery.
|
|
251
|
+
if (renderMode === 'canvas') {
|
|
252
|
+
this.txMemManager.updateSettings({
|
|
253
|
+
...textureMemory,
|
|
254
|
+
criticalThreshold: 0,
|
|
255
|
+
doNotExceedCriticalThreshold: false,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
257
259
|
this.createDefaultTexture();
|
|
258
260
|
setPremultiplyMode(renderMode);
|
|
259
261
|
|
|
@@ -509,6 +511,19 @@ export class Stage {
|
|
|
509
511
|
);
|
|
510
512
|
}
|
|
511
513
|
|
|
514
|
+
/**
|
|
515
|
+
* Trim text renderer caches back to their configured limits.
|
|
516
|
+
*
|
|
517
|
+
* Called when the stage goes idle so layout-cache eviction never competes
|
|
518
|
+
* with active rendering.
|
|
519
|
+
*/
|
|
520
|
+
cleanupTextRenderers() {
|
|
521
|
+
const textRenderers = this.textRenderers;
|
|
522
|
+
for (const key in textRenderers) {
|
|
523
|
+
textRenderers[key]!.cleanup();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
512
527
|
/**
|
|
513
528
|
* Start a new frame draw
|
|
514
529
|
*/
|
|
@@ -897,45 +912,6 @@ export class Stage {
|
|
|
897
912
|
this.root.childUpdateType |= UpdateType.RenderBounds;
|
|
898
913
|
}
|
|
899
914
|
|
|
900
|
-
/** Find all nodes at a given point
|
|
901
|
-
* @param data
|
|
902
|
-
*/
|
|
903
|
-
findNodesAtPoint(data: Point): CoreNode[] {
|
|
904
|
-
const x = data.x / this.options.deviceLogicalPixelRatio;
|
|
905
|
-
const y = data.y / this.options.deviceLogicalPixelRatio;
|
|
906
|
-
const nodes: CoreNode[] = [];
|
|
907
|
-
for (const node of this.interactiveNodes) {
|
|
908
|
-
if (node.isRenderable === false) {
|
|
909
|
-
continue;
|
|
910
|
-
}
|
|
911
|
-
if (pointInBound(x, y, node.renderBound!) === true) {
|
|
912
|
-
nodes.push(node);
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
return nodes;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
/**
|
|
919
|
-
* Find the top node at a given point
|
|
920
|
-
* @param data
|
|
921
|
-
* @returns
|
|
922
|
-
*/
|
|
923
|
-
getNodeFromPosition(data: Point): CoreNode | null {
|
|
924
|
-
const nodes: CoreNode[] = this.findNodesAtPoint(data);
|
|
925
|
-
if (nodes.length === 0) {
|
|
926
|
-
return null;
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
//get last node in array (as top node)
|
|
930
|
-
let topNode = nodes[nodes.length - 1] as CoreNode;
|
|
931
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
932
|
-
if (nodes[i]!.zIndex > topNode.zIndex) {
|
|
933
|
-
topNode = nodes[i]!;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
return topNode || null;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
915
|
/**
|
|
940
916
|
* add node to timeNodes arrays
|
|
941
917
|
* @param node
|
|
@@ -1062,7 +1038,6 @@ export class Stage {
|
|
|
1062
1038
|
rtt: props.rtt ?? false,
|
|
1063
1039
|
data,
|
|
1064
1040
|
imageType: props.imageType,
|
|
1065
|
-
interactive: props.interactive ?? false,
|
|
1066
1041
|
preventDestroy: props.preventDestroy,
|
|
1067
1042
|
componentName: props.componentName,
|
|
1068
1043
|
componentLocation: props.componentLocation,
|
|
@@ -148,7 +148,7 @@ export class TextureMemoryManager {
|
|
|
148
148
|
this.loadedTextures.add(texture);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
if (this.memUsed > this.criticalThreshold) {
|
|
151
|
+
if (this.criticalThreshold > 0 && this.memUsed > this.criticalThreshold) {
|
|
152
152
|
this.criticalCleanupRequested = true;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
@@ -156,13 +156,14 @@ export class TextureMemoryManager {
|
|
|
156
156
|
checkCleanup() {
|
|
157
157
|
return (
|
|
158
158
|
this.criticalCleanupRequested ||
|
|
159
|
-
(this.
|
|
159
|
+
(this.criticalThreshold > 0 &&
|
|
160
|
+
this.memUsed > this.targetThreshold &&
|
|
160
161
|
this.frameTime - this.lastCleanupTime >= this.cleanupInterval)
|
|
161
162
|
);
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
checkCriticalCleanup() {
|
|
165
|
-
return this.memUsed > this.criticalThreshold;
|
|
166
|
+
return this.criticalThreshold > 0 && this.memUsed > this.criticalThreshold;
|
|
166
167
|
}
|
|
167
168
|
|
|
168
169
|
/**
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { ImageWorkerManager } from './ImageWorker.js';
|
|
3
|
+
import type { CreateImageBitmapSupport } from './validateImageBitmap.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Minimal Web Worker stand-in. The real Worker spawns a thread that parses the
|
|
7
|
+
* blob script; here we only care about how many are constructed and when, so we
|
|
8
|
+
* record every instance and never auto-respond (so `getImage` calls stay
|
|
9
|
+
* "in flight" and accumulate load, simulating concurrency).
|
|
10
|
+
*/
|
|
11
|
+
class FakeWorker {
|
|
12
|
+
static instances: FakeWorker[] = [];
|
|
13
|
+
static reset() {
|
|
14
|
+
FakeWorker.instances = [];
|
|
15
|
+
}
|
|
16
|
+
onmessage: ((e: unknown) => void) | null = null;
|
|
17
|
+
onerror: ((e: unknown) => void) | null = null;
|
|
18
|
+
onmessageerror: ((e: unknown) => void) | null = null;
|
|
19
|
+
postMessage = vi.fn();
|
|
20
|
+
terminate = vi.fn();
|
|
21
|
+
constructor(public url: string) {
|
|
22
|
+
FakeWorker.instances.push(this);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const support: CreateImageBitmapSupport = {
|
|
27
|
+
basic: true,
|
|
28
|
+
options: true,
|
|
29
|
+
full: true,
|
|
30
|
+
premultiplyHonored: true,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let createObjectURL: ReturnType<typeof vi.fn>;
|
|
34
|
+
let revokeObjectURL: ReturnType<typeof vi.fn>;
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
FakeWorker.reset();
|
|
38
|
+
createObjectURL = vi.fn(() => 'blob:fake-url');
|
|
39
|
+
revokeObjectURL = vi.fn();
|
|
40
|
+
// The manager builds the worker source via Blob + object URL, then spawns
|
|
41
|
+
// Workers. None of these exist in the node test env, so stub them.
|
|
42
|
+
vi.stubGlobal(
|
|
43
|
+
'Blob',
|
|
44
|
+
class {
|
|
45
|
+
constructor(public parts: unknown[], public opts: unknown) {}
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
vi.stubGlobal('URL', { createObjectURL, revokeObjectURL });
|
|
49
|
+
vi.stubGlobal('self', { URL: { createObjectURL, revokeObjectURL } });
|
|
50
|
+
vi.stubGlobal('Worker', FakeWorker);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
vi.unstubAllGlobals();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const load = (mgr: ImageWorkerManager) =>
|
|
58
|
+
void mgr.getImage('img.png', null, null, null, null, null);
|
|
59
|
+
|
|
60
|
+
describe('ImageWorkerManager pool spawning', () => {
|
|
61
|
+
it('does not spawn any workers at construction', () => {
|
|
62
|
+
new ImageWorkerManager(3, support);
|
|
63
|
+
// Spawning is lazy — nothing happens until the first image request.
|
|
64
|
+
expect(FakeWorker.instances.length).toBe(0);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('spawns the whole pool at once on the first image request', () => {
|
|
68
|
+
const mgr = new ImageWorkerManager(3, support);
|
|
69
|
+
load(mgr);
|
|
70
|
+
expect(FakeWorker.instances.length).toBe(3);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('does not respawn the pool on subsequent requests', () => {
|
|
74
|
+
const mgr = new ImageWorkerManager(3, support);
|
|
75
|
+
load(mgr); // spawns the pool
|
|
76
|
+
expect(FakeWorker.instances.length).toBe(3);
|
|
77
|
+
// Subsequent requests reuse the existing pool — no new workers.
|
|
78
|
+
load(mgr);
|
|
79
|
+
load(mgr);
|
|
80
|
+
load(mgr);
|
|
81
|
+
expect(FakeWorker.instances.length).toBe(3);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('spawns exactly maxWorkers when that is 1', () => {
|
|
85
|
+
const mgr = new ImageWorkerManager(1, support);
|
|
86
|
+
load(mgr);
|
|
87
|
+
expect(FakeWorker.instances.length).toBe(1);
|
|
88
|
+
load(mgr);
|
|
89
|
+
load(mgr);
|
|
90
|
+
expect(FakeWorker.instances.length).toBe(1);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('serializes the worker source once, reusing the blob per worker', () => {
|
|
94
|
+
const mgr = new ImageWorkerManager(3, support);
|
|
95
|
+
load(mgr);
|
|
96
|
+
// One object URL created+revoked per spawned worker (3), serialized once.
|
|
97
|
+
expect(createObjectURL).toHaveBeenCalledTimes(3);
|
|
98
|
+
expect(revokeObjectURL).toHaveBeenCalledTimes(3);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('routes each request to a worker via postMessage', () => {
|
|
102
|
+
const mgr = new ImageWorkerManager(2, support);
|
|
103
|
+
load(mgr);
|
|
104
|
+
expect(FakeWorker.instances[0]!.postMessage).toHaveBeenCalledTimes(1);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -177,20 +177,32 @@ export class ImageWorkerManager {
|
|
|
177
177
|
workers: Worker[] = [];
|
|
178
178
|
workerLoad: number[] = [];
|
|
179
179
|
nextId = 0;
|
|
180
|
+
/** Upper bound on the pool size, from the `numImageWorkers` setting. */
|
|
181
|
+
private readonly maxWorkers: number;
|
|
182
|
+
/** Retained so the worker source is serialized once, when the pool spawns. */
|
|
183
|
+
private workerBlob: Blob | null = null;
|
|
184
|
+
private readonly createImageBitmapSupport: CreateImageBitmapSupport;
|
|
180
185
|
|
|
181
186
|
constructor(
|
|
182
187
|
numImageWorkers: number,
|
|
183
188
|
createImageBitmapSupport: CreateImageBitmapSupport,
|
|
184
189
|
) {
|
|
185
|
-
this.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
this.maxWorkers = numImageWorkers;
|
|
191
|
+
this.createImageBitmapSupport = createImageBitmapSupport;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Build the shared worker source once and spawn the full pool in a single
|
|
196
|
+
* burst. Called lazily on the first image request. No-op once spawned.
|
|
197
|
+
*/
|
|
198
|
+
private spawnWorkers(): void {
|
|
199
|
+
if (this.workers.length > 0) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
this.workerBlob = this.createWorkerBlob(this.createImageBitmapSupport);
|
|
203
|
+
for (let i = 0; i < this.maxWorkers; i++) {
|
|
204
|
+
this.spawnWorker();
|
|
205
|
+
}
|
|
194
206
|
}
|
|
195
207
|
|
|
196
208
|
private handleMessage(event: MessageEvent, workerIndex: number) {
|
|
@@ -231,10 +243,9 @@ export class ImageWorkerManager {
|
|
|
231
243
|
this.workerLoad[workerIndex] = 0;
|
|
232
244
|
}
|
|
233
245
|
|
|
234
|
-
private
|
|
235
|
-
numWorkers = 1,
|
|
246
|
+
private createWorkerBlob(
|
|
236
247
|
createImageBitmapSupport: CreateImageBitmapSupport,
|
|
237
|
-
):
|
|
248
|
+
): Blob {
|
|
238
249
|
let workerCode = `(${createImageWorker.toString()})()`;
|
|
239
250
|
|
|
240
251
|
// Replace placeholders with actual initialization values
|
|
@@ -265,19 +276,39 @@ export class ImageWorkerManager {
|
|
|
265
276
|
}
|
|
266
277
|
|
|
267
278
|
workerCode = workerCode.replace('"use strict";', '');
|
|
268
|
-
|
|
279
|
+
return new Blob([workerCode], {
|
|
269
280
|
type: 'application/javascript',
|
|
270
281
|
});
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Spawn a single worker from the shared blob and wire up its handlers.
|
|
286
|
+
* No-op once the pool has reached `maxWorkers`.
|
|
287
|
+
*/
|
|
288
|
+
private spawnWorker(): void {
|
|
289
|
+
if (this.workerBlob === null || this.workers.length >= this.maxWorkers) {
|
|
290
|
+
return;
|
|
277
291
|
}
|
|
278
|
-
|
|
292
|
+
|
|
293
|
+
const index = this.workers.length;
|
|
294
|
+
const urlFactory = self.URL ? URL : webkitURL;
|
|
295
|
+
const blobURL: string = urlFactory.createObjectURL(this.workerBlob);
|
|
296
|
+
const worker = new Worker(blobURL);
|
|
297
|
+
// The worker retains the script after construction; the URL is no longer
|
|
298
|
+
// needed once the Worker has been created from it.
|
|
279
299
|
urlFactory.revokeObjectURL(blobURL);
|
|
280
|
-
|
|
300
|
+
|
|
301
|
+
worker.onmessage = (event) => this.handleMessage(event, index);
|
|
302
|
+
worker.onerror = (event) => this.handleWorkerError(event, index);
|
|
303
|
+
worker.onmessageerror = (event) => this.handleWorkerError(event, index);
|
|
304
|
+
|
|
305
|
+
this.workers.push(worker);
|
|
306
|
+
this.workerLoad.push(0);
|
|
307
|
+
|
|
308
|
+
// Pool is full — the blob will never be needed again, so release it.
|
|
309
|
+
if (this.workers.length >= this.maxWorkers) {
|
|
310
|
+
this.workerBlob = null;
|
|
311
|
+
}
|
|
281
312
|
}
|
|
282
313
|
|
|
283
314
|
private getNextWorkerIndex(): number {
|
|
@@ -311,7 +342,14 @@ export class ImageWorkerManager {
|
|
|
311
342
|
): Promise<TextureData> {
|
|
312
343
|
return new Promise((resolve, reject) => {
|
|
313
344
|
try {
|
|
314
|
-
|
|
345
|
+
let nextWorkerIndex = this.getNextWorkerIndex();
|
|
346
|
+
if (nextWorkerIndex === -1) {
|
|
347
|
+
// Pool not spawned yet — spin up all workers at once on the first
|
|
348
|
+
// image request, off the boot/first-render critical path.
|
|
349
|
+
this.spawnWorkers();
|
|
350
|
+
nextWorkerIndex = this.getNextWorkerIndex();
|
|
351
|
+
}
|
|
352
|
+
|
|
315
353
|
if (nextWorkerIndex === -1) {
|
|
316
354
|
reject(new Error('No image workers available'));
|
|
317
355
|
return;
|
|
@@ -6,13 +6,12 @@ import type {
|
|
|
6
6
|
Vec3,
|
|
7
7
|
Vec4,
|
|
8
8
|
} from '../renderers/webgl/internal/ShaderUtils.js';
|
|
9
|
-
import { isWebGl2 } from '../renderers/webgl/internal/WebGlUtils.js';
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Optimized WebGL Context Wrapper
|
|
13
12
|
*
|
|
14
13
|
* @remarks
|
|
15
|
-
* This class contains the subset of the WebGLRenderingContext
|
|
14
|
+
* This class contains the subset of the WebGLRenderingContext
|
|
16
15
|
* API that is used by the renderer. Select high volume WebGL methods include
|
|
17
16
|
* caching optimizations to avoid making WebGL calls if the state is already set
|
|
18
17
|
* to the desired value.
|
|
@@ -94,56 +93,40 @@ export class WebGlContextWrapper {
|
|
|
94
93
|
public readonly INVALID_OPERATION: number;
|
|
95
94
|
//#endregion WebGL Enums
|
|
96
95
|
|
|
97
|
-
constructor(private gl: WebGLRenderingContext
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
this.activeTextureUnit =
|
|
105
|
-
(gl.getParameter(gl.ACTIVE_TEXTURE) as number) - gl.TEXTURE0;
|
|
96
|
+
constructor(private gl: WebGLRenderingContext) {
|
|
97
|
+
// A freshly created WebGL context is in a fully specified default state.
|
|
98
|
+
// Rather than reading that state back with getParameter/isEnabled — each a
|
|
99
|
+
// synchronous CPU<->GPU round-trip, and previously ~one per texture unit
|
|
100
|
+
// (16-32) just to enumerate the bindings — we seed the cache with those
|
|
101
|
+
// known defaults. The engine never restores a lost context in place (it
|
|
102
|
+
// reloads the app on `contextLost`), so there is no live state to recover.
|
|
103
|
+
this.activeTextureUnit = 0;
|
|
106
104
|
const maxTextureUnits = gl.getParameter(
|
|
107
105
|
gl.MAX_TEXTURE_IMAGE_UNITS,
|
|
108
106
|
) as number;
|
|
109
|
-
//
|
|
110
|
-
this.texture2dUnits = new Array<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
this.
|
|
118
|
-
this.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this.
|
|
130
|
-
|
|
131
|
-
this.
|
|
132
|
-
this.blendSrcRgb = gl.getParameter(gl.BLEND_SRC_RGB) as number;
|
|
133
|
-
this.blendDstRgb = gl.getParameter(gl.BLEND_DST_RGB) as number;
|
|
134
|
-
this.blendSrcAlpha = gl.getParameter(gl.BLEND_SRC_ALPHA) as number;
|
|
135
|
-
this.blendDstAlpha = gl.getParameter(gl.BLEND_DST_ALPHA) as number;
|
|
136
|
-
|
|
137
|
-
this.boundArrayBuffer = gl.getParameter(
|
|
138
|
-
gl.ARRAY_BUFFER_BINDING,
|
|
139
|
-
) as WebGLBuffer;
|
|
140
|
-
this.boundElementArrayBuffer = gl.getParameter(
|
|
141
|
-
gl.ELEMENT_ARRAY_BUFFER_BINDING,
|
|
142
|
-
) as WebGLBuffer;
|
|
143
|
-
|
|
144
|
-
this.curProgram = gl.getParameter(
|
|
145
|
-
gl.CURRENT_PROGRAM,
|
|
146
|
-
) as WebGLProgram | null;
|
|
107
|
+
// All texture units start unbound (TEXTURE_BINDING_2D === null).
|
|
108
|
+
this.texture2dUnits = new Array<WebGLTexture | null>(maxTextureUnits).fill(
|
|
109
|
+
null,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// SCISSOR_TEST disabled; SCISSOR_BOX defaults to the full drawing buffer.
|
|
113
|
+
this.scissorEnabled = false;
|
|
114
|
+
this.scissorX = 0;
|
|
115
|
+
this.scissorY = 0;
|
|
116
|
+
this.scissorWidth = gl.drawingBufferWidth;
|
|
117
|
+
this.scissorHeight = gl.drawingBufferHeight;
|
|
118
|
+
|
|
119
|
+
// BLEND disabled; blend func defaults to (ONE, ZERO) for both RGB and alpha.
|
|
120
|
+
this.blendEnabled = false;
|
|
121
|
+
this.blendSrcRgb = gl.ONE;
|
|
122
|
+
this.blendDstRgb = gl.ZERO;
|
|
123
|
+
this.blendSrcAlpha = gl.ONE;
|
|
124
|
+
this.blendDstAlpha = gl.ZERO;
|
|
125
|
+
|
|
126
|
+
// No buffers or program bound on a fresh context.
|
|
127
|
+
this.boundArrayBuffer = null;
|
|
128
|
+
this.boundElementArrayBuffer = null;
|
|
129
|
+
this.curProgram = null;
|
|
147
130
|
|
|
148
131
|
this.canvas = gl.canvas;
|
|
149
132
|
|
|
@@ -175,9 +158,6 @@ export class WebGlContextWrapper {
|
|
|
175
158
|
this.UNSIGNED_SHORT = gl.UNSIGNED_SHORT;
|
|
176
159
|
this.ONE = gl.ONE;
|
|
177
160
|
this.ONE_MINUS_SRC_ALPHA = gl.ONE_MINUS_SRC_ALPHA;
|
|
178
|
-
this.MAX_VERTEX_TEXTURE_IMAGE_UNITS = gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS;
|
|
179
|
-
this.TRIANGLES = gl.TRIANGLES;
|
|
180
|
-
this.UNSIGNED_SHORT = gl.UNSIGNED_SHORT;
|
|
181
161
|
this.VERTEX_SHADER = gl.VERTEX_SHADER;
|
|
182
162
|
this.FRAGMENT_SHADER = gl.FRAGMENT_SHADER;
|
|
183
163
|
this.STATIC_DRAW = gl.STATIC_DRAW;
|
|
@@ -188,15 +168,6 @@ export class WebGlContextWrapper {
|
|
|
188
168
|
this.INVALID_ENUM = gl.INVALID_ENUM;
|
|
189
169
|
this.INVALID_OPERATION = gl.INVALID_OPERATION;
|
|
190
170
|
}
|
|
191
|
-
/**
|
|
192
|
-
* Returns true if the WebGL context is WebGL2
|
|
193
|
-
*
|
|
194
|
-
* @returns
|
|
195
|
-
*/
|
|
196
|
-
isWebGl2() {
|
|
197
|
-
return isWebGl2(this.gl);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
171
|
/**
|
|
201
172
|
* ```
|
|
202
173
|
* gl.activeTexture(textureUnit + gl.TEXTURE0);
|
|
@@ -1109,33 +1080,6 @@ export class WebGlContextWrapper {
|
|
|
1109
1080
|
return this.gl.getError();
|
|
1110
1081
|
}
|
|
1111
1082
|
|
|
1112
|
-
/**
|
|
1113
|
-
* ```
|
|
1114
|
-
* gl.createVertexArray();
|
|
1115
|
-
* ```
|
|
1116
|
-
*
|
|
1117
|
-
* @returns
|
|
1118
|
-
*/
|
|
1119
|
-
createVertexArray() {
|
|
1120
|
-
if (this.gl instanceof WebGL2RenderingContext) {
|
|
1121
|
-
return this.gl.createVertexArray();
|
|
1122
|
-
}
|
|
1123
|
-
return undefined;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
/**
|
|
1127
|
-
* ```
|
|
1128
|
-
* gl.bindVertexArray(vertexArray);
|
|
1129
|
-
* ```
|
|
1130
|
-
*
|
|
1131
|
-
* @param vertexArray
|
|
1132
|
-
*/
|
|
1133
|
-
bindVertexArray(vertexArray: WebGLVertexArrayObject | null) {
|
|
1134
|
-
if (this.gl instanceof WebGL2RenderingContext) {
|
|
1135
|
-
this.gl.bindVertexArray(vertexArray);
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
1083
|
/**
|
|
1140
1084
|
* ```
|
|
1141
1085
|
* gl.getAttribLocation(program, name);
|
|
@@ -1341,19 +1285,6 @@ export class WebGlContextWrapper {
|
|
|
1341
1285
|
}
|
|
1342
1286
|
}
|
|
1343
1287
|
|
|
1344
|
-
/**
|
|
1345
|
-
* ```
|
|
1346
|
-
* gl.deleteVertexArray(vertexArray);
|
|
1347
|
-
* ```
|
|
1348
|
-
*
|
|
1349
|
-
* @param vertexArray - The vertex array object to delete
|
|
1350
|
-
*/
|
|
1351
|
-
deleteVertexArray(vertexArray: WebGLVertexArrayObject) {
|
|
1352
|
-
if (this.isWebGl2()) {
|
|
1353
|
-
(this.gl as WebGL2RenderingContext).deleteVertexArray(vertexArray);
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
1288
|
/**
|
|
1358
1289
|
* Check for WebGL errors and return error information
|
|
1359
1290
|
* @param operation Description of the operation for error reporting
|
package/src/core/lib/utils.ts
CHANGED
|
@@ -251,10 +251,6 @@ export function boundLargeThanBound(bound1: Bound, bound2: Bound) {
|
|
|
251
251
|
);
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
export function pointInBound(x: number, y: number, bound: Bound) {
|
|
255
|
-
return !(x < bound.x1 || x > bound.x2 || y < bound.y1 || y > bound.y2);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
254
|
export function isBoundPositive(bound: Bound): boolean {
|
|
259
255
|
return bound.x1 < bound.x2 && bound.y1 < bound.y2;
|
|
260
256
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { CoreShaderNode } from './CoreShaderNode.js';
|
|
3
|
+
import type { Stage } from '../Stage.js';
|
|
4
|
+
import type { CoreShaderType } from './CoreShaderNode.js';
|
|
5
|
+
|
|
6
|
+
const makeNode = (mutate: ReturnType<typeof vi.fn>) => {
|
|
7
|
+
const stage = {
|
|
8
|
+
shManager: { mutateShaderValueUsage: mutate },
|
|
9
|
+
} as unknown as Stage;
|
|
10
|
+
return new CoreShaderNode(
|
|
11
|
+
'test',
|
|
12
|
+
{ time: undefined } as CoreShaderType,
|
|
13
|
+
stage,
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('CoreShaderNode.detachNode', () => {
|
|
18
|
+
it('releases the held value-cache key so idle cleanup can evict it', () => {
|
|
19
|
+
const mutate = vi.fn();
|
|
20
|
+
const node = makeNode(mutate);
|
|
21
|
+
node.valueKey = 'color:1;node-width:10node-height:10';
|
|
22
|
+
|
|
23
|
+
node.detachNode();
|
|
24
|
+
|
|
25
|
+
expect(mutate).toHaveBeenCalledTimes(1);
|
|
26
|
+
expect(mutate).toHaveBeenCalledWith(
|
|
27
|
+
'color:1;node-width:10node-height:10',
|
|
28
|
+
-1,
|
|
29
|
+
);
|
|
30
|
+
// Key is cleared so a double-detach can't decrement twice.
|
|
31
|
+
expect(node.valueKey).toBe('');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('is a no-op when no value key is held', () => {
|
|
35
|
+
const mutate = vi.fn();
|
|
36
|
+
const node = makeNode(mutate);
|
|
37
|
+
|
|
38
|
+
node.detachNode();
|
|
39
|
+
|
|
40
|
+
expect(mutate).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('does not double-decrement on repeated detach', () => {
|
|
44
|
+
const mutate = vi.fn();
|
|
45
|
+
const node = makeNode(mutate);
|
|
46
|
+
node.valueKey = 'k';
|
|
47
|
+
|
|
48
|
+
node.detachNode();
|
|
49
|
+
node.detachNode();
|
|
50
|
+
|
|
51
|
+
expect(mutate).toHaveBeenCalledTimes(1);
|
|
52
|
+
});
|
|
53
|
+
});
|