@luma.gl/core 9.1.0-beta.8 → 9.1.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/adapter/canvas-context.d.ts +30 -73
- package/dist/adapter/canvas-context.d.ts.map +1 -1
- package/dist/adapter/canvas-context.js +97 -203
- package/dist/adapter/canvas-context.js.map +1 -1
- package/dist/adapter/device.d.ts +3 -24
- package/dist/adapter/device.d.ts.map +1 -1
- package/dist/adapter/device.js +10 -22
- package/dist/adapter/device.js.map +1 -1
- package/dist/adapter/luma.js +1 -1
- package/dist/adapter/luma.js.map +1 -1
- package/dist/adapter/resources/render-pipeline.d.ts +1 -6
- package/dist/adapter/resources/render-pipeline.d.ts.map +1 -1
- package/dist/adapter/resources/render-pipeline.js +4 -2
- package/dist/adapter/resources/render-pipeline.js.map +1 -1
- package/dist/adapter/resources/vertex-array.d.ts +2 -4
- package/dist/adapter/resources/vertex-array.d.ts.map +1 -1
- package/dist/adapter/resources/vertex-array.js +6 -3
- package/dist/adapter/resources/vertex-array.js.map +1 -1
- package/dist/dist.dev.js +115 -210
- package/dist/dist.min.js +6 -6
- package/dist/gpu-type-utils/texture-format-table.js +1 -1
- package/dist/gpu-type-utils/texture-format-table.js.map +1 -1
- package/dist/gpu-type-utils/texture-formats.d.ts +2 -2
- package/dist/gpu-type-utils/texture-formats.d.ts.map +1 -1
- package/dist/index.cjs +117 -206
- package/dist/index.cjs.map +3 -3
- package/package.json +2 -2
- package/src/adapter/canvas-context.ts +122 -272
- package/src/adapter/device.ts +15 -50
- package/src/adapter/resources/render-pipeline.ts +12 -18
- package/src/adapter/resources/vertex-array.ts +9 -8
- package/src/gpu-type-utils/texture-format-table.ts +1 -1
- package/src/gpu-type-utils/texture-formats.ts +1 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luma.gl/core",
|
|
3
|
-
"version": "9.1.0
|
|
3
|
+
"version": "9.1.0",
|
|
4
4
|
"description": "The luma.gl core Device API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"@probe.gl/stats": "^4.0.8",
|
|
47
47
|
"@types/offscreencanvas": "^2019.6.4"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "e5cb5a93674bc7cc571c26dee4d2aef841fdff7f"
|
|
50
50
|
}
|
|
@@ -7,12 +7,10 @@ import type {Device} from './device';
|
|
|
7
7
|
import type {Framebuffer} from './resources/framebuffer';
|
|
8
8
|
import {log} from '../utils/log';
|
|
9
9
|
import {uid} from '../utils/uid';
|
|
10
|
-
import type {
|
|
10
|
+
import type {TextureFormat} from '../gpu-type-utils/texture-formats';
|
|
11
11
|
|
|
12
12
|
/** Properties for a CanvasContext */
|
|
13
13
|
export type CanvasContextProps = {
|
|
14
|
-
/** Identifier, for debugging */
|
|
15
|
-
id?: string;
|
|
16
14
|
/** If a canvas not supplied, one will be created and added to the DOM. If a string, a canvas with that id will be looked up in the DOM */
|
|
17
15
|
canvas?: HTMLCanvasElement | OffscreenCanvas | string | null;
|
|
18
16
|
/** If new canvas is created, it will be created in the specified container, otherwise is appended as a child of document.body */
|
|
@@ -42,9 +40,8 @@ export type CanvasContextProps = {
|
|
|
42
40
|
*/
|
|
43
41
|
export abstract class CanvasContext {
|
|
44
42
|
static defaultProps: Required<CanvasContextProps> = {
|
|
45
|
-
id: undefined!,
|
|
46
43
|
canvas: null,
|
|
47
|
-
width: 800,
|
|
44
|
+
width: 800, // width are height are only used by headless gl
|
|
48
45
|
height: 600,
|
|
49
46
|
useDevicePixels: true,
|
|
50
47
|
autoResize: true,
|
|
@@ -55,41 +52,24 @@ export abstract class CanvasContext {
|
|
|
55
52
|
};
|
|
56
53
|
|
|
57
54
|
abstract readonly device: Device;
|
|
58
|
-
abstract readonly handle: unknown;
|
|
59
55
|
readonly id: string;
|
|
60
|
-
|
|
61
56
|
readonly props: Required<CanvasContextProps>;
|
|
62
57
|
readonly canvas: HTMLCanvasElement | OffscreenCanvas;
|
|
63
58
|
readonly htmlCanvas?: HTMLCanvasElement;
|
|
64
59
|
readonly offscreenCanvas?: OffscreenCanvas;
|
|
65
60
|
readonly type: 'html-canvas' | 'offscreen-canvas' | 'node';
|
|
66
61
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
isInitialized: boolean = false;
|
|
72
|
-
|
|
73
|
-
/** Visibility is automatically updated (via an IntersectionObserver) */
|
|
74
|
-
isVisible: boolean = true;
|
|
75
|
-
|
|
76
|
-
/** Device pixel ratio. Automatically updated via media queries */
|
|
77
|
-
devicePixelRatio: number;
|
|
62
|
+
/** Format of returned textures: "bgra8unorm", "rgba8unorm" */
|
|
63
|
+
abstract readonly format: TextureFormat;
|
|
64
|
+
/** Default stencil format for depth textures */
|
|
65
|
+
abstract readonly depthStencilFormat: TextureFormat;
|
|
78
66
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
/** Exact height of canvas in physical pixels (tracked by a ResizeObserver) */
|
|
82
|
-
pixelHeight: number;
|
|
67
|
+
width: number = 1;
|
|
68
|
+
height: number = 1;
|
|
83
69
|
|
|
84
|
-
|
|
85
|
-
drawingBufferWidth: number;
|
|
86
|
-
/** Height of drawing buffer: automatically updated if props.autoResize is true */
|
|
87
|
-
drawingBufferHeight: number;
|
|
70
|
+
readonly resizeObserver: ResizeObserver | undefined;
|
|
88
71
|
|
|
89
|
-
|
|
90
|
-
protected readonly _intersectionObserver: IntersectionObserver | undefined;
|
|
91
|
-
|
|
92
|
-
/** State used by luma.gl classes: TODO - remove */
|
|
72
|
+
/** State used by luma.gl classes: TODO - move to canvasContext*/
|
|
93
73
|
readonly _canvasSizeInfo = {clientWidth: 0, clientHeight: 0, devicePixelRatio: 1};
|
|
94
74
|
|
|
95
75
|
abstract get [Symbol.toStringTag](): string;
|
|
@@ -102,121 +82,61 @@ export abstract class CanvasContext {
|
|
|
102
82
|
this.props = {...CanvasContext.defaultProps, ...props};
|
|
103
83
|
props = this.props;
|
|
104
84
|
|
|
105
|
-
this.initialized = this._initializedResolvers.promise;
|
|
106
|
-
|
|
107
|
-
// Create a canvas element if needed
|
|
108
85
|
if (!isBrowser()) {
|
|
86
|
+
this.id = 'node-canvas-context';
|
|
87
|
+
this.type = 'node';
|
|
88
|
+
this.width = this.props.width;
|
|
89
|
+
this.height = this.props.height;
|
|
109
90
|
// TODO - does this prevent app from using jsdom style polyfills?
|
|
110
|
-
this.canvas =
|
|
111
|
-
|
|
112
|
-
|
|
91
|
+
this.canvas = null!;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!props.canvas) {
|
|
96
|
+
const canvas = createCanvas(props);
|
|
97
|
+
const container = getContainer(props?.container || null);
|
|
98
|
+
container.insertBefore(canvas, container.firstChild);
|
|
99
|
+
|
|
100
|
+
this.canvas = canvas;
|
|
101
|
+
|
|
102
|
+
if (!props?.visible) {
|
|
103
|
+
this.canvas.style.visibility = 'hidden';
|
|
104
|
+
}
|
|
113
105
|
} else if (typeof props.canvas === 'string') {
|
|
114
106
|
this.canvas = getCanvasFromDOM(props.canvas);
|
|
115
107
|
} else {
|
|
116
108
|
this.canvas = props.canvas;
|
|
117
109
|
}
|
|
118
110
|
|
|
119
|
-
if (
|
|
120
|
-
this.id =
|
|
111
|
+
if (this.canvas instanceof HTMLCanvasElement) {
|
|
112
|
+
this.id = this.canvas.id;
|
|
121
113
|
this.type = 'html-canvas';
|
|
122
114
|
this.htmlCanvas = this.canvas;
|
|
123
|
-
} else
|
|
124
|
-
this.id =
|
|
115
|
+
} else {
|
|
116
|
+
this.id = 'offscreen-canvas';
|
|
125
117
|
this.type = 'offscreen-canvas';
|
|
126
118
|
this.offscreenCanvas = this.canvas;
|
|
127
|
-
} else {
|
|
128
|
-
// TODO - Node.js support is currently untested (was used for headless-gl in luma v8)
|
|
129
|
-
this.id = props.id || 'node-canvas-context';
|
|
130
|
-
this.type = 'node';
|
|
131
119
|
}
|
|
132
120
|
|
|
133
|
-
//
|
|
134
|
-
this.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
this.
|
|
143
|
-
this._handleIntersection(entries)
|
|
144
|
-
);
|
|
145
|
-
this._intersectionObserver.observe(this.canvas);
|
|
146
|
-
|
|
147
|
-
// Track size changes
|
|
148
|
-
this._resizeObserver = new ResizeObserver(entries => this._handleResize(entries));
|
|
149
|
-
try {
|
|
150
|
-
this._resizeObserver.observe(this.canvas, {box: 'device-pixel-content-box'});
|
|
151
|
-
} catch {
|
|
152
|
-
// Safari fallback
|
|
153
|
-
this._resizeObserver.observe(this.canvas, {box: 'content-box'});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Track device pixel ratio changes.
|
|
157
|
-
// Defer call to after construction completes to ensure `this.device` is available.
|
|
158
|
-
setTimeout(() => this._observeDevicePixelRatio(), 0);
|
|
121
|
+
// React to size changes
|
|
122
|
+
if (this.canvas instanceof HTMLCanvasElement && props.autoResize) {
|
|
123
|
+
this.resizeObserver = new ResizeObserver(entries => {
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
if (entry.target === this.canvas) {
|
|
126
|
+
this.update();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
this.resizeObserver.observe(this.canvas);
|
|
159
131
|
}
|
|
160
132
|
}
|
|
161
133
|
|
|
162
134
|
/** Returns a framebuffer with properly resized current 'swap chain' textures */
|
|
163
|
-
abstract getCurrentFramebuffer(
|
|
164
|
-
depthStencilFormat?: DepthStencilTextureFormat | false;
|
|
165
|
-
}): Framebuffer;
|
|
166
|
-
|
|
167
|
-
// SIZE METHODS
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Returns the size covered by the canvas in CSS pixels
|
|
171
|
-
* @note This can be different from the actual device pixel size of a canvas due to DPR scaling, and rounding to integer pixels
|
|
172
|
-
* @note This is independent of the canvas' internal drawing buffer size (.width, .height).
|
|
173
|
-
*/
|
|
174
|
-
getCSSSize(): [number, number] {
|
|
175
|
-
if (typeof HTMLCanvasElement !== 'undefined' && this.canvas instanceof HTMLCanvasElement) {
|
|
176
|
-
return [this.canvas.clientWidth, this.canvas.clientHeight];
|
|
177
|
-
}
|
|
178
|
-
return [this.pixelWidth, this.pixelHeight];
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Returns the size covered by the canvas in actual device pixels.
|
|
183
|
-
* @note This can be different from the 'CSS' size of a canvas due to DPR scaling, and rounding to integer pixels
|
|
184
|
-
* @note This is independent of the canvas' internal drawing buffer size (.width, .height).
|
|
185
|
-
*/
|
|
186
|
-
getPixelSize(): [number, number] {
|
|
187
|
-
return [this.pixelWidth, this.pixelHeight];
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** Get the drawing buffer size (number of pixels GPU is rendering into, can be different from CSS size) */
|
|
191
|
-
getDrawingBufferSize(): [number, number] {
|
|
192
|
-
return [this.drawingBufferWidth, this.drawingBufferHeight];
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** Returns the biggest allowed framebuffer size. @todo Allow the application to limit this? */
|
|
196
|
-
getMaxDrawingBufferSize(): [number, number] {
|
|
197
|
-
const maxTextureDimension = this.device.limits.maxTextureDimension2D;
|
|
198
|
-
return [maxTextureDimension, maxTextureDimension];
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** Update the canvas drawing buffer size. Called automatically if props.autoResize is true. */
|
|
202
|
-
setDrawingBufferSize(width: number, height: number) {
|
|
203
|
-
this.canvas.width = width;
|
|
204
|
-
this.canvas.height = height;
|
|
205
|
-
|
|
206
|
-
this.drawingBufferWidth = width;
|
|
207
|
-
this.drawingBufferHeight = height;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/** @deprecated - TODO which values should we use for aspect */
|
|
211
|
-
getAspect(): number {
|
|
212
|
-
const [width, height] = this.getPixelSize();
|
|
213
|
-
return width / height;
|
|
214
|
-
}
|
|
135
|
+
abstract getCurrentFramebuffer(): Framebuffer;
|
|
215
136
|
|
|
216
137
|
/**
|
|
217
|
-
* Returns the current DPR
|
|
218
|
-
*
|
|
219
|
-
* @note This function handles the non-HTML canvas cases
|
|
138
|
+
* Returns the current DPR, if props.useDevicePixels is true
|
|
139
|
+
* Device refers to physical
|
|
220
140
|
*/
|
|
221
141
|
getDevicePixelRatio(useDevicePixels?: boolean | number): number {
|
|
222
142
|
if (typeof OffscreenCanvas !== 'undefined' && this.canvas instanceof OffscreenCanvas) {
|
|
@@ -238,6 +158,35 @@ export abstract class CanvasContext {
|
|
|
238
158
|
return useDevicePixels;
|
|
239
159
|
}
|
|
240
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Returns the size of drawing buffer in device pixels.
|
|
163
|
+
* @note This can be different from the 'CSS' size of a canvas, and also from the
|
|
164
|
+
* canvas' internal drawing buffer size (.width, .height).
|
|
165
|
+
* This is the size required to cover the canvas, adjusted for DPR
|
|
166
|
+
*/
|
|
167
|
+
getPixelSize(): [number, number] {
|
|
168
|
+
switch (this.type) {
|
|
169
|
+
case 'node':
|
|
170
|
+
return [this.width, this.height];
|
|
171
|
+
case 'offscreen-canvas':
|
|
172
|
+
return [this.canvas.width, this.canvas.height];
|
|
173
|
+
case 'html-canvas':
|
|
174
|
+
const dpr = this.getDevicePixelRatio();
|
|
175
|
+
const canvas = this.canvas as HTMLCanvasElement;
|
|
176
|
+
// If not attached to DOM client size can be 0
|
|
177
|
+
return canvas.parentElement
|
|
178
|
+
? [canvas.clientWidth * dpr, canvas.clientHeight * dpr]
|
|
179
|
+
: [this.canvas.width, this.canvas.height];
|
|
180
|
+
default:
|
|
181
|
+
throw new Error(this.type);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
getAspect(): number {
|
|
186
|
+
const [width, height] = this.getPixelSize();
|
|
187
|
+
return width / height;
|
|
188
|
+
}
|
|
189
|
+
|
|
241
190
|
/**
|
|
242
191
|
* Returns multiplier need to convert CSS size to Device size
|
|
243
192
|
*/
|
|
@@ -246,7 +195,8 @@ export abstract class CanvasContext {
|
|
|
246
195
|
// For headless gl we might have used custom width and height
|
|
247
196
|
// hence use cached clientWidth
|
|
248
197
|
const [drawingBufferWidth] = this.getDrawingBufferSize();
|
|
249
|
-
|
|
198
|
+
// _canvasSizeInfo may not be populated if `setDevicePixelRatio` is never called
|
|
199
|
+
const clientWidth = this._canvasSizeInfo.clientWidth || this.htmlCanvas?.clientWidth;
|
|
250
200
|
return clientWidth ? drawingBufferWidth / clientWidth : 1;
|
|
251
201
|
} catch {
|
|
252
202
|
return 1;
|
|
@@ -270,114 +220,11 @@ export abstract class CanvasContext {
|
|
|
270
220
|
return scalePixels(cssPixel, ratio, width, height, yInvert);
|
|
271
221
|
}
|
|
272
222
|
|
|
273
|
-
// SUBCLASS OVERRIDES
|
|
274
|
-
|
|
275
|
-
/** Performs platform specific updates (WebGPU vs WebGL) */
|
|
276
|
-
protected abstract updateSize(size: [width: number, height: number]): void;
|
|
277
|
-
|
|
278
|
-
// IMPLEMENTATION
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Allows subclass constructor to override the canvas id for auto created canvases.
|
|
282
|
-
* This can really help when debugging DOM in apps that create multiple devices
|
|
283
|
-
*/
|
|
284
|
-
protected _setAutoCreatedCanvasId(id: string) {
|
|
285
|
-
if (this.htmlCanvas?.id === 'lumagl-auto-created-canvas') {
|
|
286
|
-
this.htmlCanvas.id = id;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/** reacts to our intersection observer */
|
|
291
|
-
protected _handleIntersection(entries: IntersectionObserverEntry[]) {
|
|
292
|
-
const entry = entries.find(entry_ => entry_.target === this.canvas);
|
|
293
|
-
if (!entry) {
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
// TODO - store intersection rectangle?
|
|
297
|
-
const isVisible = entry.isIntersecting;
|
|
298
|
-
if (this.isVisible !== isVisible) {
|
|
299
|
-
this.isVisible = isVisible;
|
|
300
|
-
this.device.props.onVisibilityChange(this);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Reacts to an observed resize by using the most accurate pixel size information the browser can provide
|
|
306
|
-
* @see https://web.dev/articles/device-pixel-content-box
|
|
307
|
-
* @see https://webgpufundamentals.org/webgpu/lessons/webgpu-resizing-the-canvas.html
|
|
308
|
-
*/
|
|
309
|
-
protected _handleResize(entries: ResizeObserverEntry[]) {
|
|
310
|
-
const entry = entries.find(entry_ => entry_.target === this.canvas);
|
|
311
|
-
if (!entry) {
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Use the most accurate drawing buffer size information the current browser can provide
|
|
316
|
-
// Note: content box sizes are guaranteed to be integers
|
|
317
|
-
// Note: Safari falls back to contentBoxSize
|
|
318
|
-
const boxWidth =
|
|
319
|
-
entry.devicePixelContentBoxSize?.[0].inlineSize ||
|
|
320
|
-
entry.contentBoxSize[0].inlineSize * devicePixelRatio;
|
|
321
|
-
|
|
322
|
-
const boxHeight =
|
|
323
|
-
entry.devicePixelContentBoxSize?.[0].blockSize ||
|
|
324
|
-
entry.contentBoxSize[0].blockSize * devicePixelRatio;
|
|
325
|
-
|
|
326
|
-
// Update our drawing buffer size variables, saving the old values for logging
|
|
327
|
-
const oldPixelSize = this.getPixelSize();
|
|
328
|
-
|
|
329
|
-
// Make sure we don't overflow the maximum supported texture size
|
|
330
|
-
const [maxPixelWidth, maxPixelHeight] = this.getMaxDrawingBufferSize();
|
|
331
|
-
this.pixelWidth = Math.max(1, Math.min(boxWidth, maxPixelWidth));
|
|
332
|
-
this.pixelHeight = Math.max(1, Math.min(boxHeight, maxPixelHeight));
|
|
333
|
-
|
|
334
|
-
if (this.props.autoResize) {
|
|
335
|
-
// Update the canvas drawing buffer size
|
|
336
|
-
// TODO - This does not account for props.useDevicePixels
|
|
337
|
-
this.setDrawingBufferSize(this.pixelWidth, this.pixelHeight);
|
|
338
|
-
|
|
339
|
-
// Inform the subclass
|
|
340
|
-
this.updateSize(this.getDrawingBufferSize());
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Resolve the initialized promise
|
|
344
|
-
this._initializedResolvers.resolve();
|
|
345
|
-
this.isInitialized = true;
|
|
346
|
-
|
|
347
|
-
// Inform the device
|
|
348
|
-
this.device.props.onResize(this, {oldPixelSize});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/** Monitor DPR changes */
|
|
352
|
-
_observeDevicePixelRatio() {
|
|
353
|
-
const oldRatio = this.devicePixelRatio;
|
|
354
|
-
this.devicePixelRatio = window.devicePixelRatio;
|
|
355
|
-
|
|
356
|
-
// Inform the device
|
|
357
|
-
this.device.props.onDevicePixelRatioChange(this, {oldRatio});
|
|
358
|
-
// Set up a one time query against the current resolution.
|
|
359
|
-
matchMedia(`(resolution: ${this.devicePixelRatio}dppx)`).addEventListener(
|
|
360
|
-
'change',
|
|
361
|
-
() => this._observeDevicePixelRatio(),
|
|
362
|
-
{once: true}
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// DEPRECATED
|
|
367
|
-
|
|
368
|
-
/** @deprecated Use canvasContext.setDrawingBufferSize()
|
|
369
|
-
* Resizes the canvas. Note: Has no effect if props.autoResize is true */
|
|
370
|
-
abstract resize(options?: {
|
|
371
|
-
width?: number;
|
|
372
|
-
height?: number;
|
|
373
|
-
useDevicePixels?: boolean | number;
|
|
374
|
-
}): void;
|
|
375
|
-
|
|
376
223
|
/**
|
|
377
|
-
*
|
|
224
|
+
* Use devicePixelRatio to set canvas width and height
|
|
378
225
|
* @note this is a raw port of luma.gl v8 code. Might be worth a review
|
|
379
226
|
*/
|
|
380
|
-
|
|
227
|
+
setDevicePixelRatio(
|
|
381
228
|
devicePixelRatio: number,
|
|
382
229
|
options: {width?: number; height?: number} = {}
|
|
383
230
|
): void {
|
|
@@ -437,11 +284,42 @@ export abstract class CanvasContext {
|
|
|
437
284
|
}
|
|
438
285
|
}
|
|
439
286
|
}
|
|
287
|
+
|
|
288
|
+
// PRIVATE
|
|
289
|
+
|
|
290
|
+
/** @todo Major hack done to port the CSS methods above, base canvas context should not depend on WebGL */
|
|
291
|
+
getDrawingBufferSize(): [number, number] {
|
|
292
|
+
// @ts-expect-error This only works for WebGL
|
|
293
|
+
const gl = this.device.gl;
|
|
294
|
+
if (!gl) {
|
|
295
|
+
// use default device pixel ratio
|
|
296
|
+
throw new Error('canvas size');
|
|
297
|
+
}
|
|
298
|
+
return [gl.drawingBufferWidth, gl.drawingBufferHeight];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
abstract resize(options?: {
|
|
302
|
+
width?: number;
|
|
303
|
+
height?: number;
|
|
304
|
+
useDevicePixels?: boolean | number;
|
|
305
|
+
}): void;
|
|
306
|
+
|
|
307
|
+
/** Perform platform specific updates (WebGPU vs WebGL) */
|
|
308
|
+
protected abstract update(): void;
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Allows subclass constructor to override the canvas id for auto created canvases.
|
|
312
|
+
* This can really help when debugging DOM in apps that create multiple devices
|
|
313
|
+
*/
|
|
314
|
+
protected _setAutoCreatedCanvasId(id: string) {
|
|
315
|
+
if (this.htmlCanvas?.id === 'lumagl-auto-created-canvas') {
|
|
316
|
+
this.htmlCanvas.id = id;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
440
319
|
}
|
|
441
320
|
|
|
442
321
|
// HELPER FUNCTIONS
|
|
443
322
|
|
|
444
|
-
/** Get a container element from a string or DOM element */
|
|
445
323
|
function getContainer(container: HTMLElement | string | null): HTMLElement {
|
|
446
324
|
if (typeof container === 'string') {
|
|
447
325
|
const element = document.getElementById(container);
|
|
@@ -449,8 +327,7 @@ function getContainer(container: HTMLElement | string | null): HTMLElement {
|
|
|
449
327
|
throw new Error(`${container} is not an HTML element`);
|
|
450
328
|
}
|
|
451
329
|
return element;
|
|
452
|
-
}
|
|
453
|
-
if (container) {
|
|
330
|
+
} else if (container) {
|
|
454
331
|
return container;
|
|
455
332
|
}
|
|
456
333
|
return document.body;
|
|
@@ -459,37 +336,26 @@ function getContainer(container: HTMLElement | string | null): HTMLElement {
|
|
|
459
336
|
/** Get a Canvas element from DOM id */
|
|
460
337
|
function getCanvasFromDOM(canvasId: string): HTMLCanvasElement {
|
|
461
338
|
const canvas = document.getElementById(canvasId);
|
|
462
|
-
if (
|
|
463
|
-
typeof canvas !== 'undefined' &&
|
|
464
|
-
typeof HTMLCanvasElement !== 'undefined' &&
|
|
465
|
-
!(canvas instanceof HTMLCanvasElement)
|
|
466
|
-
) {
|
|
339
|
+
if (!(canvas instanceof HTMLCanvasElement)) {
|
|
467
340
|
throw new Error('Object is not a canvas element');
|
|
468
341
|
}
|
|
469
|
-
return canvas
|
|
342
|
+
return canvas;
|
|
470
343
|
}
|
|
471
344
|
|
|
472
345
|
/** Create a new canvas */
|
|
473
|
-
function
|
|
346
|
+
function createCanvas(props: CanvasContextProps) {
|
|
474
347
|
const {width, height} = props;
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
newCanvas.style.visibility = 'hidden';
|
|
483
|
-
}
|
|
484
|
-
// Insert the canvas in the DOM
|
|
485
|
-
const container = getContainer(props?.container || null);
|
|
486
|
-
container.insertBefore(newCanvas, container.firstChild);
|
|
487
|
-
|
|
488
|
-
return newCanvas;
|
|
348
|
+
const targetCanvas = document.createElement('canvas');
|
|
349
|
+
targetCanvas.id = uid('lumagl-auto-created-canvas');
|
|
350
|
+
targetCanvas.width = width || 1;
|
|
351
|
+
targetCanvas.height = height || 1;
|
|
352
|
+
targetCanvas.style.width = Number.isFinite(width) ? `${width}px` : '100%';
|
|
353
|
+
targetCanvas.style.height = Number.isFinite(height) ? `${height}px` : '100%';
|
|
354
|
+
return targetCanvas;
|
|
489
355
|
}
|
|
490
356
|
|
|
491
357
|
/**
|
|
492
|
-
*
|
|
358
|
+
*
|
|
493
359
|
* @param pixel
|
|
494
360
|
* @param ratio
|
|
495
361
|
* @param width
|
|
@@ -554,19 +420,3 @@ function scaleY(y: number, ratio: number, height: number, yInvert: boolean): num
|
|
|
554
420
|
? Math.max(0, height - 1 - Math.round(y * ratio))
|
|
555
421
|
: Math.min(Math.round(y * ratio), height - 1);
|
|
556
422
|
}
|
|
557
|
-
|
|
558
|
-
// TODO - replace with Promise.withResolvers once we upgrade TS baseline
|
|
559
|
-
function withResolvers<T>(): {
|
|
560
|
-
promise: Promise<T>;
|
|
561
|
-
resolve: (t: T) => void;
|
|
562
|
-
reject: (error: Error) => void;
|
|
563
|
-
} {
|
|
564
|
-
let resolve: (t: T) => void;
|
|
565
|
-
let reject: (error: Error) => void;
|
|
566
|
-
const promise = new Promise<T>((_resolve, _reject) => {
|
|
567
|
-
resolve = _resolve;
|
|
568
|
-
reject = _reject;
|
|
569
|
-
});
|
|
570
|
-
// @ts-expect-error - in fact these are no used before initialized
|
|
571
|
-
return {promise, resolve, reject};
|
|
572
|
-
}
|
package/src/adapter/device.ts
CHANGED
|
@@ -219,21 +219,12 @@ export type DeviceProps = {
|
|
|
219
219
|
powerPreference?: 'default' | 'high-performance' | 'low-power';
|
|
220
220
|
/** Hints that device creation should fail if no hardware GPU is available (if the system performance is "low"). */
|
|
221
221
|
failIfMajorPerformanceCaveat?: boolean;
|
|
222
|
+
/** Error handling */
|
|
223
|
+
onError?: (error: Error) => unknown;
|
|
222
224
|
|
|
223
225
|
/** WebGL specific: Properties passed through to WebGL2RenderingContext creation: `canvas.getContext('webgl2', props.webgl)` */
|
|
224
226
|
webgl?: WebGLContextProps;
|
|
225
227
|
|
|
226
|
-
// CALLBACKS
|
|
227
|
-
|
|
228
|
-
/** Error handling - uncaught errors */
|
|
229
|
-
onError?: (error: Error) => unknown;
|
|
230
|
-
/** Called when the size of a canvas changes */
|
|
231
|
-
onResize?: (ctx: CanvasContext, info: {oldPixelSize: [number, number]}) => unknown;
|
|
232
|
-
/** Called when the visibility of a canvas changes */
|
|
233
|
-
onVisibilityChange?: (ctx: CanvasContext) => unknown;
|
|
234
|
-
/** Called when the device pixel ratio of a canvas changes */
|
|
235
|
-
onDevicePixelRatioChange?: (ctx: CanvasContext, info: {oldRatio: number}) => unknown;
|
|
236
|
-
|
|
237
228
|
// DEBUG SETTINGS
|
|
238
229
|
|
|
239
230
|
/** Turn on implementation defined checks that slow down execution but help break where errors occur */
|
|
@@ -242,8 +233,6 @@ export type DeviceProps = {
|
|
|
242
233
|
debugShaders?: 'never' | 'errors' | 'warnings' | 'always';
|
|
243
234
|
/** Renders a small version of updated Framebuffers into the primary canvas context. Can be set in console luma.log.set('debug-framebuffers', true) */
|
|
244
235
|
debugFramebuffers?: boolean;
|
|
245
|
-
/** Traces resource caching, reuse, and destroys in the PipelineFactory */
|
|
246
|
-
debugFactories?: boolean;
|
|
247
236
|
/** WebGL specific - Trace WebGL calls (instruments WebGL2RenderingContext at the expense of performance). Can be set in console luma.log.set('debug-webgl', true) */
|
|
248
237
|
debugWebGL?: boolean;
|
|
249
238
|
/** WebGL specific - Initialize the SpectorJS WebGL debugger. Can be set in console luma.log.set('debug-spectorjs', true) */
|
|
@@ -259,12 +248,8 @@ export type DeviceProps = {
|
|
|
259
248
|
_disabledFeatures?: Partial<Record<DeviceFeature, boolean>>;
|
|
260
249
|
/** WebGL specific - Initialize all features on startup */
|
|
261
250
|
_initializeFeatures?: boolean;
|
|
262
|
-
/** Enable shader caching (via ShaderFactory) */
|
|
263
|
-
_cacheShaders?: boolean;
|
|
264
|
-
/** Enable shader caching (via PipelineFactory) */
|
|
265
|
-
_cachePipelines?: boolean;
|
|
266
251
|
/** Never destroy cached shaders and pipelines */
|
|
267
|
-
|
|
252
|
+
_factoryDestroyPolicy?: 'unused' | 'never';
|
|
268
253
|
/** Resource default overrides */
|
|
269
254
|
_resourceDefaults?: {
|
|
270
255
|
texture?: Partial<TextureProps>;
|
|
@@ -308,35 +293,12 @@ export abstract class Device {
|
|
|
308
293
|
powerPreference: 'high-performance',
|
|
309
294
|
failIfMajorPerformanceCaveat: false,
|
|
310
295
|
createCanvasContext: undefined!,
|
|
311
|
-
// WebGL specific
|
|
312
|
-
webgl: {},
|
|
313
296
|
|
|
314
297
|
// Callbacks
|
|
315
298
|
onError: (error: Error) => log.error(error.message)(),
|
|
316
|
-
onResize: (context: CanvasContext, info: {oldPixelSize: [number, number]}) => {
|
|
317
|
-
const [width, height] = context.getPixelSize();
|
|
318
|
-
const [prevWidth, prevHeight] = info.oldPixelSize;
|
|
319
|
-
log.log(1, `${context} Resized ${prevWidth}x${prevHeight} => ${width}x${height}px`)();
|
|
320
|
-
},
|
|
321
|
-
onVisibilityChange: (context: CanvasContext) =>
|
|
322
|
-
log.log(1, `${context} Visibility changed ${context.isVisible}`)(),
|
|
323
|
-
onDevicePixelRatioChange: (context: CanvasContext, info: {oldRatio: number}) =>
|
|
324
|
-
log.log(1, `${context} DPR changed ${info.oldRatio} => ${context.devicePixelRatio}`)(),
|
|
325
|
-
|
|
326
|
-
// Debug flags
|
|
327
|
-
debug: log.get('debug') || undefined!,
|
|
328
|
-
debugShaders: log.get('debug-shaders') || undefined!,
|
|
329
|
-
debugFramebuffers: Boolean(log.get('debug-framebuffers')),
|
|
330
|
-
debugFactories: Boolean(log.get('debug-factories')),
|
|
331
|
-
debugWebGL: Boolean(log.get('debug-webgl')),
|
|
332
|
-
debugSpectorJS: undefined!, // Note: log setting is queried by the spector.js code
|
|
333
|
-
debugSpectorJSUrl: undefined!,
|
|
334
299
|
|
|
335
|
-
// Experimental
|
|
336
300
|
_requestMaxLimits: true,
|
|
337
|
-
|
|
338
|
-
_cachePipelines: false,
|
|
339
|
-
_cacheDestroyPolicy: 'unused',
|
|
301
|
+
_factoryDestroyPolicy: 'unused',
|
|
340
302
|
// TODO - Change these after confirming things work as expected
|
|
341
303
|
_initializeFeatures: true,
|
|
342
304
|
_disabledFeatures: {
|
|
@@ -344,6 +306,16 @@ export abstract class Device {
|
|
|
344
306
|
},
|
|
345
307
|
_resourceDefaults: {},
|
|
346
308
|
|
|
309
|
+
// WebGL specific
|
|
310
|
+
webgl: {},
|
|
311
|
+
|
|
312
|
+
debug: log.get('debug') || undefined!,
|
|
313
|
+
debugShaders: log.get('debug-shaders') || undefined!,
|
|
314
|
+
debugFramebuffers: Boolean(log.get('debug-framebuffers')),
|
|
315
|
+
debugWebGL: Boolean(log.get('debug-webgl')),
|
|
316
|
+
debugSpectorJS: undefined!, // Note: log setting is queried by the spector.js code
|
|
317
|
+
debugSpectorJSUrl: undefined!,
|
|
318
|
+
|
|
347
319
|
// INTERNAL
|
|
348
320
|
_handle: undefined!
|
|
349
321
|
};
|
|
@@ -361,8 +333,6 @@ export abstract class Device {
|
|
|
361
333
|
readonly id: string;
|
|
362
334
|
/** type of this device */
|
|
363
335
|
abstract readonly type: 'webgl' | 'webgpu' | 'unknown';
|
|
364
|
-
abstract readonly handle: unknown;
|
|
365
|
-
|
|
366
336
|
/** A copy of the device props */
|
|
367
337
|
readonly props: Required<DeviceProps>;
|
|
368
338
|
/** Available for the application to store data on the device */
|
|
@@ -386,11 +356,6 @@ export abstract class Device {
|
|
|
386
356
|
/** WebGPU style device limits */
|
|
387
357
|
abstract get limits(): DeviceLimits;
|
|
388
358
|
|
|
389
|
-
/** Optimal TextureFormat for displaying 8-bit depth, standard dynamic range content on this system. */
|
|
390
|
-
abstract preferredColorFormat: 'rgba8unorm' | 'bgra8unorm';
|
|
391
|
-
/** Default depth format used on this system */
|
|
392
|
-
abstract preferredDepthFormat: 'depth16' | 'depth24plus' | 'depth32float';
|
|
393
|
-
|
|
394
359
|
/** Determines what operations are supported on a texture format, checking against supported device features */
|
|
395
360
|
getTextureFormatCapabilities(format: TextureFormat): DeviceTextureFormatCapabilities {
|
|
396
361
|
const genericCapabilities = getTextureFormatCapabilities(format);
|
|
@@ -633,7 +598,7 @@ export abstract class Device {
|
|
|
633
598
|
} else if (props.data instanceof Uint16Array) {
|
|
634
599
|
newProps.indexType = 'uint16';
|
|
635
600
|
} else {
|
|
636
|
-
log.warn('indices buffer content must be of type
|
|
601
|
+
log.warn('indices buffer content must be of integer type')();
|
|
637
602
|
}
|
|
638
603
|
}
|
|
639
604
|
return newProps;
|