• 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

89.13
/src/engine/Graphics/FontCache.ts
1
import { BoundingBox } from '../Collision/BoundingBox';
2
import { Color } from '../Color';
3
import { Logger } from '../Util/Log';
4
import { Font } from './Font';
5
import { FontTextInstance } from './FontTextInstance';
6

7
export class FontCache {
8
  public static FONT_TIMEOUT = 500;
1✔
9
  private static _LOGGER = Logger.getInstance();
1✔
10
  private static _TEXT_USAGE = new Map<FontTextInstance, number>();
1✔
11
  private static _TEXT_CACHE = new Map<string, FontTextInstance>();
1✔
12
  private static _MEASURE_CACHE = new Map<string, BoundingBox>();
1✔
13

14
  static measureText(text: string, font: Font, maxWidth?: number): BoundingBox {
15
    const hash = FontTextInstance.getHashCode(font, text);
1,080✔
16
    if (FontCache._MEASURE_CACHE.has(hash)) {
1,080✔
17
      return FontCache._MEASURE_CACHE.get(hash)!;
51✔
18
    }
19
    FontCache._LOGGER.debug('Font text measurement cache miss');
1,029✔
20
    const measurement = font.measureTextWithoutCache(text, maxWidth);
1,029✔
21
    FontCache._MEASURE_CACHE.set(hash, measurement);
1,029✔
22
    return measurement;
1,029✔
23
  }
24

25
  static getTextInstance(text: string, font: Font, color: Color): FontTextInstance {
26
    const hash = FontTextInstance.getHashCode(font, text, color);
25✔
27
    let textInstance = FontCache._TEXT_CACHE.get(hash);
25✔
28
    if (!textInstance) {
25✔
29
      textInstance = new FontTextInstance(font, text, color);
20✔
30
      FontCache._TEXT_CACHE.set(hash, textInstance);
20✔
31
      FontCache._LOGGER.debug('Font text instance cache miss');
20✔
32
    }
33

34
    // Cache the bitmap for certain amount of time
35
    FontCache._TEXT_USAGE.set(textInstance, performance.now());
25✔
36

37
    return textInstance;
25✔
38
  }
39

40
  static checkAndClearCache() {
41
    const deferred: FontTextInstance[] = [];
927✔
42
    const currentHashCodes = new Set<string>();
927✔
43
    for (const [textInstance, time] of FontCache._TEXT_USAGE.entries()) {
927✔
44
      // if bitmap hasn't been used in 100 ms clear it
45
      if (time + FontCache.FONT_TIMEOUT < performance.now()) {
9!
46
        FontCache._LOGGER.debug(`Text cache entry timed out ${textInstance.text}`);
9✔
47
        deferred.push(textInstance);
9✔
48
        textInstance.dispose();
9✔
49
      } else {
UNCOV
50
        const hash = textInstance.getHashCode(false);
×
UNCOV
51
        currentHashCodes.add(hash);
×
52
      }
53
    }
54
    // Deferred removal of text instances
55
    deferred.forEach((t) => {
927✔
56
      FontCache._TEXT_USAGE.delete(t);
9✔
57
    });
58

59
    // Regenerate text instance cache
60
    this._TEXT_CACHE.clear();
927✔
61
    for (const [textInstance] of this._TEXT_USAGE.entries()) {
927✔
UNCOV
62
      this._TEXT_CACHE.set(textInstance.getHashCode(), textInstance);
×
63
    }
64

65
    // Regenerated measurement cache
66
    const newTextMeasurementCache = new Map<string, BoundingBox>();
927✔
67
    for (const current of currentHashCodes) {
927✔
UNCOV
68
      if (FontCache._MEASURE_CACHE.has(current)) {
×
UNCOV
69
        newTextMeasurementCache.set(current, FontCache._MEASURE_CACHE.get(current)!);
×
70
      }
71
    }
72
    this._MEASURE_CACHE.clear();
927✔
73
    this._MEASURE_CACHE = newTextMeasurementCache;
927✔
74
  }
75

76
  public static get cacheSize() {
77
    return FontCache._TEXT_USAGE.size;
7✔
78
  }
79

80
  /**
81
   * Force clear all cached text bitmaps
82
   */
83
  public static clearCache() {
84
    for (const [textInstance] of FontCache._TEXT_USAGE.entries()) {
5✔
85
      textInstance.dispose();
11✔
86
    }
87
    FontCache._TEXT_USAGE.clear();
5✔
88
    FontCache._TEXT_CACHE.clear();
5✔
89
    FontCache._MEASURE_CACHE.clear();
5✔
90
  }
91
}
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