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

excaliburjs / Excalibur / 14840900305

05 May 2025 04:07PM UTC coverage: 87.864% (-1.5%) from 89.319%
14840900305

push

github

web-flow
chore: migrate tests to vitest (#3381)

managed to migrate the engine leak & memory reporters, although it took a bit of rigamarole. Mainly because, as opposed to karma, the reporter runs in node and not the browser. So I have to track the data needed separately in some global hooks _within_ the browser environment, which the reporter then reads and creates the logs if needed.

However, it seems the memory tracking is regularly reporting >1 mb, so I'm not sure if it's tracking properly or if things have changed with the new browsers. My only theory is that because the timing where memory is read is during an `afterEach` _before_ the test's `afterEach`, it's running before any potential cleanup. The timing of this is not something that's easy to control, unfortunately. I did try to prove this theory by doing the memory analysis on the next test's `beforeEach`, but it didnt seem to change the results, so it may not be an issue.

Implementation is done in `src/spec/vitest/__reporters__/memory.ts` and `src/spec/vitest/__reporters/memory.setup.ts`

---

I noticed CouroutineSpec was an offender for >10mb memory, which spins up additional engines. I added a short 100ms wait after the engine.dispose and the memory usage did drop, so it does seem like this is prone to scheduled garbage collecting.

---

I was able to run the garbage collector (if exposed, currently only on chrome) and this makes the reports more accurate

4998 of 6942 branches covered (72.0%)

13655 of 15541 relevant lines covered (87.86%)

25165.01 hits per line

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

93.98
/src/engine/Graphics/Context/render-target.ts
1
import { RenderSource } from './render-source';
2

3
declare global {
4
  interface WebGL2RenderingContext {
5
    /**
6
     * Experimental only in chrome
7
     */
8
    drawingBufferFormat?: number;
9
  }
10
}
11

12
export interface RenderTargetOptions {
13
  gl: WebGL2RenderingContext;
14
  width: number;
15
  height: number;
16
  transparency: boolean;
17
  /**
18
   * Optionally enable render buffer multisample anti-aliasing
19
   *
20
   * By default false
21
   */
22
  antialias?: boolean;
23
  /**
24
   * Optionally specify number of anti-aliasing samples to use
25
   *
26
   * By default the max for the platform is used if antialias is on.
27
   */
28
  samples?: number;
29
}
30

31
export class RenderTarget {
32
  width: number;
33
  height: number;
34
  transparency: boolean;
35
  antialias: boolean = false;
3,396✔
36
  samples: number = 1;
3,396✔
37
  private _gl: WebGL2RenderingContext;
38
  public readonly bufferFormat: number;
39
  constructor(options: RenderTargetOptions) {
40
    this._gl = options.gl;
3,396✔
41
    this.width = options.width;
3,396✔
42
    this.height = options.height;
3,396✔
43
    this.transparency = options.transparency;
3,396✔
44
    this.antialias = options.antialias ?? this.antialias;
3,396✔
45
    this.samples = options.samples ?? this._gl.getParameter(this._gl.MAX_SAMPLES);
3,396!
46

47
    const gl = this._gl;
3,396✔
48
    // Determine current context format for blitting later needs to match
49
    if (gl.drawingBufferFormat) {
3,396!
50
      this.bufferFormat = gl.drawingBufferFormat;
3,396✔
51
    } else {
52
      // Documented in webgl spec
53
      // https://registry.khronos.org/webgl/specs/latest/1.0/
54
      if (this.transparency) {
×
55
        this.bufferFormat = gl.RGBA8;
×
56
      } else {
57
        this.bufferFormat = gl.RGB8;
×
58
      }
59
    }
60

61
    this._setupRenderBuffer();
3,396✔
62
    this._setupFramebuffer();
3,396✔
63
  }
64

65
  setResolution(width: number, height: number) {
66
    const gl = this._gl;
3,044✔
67
    this.width = width;
3,044✔
68
    this.height = height;
3,044✔
69

70
    // update backing texture size
71
    gl.bindTexture(gl.TEXTURE_2D, this._frameTexture);
3,044✔
72
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
3,044✔
73

74
    // update render buffer size
75
    if (this._renderBuffer) {
3,044✔
76
      gl.bindRenderbuffer(gl.RENDERBUFFER, this._renderBuffer);
16✔
77
      gl.renderbufferStorageMultisample(
16✔
78
        gl.RENDERBUFFER,
79
        Math.min(this.samples, gl.getParameter(gl.MAX_SAMPLES)),
80
        this.bufferFormat,
81
        this.width,
82
        this.height
83
      );
84
    }
85
  }
86

87
  private _renderBuffer!: WebGLRenderbuffer;
88
  public get renderBuffer() {
89
    return this._renderBuffer;
12✔
90
  }
91
  private _renderFrameBuffer!: WebGLFramebuffer;
92
  public get renderFrameBuffer() {
93
    return this._renderFrameBuffer;
40✔
94
  }
95

96
  private _frameBuffer!: WebGLFramebuffer;
97
  public get frameBuffer() {
98
    return this._frameBuffer;
1,817✔
99
  }
100
  private _frameTexture!: WebGLTexture;
101
  public get frameTexture() {
102
    return this._frameTexture;
×
103
  }
104

105
  private _setupRenderBuffer() {
106
    if (this.antialias) {
3,396✔
107
      const gl = this._gl;
119✔
108
      // Render buffers can be used as an input to a shader
109
      this._renderBuffer = gl.createRenderbuffer()!;
119✔
110
      this._renderFrameBuffer = gl.createFramebuffer()!;
119✔
111
      gl.bindRenderbuffer(gl.RENDERBUFFER, this._renderBuffer);
119✔
112
      gl.renderbufferStorageMultisample(
119✔
113
        gl.RENDERBUFFER,
114
        Math.min(this.samples, gl.getParameter(gl.MAX_SAMPLES)),
115
        this.bufferFormat,
116
        this.width,
117
        this.height
118
      );
119
      gl.bindFramebuffer(gl.FRAMEBUFFER, this._renderFrameBuffer);
119✔
120
      gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, this._renderBuffer);
119✔
121
    }
122
  }
123

124
  private _setupFramebuffer() {
125
    // Allocates frame buffer
126
    const gl = this._gl;
3,396✔
127
    this._frameTexture = gl.createTexture()!;
3,396✔
128
    gl.bindTexture(gl.TEXTURE_2D, this._frameTexture);
3,396✔
129
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
3,396✔
130

131
    // set the filtering so we don't need mips
132
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
3,396✔
133
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
3,396✔
134
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
3,396✔
135
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
3,396✔
136

137
    // attach the texture as the first color attachment
138
    const attachmentPoint = gl.COLOR_ATTACHMENT0;
3,396✔
139

140
    // After this bind all draw calls will draw to this framebuffer texture
141
    this._frameBuffer = gl.createFramebuffer()!;
3,396✔
142
    gl.bindFramebuffer(gl.FRAMEBUFFER, this._frameBuffer);
3,396✔
143
    gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, this._frameTexture, 0);
3,396✔
144

145
    // Reset after initialized
146
    this.disable();
3,396✔
147
  }
