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

excaliburjs / Excalibur / 13488918148

24 Feb 2025 01:18AM UTC coverage: 89.318% (-0.7%) from 90.052%
13488918148

push

github

web-flow
feat: Implement Uniform Buffers Object Support (#3360)

This PR implements UBO support in Excalibur

```typescript
var material = game.graphicsContext.createMaterial({
  name: 'light-material',
  fragmentSource: `#version 300 es
    precision mediump float;

    struct Light {
     vec2 pos;
     float radius;
     vec4 color;
    };

    layout(std140) uniform Lighting {
        Light lights[2];
    };

    in vec2 v_uv;
    out vec4 color;

    void main() {
        float distanceToLights = 1.0;
        vec4 finalColor = vec4(0.0, 0.0, 0.0, 1.0);
        for (int i = 0; i < 2; i++) {
            float dist = length(lights[i].pos - v_uv);
            dist = smoothstep(lights[i].radius-.2, lights[i].radius+.2, dist);
            finalColor += lights[i].color * (1.0 - dist);
        }

        color = finalColor;
        // premultiply alpha
        color.rgb = color.rgb * color.a;
    }`,
  uniforms: {
    // prettier-ignore
    Lighting: new Float32Array([
      0.5, 0.5, 0.1, 0.1, // light 1 pos
      0, 1, 0, 1, // light 1 color
      1, 1, 0.1, 0.1, // light2 pos
      0, 0, 1, 1 // light 2 color
    ])
  }
}) as ex.Material;

actor.graphics.material = material;
ex.coroutine(
  game,
  function* () {
    let time = 0;
    while (true) {
      const elapsed = yield;
      time += elapsed / 1000;
      const x1 = Math.cos(time);
      const y1 = Math.sin(time);
      const x2 = Math.cos(-time);
      const y2 = Math.sin(-time);

      // prettier-ignore
      material.uniforms.Lighting = new Float32Array([
        0.2 * x1 + 0.2, 0.2 * y1 + 0.2, 0.1, 0,
        0, 1, 0, 1,
        0.5 * x2 + 0.5, 0.5 * y2 + 0.5, 0.1, 0,
        0, 0, 1, 1
      ]);    }
  }.bind(this)
);

game.start(new ex.Loader([tex]));
```

Additionally there are some DX enhancements to working with uniforms
- You can now set uniform values directly on materials/shader instances and will be upload on next `.use()`
   `material.uniforms.Lighting = ...`

6332 of 8231 branches covered (76.93%)

79 of 209 new or added lines in 9 files covered. (37.8%)

5 existing lines in 1 file now uncovered.

13839 of 15494 relevant lines covered (89.32%)

25321.43 hits per line

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

0.0
/src/engine/Graphics/Context/uniform-buffer.ts
1
export interface UniformBufferOptions {
2
  /**
3
   * WebGL2RenderingContext this layout will be attached to, these cannot be reused across contexts.
4
   */
5
  gl: WebGL2RenderingContext;
6
  /**
7
   * Size in number of floats, so [4.2, 4.0, 2.1] is size = 3
8
   *
9
   * **NOTE It is recommended you use multiples of 4's to avoid hardware implemenetation bugs, vec4's the the most supported**
10
   *
11
   * Ignored if data is passed directly
12
   */
13
  size?: number;
14
  /**
15
   * If the vertices never change switching 'static' can be more efficient on the gpu
16
   *
17
   * Default is 'dynamic'
18
   */
19
  type?: 'static' | 'dynamic';
20

21
  /**
22
   * Optionally pass pre-seeded data, size parameter is ignored
23
   */
24
  data?: Float32Array;
25
}
26

27
export class UniformBuffer {
28
  // TODO provide a struct layout?
29
  private _gl: WebGL2RenderingContext;
30
  /**
31
   * Access to the webgl buffer handle
32
   */
33
  public readonly buffer: WebGLBuffer;
34
  public readonly bufferData: Float32Array;
NEW
35
  public type: 'static' | 'dynamic' = 'static';
×
36
  private _maxFloats: any;
37
  constructor(options: UniformBufferOptions) {
NEW
38
    const { gl, size, type, data } = options;
×
NEW
39
    this._gl = gl;
×
NEW
40
    this.buffer = gl.createBuffer()!;
×
NEW
41
    this._maxFloats = gl.getParameter(gl.MAX_UNIFORM_BLOCK_SIZE) / 32;
×
NEW
42
    if (!data && !size) {
×
NEW
43
      throw Error('Must either provide data or a size to the UniformBuffer');
×
44
    }
45

NEW
46
    if (!data) {
×
NEW
47
      this.bufferData = new Float32Array(size!);
×
48
    } else {
NEW
49
      this.bufferData = data;
×
50
    }
51

NEW
52
    if (this.bufferData.length > this._maxFloats) {
×
NEW
53
      throw Error(`UniformBuffer exceeds browsers allowed number of floats ${this._maxFloats}`);
×
54
    }
NEW
55
    this.type = type ?? this.type;
×
56
    // Allocate buffer
NEW
57
    gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
×
NEW
58
    gl.bufferData(gl.UNIFORM_BUFFER, this.bufferData, this.type === 'static' ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW);
×
59
  }
60

61
  /**
62
   * Bind this uniform buffer
63
   */
64
  bind() {
NEW
65
    const gl = this._gl;
×
NEW
66
    gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
×
67
  }
68

69
  unbind() {
NEW
70
    const gl = this._gl;
×
NEW
71
    gl.bindBuffer(gl.UNIFORM_BUFFER, null);
×
72
  }
73

74
  upload(count?: number) {
NEW
75
    const gl = this._gl;
×
NEW
76
    gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
×
NEW
77
    if (count) {
×
NEW
78
      gl.bufferSubData(gl.UNIFORM_BUFFER, 0, this.bufferData, 0, count);
×
79
    } else {
NEW
80
      gl.bufferData(gl.UNIFORM_BUFFER, this.bufferData, this.type === 'static' ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW);
×
81
    }
82
  }
83

84
  dispose() {
NEW
85
    const gl = this._gl;
×
NEW
86
    gl.deleteBuffer(this.buffer);
×
NEW
87
    this._gl = null as any;
×
88
  }
89
}
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