• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / luma.gl / 17192022436

24 Aug 2025 06:02PM UTC coverage: 62.079% (-13.2%) from 75.234%
17192022436

Pull #2437

github

web-flow
Merge 562c391b0 into 8314ecefa
Pull Request #2437: test(engine): add ShaderPassRenderer test

956 of 1559 branches covered (61.32%)

Branch coverage included in aggregate %.

491 of 666 new or added lines in 7 files covered. (73.72%)

5291 existing lines in 117 files now uncovered.

23238 of 37414 relevant lines covered (62.11%)

3.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.26
/modules/engine/src/animation-loop/animation-loop.ts
1
// luma.gl
1✔
2
// SPDX-License-Identifier: MIT
1✔
3
// Copyright (c) vis.gl contributors
1✔
4

1✔
5
import {luma, Device} from '@luma.gl/core';
1✔
6
import {
1✔
7
  requestAnimationFramePolyfill,
1✔
8
  cancelAnimationFramePolyfill
1✔
9
} from './request-animation-frame';
1✔
10
import {Timeline} from '../animation/timeline';
1✔
11
import {AnimationProps} from './animation-props';
1✔
12
import {Stats, Stat} from '@probe.gl/stats';
1✔
13

1✔
14
let statIdCounter = 0;
1✔
15

1✔
16
/** AnimationLoop properties */
1✔
17
export type AnimationLoopProps = {
1✔
18
  device: Device | Promise<Device>;
1✔
19

1✔
20
  onAddHTML?: (div: HTMLDivElement) => string; // innerHTML
1✔
21
  onInitialize?: (animationProps: AnimationProps) => Promise<unknown>;
1✔
22
  onRender?: (animationProps: AnimationProps) => unknown;
1✔
23
  onFinalize?: (animationProps: AnimationProps) => void;
1✔
24
  onError?: (reason: Error) => void;
1✔
25

1✔
26
  stats?: Stats;
1✔
27

1✔
28
  // view parameters - TODO move to CanvasContext?
1✔
29
  autoResizeViewport?: boolean;
1✔
30
};
1✔
31

1✔
32
export type MutableAnimationLoopProps = {
1✔
33
  // view parameters
1✔
34
  autoResizeViewport?: boolean;
1✔
35
};
1✔
36