148

149
  public toRenderSource() {
150
    if (this.renderBuffer) {
12!
151
      this.blitRenderBufferToFrameBuffer();
×
152
    }
153
    const source = new RenderSource(this._gl, this._frameTexture);
12✔
154
    return source;
12✔
155
  }
156

157
  public blitToScreen() {
158
    const gl = this._gl;
1,853✔
159
    // set to size of canvas's drawingBuffer
160
    if (this._renderBuffer) {
1,853✔
161
      gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.renderFrameBuffer);
38✔
162
      gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
38✔
163
      gl.clearBufferfv(gl.COLOR, 0, [0.0, 0.0, 1.0, 1.0]);
38✔
164
      gl.blitFramebuffer(0, 0, this.width, this.height, 0, 0, this.width, this.height, gl.COLOR_BUFFER_BIT, gl.LINEAR);
38✔
165
    } else {
166
      gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.frameBuffer);
1,815✔
167
      gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
1,815✔
168
      gl.clearBufferfv(gl.COLOR, 0, [0.0, 0.0, 1.0, 1.0]);
1,815✔
169
      gl.blitFramebuffer(0, 0, this.width, this.height, 0, 0, this.width, this.height, gl.COLOR_BUFFER_BIT, gl.LINEAR);
1,815✔
170
    }
171
  }
172

173
  public blitRenderBufferToFrameBuffer() {
174
    if (this._renderBuffer) {
2!
175
      const gl = this._gl;
2✔
176
      gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.renderFrameBuffer);
2✔
177
      gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.frameBuffer);
2✔
178
      gl.clearBufferfv(gl.COLOR, 0, [0.0, 0.0, 1.0, 1.0]);
2✔
179
      gl.blitFramebuffer(0, 0, this.width, this.height, 0, 0, this.width, this.height, gl.COLOR_BUFFER_BIT, gl.LINEAR);
2✔
180
    }
181
  }
182

183
  public copyToTexture(texture: WebGLTexture) {
184
    const gl = this._gl;
2✔
185
    if (this._renderBuffer) {
2!
186
      this.blitRenderBufferToFrameBuffer();
2✔
187
    }
188
    gl.bindFramebuffer(gl.FRAMEBUFFER, this._frameBuffer);
2✔
189
    gl.bindTexture(gl.TEXTURE_2D, texture);
2✔
190
    gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, this.width, this.height, 0);
2✔
191
  }
192

193
  /**
194
   * When called, all drawing gets redirected to this render target
195
   */
196
  public use() {
197
    const gl = this._gl;
3,669✔
198
    if (this.antialias) {
3,669✔
199
      gl.bindFramebuffer(gl.FRAMEBUFFER, this._renderFrameBuffer);
77✔
200
    } else {
201
      gl.bindFramebuffer(gl.FRAMEBUFFER, this._frameBuffer);
3,592✔
202
    }
203

204
    // very important to set the viewport to the size of the framebuffer texture
205
    gl.viewport(0, 0, this.width, this.height);
3,669✔
206
  }
207

208
  /**
209
   * When called, all drawing is sent back to the canvas
210
   */
211
  public disable() {
212
    const gl = this._gl;
5,249✔
213
    // passing null switches rendering back to the canvas
214
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
5,249✔
215
    gl.bindTexture(gl.TEXTURE_2D, null);
5,249✔
216
  }
217
}
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