@luma.gl/test-utils 9.0.0-beta.3 → 9.0.0-beta.5

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.
Files changed (48) hide show
  1. package/dist/check-type.js +5 -2
  2. package/dist/create-test-device.d.ts +2 -6
  3. package/dist/create-test-device.d.ts.map +1 -1
  4. package/dist/create-test-device.js +43 -54
  5. package/dist/engine/classic-animation-loop.d.ts +5 -5
  6. package/dist/engine/classic-animation-loop.d.ts.map +1 -1
  7. package/dist/engine/classic-animation-loop.js +515 -422
  8. package/dist/index.cjs +57 -38
  9. package/dist/index.cjs.map +7 -0
  10. package/dist/index.d.ts +5 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +11 -7
  13. package/dist/performance-test-runner.js +36 -43
  14. package/dist/register-devices.js +3 -4
  15. package/dist/snapshot-test-runner.js +42 -37
  16. package/dist/test-runner.js +181 -154
  17. package/dist/utils/check-type.d.ts +6 -0
  18. package/dist/utils/check-type.d.ts.map +1 -0
  19. package/dist/utils/check-type.js +5 -0
  20. package/dist/utils/deep-copy.d.ts +3 -0
  21. package/dist/utils/deep-copy.d.ts.map +1 -0
  22. package/dist/utils/deep-copy.js +14 -0
  23. package/dist/utils/get-bounding-box.d.ts +7 -0
  24. package/dist/utils/get-bounding-box.d.ts.map +1 -0
  25. package/dist/utils/get-bounding-box.js +10 -0
  26. package/dist/utils/resource-tracker.d.ts +6 -0
  27. package/dist/utils/resource-tracker.d.ts.map +1 -0
  28. package/dist/utils/resource-tracker.js +23 -0
  29. package/dist/utils.js +8 -8
  30. package/package.json +17 -25
  31. package/src/create-test-device.ts +4 -13
  32. package/src/engine/classic-animation-loop.ts +5 -9
  33. package/src/index.ts +9 -2
  34. package/src/register-devices.ts +1 -4
  35. package/src/snapshot-test-runner.ts +1 -1
  36. package/src/utils/deep-copy.ts +16 -0
  37. package/src/utils/resource-tracker.ts +24 -0
  38. package/dist/check-type.js.map +0 -1
  39. package/dist/create-test-device.js.map +0 -1
  40. package/dist/engine/classic-animation-loop.js.map +0 -1
  41. package/dist/index.js.map +0 -1
  42. package/dist/performance-test-runner.js.map +0 -1
  43. package/dist/register-devices.js.map +0 -1
  44. package/dist/snapshot-test-runner.js.map +0 -1
  45. package/dist/test-runner.js.map +0 -1
  46. package/dist/utils.js.map +0 -1
  47. /package/src/{check-type.ts → utils/check-type.ts} +0 -0
  48. /package/src/{utils.ts → utils/get-bounding-box.ts} +0 -0
@@ -1,434 +1,527 @@
1
+ // TODO - replace createGLContext, instrumentGLContext, resizeGLContext?
2
+ // TODO - remove dependency on framebuffer (bundle size impact)
1
3
  import { luma, log, requestAnimationFrame, cancelAnimationFrame } from '@luma.gl/core';
2
4
  import { isBrowser } from '@probe.gl/env';
3
- import { isWebGL, resetGLParameters } from '@luma.gl/webgl';
5
+ import { resetGLParameters } from '@luma.gl/webgl';
4
6
  const isPage = isBrowser() && typeof document !== 'undefined';
5
7
  function getHTMLCanvasElement(canvas) {
6
- return typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement ? canvas : null;
8
+ return typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement
9
+ ? canvas
10
+ : null;
7
11
  }
8
12
  let statIdCounter = 0;
9
13
  const DEFAULT_CLASSIC_ANIMATION_LOOP_PROPS = {
10
- onCreateDevice: props => luma.createDevice(props),
11
- onCreateContext: undefined,
12
- onAddHTML: undefined,
13
- onInitialize: () => ({}),
14
- onRender: () => {},
15
- onFinalize: () => {},
16
- onError: error => console.error(error),
17
- device: null,
18
- useDevicePixels: true,
19
- autoResizeViewport: true,
20
- autoResizeDrawingBuffer: true,
21
- stats: luma.stats.get(`animation-loop-${statIdCounter++}`),
22
- gl: undefined,
23
- glOptions: {},
24
- createFramebuffer: false
14
+ onCreateDevice: (props) => luma.createDevice(props),
15
+ onCreateContext: undefined,
16
+ onAddHTML: undefined,
17
+ onInitialize: () => ({}),
18
+ onRender: () => { },
19
+ onFinalize: () => { },
20
+ onError: (error) => console.error(error), // eslint-disable-line no-console
21
+ device: null,
22
+ // debug: true,
23
+ // view parameters
24
+ useDevicePixels: true,
25
+ autoResizeViewport: true,
26
+ autoResizeDrawingBuffer: true,
27
+ stats: luma.stats.get(`animation-loop-${statIdCounter++}`),
28
+ // deprecated
29
+ // onCreateContext: (opts) => createGLContext(opts),
30
+ gl: undefined,
31
+ glOptions: {},
32
+ createFramebuffer: false
25
33
  };
