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

excaliburjs / Excalibur / 19716357641

26 Nov 2025 08:18PM UTC coverage: 88.641% (+0.07%) from 88.576%
19716357641

Pull #3585

github

web-flow
Merge 7d0f94fd5 into 3b683c589
Pull Request #3585: feat!: debug draw improvements

5289 of 7219 branches covered (73.26%)

280 of 306 new or added lines in 18 files covered. (91.5%)

4 existing lines in 2 files now uncovered.

14655 of 16533 relevant lines covered (88.64%)

24567.88 hits per line

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

90.83
/src/engine/Graphics/Context/debug-circle-renderer/debug-circle-renderer.ts
1
import { Color } from '../../../Color';
2
import type { Vector } from '../../../Math/vector';
3
import { vec } from '../../../Math/vector';
4
import { GraphicsDiagnostics } from '../../GraphicsDiagnostics';
5
import type { ExcaliburGraphicsContextWebGL } from '../ExcaliburGraphicsContextWebGL';
6
import { pixelSnapEpsilon } from '../ExcaliburGraphicsContextWebGL';
7
import { QuadIndexBuffer } from '../quad-index-buffer';
8
import type { RendererPlugin } from '../renderer';
9
import { Shader } from '../shader';
10
import { VertexBuffer } from '../vertex-buffer';
11
import { VertexLayout } from '../vertex-layout';
12

13
import frag from './debug-circle-renderer.frag.glsl?raw';
14
import vert from './debug-circle-renderer.vert.glsl?raw';
15

