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

excaliburjs / Excalibur / 14804036802

02 May 2025 09:58PM UTC coverage: 5.927% (-83.4%) from 89.28%
14804036802

Pull #3404

github

web-flow
Merge 5c103d7f8 into 0f2ccaeb2
Pull Request #3404: feat: added Graph module to Math

234 of 8383 branches covered (2.79%)

229 of 246 new or added lines in 1 file covered. (93.09%)

13145 existing lines in 208 files now uncovered.

934 of 15759 relevant lines covered (5.93%)

4.72 hits per line

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

2.63
/src/engine/Graphics/Context/material.ts
1
import { Color } from '../../Color';
2
import { ExcaliburGraphicsContext } from './ExcaliburGraphicsContext';
3
import { ExcaliburGraphicsContextWebGL } from './ExcaliburGraphicsContextWebGL';
4
import { Shader, UniformDictionary } from './shader';
5
import { Logger } from '../../Util/Log';
6
import { ImageSource } from '../ImageSource';
7
import { ImageFiltering } from '../Filtering';
8

9
export interface MaterialOptions {
10
  /**
11
   * Name the material for debugging
12
   */
13
  name?: string;
14

15
  /**
16
   * Excalibur graphics context to create the material (only WebGL is supported at the moment)
17
   */
18
  graphicsContext?: ExcaliburGraphicsContext;
19

20
  /**
21
   * Optionally specify a vertex shader
22
   *
23
   * If none supplied the default will be used
24
   *
25
   * ```
26
   *  #version 300 es
27
   *  // vertex position in local space
28
   *  in vec2 a_position;
29
   *  in vec2 a_uv;
30
   *  out vec2 v_uv;
31
   *  // orthographic projection matrix
32
   *  uniform mat4 u_matrix;
33
   *  // world space transform matrix
34
   *  uniform mat4 u_transform;
35
   *  void main() {
36
   *    // Set the vertex position using the ortho & transform matrix
37
   *    gl_Position = u_matrix * u_transform * vec4(a_position, 0.0, 1.0);
38
   *    // Pass through the UV coord to the fragment shader
39
   *    v_uv = a_uv;
40
   *  }
41
   * ```
42
   */
43
  vertexSource?: string;
44

45
  /**
46
   * Add custom fragment shader
47
   *
48
   * *Note: Excalibur image alpha's are pre-multiplied
49
   *
50
   * Pre-built varyings:
51
   *
52
   * * `in vec2 v_uv` - UV coordinate
53
   * * `in vec2 v_screenuv` - UV coordinate
54
   *
55
   * Pre-built uniforms:
56
   *
57
   * * `uniform sampler2D u_graphic` - The current graphic displayed by the GraphicsComponent
58
   * * `uniform vec2 u_resolution` - The current resolution of the screen
59
   * * `uniform vec2 u_size;` - The current size of the graphic
60
   * * `uniform vec4 u_color` - The current color of the material
61
   * * `uniform float u_opacity` - The current opacity of the graphics context
62
   *
63
   */
64
  fragmentSource: string;
65

66
  /**
67
   * Add custom color, by default ex.Color.Transparent
68
   */
69
  color?: Color;
70

71
  /**
72
   * Add additional images to the material, you are limited by the GPU's maximum texture slots
73
   *
74
   * Specify a dictionary of uniform sampler names to ImageSource
75
   */
76
  images?: Record<string, ImageSource>;
77

78
  /**
79
   * Optionally set starting uniforms on a shader
80
   */
81
  uniforms?: UniformDictionary;
82
}
83

84
const defaultVertexSource = `#version 300 es
1✔
85
in vec2 a_position;
86

87
in vec2 a_uv;
88
out vec2 v_uv;
89

90
in vec2 a_screenuv;
91
out vec2 v_screenuv;
92

93
uniform mat4 u_matrix;
94
uniform mat4 u_transform;
95

96
void main() {
97
  // Set the vertex position using the ortho & transform matrix
98
  gl_Position = u_matrix * u_transform * vec4(a_position, 0.0, 1.0);
99

100
  // Pass through the UV coord to the fragment shader
101
  v_uv = a_uv;
102
  v_screenuv = a_screenuv;
103
}
104
`;
105

