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

visgl / luma.gl / 29583177873

17 Jul 2026 01:14PM UTC coverage: 72.4%. First build
29583177873

Pull #2742

github

web-flow
Merge f290f1071 into 01dd287e7
Pull Request #2742: fix(core): manage device canvas context lifecycle

11341 of 17632 branches covered (64.32%)

Branch coverage included in aggregate %.

73 of 81 new or added lines in 11 files covered. (90.12%)

21961 of 28365 relevant lines covered (77.42%)

5588.52 hits per line

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

69.83
/modules/webgpu/src/adapter/webgpu-presentation-context.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
// biome-ignore format: preserve layout
6
// / <reference types="@webgpu/types" />
7

8
import type {PresentationContextProps, TextureFormatDepthStencil} from '@luma.gl/core';
9
import {PresentationContext, Texture, log} from '@luma.gl/core';
10
import type {WebGPUDevice} from './webgpu-device';
11
import {WebGPUFramebuffer} from './resources/webgpu-framebuffer';
12
import {WebGPUTexture} from './resources/webgpu-texture';
13
import {getCpuHotspotProfiler, getTimestamp} from './helpers/cpu-hotspot-profiler';
14

15
/**
16
 * A WebGPU PresentationContext renders directly into its destination canvas.
17
 */
18
export class WebGPUPresentationContext extends PresentationContext {
19
  readonly device: WebGPUDevice;
20
  readonly handle: GPUCanvasContext;
21

22
  private colorAttachment: WebGPUTexture | null = null;
3✔
23
  private depthStencilAttachment: WebGPUTexture | null = null;
3✔
24
  private framebuffer: WebGPUFramebuffer | null = null;
3✔
25

26
  get [Symbol.toStringTag](): string {
27
    return 'WebGPUPresentationContext';
3✔
28
  }
29

30
  constructor(device: WebGPUDevice, props: PresentationContextProps = {}) {
×
31
    super(props);
3✔
32
    const contextLabel = `${this[Symbol.toStringTag]}(${this.id})`;
3✔
33

34
    const context = this.canvas.getContext('webgpu');
3✔
35
    if (!context) {
3!
36
      throw new Error(`${contextLabel}: Failed to create WebGPU presentation context`);
×
37
    }
38
    this.device = device;
3✔
39
    this.handle = context;
3✔
40

41
    this._setAutoCreatedCanvasId(`${this.device.id}-presentation-canvas`);
3✔
42
    this._configureDevice();
3✔
43
    this._startObservers();
3✔
44
  }
45

46
  override destroy(): void {
47
    if (this.destroyed) {
3!
NEW
48
      return;
×
49
    }
50

51
    if (this.framebuffer) {
3!
52
      this.framebuffer.destroy();
3✔
53
      this.framebuffer = null;
3✔
54
    }
55
    if (this.colorAttachment) {
3!
56
      this.colorAttachment.destroy();
3✔
57
      this.colorAttachment = null;
3✔
58
    }
59
    if (this.depthStencilAttachment) {
3!
60
      this.depthStencilAttachment.destroy();
3✔
61
      this.depthStencilAttachment = null;
3✔
62
    }
63
    this.handle.unconfigure();
3✔
64
    super.destroy();
3✔
65
  }
66

67
  present(): void {
68
    this.device.submit();
1✔
69
  }
70

71
  protected override _configureDevice(): void {
72
    if (this.depthStencilAttachment) {
3!
73
      this.depthStencilAttachment.destroy();
×
74
      this.depthStencilAttachment = null;
×
75
    }
76

77
    this.handle.configure({
3✔
78
      device: this.device.handle,
79
      format: this.device.preferredColorFormat,
80
      colorSpace: this.props.colorSpace,
81
      alphaMode: this.props.alphaMode
82
    });
83

84
    this._createDepthStencilAttachment(this.device.preferredDepthFormat);
3✔
85
  }
86

87
  protected override _getCurrentFramebuffer(
88
    options: {depthStencilFormat?: TextureFormatDepthStencil | false} = {
4✔
89
      depthStencilFormat: 'depth24plus'
90
    }
91
  ): WebGPUFramebuffer {
92
    const profiler = getCpuHotspotProfiler(this.device);
4✔
93
    const startTime = getTimestamp();
4✔
94
    if (profiler) {
4!
95
      profiler.framebufferAcquireCount = (profiler.framebufferAcquireCount || 0) + 1;
×
96
    }
97

98
    try {
4✔
99
      const currentColorAttachment = this._getCurrentTexture();
4✔
100
      if (
4!
101
        currentColorAttachment.width !== this.drawingBufferWidth ||
8✔
102
        currentColorAttachment.height !== this.drawingBufferHeight
103
      ) {
104
        const [oldWidth, oldHeight] = this.getDrawingBufferSize();
×
105
        this.drawingBufferWidth = currentColorAttachment.width;
×
106
        this.drawingBufferHeight = currentColorAttachment.height;
×
107
        log.log(
×
108
          1,
109
          `${this[Symbol.toStringTag]}(${this.id}): Resized to compensate for initial canvas size mismatch ${oldWidth}x${oldHeight} => ${this.drawingBufferWidth}x${this.drawingBufferHeight}px`
110
        )();
111
      }
112

113
      if (options?.depthStencilFormat) {
4!
114
        this._createDepthStencilAttachment(options.depthStencilFormat);
4✔
115
      }
116

117
      this.framebuffer ||= new WebGPUFramebuffer(this.device, {
4✔
118
        id: `${this.id}#framebuffer`,
119
        colorAttachments: [currentColorAttachment],
120
        depthStencilAttachment: null
121
      });
122
      this.framebuffer._reinitialize(
4✔
123
        currentColorAttachment.view,
124
        options?.depthStencilFormat ? this.depthStencilAttachment?.view || null : null
8!
125
      );
126
      return this.framebuffer;
4✔
127
    } finally {
128
      const acquireTimeMs = getTimestamp() - startTime;
4✔
129
      if (profiler) {
4!
130
        profiler.framebufferAcquireTimeMs =
×
131
          (profiler.framebufferAcquireTimeMs || 0) + acquireTimeMs;
×
132
      }
133
    }
134
  }
135

136
  private _getCurrentTexture(): WebGPUTexture {
137
    const profiler = getCpuHotspotProfiler(this.device);
4✔
138
    const currentTextureStartTime = profiler ? getTimestamp() : 0;
4!
139
    const handle = this.handle.getCurrentTexture();
4✔
140
    if (profiler) {
4!
141
      profiler.currentTextureAcquireCount = (profiler.currentTextureAcquireCount || 0) + 1;
×
142
      profiler.currentTextureAcquireTimeMs =
×
143
        (profiler.currentTextureAcquireTimeMs || 0) + (getTimestamp() - currentTextureStartTime);
×
144
    }
145
    if (!this.colorAttachment) {
4✔
146
      this.colorAttachment = this.device.createTexture({
3✔
147
        id: `${this.id}#color-texture`,
148
        handle,
149
        format: this.device.preferredColorFormat,
150
        width: handle.width,
151
        height: handle.height
152
      });
153
      return this.colorAttachment;
3✔
154
    }
155

156
    this.colorAttachment._reinitialize(handle, {
1✔
157
      handle,
158
      format: this.device.preferredColorFormat,
159
      width: handle.width,
160
      height: handle.height
161
    });
162
    return this.colorAttachment;
1✔
163
  }
164

165
  private _createDepthStencilAttachment(
166
    depthStencilFormat: TextureFormatDepthStencil
167
  ): WebGPUTexture {
168
    const needsNewDepthStencilAttachment =
169
      !this.depthStencilAttachment ||
7✔
170
      this.depthStencilAttachment.width !== this.drawingBufferWidth ||
171
      this.depthStencilAttachment.height !== this.drawingBufferHeight ||
172
      this.depthStencilAttachment.format !== depthStencilFormat;
173
    if (needsNewDepthStencilAttachment) {
7✔
174
      this.depthStencilAttachment?.destroy();
3✔
175
      this.depthStencilAttachment = this.device.createTexture({
3✔
176
        id: `${this.id}#depth-stencil-texture`,
177
        usage: Texture.RENDER_ATTACHMENT | Texture.SAMPLE,
178
        format: depthStencilFormat,
179
        width: this.drawingBufferWidth,
180
        height: this.drawingBufferHeight
181
      });
182
    }
183
    return this.depthStencilAttachment!;
7✔
184
  }
185
}
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