@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
|
@@ -96,6 +96,12 @@ export class CoreShaderNode<Props extends object = Record<string, unknown>> {
|
|
|
96
96
|
protected node: CoreNode | null = null;
|
|
97
97
|
readonly time: CoreShaderType['time'] = undefined;
|
|
98
98
|
update: (() => void) | undefined = undefined;
|
|
99
|
+
/**
|
|
100
|
+
* The shader-value cache key currently held by this node (set by the
|
|
101
|
+
* subclass `update()` after the most recent successful value resolution).
|
|
102
|
+
* Tracked on the base so {@link detachNode} can release it on teardown.
|
|
103
|
+
*/
|
|
104
|
+
valueKey = '';
|
|
99
105
|
private _valueKeyCache = '';
|
|
100
106
|
private _valueKeyDirty = true;
|
|
101
107
|
private _lastW = 0;
|
|
@@ -162,6 +168,23 @@ export class CoreShaderNode<Props extends object = Record<string, unknown>> {
|
|
|
162
168
|
this.node = node;
|
|
163
169
|
}
|
|
164
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Release this node's currently-held shader-value cache entry so the shader
|
|
173
|
+
* manager's idle `cleanup()` can reclaim it, and detach from the owning node.
|
|
174
|
+
*
|
|
175
|
+
* Called from {@link CoreNode.destroy}. Mirrors the `prevKey` release done on
|
|
176
|
+
* every value-key change in the subclass `update()`; without it a destroyed
|
|
177
|
+
* node's last value-cache entry stays pinned at usage >= 1 forever and can
|
|
178
|
+
* never be evicted.
|
|
179
|
+
*/
|
|
180
|
+
detachNode() {
|
|
181
|
+
if (this.valueKey.length > 0) {
|
|
182
|
+
this.stage.shManager.mutateShaderValueUsage(this.valueKey, -1);
|
|
183
|
+
this.valueKey = '';
|
|
184
|
+
}
|
|
185
|
+
this.node = null;
|
|
186
|
+
}
|
|
187
|
+
|
|
165
188
|
createValueKey() {
|
|
166
189
|
if (
|
|
167
190
|
this._valueKeyDirty === false &&
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { CanvasRenderer } from './CanvasRenderer.js';
|
|
3
|
+
import { TextureType } from '../../textures/Texture.js';
|
|
4
|
+
|
|
5
|
+
describe('CanvasRenderer.renderContext', () => {
|
|
6
|
+
it('skips drawing when canvas texture image is undefined', () => {
|
|
7
|
+
const drawImage = vi.fn();
|
|
8
|
+
|
|
9
|
+
const renderer = Object.create(CanvasRenderer.prototype) as CanvasRenderer;
|
|
10
|
+
(renderer as any).context = {
|
|
11
|
+
drawImage,
|
|
12
|
+
globalAlpha: 1,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const node = {
|
|
16
|
+
premultipliedColorTl: 0xffffffff,
|
|
17
|
+
globalTransform: { tx: 10, ty: 20 },
|
|
18
|
+
props: { w: 100, h: 50 },
|
|
19
|
+
worldAlpha: 1,
|
|
20
|
+
textureCoords: { x1: 0, y1: 0, x2: 1, y2: 1 },
|
|
21
|
+
} as any;
|
|
22
|
+
|
|
23
|
+
const texture = {
|
|
24
|
+
type: TextureType.image,
|
|
25
|
+
ctxTexture: {
|
|
26
|
+
getImage: () => undefined,
|
|
27
|
+
},
|
|
28
|
+
} as any;
|
|
29
|
+
|
|
30
|
+
expect(() => renderer.renderContext(node, texture)).not.toThrow();
|
|
31
|
+
expect(drawImage).not.toHaveBeenCalled();
|
|
32
|
+
expect((renderer as any).context.globalAlpha).toBe(1);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -63,7 +63,19 @@ export class CanvasRenderer extends CoreRenderer {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const hasTransform = ta !== 1;
|
|
66
|
-
const
|
|
66
|
+
const clippingValid = clippingRect.valid === true;
|
|
67
|
+
|
|
68
|
+
// If the clipping rect is valid but zero-area, the node is fully clipped — skip rendering
|
|
69
|
+
if (
|
|
70
|
+
clippingValid === true &&
|
|
71
|
+
clippingRect.w === 0 &&
|
|
72
|
+
clippingRect.h === 0
|
|
73
|
+
) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const hasClipping =
|
|
78
|
+
clippingValid === true && clippingRect.w !== 0 && clippingRect.h !== 0;
|
|
67
79
|
const shader = node.props.shader;
|
|
68
80
|
const hasShader = shader !== null;
|
|
69
81
|
|
|
@@ -124,7 +136,7 @@ export class CanvasRenderer extends CoreRenderer {
|
|
|
124
136
|
|
|
125
137
|
if (textureType !== TextureType.color) {
|
|
126
138
|
const tintColor = parseColor(color);
|
|
127
|
-
let image: ImageBitmap | HTMLCanvasElement | HTMLImageElement;
|
|
139
|
+
let image: ImageBitmap | HTMLCanvasElement | HTMLImageElement | null;
|
|
128
140
|
|
|
129
141
|
if (textureType === TextureType.subTexture) {
|
|
130
142
|
image = (
|
|
@@ -134,12 +146,29 @@ export class CanvasRenderer extends CoreRenderer {
|
|
|
134
146
|
image = (texture.ctxTexture as CanvasTexture).getImage(tintColor);
|
|
135
147
|
}
|
|
136
148
|
|
|
149
|
+
// The texture can disappear while an async load/fetch is racing (e.g. 404);
|
|
150
|
+
// skip drawing this frame instead of dereferencing an invalid image object.
|
|
151
|
+
if (image === null || image === undefined) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const imageWidth = image.width;
|
|
156
|
+
const imageHeight = image.height;
|
|
157
|
+
if (
|
|
158
|
+
typeof imageWidth !== 'number' ||
|
|
159
|
+
typeof imageHeight !== 'number' ||
|
|
160
|
+
imageWidth <= 0 ||
|
|
161
|
+
imageHeight <= 0
|
|
162
|
+
) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
137
166
|
this.context.globalAlpha = tintColor.a ?? node.worldAlpha;
|
|
138
167
|
|
|
139
168
|
const txCoords = node.textureCoords;
|
|
140
169
|
if (txCoords) {
|
|
141
|
-
const ix =
|
|
142
|
-
const iy =
|
|
170
|
+
const ix = imageWidth;
|
|
171
|
+
const iy = imageHeight;
|
|
143
172
|
|
|
144
173
|
let sx = txCoords.x1 * ix;
|
|
145
174
|
let sy = txCoords.y1 * iy;
|
|
@@ -26,7 +26,6 @@ export class CanvasShaderNode<
|
|
|
26
26
|
> extends CoreShaderNode<Props> {
|
|
27
27
|
private updater: ((node: CoreNode, props?: Props) => void) | undefined =
|
|
28
28
|
undefined;
|
|
29
|
-
private valueKey: string = '';
|
|
30
29
|
computed: Partial<Computed> = {};
|
|
31
30
|
applySNR: boolean;
|
|
32
31
|
render: CanvasShaderType<Props>['render'];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { CanvasTexture } from './CanvasTexture.js';
|
|
3
|
+
|
|
4
|
+
describe('CanvasTexture.load', () => {
|
|
5
|
+
it('fails gracefully when textureData.data is null', async () => {
|
|
6
|
+
const textureSource = {
|
|
7
|
+
textureData: { data: null },
|
|
8
|
+
state: 'initial',
|
|
9
|
+
dimensions: null,
|
|
10
|
+
setState(nextState: string) {
|
|
11
|
+
this.state = nextState;
|
|
12
|
+
},
|
|
13
|
+
freeTextureData() {
|
|
14
|
+
// no-op
|
|
15
|
+
},
|
|
16
|
+
} as any;
|
|
17
|
+
|
|
18
|
+
const memManager = {
|
|
19
|
+
setTextureMemUse() {
|
|
20
|
+
// no-op
|
|
21
|
+
},
|
|
22
|
+
} as any;
|
|
23
|
+
|
|
24
|
+
const ctxTexture = new CanvasTexture(memManager, textureSource);
|
|
25
|
+
|
|
26
|
+
await expect(ctxTexture.load()).rejects.toThrow(
|
|
27
|
+
'CanvasTexture: Texture data is null',
|
|
28
|
+
);
|
|
29
|
+
expect(textureSource.state).toBe('failed');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -2,6 +2,7 @@ import type { Dimensions } from '../../../common/CommonTypes.js';
|
|
|
2
2
|
import { assertTruthy } from '../../../utils.js';
|
|
3
3
|
import { formatRgba, type IParsedColor } from '../../lib/colorParser.js';
|
|
4
4
|
import { CoreContextTexture } from '../CoreContextTexture.js';
|
|
5
|
+
import type { Texture } from '../../textures/Texture.js';
|
|
5
6
|
|
|
6
7
|
export class CanvasTexture extends CoreContextTexture {
|
|
7
8
|
protected image:
|
|
@@ -17,10 +18,23 @@ export class CanvasTexture extends CoreContextTexture {
|
|
|
17
18
|
| undefined;
|
|
18
19
|
|
|
19
20
|
async load(): Promise<void> {
|
|
21
|
+
// Capture textureData synchronously before any await - a pending
|
|
22
|
+
// freeTextureDataTask microtask could null textureSource.textureData
|
|
23
|
+
// during the first async suspension, causing onLoadRequest to fail.
|
|
24
|
+
const textureData = this.textureSource.textureData;
|
|
25
|
+
assertTruthy(textureData?.data, 'Texture data is null before load');
|
|
26
|
+
|
|
20
27
|
this.textureSource.setState('loading');
|
|
21
28
|
|
|
22
29
|
try {
|
|
23
|
-
const size = await this.onLoadRequest();
|
|
30
|
+
const size = await this.onLoadRequest(textureData.data);
|
|
31
|
+
|
|
32
|
+
// Guard against the texture being freed while the load was in flight
|
|
33
|
+
if (this.textureSource.state === 'freed') {
|
|
34
|
+
this.image = undefined;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
24
38
|
this.textureSource.setState('loaded', size);
|
|
25
39
|
this.textureSource.freeTextureData();
|
|
26
40
|
this.updateMemSize();
|
|
@@ -63,9 +77,11 @@ export class CanvasTexture extends CoreContextTexture {
|
|
|
63
77
|
|
|
64
78
|
getImage(
|
|
65
79
|
color: IParsedColor,
|
|
66
|
-
): ImageBitmap | HTMLCanvasElement | HTMLImageElement {
|
|
80
|
+
): ImageBitmap | HTMLCanvasElement | HTMLImageElement | null {
|
|
67
81
|
const image = this.image;
|
|
68
|
-
|
|
82
|
+
if (image === undefined) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
69
85
|
|
|
70
86
|
if (color.isWhite) {
|
|
71
87
|
if (this.tintCache) {
|
|
@@ -114,9 +130,21 @@ export class CanvasTexture extends CoreContextTexture {
|
|
|
114
130
|
return canvas;
|
|
115
131
|
}
|
|
116
132
|
|
|
117
|
-
private async onLoadRequest(
|
|
118
|
-
|
|
119
|
-
|
|
133
|
+
private async onLoadRequest(
|
|
134
|
+
data: NonNullable<Texture['textureData']>['data'],
|
|
135
|
+
): Promise<Dimensions> {
|
|
136
|
+
if (data === null) {
|
|
137
|
+
throw new Error('CanvasTexture: Texture data is null');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// CompressedData objects (KTX, PVR, ASTC) carry GPU-format mipmap buffers
|
|
141
|
+
// that cannot be decoded by Canvas2D. Reject explicitly rather than falling
|
|
142
|
+
// through silently and leaving this.image unassigned.
|
|
143
|
+
if (typeof data === 'object' && 'mipmaps' in data) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
'CanvasTexture: Compressed texture data is not supported in Canvas2D render mode',
|
|
146
|
+
);
|
|
147
|
+
}
|
|
120
148
|
|
|
121
149
|
// TODO: canvas from text renderer should be able to provide the canvas directly
|
|
122
150
|
// instead of having to re-draw it into a new canvas...
|
|
@@ -125,7 +153,7 @@ export class CanvasTexture extends CoreContextTexture {
|
|
|
125
153
|
canvas.width = data.width;
|
|
126
154
|
canvas.height = data.height;
|
|
127
155
|
const ctx = canvas.getContext('2d');
|
|
128
|
-
if (ctx) ctx.putImageData(data, 0, 0);
|
|
156
|
+
if (ctx !== null) ctx.putImageData(data, 0, 0);
|
|
129
157
|
this.image = canvas;
|
|
130
158
|
return { w: data.width, h: data.height };
|
|
131
159
|
} else if (
|
|
@@ -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
|
|
|
@@ -53,7 +52,6 @@ export class WebGlShaderNode<
|
|
|
53
52
|
readonly program: WebGlShaderProgram;
|
|
54
53
|
private updater: ((node: CoreNode, props?: Props) => void) | undefined =
|
|
55
54
|
undefined;
|
|
56
|
-
private valueKey: string = '';
|
|
57
55
|
uniforms: UniformCollection = {
|
|
58
56
|
single: {},
|
|
59
57
|
vec2: {},
|
|
@@ -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>>();
|
|
@@ -40,10 +40,22 @@ const layoutCache = new Map<
|
|
|
40
40
|
}
|
|
41
41
|
>();
|
|
42
42
|
|
|
43
|
+
// Upper bound on layoutCache entries, enforced on idle via `cleanup`.
|
|
44
|
+
// Overridden from stage options in `init`. Note: the Canvas path does not
|
|
45
|
+
// currently populate `layoutCache`, so this is effectively inert today and
|
|
46
|
+
// exists to keep the eviction policy uniform with the SDF backend should
|
|
47
|
+
// Canvas layout caching be wired up later.
|
|
48
|
+
let maxLayoutCacheSize = 250;
|
|
49
|
+
|
|
43
50
|
// Initialize the Text Renderer
|
|
44
51
|
const init = (stage: Stage): void => {
|
|
45
52
|
const dpr = stage.options.devicePhysicalPixelRatio;
|
|
46
53
|
|
|
54
|
+
const configuredCacheSize = stage.options.textLayoutCacheSize;
|
|
55
|
+
if (configuredCacheSize !== undefined) {
|
|
56
|
+
maxLayoutCacheSize = configuredCacheSize;
|
|
57
|
+
}
|
|
58
|
+
|
|
47
59
|
// Drawing canvas and context
|
|
48
60
|
canvas = stage.platform.createCanvas() as HTMLCanvasElement | OffscreenCanvas;
|
|
49
61
|
context = canvas.getContext('2d', { willReadFrequently: true }) as
|
|
@@ -224,6 +236,19 @@ const clearLayoutCache = (): void => {
|
|
|
224
236
|
layoutCache.clear();
|
|
225
237
|
};
|
|
226
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Trim the layout cache back down to `maxLayoutCacheSize`, evicting the
|
|
241
|
+
* least-recently-used entries first. Called when the stage goes idle. The
|
|
242
|
+
* Canvas path does not currently populate `layoutCache`, so this is a no-op in
|
|
243
|
+
* practice today; it mirrors the SDF backend's eviction policy.
|
|
244
|
+
*/
|
|
245
|
+
const cleanup = (): void => {
|
|
246
|
+
while (layoutCache.size > maxLayoutCacheSize) {
|
|
247
|
+
const oldest = layoutCache.keys().next().value as string;
|
|
248
|
+
layoutCache.delete(oldest);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
227
252
|
/**
|
|
228
253
|
* Add quads for rendering (Canvas doesn't use quads)
|
|
229
254
|
*/
|
|
@@ -252,6 +277,7 @@ const CanvasTextRenderer = {
|
|
|
252
277
|
renderQuads,
|
|
253
278
|
init,
|
|
254
279
|
clearLayoutCache,
|
|
280
|
+
cleanup,
|
|
255
281
|
};
|
|
256
282
|
|
|
257
283
|
export default CanvasTextRenderer;
|