16
export class DebugCircleRenderer implements RendererPlugin {
17
  public readonly type = 'ex.debug-circle';
4✔
18
  public priority: number = 0;
4✔
19

20
  private _maxCircles: number = 10922; // max(uint16) / 6 verts
4✔
21

22
  private _shader!: Shader;
23
  private _context!: ExcaliburGraphicsContextWebGL;
24
  private _gl!: WebGLRenderingContext;
25
  private _buffer!: VertexBuffer;
26
  private _layout!: VertexLayout;
27
  private _quads!: QuadIndexBuffer;
28

29
  private _circleCount: number = 0;
4✔
30
  private _vertexIndex: number = 0;
4✔
31

32
  initialize(gl: WebGL2RenderingContext, context: ExcaliburGraphicsContextWebGL): void {
33
    this._gl = gl;
4✔
34
    this._context = context;
4✔
35
    this._shader = new Shader({
4✔
36
      graphicsContext: context,
37
      fragmentSource: frag,
38
      vertexSource: vert
39
    });
40
    this._shader.compile();
4✔
41

42
    // setup uniforms
43
    this._shader.use();
4✔
44
    this._shader.setUniformMatrix('u_matrix', context.ortho);
4✔
45

46
    this._buffer = new VertexBuffer({
4✔
47
      gl,
48
      size: 15 * 4 * this._maxCircles,
49
      type: 'dynamic'
50
    });
51

52
    this._layout = new VertexLayout({
4✔
53
      gl,
54
      shader: this._shader,
55
      vertexBuffer: this._buffer,
56
      attributes: [
57
        ['a_position', 2],
58
        ['a_uv', 2],
59
        ['a_opacity', 1],
60
        ['a_color', 4],
61
        ['a_strokeColor', 4],
62
        ['a_strokeThickness', 1],
63
        ['a_radius', 1]
64
      ]
65
    });
66

67
    this._quads = new QuadIndexBuffer(gl, this._maxCircles, true);
4✔
68
  }
69

70
  public dispose() {
71
    this._buffer.dispose();
4✔
72
    this._quads.dispose();
4✔
73
    this._shader.dispose();
4✔
74
    this._context = null as any;
4✔
75
    this._gl = null as any;
4✔
76
  }
77

78
  private _isFull() {
79
    if (this._circleCount >= this._maxCircles) {
229!
NEW
80
      return true;
×
81
    }
82
    return false;
229✔
83
  }
84

85
  draw(pos: Vector, radius: number, color: Color, stroke: Color = Color.Transparent, strokeThickness: number = 0): void {
454✔
86
    if (this._isFull()) {
229!
NEW
87
      this.flush();
×
88
    }
89
    this._circleCount++;
229✔
90

91
    // transform based on current context
92
    const transform = this._context.getTransform();
229✔
93
    const scale = transform.getScaleX();
229✔
94
    const opacity = this._context.opacity;
229✔
95
    const snapToPixel = this._context.snapToPixel;
229✔
96

97
    const topLeft = transform.multiply(pos.add(vec(-radius, -radius)));
229✔
98
    const topRight = transform.multiply(pos.add(vec(radius, -radius)));
229✔
99
    const bottomRight = transform.multiply(pos.add(vec(radius, radius)));
229✔
100
    const bottomLeft = transform.multiply(pos.add(vec(-radius, radius)));
229✔
101

102
    if (snapToPixel) {
229!
NEW
103
      topLeft.x = ~~(topLeft.x + pixelSnapEpsilon);
×
NEW
104
      topLeft.y = ~~(topLeft.y + pixelSnapEpsilon);
×
105

NEW
106
      topRight.x = ~~(topRight.x + pixelSnapEpsilon);
×
NEW
107
      topRight.y = ~~(topRight.y + pixelSnapEpsilon);
×
108

NEW
109
      bottomLeft.x = ~~(bottomLeft.x + pixelSnapEpsilon);
×
NEW
110
      bottomLeft.y = ~~(bottomLeft.y + pixelSnapEpsilon);
×
111

NEW
112
      bottomRight.x = ~~(bottomRight.x + pixelSnapEpsilon);
×
NEW
113
      bottomRight.y = ~~(bottomRight.y + pixelSnapEpsilon);
×
114
    }
115

116
    // TODO UV could be static vertex buffer
117
    const uvx0 = 0;
229✔
118
    const uvy0 = 0;
229✔
119
    const uvx1 = 1;
229✔
120
    const uvy1 = 1;
229✔
121

122
    // update data
123
    const vertexBuffer = this._layout.vertexBuffer.bufferData;
229✔
124

125
    // (0, 0) - 0
126
    vertexBuffer[this._vertexIndex++] = topLeft.x;
229✔
127
    vertexBuffer[this._vertexIndex++] = topLeft.y;
229✔
128
    vertexBuffer[this._vertexIndex++] = uvx0;
229✔
129
    vertexBuffer[this._vertexIndex++] = uvy0;
229✔
130
    vertexBuffer[this._vertexIndex++] = opacity;
229✔
131
    vertexBuffer[this._vertexIndex++] = color.r / 255;
229✔
132
    vertexBuffer[this._vertexIndex++] = color.g / 255;
229✔
133
    vertexBuffer[this._vertexIndex++] = color.b / 255;
229✔
134
    vertexBuffer[this._vertexIndex++] = color.a;
229✔
135
    vertexBuffer[this._vertexIndex++] = stroke.r / 255;
229✔
136
    vertexBuffer[this._vertexIndex++] = stroke.g / 255;
229✔
137
    vertexBuffer[this._vertexIndex++] = stroke.b / 255;
229✔
138
    vertexBuffer[this._vertexIndex++] = stroke.a;
229✔
139
    vertexBuffer[this._vertexIndex++] = strokeThickness;
229✔
140
    vertexBuffer[this._vertexIndex++] = radius * scale;
229✔
141

142
    // (0, 1) - 1
143
    vertexBuffer[this._vertexIndex++] = bottomLeft.x;
229✔
144
    vertexBuffer[this._vertexIndex++] = bottomLeft.y;
229✔
145
    vertexBuffer[this._vertexIndex++] = uvx0;
229✔
146
    vertexBuffer[this._vertexIndex++] = uvy1;
229✔
147
    vertexBuffer[this._vertexIndex++] = opacity;
229✔
148
    vertexBuffer[this._vertexIndex++] = color.r / 255;
229✔
149
    vertexBuffer[this._vertexIndex++] = color.g / 255;
229✔
150
    vertexBuffer[this._vertexIndex++] = color.b / 255;
229✔
151
    vertexBuffer[this._vertexIndex++] = color.a;
229✔
152
    vertexBuffer[this._vertexIndex++] = stroke.r / 255;
229✔
153
    vertexBuffer[this._vertexIndex++] = stroke.g / 255;
229✔
154
    vertexBuffer[this._vertexIndex++] = stroke.b / 255;
229✔
155
    vertexBuffer[this._vertexIndex++] = stroke.a;
229✔
156
    vertexBuffer[this._vertexIndex++] = strokeThickness;
229✔
157
    vertexBuffer[this._vertexIndex++] = radius * scale;
229✔
158

159
    // (1, 0) - 2
160
    vertexBuffer[this._vertexIndex++] = topRight.x;
229✔
161
    vertexBuffer[this._vertexIndex++] = topRight.y;
229✔
162
    vertexBuffer[this._vertexIndex++] = uvx1;
229✔
163
    vertexBuffer[this._vertexIndex++] = uvy0;
229✔
164
    vertexBuffer[this._vertexIndex++] = opacity;
229✔
165
    vertexBuffer[this._vertexIndex++] = color.r / 255;
229✔
166
    vertexBuffer[this._vertexIndex++] = color.g / 255;
229✔
167
    vertexBuffer[this._vertexIndex++] = color.b / 255;
229✔
168
    vertexBuffer[this._vertexIndex++] = color.a;
229✔
169
    vertexBuffer[this._vertexIndex++] = stroke.r / 255;
229✔
170
    vertexBuffer[this._vertexIndex++] = stroke.g / 255;
229✔
171
    vertexBuffer[this._vertexIndex++] = stroke.b / 255;
229✔
172
    vertexBuffer[this._vertexIndex++] = stroke.a;
229✔
173
    vertexBuffer[this._vertexIndex++] = strokeThickness;
229✔
174
    vertexBuffer[this._vertexIndex++] = radius * scale;
229✔
175

176
    // (1, 1) - 3
177
    vertexBuffer[this._vertexIndex++] = bottomRight.x;
229✔
178
    vertexBuffer[this._vertexIndex++] = bottomRight.y;
229✔
179
    vertexBuffer[this._vertexIndex++] = uvx1;
229✔
180
    vertexBuffer[this._vertexIndex++] = uvy1;
229✔
181
    vertexBuffer[this._vertexIndex++] = opacity;
229✔
182
    vertexBuffer[this._vertexIndex++] = color.r / 255;
229✔
183
    vertexBuffer[this._vertexIndex++] = color.g / 255;
229✔
184
    vertexBuffer[this._vertexIndex++] = color.b / 255;
229✔
185
    vertexBuffer[this._vertexIndex++] = color.a;
229✔
186
    vertexBuffer[this._vertexIndex++] = stroke.r / 255;
229✔
187
    vertexBuffer[this._vertexIndex++] = stroke.g / 255;
229✔
188
    vertexBuffer[this._vertexIndex++] = stroke.b / 255;
229✔
189
    vertexBuffer[this._vertexIndex++] = stroke.a;
229✔
190
    vertexBuffer[this._vertexIndex++] = strokeThickness;
229✔
191
    vertexBuffer[this._vertexIndex++] = radius * scale;
229✔
192
  }
193

194
  hasPendingDraws(): boolean {
195
    return this._circleCount !== 0;
2✔
196
  }
197

198
  flush(): void {
199
    // nothing to draw early exit
200
    if (this._circleCount === 0) {
4!
NEW
201
      return;
×
202
    }
203

204
    const gl = this._gl;
4✔
205
    // Bind the shader
206
    this._shader.use();
4✔
207

208
    // Bind the memory layout and upload data
209
    this._layout.use(true);
4✔
210

211
    // Update ortho matrix uniform
212
    this._shader.setUniformMatrix('u_matrix', this._context.ortho);
4✔
213

214
    // Bind index buffer
215
    this._quads.bind();
4✔
216

217
    // Draw all the quads
218
    gl.drawElements(gl.TRIANGLES, this._circleCount * 6, this._quads.bufferGlType, 0);
4✔
219

220
    GraphicsDiagnostics.DrawnImagesCount += this._circleCount;
4✔
221
    GraphicsDiagnostics.DrawCallCount++;
4✔
222

223
    // Reset
224
    this._circleCount = 0;
4✔
225
    this._vertexIndex = 0;
4✔
226
  }
227
}
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