106
export interface MaterialImageOptions {
107
  filtering?: ImageFiltering;
108
}
109

110
export class Material {
UNCOV
111
  private _logger = Logger.getInstance();
×
112
  private _name: string;
113
  private _shader!: Shader;
UNCOV
114
  private _color: Color = Color.Transparent;
×
UNCOV
115
  private _initialized = false;
×
116
  private _fragmentSource: string;
117
  private _vertexSource: string;
118

UNCOV
119
  private _images: Record<string, ImageSource> = {};
×
UNCOV
120
  private _uniforms: UniformDictionary = {};
×
121

122
  constructor(options: MaterialOptions) {
UNCOV
123
    const { color, name, vertexSource, fragmentSource, graphicsContext, images, uniforms } = options;
×
124

UNCOV
125
    this._name = name ?? 'anonymous material';
×
UNCOV
126
    this._vertexSource = vertexSource ?? defaultVertexSource;
×
UNCOV
127
    this._fragmentSource = fragmentSource;
×
UNCOV
128
    this._color = color ?? this._color;
×
UNCOV
129
    this._uniforms = uniforms ?? this._uniforms;
×
UNCOV
130
    this._images = images ?? this._images;
×
131

UNCOV
132
    if (!graphicsContext) {
×
133
      throw Error(`Material ${name} must be provided an excalibur webgl graphics context`);
×
134
    }
135

UNCOV
136
    if (graphicsContext instanceof ExcaliburGraphicsContextWebGL) {
×
UNCOV
137
      this._initialize(graphicsContext);
×
138
    } else {
139
      this._logger.warn(`Material ${name} was created in 2D Canvas mode, currently only WebGL is supported`);
×
140
    }
141
  }
142

143
  private _initialize(graphicsContextWebGL: ExcaliburGraphicsContextWebGL) {
UNCOV
144
    if (this._initialized) {
×
145
      return;
×
146
    }
147

UNCOV
148
    this._shader = graphicsContextWebGL.createShader({
×
149
      name: this._name,
150
      vertexSource: this._vertexSource,
151
      fragmentSource: this._fragmentSource,
152
      uniforms: this._uniforms,
153
      images: this._images,
154
      // max texture slots
155
      // - 2 for the graphic texture and screen texture
156
      // - 1 if just graphic
157
      startingTextureSlot: this.isUsingScreenTexture ? 2 : 1
×
158
    });
UNCOV
159
    this._initialized = true;
×
160
  }
161

162
  public get uniforms(): UniformDictionary {
163
    return this._shader.uniforms;
×
164
  }
165

166
  public get images(): Record<string, ImageSource> {
167
    return this._shader.images;
×
168
  }
169

170
  get color(): Color {
171
    return this._color;
×
172
  }
173

174
  set color(c: Color) {
175
    this._color = c;
×
176
  }
177

178
  get name() {
UNCOV
179
    return this._name;
×
180
  }
181

182
  get isUsingScreenTexture() {
UNCOV
183
    return this._fragmentSource.includes('u_screen_texture');
×
184
  }
185

186
  update(callback: (shader: Shader) => any) {
UNCOV
187
    if (this._shader) {
×
UNCOV
188
      this._shader.use();
×
UNCOV
189
      callback(this._shader);
×
190
    }
191
  }
192

193
  getShader(): Shader | null {
UNCOV
194
    return this._shader;
×
195
  }
196

197
  addImageSource(samplerName: string, image: ImageSource) {
198
    this._shader.addImageSource(samplerName, image);
×
199
  }
200

201
  removeImageSource(samplerName: string) {
202
    this._shader.removeImageSource(samplerName);
×
203
  }
204

205
  use() {
UNCOV
206
    if (this._initialized) {
×
207
      // bind the shader
UNCOV
208
      this._shader.use();
×
209
      // Apply standard uniforms
UNCOV
210
      this._shader.trySetUniformFloatColor('u_color', this._color);
×
211
    } else {
212
      throw Error(`Material ${this.name} not yet initialized, use the ExcaliburGraphicsContext.createMaterial() to work around this.`);
×
213
    }
214
  }
215
}
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