34
+ /**
35
+ * Convenient animation loop
36
+ * @deprecated Use `@luma.gl/engine` AnimationLoop
37
+ */
26
38
  export class ClassicAnimationLoop {
27
- constructor() {
28
- let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
29
- this.device = void 0;
30
- this.canvas = void 0;
31
- this.props = void 0;
32
- this.animationProps = void 0;
33
- this.timeline = null;
34
- this.stats = void 0;
35
- this.cpuTime = void 0;
36
- this.gpuTime = void 0;
37
- this.frameRate = void 0;
38
- this.display = void 0;
39
- this.needsRedraw = 'initialized';
40
- this._initialized = false;
41
- this._running = false;
42
- this._animationFrameId = null;
43
- this._pageLoadPromise = null;
44
- this._nextFramePromise = null;
45
- this._resolveNextFrame = null;
46
- this._cpuStartTime = 0;
47
- this.gl = void 0;
48
- this.props = {
49
- ...DEFAULT_CLASSIC_ANIMATION_LOOP_PROPS,
50
- ...props
51
- };
52
- props = this.props;
53
- let {
54
- useDevicePixels = true
55
- } = this.props;
56
- if ('useDevicePixelRatio' in props) {
57
- log.deprecated('useDevicePixelRatio', 'useDevicePixels')();
58
- useDevicePixels = props.useDevicePixelRatio;
59
- }
60
- this.device = props.device;
61
- this.gl = this.device && this.device.gl || props.gl;
62
- this.stats = props.stats;
63
- this.cpuTime = this.stats.get('CPU Time');
64
- this.gpuTime = this.stats.get('GPU Time');
65
- this.frameRate = this.stats.get('Frame Rate');
66
- this.setProps({
67
- autoResizeViewport: props.autoResizeViewport,
68
- autoResizeDrawingBuffer: props.autoResizeDrawingBuffer,
69
- useDevicePixels
70
- });
71
- this.start = this.start.bind(this);
72
- this.stop = this.stop.bind(this);
73
- this._onMousemove = this._onMousemove.bind(this);
74
- this._onMouseleave = this._onMouseleave.bind(this);
75
- }
76
- destroy() {
77
- this.stop();
78
- this._setDisplay(null);
79
- }
80
- delete() {
81
- this.destroy();
82
- }
83
- setNeedsRedraw(reason) {
84
- this.needsRedraw = this.needsRedraw || reason;
85
- return this;
86
- }
87
- setProps(props) {
88
- if ('autoResizeViewport' in props) {
89
- this.props.autoResizeViewport = props.autoResizeViewport;
90
- }
91
- if ('autoResizeDrawingBuffer' in props) {
92
- this.props.autoResizeDrawingBuffer = props.autoResizeDrawingBuffer;
93
- }
94
- if ('useDevicePixels' in props) {
95
- this.props.useDevicePixels = props.useDevicePixels;
96
- }
97
- return this;
98
- }
99
- start() {
100
- let opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
101
- this._start(opts);
102
- return this;
103
- }
104
- async _start(props) {
105
- if (this._running) {
106
- return this;
107
- }
108
- this._running = true;
109
- try {
110
- await this._getPageLoadPromise();
111
- if (!this._running) {
112
- return null;
113
- }
114
- let appContext;
115
- if (!this._initialized) {
116
- this._initialized = true;
117
- await this._createDevice(props);
118
- this._initialize(props);
119
- appContext = await this.onInitialize(this.animationProps);
120
- this._addCallbackData(appContext || {});
121
- }
122
- if (!this._running) {
123
- return null;
124
- }
125
- if (appContext !== false) {
126
- this._cancelAnimationFrame();
127
- this._requestAnimationFrame();
128
- }
129
- return this;
130
- } catch (error) {
131
- this.props.onError(error);
132
- return null;
133
- }
134
- }
135
- redraw() {
136
- if (this.isContextLost()) {
137
- return this;
138
- }
139
- this._beginTimers();
140
- this._setupFrame();
141
- this._updateCallbackData();
142
- this._renderFrame(this.animationProps);
143
- this._clearNeedsRedraw();
144
- if (this._resolveNextFrame) {
145
- this._resolveNextFrame(this);
146
- this._nextFramePromise = null;
147
- this._resolveNextFrame = null;
148
- }
149
- this._endTimers();
150
- return this;
151
- }
152
- stop() {
153
- if (this._running) {
154
- this._finalizeCallbackData();
155
- this._cancelAnimationFrame();
156
- this._nextFramePromise = null;
157
- this._resolveNextFrame = null;
158
- this._running = false;
159
- }
160
- return this;
161
- }
162
- attachTimeline(timeline) {
163
- this.timeline = timeline;
164
- return this.timeline;
165
- }
166
- detachTimeline() {
167
- this.timeline = null;
168
- }
169
- waitForRender() {
170
- this.setNeedsRedraw('waitForRender');
171
- if (!this._nextFramePromise) {
172
- this._nextFramePromise = new Promise(resolve => {
173
- this._resolveNextFrame = resolve;
174
- });
175
- }
176
- return this._nextFramePromise;
177
- }
178
- async toDataURL() {
179
- var _getHTMLCanvasElement;
180
- this.setNeedsRedraw('toDataURL');
181
- await this.waitForRender();
182
- return (_getHTMLCanvasElement = getHTMLCanvasElement(this.gl.canvas)) === null || _getHTMLCanvasElement === void 0 ? void 0 : _getHTMLCanvasElement.toDataURL();
183
- }
184
- isContextLost() {
185
- return this.gl.isContextLost();
186
- }
187
- onCreateDevice(deviceProps) {
188
- const {
189
- onCreateDevice
190
- } = this.props;
191
- return onCreateDevice(deviceProps);
192
- }
193
- onInitialize(animationProps) {
194
- const {
195
- onInitialize
196
- } = this.props;
197
- return onInitialize(animationProps);
198
- }
199
- onRender(animationProps) {
200
- const {
201
- onRender
202
- } = this.props;
203
- return onRender(animationProps);
204
- }
205
- onFinalize(animationProps) {
206
- const {
207
- onFinalize
208
- } = this.props;
209
- return onFinalize(animationProps);
210
- }
211
- onCreateContext(props) {
212
- const {
213
- onCreateContext
214
- } = this.props;
215
- return onCreateContext(props);
216
- }
217
- getHTMLControlValue(id) {
218
- let defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
219
- const element = document.getElementById(id);
220
- return element ? Number(element.value) : defaultValue;
221
- }
222
- _initialize(props) {
223
- this._createFramebuffer();
224
- this._startEventHandling();
225
- this._initializeCallbackData();
226
- this._updateCallbackData();
227
- this._resizeCanvasDrawingBuffer();
228
- this._resizeViewport();
229
- }
230
- _getPageLoadPromise() {
231
- if (!this._pageLoadPromise) {
232
- this._pageLoadPromise = isPage ? new Promise((resolve, reject) => {
233
- if (isPage && document.readyState === 'complete') {
234
- resolve(document);
235
- return;
39
+ device;
40
+ canvas;
41
+ props;
42
+ animationProps;
43
+ // framebuffer: ClassicFramebuffer = null;
44
+ timeline = null;
45
+ stats;
46
+ cpuTime;
47
+ gpuTime;
48
+ frameRate;
49
+ display;
50
+ needsRedraw = 'initialized';
51
+ _initialized = false;
52
+ _running = false;
53
+ _animationFrameId = null;
54
+ _pageLoadPromise = null;
55
+ _nextFramePromise = null;
56
+ _resolveNextFrame = null;
57
+ _cpuStartTime = 0;
58
+ // _gpuTimeQuery: Query | null = null;
59
+ /** @deprecated */
60
+ gl;
61
+ /*
62
+ */
63
+ constructor(props = {}) {
64
+ this.props = { ...DEFAULT_CLASSIC_ANIMATION_LOOP_PROPS, ...props };
65
+ props = this.props;
66
+ let { useDevicePixels = true } = this.props;
67
+ if ('useDevicePixelRatio' in props) {
68
+ log.deprecated('useDevicePixelRatio', 'useDevicePixels')();
69
+ // @ts-expect-error
70
+ useDevicePixels = props.useDevicePixelRatio;
236
71
  }
237
- window.addEventListener('load', () => {
238
- resolve(document);
72
+ // state
73
+ this.device = props.device;
74
+ // @ts-expect-error
75
+ this.gl = (this.device && this.device.gl) || props.gl;
76
+ this.stats = props.stats;
77
+ this.cpuTime = this.stats.get('CPU Time');
78
+ this.gpuTime = this.stats.get('GPU Time');
79
+ this.frameRate = this.stats.get('Frame Rate');
80
+ this.setProps({
81
+ autoResizeViewport: props.autoResizeViewport,
82
+ autoResizeDrawingBuffer: props.autoResizeDrawingBuffer,
83
+ useDevicePixels
239
84
  });
240
- }) : Promise.resolve({});
241
- }
242
- return this._pageLoadPromise;
243
- }
244
- _setDisplay(display) {
245
- if (this.display) {
246
- this.display.destroy();
247
- this.display.animationLoop = null;
248
- }
249
- if (display) {
250
- display.animationLoop = this;
251
- }
252
- this.display = display;
253
- }
254
- _requestAnimationFrame() {
255
- if (!this._running) {
256
- return;
257
- }
258
- this._animationFrameId = requestAnimationFrame(this._animationFrame.bind(this));
259
- }
260
- _cancelAnimationFrame() {
261
- if (this._animationFrameId !== null) {
262
- return;
263
- }
264
- cancelAnimationFrame(this._animationFrameId);
265
- this._animationFrameId = null;
266
- }
267
- _animationFrame() {
268
- if (!this._running) {
269
- return;
270
- }
271
- this.redraw();
272
- this._requestAnimationFrame();
273
- }
274
- _renderFrame(props) {
275
- if (this.display) {
276
- this.display._renderFrame(props);
277
- return;
278
- }
279
- this.onRender(props);
280
- }
281
- _clearNeedsRedraw() {
282
- this.needsRedraw = null;
283
- }
284
- _setupFrame() {
285
- this._resizeCanvasDrawingBuffer();
286
- this._resizeViewport();
287
- this._resizeFramebuffer();
288
- }
289
- _initializeCallbackData() {
290
- this.animationProps = {
291
- device: this.device,
292
- gl: this.gl,
293
- stop: this.stop,
294
- canvas: this.gl.canvas,
295
- useDevicePixels: this.props.useDevicePixels,
296
- needsRedraw: null,
297
- startTime: Date.now(),
298
- engineTime: 0,
299
- tick: 0,
300
- tock: 0,
301
- timeline: this.timeline,
302
- animationLoop: this,
303
- time: 0,
304
- _mousePosition: null,
305
- _timeline: this.timeline,
306
- _loop: this,
307
- _animationLoop: this
308
- };
309
- }
310
- _updateCallbackData() {
311
- const {
312
- width,
313
- height,
314
- aspect
315
- } = this._getSizeAndAspect();
316
- if (width !== this.animationProps.width || height !== this.animationProps.height) {
317
- this.setNeedsRedraw('drawing buffer resized');
318
- }
319
- if (aspect !== this.animationProps.aspect) {
320
- this.setNeedsRedraw('drawing buffer aspect changed');
321
- }
322
- this.animationProps.width = width;
323
- this.animationProps.height = height;
324
- this.animationProps.aspect = aspect;
325
- this.animationProps.needsRedraw = this.needsRedraw;
326
- this.animationProps.engineTime = Date.now() - this.animationProps.startTime;
327
- if (this.timeline) {
328
- this.timeline.update(this.animationProps.engineTime);
329
- }
330
- this.animationProps.tick = Math.floor(this.animationProps.time / 1000 * 60);
331
- this.animationProps.tock++;
332
- this.animationProps.time = this.timeline ? this.timeline.getTime() : this.animationProps.engineTime;
333
- }
334
- _finalizeCallbackData() {
335
- this.onFinalize(this.animationProps);
336
- }
337
- _addCallbackData(appContext) {
338
- if (typeof appContext === 'object' && appContext !== null) {
339
- this.animationProps = Object.assign({}, this.animationProps, appContext);
340
- }
341
- }
342
- async _createDevice(props) {
343
- const deviceProps = {
344
- ...this.props,
345
- ...props,
346
- ...this.props.glOptions
347
- };
348
- this.device = await this.onCreateDevice(deviceProps);
349
- this.gl = this.device.gl;
350
- if (!isWebGL(this.gl)) {
351
- throw new Error('ClassicAnimationLoop.onCreateContext - illegal context returned');
352
- }
353
- resetGLParameters(this.gl);
354
- this._createInfoDiv();
355
- }
356
- _createInfoDiv() {
357
- const canvas = getHTMLCanvasElement(this.gl.canvas);
358
- if (canvas && this.props.onAddHTML) {
359
- const wrapperDiv = document.createElement('div');
360
- document.body.appendChild(wrapperDiv);
361
- wrapperDiv.style.position = 'relative';
362
- const div = document.createElement('div');
363
- div.style.position = 'absolute';
364
- div.style.left = '10px';
365
- div.style.bottom = '10px';
366
- div.style.width = '300px';
367
- div.style.background = 'white';
368
- if (canvas) {
369
- wrapperDiv.appendChild(canvas);
370
- }
371
- wrapperDiv.appendChild(div);
372
- const html = this.props.onAddHTML(div);
373
- if (html) {
374
- div.innerHTML = html;
375
- }
376
- }
377
- }
378
- _getSizeAndAspect() {
379
- const width = this.gl.drawingBufferWidth;
380
- const height = this.gl.drawingBufferHeight;
381
- let aspect = 1;
382
- const canvas = getHTMLCanvasElement(this.gl.canvas);
383
- if (canvas && canvas.clientHeight) {
384
- aspect = canvas.clientWidth / canvas.clientHeight;
385
- } else if (width > 0 && height > 0) {
386
- aspect = width / height;
387
- }
388
- return {
389
- width,
390
- height,
391
- aspect
392
- };
393
- }
394
- _resizeViewport() {
395
- if (this.props.autoResizeViewport) {
396
- this.gl.viewport(0, 0, this.gl.drawingBufferWidth, this.gl.drawingBufferHeight);
397
- }
398
- }
399
- _resizeCanvasDrawingBuffer() {
400
- if (this.props.autoResizeDrawingBuffer) {
401
- this.device.canvasContext.resize({
402
- useDevicePixels: this.props.useDevicePixels
403
- });
404
- }
405
- }
406
- _beginTimers() {
407
- this.frameRate.timeEnd();
408
- this.frameRate.timeStart();
409
- this.cpuTime.timeStart();
410
- }
411
- _endTimers() {
412
- this.cpuTime.timeEnd();
413
- }
414
- _startEventHandling() {
415
- const {
416
- canvas
417
- } = this.gl;
418
- if (canvas) {
419
- canvas.addEventListener('mousemove', this._onMousemove);
420
- canvas.addEventListener('mouseleave', this._onMouseleave);
421
- }
422
- }
423
- _onMousemove(e) {
424
- this.animationProps._mousePosition = [e.offsetX, e.offsetY];
425
- }
426
- _onMouseleave(e) {
427
- this.animationProps._mousePosition = null;
428
- }
429
- _createFramebuffer() {
430
- if (this.props.createFramebuffer) {}
431
- }
432
- _resizeFramebuffer() {}
85
+ // Bind methods
86
+ this.start = this.start.bind(this);
87
+ this.stop = this.stop.bind(this);
88
+ this._onMousemove = this._onMousemove.bind(this);
89
+ this._onMouseleave = this._onMouseleave.bind(this);
90
+ }
91
+ destroy() {
92
+ this.stop();
93
+ this._setDisplay(null);
94
+ }
95
+ /** @deprecated Use .destroy() */
96
+ delete() {
97
+ this.destroy();
98
+ }
99
+ setNeedsRedraw(reason) {
100
+ this.needsRedraw = this.needsRedraw || reason;
101
+ return this;
102
+ }
103
+ setProps(props) {
104
+ if ('autoResizeViewport' in props) {
105
+ this.props.autoResizeViewport = props.autoResizeViewport;
106
+ }
107
+ if ('autoResizeDrawingBuffer' in props) {
108
+ this.props.autoResizeDrawingBuffer = props.autoResizeDrawingBuffer;
109
+ }
110
+ if ('useDevicePixels' in props) {
111
+ this.props.useDevicePixels = props.useDevicePixels;
112
+ }
113
+ return this;
114
+ }
115
+ start(opts = {}) {
116
+ this._start(opts);
117
+ return this;
118
+ }
119
+ /** Starts a render loop if not already running */
120
+ async _start(props) {
121
+ if (this._running) {
122
+ return this;
123
+ }
124
+ this._running = true;
125
+ // console.debug(`Starting ${this.constructor.name}`);
126
+ // Wait for start promise before rendering frame
127
+ try {
128
+ await this._getPageLoadPromise();
129
+ // check that we haven't been stopped
130
+ if (!this._running) {
131
+ return null;
132
+ }
133
+ let appContext;
134
+ if (!this._initialized) {
135
+ this._initialized = true;
136
+ // Create the WebGL context
137
+ await this._createDevice(props);
138
+ this._initialize(props);
139
+ // Note: onIntialize can return a promise (in case app needs to load resources)
140
+ // eslint-disable-next-line @typescript-eslint/await-thenable
141
+ appContext = await this.onInitialize(this.animationProps);
142
+ this._addCallbackData(appContext || {});
143
+ }
144
+ // check that we haven't been stopped
145
+ if (!this._running) {
146
+ return null;
147
+ }
148
+ // Start the loop
149
+ if (appContext !== false) {
150
+ // cancel any pending renders to ensure only one loop can ever run
151
+ this._cancelAnimationFrame();
152
+ this._requestAnimationFrame();
153
+ }
154
+ return this;
155
+ }
156
+ catch (error) {
157
+ this.props.onError(error);
158
+ // this._running = false; // TODO
159
+ return null;
160
+ }
161
+ }
162
+ /** Explicitly draw a frame */
163
+ redraw() {
164
+ if (this.isContextLost()) {
165
+ return this;
166
+ }
167
+ this._beginTimers();
168
+ this._setupFrame();
169
+ this._updateCallbackData();
170
+ this._renderFrame(this.animationProps);
171
+ // clear needsRedraw flag
172
+ this._clearNeedsRedraw();
173
+ if (this._resolveNextFrame) {
174
+ this._resolveNextFrame(this);
175
+ this._nextFramePromise = null;
176
+ this._resolveNextFrame = null;
177
+ }
178
+ this._endTimers();
179
+ return this;
180
+ }
181
+ // Stops a render loop if already running, finalizing
182
+ stop() {
183
+ // console.debug(`Stopping ${this.constructor.name}`);
184
+ if (this._running) {
185
+ this._finalizeCallbackData();
186
+ this._cancelAnimationFrame();
187
+ this._nextFramePromise = null;
188
+ this._resolveNextFrame = null;
189
+ this._running = false;
190
+ }
191
+ return this;
192
+ }
193
+ attachTimeline(timeline) {
194
+ this.timeline = timeline;
195
+ return this.timeline;
196
+ }
197
+ detachTimeline() {
198
+ this.timeline = null;
199
+ }
200
+ waitForRender() {
201
+ this.setNeedsRedraw('waitForRender');
202
+ if (!this._nextFramePromise) {
203
+ this._nextFramePromise = new Promise((resolve) => {
204
+ this._resolveNextFrame = resolve;
205
+ });
206
+ }
207
+ return this._nextFramePromise;
208
+ }
209
+ async toDataURL() {
210
+ this.setNeedsRedraw('toDataURL');
211
+ await this.waitForRender();
212
+ return getHTMLCanvasElement(this.gl.canvas)?.toDataURL();
213
+ }
214
+ isContextLost() {
215
+ return this.gl.isContextLost();
216
+ }
217
+ onCreateDevice(deviceProps) {
218
+ const { onCreateDevice } = this.props;
219
+ return onCreateDevice(deviceProps);
220
+ }
221
+ onInitialize(animationProps) {
222
+ const { onInitialize } = this.props;
223
+ return onInitialize(animationProps);
224
+ }
225
+ onRender(animationProps) {
226
+ const { onRender } = this.props;
227
+ return onRender(animationProps);
228
+ }
229
+ onFinalize(animationProps) {
230
+ const { onFinalize } = this.props;
231
+ return onFinalize(animationProps);
232
+ }
233
+ // DEPRECATED/REMOVED METHODS
234
+ /** @deprecated Use .onCreateDevice() */
235
+ onCreateContext(props) {
236
+ const { onCreateContext } = this.props;
237
+ return onCreateContext(props);
238
+ }
239
+ /** @deprecated */
240
+ getHTMLControlValue(id, defaultValue = 1) {
241
+ const element = document.getElementById(id);
242
+ // @ts-expect-error Not all html elements have value
243
+ return element ? Number(element.value) : defaultValue;
244
+ }
245
+ // PRIVATE METHODS
246
+ _initialize(props) {
247
+ this._createFramebuffer();
248
+ this._startEventHandling();
249
+ // Initialize the callback data
250
+ this._initializeCallbackData();
251
+ this._updateCallbackData();
252
+ // Default viewport setup, in case onInitialize wants to render
253
+ this._resizeCanvasDrawingBuffer();
254
+ this._resizeViewport();
255
+ // this._gpuTimeQuery = Query.isSupported(this.gl, ['timers']) ? new Query(this.gl) : null;
256
+ }
257
+ _getPageLoadPromise() {
258
+ if (!this._pageLoadPromise) {
259
+ this._pageLoadPromise = isPage
260
+ ? new Promise((resolve, reject) => {
261
+ if (isPage && document.readyState === 'complete') {
262
+ resolve(document);
263
+ return;
264
+ }
265
+ window.addEventListener('load', () => {
266
+ resolve(document);
267
+ });
268
+ })
269
+ : Promise.resolve({});
270
+ }
271
+ return this._pageLoadPromise;
272
+ }
273
+ _setDisplay(display) {
274
+ if (this.display) {
275
+ this.display.destroy();
276
+ this.display.animationLoop = null;
277
+ }
278
+ // store animation loop on the display
279
+ if (display) {
280
+ display.animationLoop = this;
281
+ }
282
+ this.display = display;
283
+ }
284
+ _requestAnimationFrame() {
285
+ if (!this._running) {
286
+ return;
287
+ }
288
+ // VR display has a separate animation frame to sync with headset
289
+ // TODO WebVR API discontinued, replaced by WebXR: https://immersive-web.github.io/webxr/
290
+ // See https://developer.mozilla.org/en-US/docs/Web/API/VRDisplay/requestAnimationFrame
291
+ // if (this.display && this.display.requestAnimationFrame) {
292
+ // this._animationFrameId = this.display.requestAnimationFrame(this._animationFrame.bind(this));
293
+ // }
294
+ this._animationFrameId = requestAnimationFrame(this._animationFrame.bind(this));
295
+ }
296
+ _cancelAnimationFrame() {
297
+ if (this._animationFrameId !== null) {
298
+ return;
299
+ }
300
+ // VR display has a separate animation frame to sync with headset
301
+ // TODO WebVR API discontinued, replaced by WebXR: https://immersive-web.github.io/webxr/
302
+ // See https://developer.mozilla.org/en-US/docs/Web/API/VRDisplay/requestAnimationFrame
303
+ // if (this.display && this.display.cancelAnimationFrame) {
304
+ // this.display.cancelAnimationFrame(this._animationFrameId);
305
+ // }
306
+ cancelAnimationFrame(this._animationFrameId);
307
+ this._animationFrameId = null;
308
+ }
309
+ _animationFrame() {
310
+ if (!this._running) {
311
+ return;
312
+ }
313
+ this.redraw();
314
+ this._requestAnimationFrame();
315
+ }
316
+ // Called on each frame, can be overridden to call onRender multiple times
317
+ // to support e.g. stereoscopic rendering
318
+ _renderFrame(props) {
319
+ // Allow e.g. VR display to render multiple frames.
320
+ if (this.display) {
321
+ this.display._renderFrame(props);
322
+ return;
323
+ }
324
+ // call callback
325
+ this.onRender(props);
326
+ // end callback
327
+ }
328
+ _clearNeedsRedraw() {
329
+ this.needsRedraw = null;
330
+ }
331
+ _setupFrame() {
332
+ this._resizeCanvasDrawingBuffer();
333
+ this._resizeViewport();
334
+ this._resizeFramebuffer();
335
+ }
336
+ /* eslint-disable @typescript-eslint/unbound-method */
337
+ // Initialize the object that will be passed to app callbacks
338
+ _initializeCallbackData() {
339
+ this.animationProps = {
340
+ device: this.device,
341
+ gl: this.gl,
342
+ stop: this.stop,
343
+ canvas: this.gl.canvas,
344
+ // Initial values
345
+ useDevicePixels: this.props.useDevicePixels,
346
+ needsRedraw: null,
347
+ // Animation props
348
+ startTime: Date.now(),
349
+ engineTime: 0,
350
+ tick: 0,
351
+ tock: 0,
352
+ timeline: this.timeline,
353
+ // @ts-ignore
354
+ animationLoop: this,
355
+ // Timeline time for back compatibility
356
+ time: 0,
357
+ // Experimental
358
+ _mousePosition: null, // Event props
359
+ /** @deprecated */
360
+ // framebuffer: this.framebuffer,
361
+ /** @deprecated */
362
+ _timeline: this.timeline,
363
+ /** @deprecated */
364
+ _loop: this,
365
+ /** @deprecated */
366
+ _animationLoop: this
367
+ };
368
+ }
369
+ // Update the context object that will be passed to app callbacks
370
+ _updateCallbackData() {
371
+ const { width, height, aspect } = this._getSizeAndAspect();
372
+ if (width !== this.animationProps.width || height !== this.animationProps.height) {
373
+ this.setNeedsRedraw('drawing buffer resized');
374
+ }
375
+ if (aspect !== this.animationProps.aspect) {
376
+ this.setNeedsRedraw('drawing buffer aspect changed');
377
+ }
378
+ this.animationProps.width = width;
379
+ this.animationProps.height = height;
380
+ this.animationProps.aspect = aspect;
381
+ this.animationProps.needsRedraw = this.needsRedraw;
382
+ // Update time properties
383
+ this.animationProps.engineTime = Date.now() - this.animationProps.startTime;
384
+ if (this.timeline) {
385
+ this.timeline.update(this.animationProps.engineTime);
386
+ }
387
+ this.animationProps.tick = Math.floor((this.animationProps.time / 1000) * 60);
388
+ this.animationProps.tock++;
389
+ // For back compatibility
390
+ this.animationProps.time = this.timeline
391
+ ? this.timeline.getTime()
392
+ : this.animationProps.engineTime;
393
+ }
394
+ _finalizeCallbackData() {
395
+ // call callback
396
+ this.onFinalize(this.animationProps);
397
+ // end callback
398
+ }
399
+ /** Add application's data to the app context object */
400
+ _addCallbackData(appContext) {
401
+ if (typeof appContext === 'object' && appContext !== null) {
402
+ this.animationProps = Object.assign({}, this.animationProps, appContext);
403
+ }
404
+ }
405
+ /** Either uses supplied or existing context, or calls provided callback to create one */
406
+ async _createDevice(props) {
407
+ const deviceProps = { ...this.props, ...props, ...this.props.glOptions };
408
+ // TODO - support this.onCreateContext
409
+ // Create the WebGL context if necessary
410
+ // this.gl = this.props.gl ? instrumentGLContext(this.props.gl, deviceProps) : this.onCreateContext(deviceProps);
411
+ this.device = await this.onCreateDevice(deviceProps);
412
+ // @ts-expect-error
413
+ this.gl = this.device.gl;
414
+ // Reset the WebGL context.
415
+ resetGLParameters(this.gl);
416
+ this._createInfoDiv();
417
+ }
418
+ _createInfoDiv() {
419
+ const canvas = getHTMLCanvasElement(this.gl.canvas);
420
+ if (canvas && this.props.onAddHTML) {
421
+ const wrapperDiv = document.createElement('div');
422
+ document.body.appendChild(wrapperDiv);
423
+ wrapperDiv.style.position = 'relative';
424
+ const div = document.createElement('div');
425
+ div.style.position = 'absolute';
426
+ div.style.left = '10px';
427
+ div.style.bottom = '10px';
428
+ div.style.width = '300px';
429
+ div.style.background = 'white';
430
+ if (canvas) {
431
+ wrapperDiv.appendChild(canvas);
432
+ }
433
+ wrapperDiv.appendChild(div);
434
+ const html = this.props.onAddHTML(div);
435
+ if (html) {
436
+ div.innerHTML = html;
437
+ }
438
+ }
439
+ }
440
+ _getSizeAndAspect() {
441
+ // https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
442
+ const width = this.gl.drawingBufferWidth;
443
+ const height = this.gl.drawingBufferHeight;
444
+ // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
445
+ let aspect = 1;
446
+ const canvas = getHTMLCanvasElement(this.gl.canvas);
447
+ if (canvas && canvas.clientHeight) {
448
+ aspect = canvas.clientWidth / canvas.clientHeight;
449
+ }
450
+ else if (width > 0 && height > 0) {
451
+ aspect = width / height;
452
+ }
453
+ return { width, height, aspect };
454
+ }
455
+ /** Default viewport setup */
456
+ _resizeViewport() {
457
+ if (this.props.autoResizeViewport) {
458
+ this.gl.viewport(0, 0, this.gl.drawingBufferWidth, this.gl.drawingBufferHeight);
459
+ }
460
+ }
461
+ /**
462
+ * Resize the render buffer of the canvas to match canvas client size
463
+ * Optionally multiplying with devicePixel ratio
464
+ */
465
+ _resizeCanvasDrawingBuffer() {
466
+ if (this.props.autoResizeDrawingBuffer) {
467
+ this.device.canvasContext.resize({ useDevicePixels: this.props.useDevicePixels });
468
+ }
469
+ }
470
+ _beginTimers() {
471
+ this.frameRate.timeEnd();
472
+ this.frameRate.timeStart();
473
+ // Check if timer for last frame has completed.
474
+ // GPU timer results are never available in the same
475
+ // frame they are captured.
476
+ // if (
477
+ // this._gpuTimeQuery &&
478
+ // this._gpuTimeQuery.isResultAvailable() &&
479
+ // !this._gpuTimeQuery.isTimerDisjoint()
480
+ // ) {
481
+ // this.stats.get('GPU Time').addTime(this._gpuTimeQuery.getTimerMilliseconds());
482
+ // }
483
+ // if (this._gpuTimeQuery) {
484
+ // // GPU time query start
485
+ // this._gpuTimeQuery.beginTimeElapsedQuery();
486
+ // }
487
+ this.cpuTime.timeStart();
488
+ }
489
+ _endTimers() {
490
+ this.cpuTime.timeEnd();
491
+ // if (this._gpuTimeQuery) {
492
+ // // GPU time query end. Results will be available on next frame.
493
+ // this._gpuTimeQuery.end();
494
+ // }
495
+ }
496
+ // Event handling
497
+ _startEventHandling() {
498
+ const { canvas } = this.gl;
499
+ if (canvas) {
500
+ canvas.addEventListener('mousemove', this._onMousemove);
501
+ canvas.addEventListener('mouseleave', this._onMouseleave);
502
+ }
503
+ }
504
+ _onMousemove(e) {
505
+ this.animationProps._mousePosition = [e.offsetX, e.offsetY];
506
+ }
507
+ _onMouseleave(e) {
508
+ this.animationProps._mousePosition = null;
509
+ }
510
+ // Deprecated
511
+ /** @deprecated */
512
+ _createFramebuffer() {
513
+ // Setup default framebuffer
514
+ if (this.props.createFramebuffer) {
515
+ // this.framebuffer = new ClassicFramebuffer(this.gl);
516
+ }
517
+ }
518
+ /** @deprecated */
519
+ _resizeFramebuffer() {
520
+ // if (this.framebuffer) {
521
+ // this.framebuffer.resize({
522
+ // width: this.gl.drawingBufferWidth,
523
+ // height: this.gl.drawingBufferHeight
524
+ // });
525
+ // }
526
+ }
433
527
  }
434
- //# sourceMappingURL=classic-animation-loop.js.map