@solidtv/renderer 1.3.2 → 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 -2
- package/dist/src/core/CoreNode.js.map +1 -1
- package/dist/src/core/Stage.js +1 -2
- package/dist/src/core/Stage.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/renderers/CoreRenderer.d.ts +0 -1
- package/dist/src/core/renderers/CoreRenderer.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 -3
- 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/SdfTextRenderer.js +23 -4
- package/dist/src/core/text-rendering/SdfTextRenderer.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 +0 -10
- package/dist/src/main-api/Renderer.js +1 -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 +0 -3
- package/src/core/Stage.ts +0 -2
- 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/renderers/CoreRenderer.ts +0 -1
- package/src/core/renderers/webgl/WebGlRenderer.ts +9 -26
- package/src/core/renderers/webgl/WebGlShaderNode.ts +2 -3
- 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/SdfTextRenderer.test.ts +17 -0
- package/src/core/text-rendering/SdfTextRenderer.ts +24 -4
- package/src/main-api/Inspector.ts +46 -16
- package/src/main-api/Renderer.ts +1 -14
- package/src/utils.ts +1 -2
- package/src/core/renderers/webgl/internal/WebGlUtils.ts +0 -16
|
@@ -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
|
|
@@ -14,10 +14,6 @@ import { SdfRenderOp } from './SdfRenderOp.js';
|
|
|
14
14
|
import type { CoreContextTexture } from '../CoreContextTexture.js';
|
|
15
15
|
import {
|
|
16
16
|
createIndexBuffer,
|
|
17
|
-
type CoreWebGlParameters,
|
|
18
|
-
type CoreWebGlExtensions,
|
|
19
|
-
getWebGlParameters,
|
|
20
|
-
getWebGlExtensions,
|
|
21
17
|
type WebGlColor,
|
|
22
18
|
} from './internal/RendererUtils.js';
|
|
23
19
|
import { WebGlCtxTexture } from './WebGlCtxTexture.js';
|
|
@@ -51,17 +47,11 @@ const GL_OUT_OF_MEMORY = 0x0505;
|
|
|
51
47
|
*/
|
|
52
48
|
const MAX_DRAINED_GL_ERRORS = 8;
|
|
53
49
|
|
|
54
|
-
interface CoreWebGlSystem {
|
|
55
|
-
parameters: CoreWebGlParameters;
|
|
56
|
-
extensions: CoreWebGlExtensions;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
50
|
export type WebGlRenderOp = CoreNode | SdfRenderOp;
|
|
60
51
|
|
|
61
52
|
export class WebGlRenderer extends CoreRenderer {
|
|
62
53
|
//// WebGL Native Context and Data
|
|
63
54
|
glw: WebGlContextWrapper;
|
|
64
|
-
system: CoreWebGlSystem;
|
|
65
55
|
|
|
66
56
|
//// Persistent data
|
|
67
57
|
quadBuffer: ArrayBuffer;
|
|
@@ -169,17 +159,20 @@ export class WebGlRenderer extends CoreRenderer {
|
|
|
169
159
|
constructor(options: WebGlRendererOptions) {
|
|
170
160
|
super(options);
|
|
171
161
|
|
|
162
|
+
// CPU-side vertex buffers, reused every frame. Their GL buffers and
|
|
163
|
+
// BufferCollections are wired up below, once the GL context exists.
|
|
172
164
|
this.quadBuffer = new ArrayBuffer(this.stage.options.quadBufferSize);
|
|
173
165
|
this.fQuadBuffer = new Float32Array(this.quadBuffer);
|
|
174
166
|
this.uiQuadBuffer = new Uint32Array(this.quadBuffer);
|
|
175
167
|
|
|
168
|
+
// Shared SDF vertex buffer: 512 KB for ~3600 glyphs.
|
|
169
|
+
this.sdfBuffer = new ArrayBuffer(512 * 1024);
|
|
170
|
+
this.fSdfBuffer = new Float32Array(this.sdfBuffer);
|
|
171
|
+
this.uiSdfBuffer = new Uint32Array(this.sdfBuffer);
|
|
172
|
+
|
|
176
173
|
this.mode = 'webgl';
|
|
177
174
|
|
|
178
|
-
const gl = createWebGLContext(
|
|
179
|
-
options.canvas,
|
|
180
|
-
options.forceWebGL2,
|
|
181
|
-
options.contextSpy,
|
|
182
|
-
);
|
|
175
|
+
const gl = createWebGLContext(options.canvas, options.contextSpy);
|
|
183
176
|
const glw = (this.glw = new WebGlContextWrapper(gl));
|
|
184
177
|
glw.viewport(0, 0, options.canvas.width, options.canvas.height);
|
|
185
178
|
|
|
@@ -192,11 +185,6 @@ export class WebGlRenderer extends CoreRenderer {
|
|
|
192
185
|
|
|
193
186
|
createIndexBuffer(glw, this.stage.bufferMemory);
|
|
194
187
|
|
|
195
|
-
this.system = {
|
|
196
|
-
parameters: getWebGlParameters(this.glw),
|
|
197
|
-
extensions: getWebGlExtensions(this.glw),
|
|
198
|
-
};
|
|
199
|
-
|
|
200
188
|
// Create the static node coords buffer
|
|
201
189
|
// 80 is the magic number used in createIndexBuffer
|
|
202
190
|
// @see RendererUtils.ts
|
|
@@ -261,13 +249,8 @@ export class WebGlRenderer extends CoreRenderer {
|
|
|
261
249
|
},
|
|
262
250
|
},
|
|
263
251
|
]);
|
|
264
|
-
// --- Shared SDF buffer ---------------------------------------------------
|
|
265
|
-
// Allocate 512 KB for SDF vertex data (~3600 glyphs).
|
|
266
|
-
const sdfBufSize = 512 * 1024;
|
|
267
|
-
this.sdfBuffer = new ArrayBuffer(sdfBufSize);
|
|
268
|
-
this.fSdfBuffer = new Float32Array(this.sdfBuffer);
|
|
269
|
-
this.uiSdfBuffer = new Uint32Array(this.sdfBuffer);
|
|
270
252
|
|
|
253
|
+
// --- Shared SDF buffer collection (CPU buffer allocated above) ----------
|
|
271
254
|
const sdfWebGlBuffer = glw.createBuffer();
|
|
272
255
|
const sdfStride = 6 * Float32Array.BYTES_PER_ELEMENT; // 24 bytes
|
|
273
256
|
this.sdfQuadBufferCollection = new BufferCollection([
|
|
@@ -21,11 +21,11 @@ export type ShaderSource<T> =
|
|
|
21
21
|
export type WebGlShaderType<T extends object = Record<string, unknown>> =
|
|
22
22
|
CoreShaderType<T> & {
|
|
23
23
|
/**
|
|
24
|
-
* fragment shader source for WebGl
|
|
24
|
+
* fragment shader source for WebGl
|
|
25
25
|
*/
|
|
26
26
|
fragment: ShaderSource<T>;
|
|
27
27
|
/**
|
|
28
|
-
* vertex shader source for WebGl
|
|
28
|
+
* vertex shader source for WebGl
|
|
29
29
|
*/
|
|
30
30
|
vertex?: ShaderSource<T>;
|
|
31
31
|
/**
|
|
@@ -43,7 +43,6 @@ export type WebGlShaderType<T extends object = Record<string, unknown>> =
|
|
|
43
43
|
* extensions required for specific shader?
|
|
44
44
|
*/
|
|
45
45
|
webgl1Extensions?: string[];
|
|
46
|
-
webgl2Extensions?: string[];
|
|
47
46
|
supportsIndexedTextures?: boolean;
|
|
48
47
|
};
|
|
49
48
|
|
|
@@ -19,13 +19,6 @@ import { CoreNode } from '../../CoreNode.js';
|
|
|
19
19
|
|
|
20
20
|
export class WebGlShaderProgram implements CoreShaderProgram {
|
|
21
21
|
protected program: WebGLProgram | null;
|
|
22
|
-
/**
|
|
23
|
-
* Vertex Array Object
|
|
24
|
-
*
|
|
25
|
-
* @remarks
|
|
26
|
-
* Used by WebGL2 Only
|
|
27
|
-
*/
|
|
28
|
-
protected vao: WebGLVertexArrayObject | undefined;
|
|
29
22
|
protected renderer: WebGlRenderer;
|
|
30
23
|
protected glw: WebGlContextWrapper;
|
|
31
24
|
protected attributeLocations: string[];
|
|
@@ -45,22 +38,15 @@ export class WebGlShaderProgram implements CoreShaderProgram {
|
|
|
45
38
|
this.renderer = renderer;
|
|
46
39
|
const glw = (this.glw = renderer.glw);
|
|
47
40
|
|
|
48
|
-
// Check that extensions are supported
|
|
49
|
-
const webGl2 = glw.isWebGl2();
|
|
50
|
-
let requiredExtensions: string[] = [];
|
|
51
|
-
|
|
52
41
|
this.supportsIndexedTextures =
|
|
53
42
|
config.supportsIndexedTextures || this.supportsIndexedTextures;
|
|
54
|
-
requiredExtensions =
|
|
55
|
-
(webGl2 && config.webgl2Extensions) ||
|
|
56
|
-
(!webGl2 && config.webgl1Extensions) ||
|
|
57
|
-
[];
|
|
58
43
|
|
|
59
|
-
|
|
44
|
+
// Check that required extensions are supported
|
|
45
|
+
const requiredExtensions = config.webgl1Extensions || [];
|
|
60
46
|
requiredExtensions.forEach((extensionName) => {
|
|
61
47
|
if (!glw.getExtension(extensionName)) {
|
|
62
48
|
throw new Error(
|
|
63
|
-
`Shader "${this.constructor.name}" requires extension "${extensionName}" for WebGL
|
|
49
|
+
`Shader "${this.constructor.name}" requires extension "${extensionName}" for WebGL 1.0 but wasn't found`,
|
|
64
50
|
);
|
|
65
51
|
}
|
|
66
52
|
});
|
|
@@ -310,9 +296,6 @@ export class WebGlShaderProgram implements CoreShaderProgram {
|
|
|
310
296
|
return;
|
|
311
297
|
}
|
|
312
298
|
this.glw.useProgram(this.program, this.uniformLocations!);
|
|
313
|
-
if (this.glw.isWebGl2() && this.vao) {
|
|
314
|
-
this.glw.bindVertexArray(this.vao);
|
|
315
|
-
}
|
|
316
299
|
}
|
|
317
300
|
|
|
318
301
|
detach(): void {
|
|
@@ -1,92 +1,5 @@
|
|
|
1
1
|
import type { WebGlContextWrapper } from '../../../lib/WebGlContextWrapper.js';
|
|
2
2
|
|
|
3
|
-
export interface CoreWebGlParameters {
|
|
4
|
-
MAX_RENDERBUFFER_SIZE: number;
|
|
5
|
-
MAX_TEXTURE_SIZE: number;
|
|
6
|
-
MAX_VIEWPORT_DIMS: Int32Array;
|
|
7
|
-
MAX_VERTEX_TEXTURE_IMAGE_UNITS: number;
|
|
8
|
-
MAX_TEXTURE_IMAGE_UNITS: number;
|
|
9
|
-
MAX_COMBINED_TEXTURE_IMAGE_UNITS: number;
|
|
10
|
-
MAX_VERTEX_ATTRIBS: number;
|
|
11
|
-
MAX_VARYING_VECTORS: number;
|
|
12
|
-
MAX_VERTEX_UNIFORM_VECTORS: number;
|
|
13
|
-
MAX_FRAGMENT_UNIFORM_VECTORS: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Get device specific webgl parameters
|
|
18
|
-
* @param glw
|
|
19
|
-
*/
|
|
20
|
-
export function getWebGlParameters(
|
|
21
|
-
glw: WebGlContextWrapper,
|
|
22
|
-
): CoreWebGlParameters {
|
|
23
|
-
const params: CoreWebGlParameters = {
|
|
24
|
-
MAX_RENDERBUFFER_SIZE: 0,
|
|
25
|
-
MAX_TEXTURE_SIZE: 0,
|
|
26
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
|
|
27
|
-
MAX_VIEWPORT_DIMS: 0 as any, // Code below will replace this with an Int32Array
|
|
28
|
-
MAX_VERTEX_TEXTURE_IMAGE_UNITS: 0,
|
|
29
|
-
MAX_TEXTURE_IMAGE_UNITS: 0,
|
|
30
|
-
MAX_COMBINED_TEXTURE_IMAGE_UNITS: 0,
|
|
31
|
-
MAX_VERTEX_ATTRIBS: 0,
|
|
32
|
-
MAX_VARYING_VECTORS: 0,
|
|
33
|
-
MAX_VERTEX_UNIFORM_VECTORS: 0,
|
|
34
|
-
MAX_FRAGMENT_UNIFORM_VECTORS: 0,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Map over all parameters and get them
|
|
38
|
-
const keys = Object.keys(params) as Array<keyof CoreWebGlParameters>;
|
|
39
|
-
keys.forEach((key) => {
|
|
40
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
41
|
-
params[key] = glw.getParameter(glw[key]);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
return params;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface CoreWebGlExtensions {
|
|
48
|
-
ANGLE_instanced_arrays: ANGLE_instanced_arrays | null;
|
|
49
|
-
WEBGL_compressed_texture_s3tc: WEBGL_compressed_texture_s3tc | null;
|
|
50
|
-
WEBGL_compressed_texture_astc: WEBGL_compressed_texture_astc | null;
|
|
51
|
-
WEBGL_compressed_texture_etc: WEBGL_compressed_texture_etc | null;
|
|
52
|
-
WEBGL_compressed_texture_etc1: WEBGL_compressed_texture_etc1 | null;
|
|
53
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
-
WEBGL_compressed_texture_pvrtc: any | null;
|
|
55
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
|
-
WEBKIT_WEBGL_compressed_texture_pvrtc: any | null;
|
|
57
|
-
WEBGL_compressed_texture_s3tc_srgb: WEBGL_compressed_texture_s3tc_srgb | null;
|
|
58
|
-
OES_vertex_array_object: OES_vertex_array_object | null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Get device webgl extensions
|
|
63
|
-
* @param glw
|
|
64
|
-
*/
|
|
65
|
-
export function getWebGlExtensions(
|
|
66
|
-
glw: WebGlContextWrapper,
|
|
67
|
-
): CoreWebGlExtensions {
|
|
68
|
-
const extensions: CoreWebGlExtensions = {
|
|
69
|
-
ANGLE_instanced_arrays: null,
|
|
70
|
-
WEBGL_compressed_texture_s3tc: null,
|
|
71
|
-
WEBGL_compressed_texture_astc: null,
|
|
72
|
-
WEBGL_compressed_texture_etc: null,
|
|
73
|
-
WEBGL_compressed_texture_etc1: null,
|
|
74
|
-
WEBGL_compressed_texture_pvrtc: null,
|
|
75
|
-
WEBKIT_WEBGL_compressed_texture_pvrtc: null,
|
|
76
|
-
WEBGL_compressed_texture_s3tc_srgb: null,
|
|
77
|
-
OES_vertex_array_object: null,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// Map over all extensions and get them
|
|
81
|
-
const keys = Object.keys(extensions) as Array<keyof CoreWebGlExtensions>;
|
|
82
|
-
keys.forEach((key) => {
|
|
83
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
84
|
-
extensions[key] = glw.getExtension(key);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
return extensions;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
3
|
/**
|
|
91
4
|
* Allocate big memory chunk that we
|
|
92
5
|
* can re-use to draw quads
|
|
@@ -19,12 +19,6 @@ interface CanvasFont {
|
|
|
19
19
|
metrics?: FontMetrics;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
/**
|
|
23
|
-
* Global font set regardless of if run in the main thread or a web worker
|
|
24
|
-
*/
|
|
25
|
-
// const globalFontSet: FontFaceSet = (resolvedGlobal.document?.fonts ||
|
|
26
|
-
// (resolvedGlobal as unknown as { fonts: FontFaceSet }).fonts) as FontFaceSet;
|
|
27
|
-
|
|
28
22
|
// Global state variables for fontHandler
|
|
29
23
|
const fontFamilies: Record<string, FontFace> = {};
|
|
30
24
|
const fontLoadPromises = new Map<string, Promise<void>>();
|