1✔
37
/** Convenient animation loop */
1✔
38
export class AnimationLoop {
1!
UNCOV
39
  static defaultAnimationLoopProps = {
×
UNCOV
40
    device: null!,
×
UNCOV
41

×
UNCOV
42
    onAddHTML: () => '',
×
UNCOV
43
    onInitialize: async () => null,
×
UNCOV
44
    onRender: () => {},
×
UNCOV
45
    onFinalize: () => {},
×
UNCOV
46
    onError: error => console.error(error), // eslint-disable-line no-console
×
UNCOV
47

×
UNCOV
48
    stats: luma.stats.get(`animation-loop-${statIdCounter++}`),
×
UNCOV
49

×
UNCOV
50
    // view parameters
×
UNCOV
51
    autoResizeViewport: false
×
UNCOV
52
  } as const satisfies Readonly<Required<AnimationLoopProps>>;
×
UNCOV
53

×
UNCOV
54
  device: Device | null = null;
×
UNCOV
55
  canvas: HTMLCanvasElement | OffscreenCanvas | null = null;
×
UNCOV
56

×
UNCOV
57
  props: Required<AnimationLoopProps>;
×
UNCOV
58
  animationProps: AnimationProps | null = null;
×
UNCOV
59
  timeline: Timeline | null = null;
×
UNCOV
60
  stats: Stats;
×
UNCOV
61
  cpuTime: Stat;
×
UNCOV
62
  gpuTime: Stat;
×
UNCOV
63
  frameRate: Stat;
×
UNCOV
64

×
UNCOV
65
  display: any;
×
UNCOV
66

×
UNCOV
67
  needsRedraw: string | false = 'initialized';
×
UNCOV
68

×
UNCOV
69
  _initialized: boolean = false;
×
UNCOV
70
  _running: boolean = false;
×
UNCOV
71
  _animationFrameId: any = null;
×
UNCOV
72
  _nextFramePromise: Promise<AnimationLoop> | null = null;
×
UNCOV
73
  _resolveNextFrame: ((animationLoop: AnimationLoop) => void) | null = null;
×
UNCOV
74
  _cpuStartTime: number = 0;
×
UNCOV
75
  _error: Error | null = null;
×
UNCOV
76

×
UNCOV
77
  // _gpuTimeQuery: Query | null = null;
×
UNCOV
78

×
UNCOV
79
  /*
×
UNCOV
80
   * @param {HTMLCanvasElement} canvas - if provided, width and height will be passed to context
×
UNCOV
81
   */
×
UNCOV
82
  constructor(props: AnimationLoopProps) {
×
UNCOV
83
    this.props = {...AnimationLoop.defaultAnimationLoopProps, ...props};
×
UNCOV
84
    props = this.props;
×
UNCOV
85

×
UNCOV
86
    if (!props.device) {
×
87
      throw new Error('No device provided');
×
88
    }
×
UNCOV
89

×
UNCOV
90
    // state
×
UNCOV
91
    this.stats = props.stats || new Stats({id: 'animation-loop-stats'});
×
UNCOV
92
    this.cpuTime = this.stats.get('CPU Time');
×
UNCOV
93
    this.gpuTime = this.stats.get('GPU Time');
×
UNCOV
94
    this.frameRate = this.stats.get('Frame Rate');
×
UNCOV
95

×
UNCOV
96
    this.setProps({autoResizeViewport: props.autoResizeViewport});
×
UNCOV
97

×
UNCOV
98
    // Bind methods
×
UNCOV
99
    this.start = this.start.bind(this);
×
UNCOV
100
    this.stop = this.stop.bind(this);
×
UNCOV
101

×
UNCOV
102
    this._onMousemove = this._onMousemove.bind(this);
×
UNCOV
103
    this._onMouseleave = this._onMouseleave.bind(this);
×
UNCOV
104
  }
×
UNCOV
105

×
UNCOV
106
  destroy(): void {
×
107
    this.stop();
×
108
    this._setDisplay(null);
×
109
  }
×
UNCOV
110

×
UNCOV
111
  /** @deprecated Use .destroy() */
×
UNCOV
112
  delete(): void {
×
113
    this.destroy();
×
114
  }
×
UNCOV
115

×
UNCOV
116
  reportError(error: Error): void {
×
117
    this.props.onError(error);
×
118
    this._error = error;
×
119
  }
×
UNCOV
120

×
UNCOV
121
  /** Flags this animation loop as needing redraw */
×
UNCOV
122
  setNeedsRedraw(reason: string): this {
×
UNCOV
123
    this.needsRedraw = this.needsRedraw || reason;
×
UNCOV
124
    return this;
×
UNCOV
125
  }
×
UNCOV
126

×
UNCOV
127
  setProps(props: MutableAnimationLoopProps): this {
×
UNCOV
128
    if ('autoResizeViewport' in props) {
×
UNCOV
129
      this.props.autoResizeViewport = props.autoResizeViewport || false;
×
UNCOV
130
    }
×
UNCOV
131
    return this;
×
UNCOV
132
  }
×
UNCOV
133

×
UNCOV
134
  /** Starts a render loop if not already running */
×
UNCOV
135
  async start() {
×
UNCOV
136
    if (this._running) {
×
UNCOV
137
      return this;
×
UNCOV
138
    }
×
UNCOV
139
    this._running = true;
×
UNCOV
140

×
UNCOV
141
    try {
×
UNCOV
142
      let appContext;
×
UNCOV
143
      if (!this._initialized) {
×
UNCOV
144
        this._initialized = true;
×
UNCOV
145
        // Create the WebGL context
×
UNCOV
146
        await this._initDevice();
×
UNCOV
147
        this._initialize();
×
UNCOV
148

×
UNCOV
149
        // Note: onIntialize can return a promise (e.g. in case app needs to load resources)
×
UNCOV
150
        await this.props.onInitialize(this._getAnimationProps());
×
UNCOV
151
      }
×
UNCOV
152

×
UNCOV
153
      // check that we haven't been stopped
×
UNCOV
154
      if (!this._running) {
×
UNCOV
155
        return null;
×
UNCOV
156
      }
×
UNCOV
157

×
UNCOV
158
      // Start the loop
×
UNCOV
159
      if (appContext !== false) {
×
UNCOV
160
        // cancel any pending renders to ensure only one loop can ever run
×
UNCOV
161
        this._cancelAnimationFrame();
×
UNCOV
162
        this._requestAnimationFrame();
×
UNCOV
163
      }
×
UNCOV
164

×
UNCOV
165
      return this;
×
UNCOV
166
    } catch (err: unknown) {
×
167
      const error = err instanceof Error ? err : new Error('Unknown error');
×
168
      this.props.onError(error);
×
169
      // this._running = false; // TODO
×
170
      throw error;
×
171
    }
×
UNCOV
172
  }
×
UNCOV
173

×
UNCOV
174
  /** Stops a render loop if already running, finalizing */
×
UNCOV
175
  stop() {
×
UNCOV
176
    // console.debug(`Stopping ${this.constructor.name}`);
×
UNCOV
177
    if (this._running) {
×
UNCOV
178
      // call callback
×
UNCOV
179
      // If stop is called immediately, we can end up in a state where props haven't been initialized...
×
UNCOV
180
      if (this.animationProps && !this._error) {
×
UNCOV
181
        this.props.onFinalize(this.animationProps);
×
UNCOV
182
      }
×
UNCOV
183

×
UNCOV
184
      this._cancelAnimationFrame();
×
UNCOV
185
      this._nextFramePromise = null;
×
UNCOV
186
      this._resolveNextFrame = null;
×
UNCOV
187
      this._running = false;
×
UNCOV
188
    }
×
UNCOV
189
    return this;
×
UNCOV
190
  }
×
UNCOV
191

×
UNCOV
192
  /** Explicitly draw a frame */
×
UNCOV
193
  redraw(): this {
×
UNCOV
194
    if (this.device?.isLost || this._error) {
×
195
      return this;
×
196
    }
×
UNCOV
197

×
UNCOV
198
    this._beginFrameTimers();
×
UNCOV
199

×
UNCOV
200
    this._setupFrame();
×
UNCOV
201
    this._updateAnimationProps();
×
UNCOV
202

×
UNCOV
203
    this._renderFrame(this._getAnimationProps());
×
UNCOV
204

×
UNCOV
205
    // clear needsRedraw flag
×
UNCOV
206
    this._clearNeedsRedraw();
×
UNCOV
207

×
UNCOV
208
    if (this._resolveNextFrame) {
×
UNCOV
209
      this._resolveNextFrame(this);
×
UNCOV
210
      this._nextFramePromise = null;
×
UNCOV
211
      this._resolveNextFrame = null;
×
UNCOV
212
    }
×
UNCOV
213

×
UNCOV
214
    this._endFrameTimers();
×
UNCOV
215

×
UNCOV
216
    return this;
×
UNCOV
217
  }
×
UNCOV
218

×
UNCOV
219
  /** Add a timeline, it will be automatically updated by the animation loop. */
×
UNCOV
220
  attachTimeline(timeline: Timeline): Timeline {
×
221
    this.timeline = timeline;
×
222
    return this.timeline;
×
223
  }
×
UNCOV
224

×
UNCOV
225
  /** Remove a timeline */
×
UNCOV
226
  detachTimeline(): void {
×
227
    this.timeline = null;
×
228
  }
×
UNCOV
229

×
UNCOV
230
  /** Wait until a render completes */
×
UNCOV
231
  waitForRender(): Promise<AnimationLoop> {
×
UNCOV
232
    this.setNeedsRedraw('waitForRender');
×
UNCOV
233

×
UNCOV
234
    if (!this._nextFramePromise) {
×
UNCOV
235
      this._nextFramePromise = new Promise(resolve => {
×
UNCOV
236
        this._resolveNextFrame = resolve;
×
UNCOV
237
      });
×
UNCOV
238
    }
×
UNCOV
239
    return this._nextFramePromise;
×
UNCOV
240
  }
×
UNCOV
241

×
UNCOV
242
  /** TODO - should use device.deviceContext */
×
UNCOV
243
  async toDataURL(): Promise<string> {
×
244
    this.setNeedsRedraw('toDataURL');
×
245
    await this.waitForRender();
×
246
    if (this.canvas instanceof HTMLCanvasElement) {
×
247
      return this.canvas.toDataURL();
×
248
    }
×
249
    throw new Error('OffscreenCanvas');
×
250
  }
×
UNCOV
251

×
UNCOV
252
  // PRIVATE METHODS
×
UNCOV
253

×
UNCOV
254
  _initialize(): void {
×
UNCOV
255
    this._startEventHandling();
×
UNCOV
256

×
UNCOV
257
    // Initialize the callback data
×
UNCOV
258
    this._initializeAnimationProps();
×
UNCOV
259
    this._updateAnimationProps();
×
UNCOV
260

×
UNCOV
261
    // Default viewport setup, in case onInitialize wants to render
×
UNCOV
262
    this._resizeViewport();
×
UNCOV
263

×
UNCOV
264
    // this._gpuTimeQuery = Query.isSupported(this.gl, ['timers']) ? new Query(this.gl) : null;
×
UNCOV
265
  }
×
UNCOV
266

×
UNCOV
267
  _setDisplay(display: any): void {
×
268
    if (this.display) {
×
269
      this.display.destroy();
×
270
      this.display.animationLoop = null;
×
271
    }
×
272

×
273
    // store animation loop on the display
×
274
    if (display) {
×
275
      display.animationLoop = this;
×
276
    }
×
277

×
278
    this.display = display;
×
279
  }
×
UNCOV
280

×
UNCOV
281
  _requestAnimationFrame(): void {
×
UNCOV
282
    if (!this._running) {
×
UNCOV
283
      return;
×
UNCOV
284
    }
×
UNCOV
285

×
UNCOV
286
    // VR display has a separate animation frame to sync with headset
×
UNCOV
287
    // TODO WebVR API discontinued, replaced by WebXR: https://immersive-web.github.io/webxr/
×
UNCOV
288
    // See https://developer.mozilla.org/en-US/docs/Web/API/VRDisplay/requestAnimationFrame
×
UNCOV
289
    // if (this.display && this.display.requestAnimationFrame) {
×
UNCOV
290
    //   this._animationFrameId = this.display.requestAnimationFrame(this._animationFrame.bind(this));
×
UNCOV
291
    // }
×
UNCOV
292
    this._animationFrameId = requestAnimationFramePolyfill(this._animationFrame.bind(this));
×
UNCOV
293
  }
×
UNCOV
294

×
UNCOV
295
  _cancelAnimationFrame(): void {
×
UNCOV
296
    if (this._animationFrameId === null) {
×
UNCOV
297
      return;
×
UNCOV
298
    }
×
UNCOV
299

×
UNCOV
300
    // VR display has a separate animation frame to sync with headset
×
UNCOV
301
    // TODO WebVR API discontinued, replaced by WebXR: https://immersive-web.github.io/webxr/
×
UNCOV
302
    // See https://developer.mozilla.org/en-US/docs/Web/API/VRDisplay/requestAnimationFrame
×
UNCOV
303
    // if (this.display && this.display.cancelAnimationFramePolyfill) {
×
UNCOV
304
    //   this.display.cancelAnimationFrame(this._animationFrameId);
×
UNCOV
305
    // }
×
UNCOV
306
    cancelAnimationFramePolyfill(this._animationFrameId);
×
UNCOV
307
    this._animationFrameId = null;
×
UNCOV
308
  }
×
UNCOV
309

×
UNCOV
310
  _animationFrame(): void {
×
UNCOV
311
    if (!this._running) {
×
312
      return;
×
313
    }
×
UNCOV
314
    this.redraw();
×
UNCOV
315
    this._requestAnimationFrame();
×
UNCOV
316
  }
×
UNCOV
317

×
UNCOV
318
  // Called on each frame, can be overridden to call onRender multiple times
×
UNCOV
319
  // to support e.g. stereoscopic rendering
×
UNCOV
320
  _renderFrame(animationProps: AnimationProps): void {
×
UNCOV
321
    // Allow e.g. VR display to render multiple frames.
×
UNCOV
322
    if (this.display) {
×
323
      this.display._renderFrame(animationProps);
×
324
      return;
×
325
    }
×
UNCOV
326

×
UNCOV
327
    // call callback
×
UNCOV
328
    this.props.onRender(this._getAnimationProps());
×
UNCOV
329
    // end callback
×
UNCOV
330

×
UNCOV
331
    // Submit commands (necessary on WebGPU)
×
UNCOV
332
    this.device?.submit();
×
UNCOV
333
  }
×
UNCOV
334

×
UNCOV
335
  _clearNeedsRedraw(): void {
×
UNCOV
336
    this.needsRedraw = false;
×
UNCOV
337
  }
×
UNCOV
338

×
UNCOV
339
  _setupFrame(): void {
×
UNCOV
340
    this._resizeViewport();
×
UNCOV
341
  }
×
UNCOV
342

×
UNCOV
343
  // Initialize the  object that will be passed to app callbacks
×
UNCOV
344
  _initializeAnimationProps(): void {
×
UNCOV
345
    const canvasContext = this.device?.getDefaultCanvasContext();
×
UNCOV
346
    if (!this.device || !canvasContext) {
×
347
      throw new Error('loop');
×
348
    }
×
UNCOV
349

×
UNCOV
350
    const canvas = canvasContext?.canvas;
×
UNCOV
351
    const useDevicePixels = canvasContext.props.useDevicePixels;
×
UNCOV
352

×
UNCOV
353
    this.animationProps = {
×
UNCOV
354
      animationLoop: this,
×
UNCOV
355

×
UNCOV
356
      device: this.device,
×
UNCOV
357
      canvasContext,
×
UNCOV
358
      canvas,
×
UNCOV
359
      // @ts-expect-error Deprecated
×
UNCOV
360
      useDevicePixels,
×
UNCOV
361

×
UNCOV
362
      timeline: this.timeline,
×
UNCOV
363

×
UNCOV
364
      needsRedraw: false,
×
UNCOV
365

×
UNCOV
366
      // Placeholders
×
UNCOV
367
      width: 1,
×
UNCOV
368
      height: 1,
×
UNCOV
369
      aspect: 1,
×
UNCOV
370

×
UNCOV
371
      // Animation props
×
UNCOV
372
      time: 0,
×
UNCOV
373
      startTime: Date.now(),
×
UNCOV
374
      engineTime: 0,
×
UNCOV
375
      tick: 0,
×
UNCOV
376
      tock: 0,
×
UNCOV
377

×
UNCOV
378
      // Experimental
×
UNCOV
379
      _mousePosition: null // Event props
×
UNCOV
380
    };
×
UNCOV
381
  }
×
UNCOV
382

×
UNCOV
383
  _getAnimationProps(): AnimationProps {
×
UNCOV
384
    if (!this.animationProps) {
×
385
      throw new Error('animationProps');
×
386
    }
×
UNCOV
387
    return this.animationProps;
×
UNCOV
388
  }
×
UNCOV
389

×
UNCOV
390
  // Update the context object that will be passed to app callbacks
×
UNCOV
391
  _updateAnimationProps(): void {
×
UNCOV
392
    if (!this.animationProps) {
×
393
      return;
×
394
    }
×
UNCOV
395

×
UNCOV
396
    // Can this be replaced with canvas context?
×
UNCOV
397
    const {width, height, aspect} = this._getSizeAndAspect();
×
UNCOV
398
    if (width !== this.animationProps.width || height !== this.animationProps.height) {
×
399
      this.setNeedsRedraw('drawing buffer resized');
×
400
    }
×
UNCOV
401
    if (aspect !== this.animationProps.aspect) {
×
402
      this.setNeedsRedraw('drawing buffer aspect changed');
×
403
    }
×
UNCOV
404

×
UNCOV
405
    this.animationProps.width = width;
×
UNCOV
406
    this.animationProps.height = height;
×
UNCOV
407
    this.animationProps.aspect = aspect;
×
UNCOV
408

×
UNCOV
409
    this.animationProps.needsRedraw = this.needsRedraw;
×
UNCOV
410

×
UNCOV
411
    // Update time properties
×
UNCOV
412
    this.animationProps.engineTime = Date.now() - this.animationProps.startTime;
×
UNCOV
413

×
UNCOV
414
    if (this.timeline) {
×
415
      this.timeline.update(this.animationProps.engineTime);
×
416
    }
×
UNCOV
417

×
UNCOV
418
    this.animationProps.tick = Math.floor((this.animationProps.time / 1000) * 60);
×
UNCOV
419
    this.animationProps.tock++;
×
UNCOV
420

×
UNCOV
421
    // For back compatibility
×
UNCOV
422
    this.animationProps.time = this.timeline
×
423
      ? this.timeline.getTime()
×
UNCOV
424
      : this.animationProps.engineTime;
×
UNCOV
425
  }
×
UNCOV
426

×
UNCOV
427
  /** Wait for supplied device */
×
UNCOV
428
  async _initDevice() {
×
UNCOV
429
    this.device = await this.props.device;
×
UNCOV
430
    if (!this.device) {
×
431
      throw new Error('No device provided');
×
432
    }
×
UNCOV
433
    this.canvas = this.device.getDefaultCanvasContext().canvas || null;
×
UNCOV
434
    // this._createInfoDiv();
×
UNCOV
435
  }
×
UNCOV
436

×
UNCOV
437
  _createInfoDiv(): void {
×
438
    if (this.canvas && this.props.onAddHTML) {
×
439
      const wrapperDiv = document.createElement('div');
×
440
      document.body.appendChild(wrapperDiv);
×
441
      wrapperDiv.style.position = 'relative';
×
442
      const div = document.createElement('div');
×
443
      div.style.position = 'absolute';
×
444
      div.style.left = '10px';
×
445
      div.style.bottom = '10px';
×
446
      div.style.width = '300px';
×
447
      div.style.background = 'white';
×
448
      if (this.canvas instanceof HTMLCanvasElement) {
×
449
        wrapperDiv.appendChild(this.canvas);
×
450
      }
×
451
      wrapperDiv.appendChild(div);
×
452
      const html = this.props.onAddHTML(div);
×
453
      if (html) {
×
454
        div.innerHTML = html;
×
455
      }
×
456
    }
×
457
  }
×
UNCOV
458

×
UNCOV
459
  _getSizeAndAspect(): {width: number; height: number; aspect: number} {
×
UNCOV
460
    if (!this.device) {
×
461
      return {width: 1, height: 1, aspect: 1};
×
462
    }
×
UNCOV
463
    // https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
×
UNCOV
464
    const [width, height] = this.device?.getDefaultCanvasContext().getDevicePixelSize() || [1, 1];
×
UNCOV
465

×
UNCOV
466
    // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
×
UNCOV
467
    let aspect = 1;
×
UNCOV
468
    const canvas = this.device?.getDefaultCanvasContext().canvas;
×
UNCOV
469

×
UNCOV
470
    // @ts-expect-error
×
UNCOV
471
    if (canvas && canvas.clientHeight) {
×
UNCOV
472
      // @ts-expect-error
×
UNCOV
473
      aspect = canvas.clientWidth / canvas.clientHeight;
×
UNCOV
474
    } else if (width > 0 && height > 0) {
×
475
      aspect = width / height;
×
476
    }
×
UNCOV
477

×
UNCOV
478
    return {width, height, aspect};
×
UNCOV
479
  }
×
UNCOV
480

×
UNCOV
481
  /** @deprecated Default viewport setup */
×
UNCOV
482
  _resizeViewport(): void {
×
UNCOV
483
    // TODO can we use canvas context to code this in a portable way?
×
UNCOV
484
    // @ts-expect-error Expose on canvasContext
×
UNCOV
485
    if (this.props.autoResizeViewport && this.device.gl) {
×
486
      // @ts-expect-error Expose canvasContext
×
487
      this.device.gl.viewport(
×
488
        0,
×
489
        0,
×
490
        // @ts-expect-error Expose canvasContext
×
491
        this.device.gl.drawingBufferWidth,
×
492
        // @ts-expect-error Expose canvasContext
×
493
        this.device.gl.drawingBufferHeight
×
494
      );
×
495
    }
×
UNCOV
496
  }
×
UNCOV
497

×
UNCOV
498
  _beginFrameTimers() {
×
UNCOV
499
    this.frameRate.timeEnd();
×
UNCOV
500
    this.frameRate.timeStart();
×
UNCOV
501

×
UNCOV
502
    // Check if timer for last frame has completed.
×
UNCOV
503
    // GPU timer results are never available in the same
×
UNCOV
504
    // frame they are captured.
×
UNCOV
505
    // if (
×
UNCOV
506
    //   this._gpuTimeQuery &&
×
UNCOV
507
    //   this._gpuTimeQuery.isResultAvailable() &&
×
UNCOV
508
    //   !this._gpuTimeQuery.isTimerDisjoint()
×
UNCOV
509
    // ) {
×
UNCOV
510
    //   this.stats.get('GPU Time').addTime(this._gpuTimeQuery.getTimerMilliseconds());
×
UNCOV
511
    // }
×
UNCOV
512

×
UNCOV
513
    // if (this._gpuTimeQuery) {
×
UNCOV
514
    //   // GPU time query start
×
UNCOV
515
    //   this._gpuTimeQuery.beginTimeElapsedQuery();
×
UNCOV
516
    // }
×
UNCOV
517

×
UNCOV
518
    this.cpuTime.timeStart();
×
UNCOV
519
  }
×
UNCOV
520

×
UNCOV
521
  _endFrameTimers() {
×
UNCOV
522
    this.cpuTime.timeEnd();
×
UNCOV
523

×
UNCOV
524
    // if (this._gpuTimeQuery) {
×
UNCOV
525
    //   // GPU time query end. Results will be available on next frame.
×
UNCOV
526
    //   this._gpuTimeQuery.end();
×
UNCOV
527
    // }
×
UNCOV
528
  }
×
UNCOV
529

×
UNCOV
530
  // Event handling
×
UNCOV
531

×
UNCOV
532
  _startEventHandling() {
×
UNCOV
533
    if (this.canvas) {
×
UNCOV
534
      this.canvas.addEventListener('mousemove', this._onMousemove.bind(this));
×
UNCOV
535
      this.canvas.addEventListener('mouseleave', this._onMouseleave.bind(this));
×
UNCOV
536
    }
×
UNCOV
537
  }
×
UNCOV
538

×
UNCOV
539
  _onMousemove(event: Event) {
×
540
    if (event instanceof MouseEvent) {
×
541
      this._getAnimationProps()._mousePosition = [event.offsetX, event.offsetY];
×
542
    }
×
543
  }
×
UNCOV
544

×
UNCOV
545
  _onMouseleave(event: Event) {
×
546
    this._getAnimationProps()._mousePosition = null;
×
547
  }
×
UNCOV
548
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc