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

visgl / luma.gl / 14683349798

26 Apr 2025 05:08PM UTC coverage: 74.055% (-0.9%) from 74.913%
14683349798

push

github

web-flow
feat(core): TextureFormat generics (#2377)

2019 of 2652 branches covered (76.13%)

Branch coverage included in aggregate %.

62 of 262 new or added lines in 15 files covered. (23.66%)

196 existing lines in 9 files now uncovered.

26575 of 35960 relevant lines covered (73.9%)

47.35 hits per line

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

18.18
/modules/engine/src/compute/computation.ts
1
// luma.gl
1✔
2
// SPDX-License-Identifier: MIT
1✔
3
// Copyright (c) vis.gl contributors
1✔
4

1✔
5
import type {DeviceFeature, ComputePipelineProps, Shader, Binding} from '@luma.gl/core';
1✔
6
import {
1✔
7
  Device,
1✔
8
  Buffer,
1✔
9
  ComputePipeline,
1✔
10
  ComputePass,
1✔
11
  UniformStore,
1✔
12
  log,
1✔
13
  getTypedArrayConstructor
1✔
14
} from '@luma.gl/core';
1✔
15
import type {ShaderModule, PlatformInfo} from '@luma.gl/shadertools';
1✔
16
import {ShaderAssembler, getShaderLayoutFromWGSL} from '@luma.gl/shadertools';
1✔
17
import {TypedArray, isNumericArray} from '@math.gl/types';
1✔
18
import {ShaderInputs} from '../shader-inputs';
1✔
19
import {PipelineFactory} from '../factories/pipeline-factory';
1✔
20
import {ShaderFactory} from '../factories/shader-factory';
1✔
21
import {uid} from '../utils/uid';
1✔
22
// import {getDebugTableForShaderLayout} from '../debug/debug-shader-layout';
1✔
23

1✔
24
const LOG_DRAW_PRIORITY = 2;
1✔
25
const LOG_DRAW_TIMEOUT = 10000;
1✔
26

1✔
27
export type ComputationProps = Omit<ComputePipelineProps, 'shader'> & {
1✔
28
  source?: string;
1✔
29

1✔
30
  /** shadertool shader modules (added to shader code) */
1✔
31
  modules?: ShaderModule[];
1✔
32
  /** Shadertool module defines (configures shader code)*/
1✔
33
  defines?: Record<string, boolean>;
1✔
34
  // TODO - injections, hooks etc?
1✔
35

1✔
36
  /** Shader inputs, used to generated uniform buffers and bindings */
1✔
37
  shaderInputs?: ShaderInputs;
1✔
38

1✔
39
  /** Bindings */
1✔
40
  bindings?: Record<string, Binding>;
1✔
41

1✔
42
  /** Show shader source in browser? */
1✔
43
  debugShaders?: 'never' | 'errors' | 'warnings' | 'always';
1✔
44

1✔
45
  /** Factory used to create a {@link ComputePipeline}. Defaults to {@link Device} default factory. */
1✔
46
  pipelineFactory?: PipelineFactory;
1✔
47
  /** Factory used to create a {@link Shader}. Defaults to {@link Device} default factory. */
1✔
48
  shaderFactory?: ShaderFactory;
1✔
49
  /** Shader assembler. Defaults to the ShaderAssembler.getShaderAssembler() */
1✔
50
  shaderAssembler?: ShaderAssembler;
1✔
51
};
1✔
52

1✔
53
/**
1✔
54
 * v9 Model API
1✔
55
 * A model
1✔
56
 * - automatically reuses pipelines (programs) when possible
1✔
57
 * - automatically rebuilds pipelines if necessary to accommodate changed settings
1✔
58
 * shadertools integration
1✔
59
 * - accepts modules and performs shader transpilation
1✔
60
 */
1✔
61
export class Computation {
1!
62
  static defaultProps: Required<ComputationProps> = {
×
63
    ...ComputePipeline.defaultProps,
×
64
    id: 'unnamed',
×
65
    handle: undefined,
×
66
    userData: {},
×
67

×
68
    source: '',
×
69
    modules: [],
×
70
    defines: {},
×
71

×
72
    bindings: undefined!,
×
73
    shaderInputs: undefined!,
×
74

×
75
    pipelineFactory: undefined!,
×
76
    shaderFactory: undefined!,
×
77
    shaderAssembler: ShaderAssembler.getDefaultShaderAssembler(),
×
78

×
79
    debugShaders: undefined!
×
80
  };
×
81

×
82
  readonly device: Device;
×
83
  readonly id: string;
×
84

×
85
  readonly pipelineFactory: PipelineFactory;
×
86
  readonly shaderFactory: ShaderFactory;
×
87

×
88
  userData: {[key: string]: any} = {};
×
89

×
90
  /** Bindings (textures, samplers, uniform buffers) */
×
91
  bindings: Record<string, Binding> = {};
×
92

×
93
  /** The underlying GPU pipeline. */
×
94
  pipeline: ComputePipeline;
×
95
  /** Assembled compute shader source */
×
96
  source: string;
×
97
  /** the underlying compiled compute shader */
×
98
  // @ts-ignore Set in function called from constructor
×
99
  shader: Shader;
×
100

×
101
  /** ShaderInputs instance */
×
102
  shaderInputs: ShaderInputs;
×
103

×
104
  // @ts-ignore Set in function called from constructor
×
105
  _uniformStore: UniformStore;
×
106

×
107
  _pipelineNeedsUpdate: string | false = 'newly created';
×
108

×
109
  private _getModuleUniforms: (props?: Record<string, Record<string, any>>) => Record<string, any>;
×
110
  private props: Required<ComputationProps>;
×
111

×
112
  private _destroyed = false;
×
113

×
114
  constructor(device: Device, props: ComputationProps) {
×
115
    if (device.type !== 'webgpu') {
×
116
      throw new Error('Computation is only supported in WebGPU');
×
117
    }
×
118

×
119
    this.props = {...Computation.defaultProps, ...props};
×
120
    props = this.props;
×
121
    this.id = props.id || uid('model');
×
122
    this.device = device;
×
123

×
124
    Object.assign(this.userData, props.userData);
×
125

×
126
    // Setup shader module inputs
×
127
    const moduleMap = Object.fromEntries(
×
128
      this.props.modules?.map(module => [module.name, module]) || []
×
129
    );
×
130
    // @ts-ignore TODO - fix up typing?
×
131
    this.shaderInputs = props.shaderInputs || new ShaderInputs(moduleMap);
×
132
    this.setShaderInputs(this.shaderInputs);
×
133

×
134
    // Support WGSL shader layout introspection
×
135
    // TODO - Don't modify props!!
×
136
    this.props.shaderLayout ||= getShaderLayoutFromWGSL(this.props.source);
×
137

×
138
    // Setup shader assembler
×
139
    const platformInfo = getPlatformInfo(device);
×
140

×
141
    // Extract modules from shader inputs if not supplied
×
142
    const modules =
×
143
      (this.props.modules?.length > 0 ? this.props.modules : this.shaderInputs?.getModules()) || [];
×
144

×
145
    this.pipelineFactory =
×
146
      props.pipelineFactory || PipelineFactory.getDefaultPipelineFactory(this.device);
×
147
    this.shaderFactory = props.shaderFactory || ShaderFactory.getDefaultShaderFactory(this.device);
×
148

×
149
    const {source, getUniforms} = this.props.shaderAssembler.assembleWGSLShader({
×
150
      platformInfo,
×
151
      ...this.props,
×
152
      modules
×
153
    });
×
154

×
155
    this.source = source;
×
156
    // @ts-ignore
×
157
    this._getModuleUniforms = getUniforms;
×
158

×
159
    // Create the pipeline
×
160
    // @note order is important
×
161
    this.pipeline = this._updatePipeline();
×
162

×
163
    // Apply any dynamic settings that will not trigger pipeline change
×
164
    if (props.bindings) {
×
165
      this.setBindings(props.bindings);
×
166
    }
×
167

×
168
    // Catch any access to non-standard props
×
169
    Object.seal(this);
×
170
  }
×
171

×
172
  destroy(): void {
×
173
    if (this._destroyed) return;
×
174
    this.pipelineFactory.release(this.pipeline);
×
175
    this.shaderFactory.release(this.shader);
×
176
    this._uniformStore.destroy();
×
177
    this._destroyed = true;
×
178
  }
×
179

×
180
  // Draw call
×
181

×
182
  predraw() {
×
183
    // Update uniform buffers if needed
×
184
    this.updateShaderInputs();
×
185
  }
×
186

×
187
  dispatch(computePass: ComputePass, x: number, y?: number, z?: number): void {
×
188
    try {
×
189
      this._logDrawCallStart();
×
190

×
191
      // Check if the pipeline is invalidated
×
192
      // TODO - this is likely the worst place to do this from performance perspective. Perhaps add a predraw()?
×
193
      this.pipeline = this._updatePipeline();
×
194

×
195
      // Set pipeline state, we may be sharing a pipeline so we need to set all state on every draw
×
196
      // Any caching needs to be done inside the pipeline functions
×
197
      this.pipeline.setBindings(this.bindings);
×
198
      computePass.setPipeline(this.pipeline);
×
199
      // @ts-expect-error
×
200
      computePass.setBindings([]);
×
201

×
202
      computePass.dispatch(x, y, z);
×
203
    } finally {
×
204
      this._logDrawCallEnd();
×
205
    }
×
206
  }
×
207

×
208
  // Update fixed fields (can trigger pipeline rebuild)
×
209

×
210
  // Update dynamic fields
×
211

×
212
  /**
×
213
   * Updates the vertex count (used in draw calls)
×
214
   * @note Any attributes with stepMode=vertex need to be at least this big
×
215
   */
×
216
  setVertexCount(vertexCount: number): void {
×
217
    // this.vertexCount = vertexCount;
×
218
  }
×
219

×
220
  /**
×
221
   * Updates the instance count (used in draw calls)
×
222
   * @note Any attributes with stepMode=instance need to be at least this big
×
223
   */
×
224
  setInstanceCount(instanceCount: number): void {
×
225
    // this.instanceCount = instanceCount;
×
226
  }
×
227

×
228
  setShaderInputs(shaderInputs: ShaderInputs): void {
×
229
    this.shaderInputs = shaderInputs;
×
230
    this._uniformStore = new UniformStore(this.shaderInputs.modules);
×
231
    // Create uniform buffer bindings for all modules
×
232
    for (const moduleName of Object.keys(this.shaderInputs.modules)) {
×
233
      const uniformBuffer = this._uniformStore.getManagedUniformBuffer(this.device, moduleName);
×
234
      this.bindings[`${moduleName}Uniforms`] = uniformBuffer;
×
235
    }
×
236
  }
×
237

×
238
  /**
×
239
   * Updates shader module settings (which results in uniforms being set)
×
240
   */
×
241
  setShaderModuleProps(props: Record<string, any>): void {
×
242
    const uniforms = this._getModuleUniforms(props);
×
243

×
244
    // Extract textures & framebuffers set by the modules
×
245
    // TODO better way to extract bindings
×
246
    const keys = Object.keys(uniforms).filter(k => {
×
247
      const uniform = uniforms[k];
×
248
      return (
×
249
        !isNumericArray(uniform) && typeof uniform !== 'number' && typeof uniform !== 'boolean'
×
250
      );
×
251
    });
×
252
    const bindings: Record<string, Binding> = {};
×
253
    for (const k of keys) {
×
254
      bindings[k] = uniforms[k];
×
255
      delete uniforms[k];
×
256
    }
×
257
  }
×
258

×
259
  updateShaderInputs(): void {
×
260
    this._uniformStore.setUniforms(this.shaderInputs.getUniformValues());
×
261
  }
×
262

×
263
  /**
×
264
   * Sets bindings (textures, samplers, uniform buffers)
×
265
   */
×
266
  setBindings(bindings: Record<string, Binding>): void {
×
267
    Object.assign(this.bindings, bindings);
×
268
  }
×
269

×
270
  _setPipelineNeedsUpdate(reason: string): void {
×
271
    this._pipelineNeedsUpdate = this._pipelineNeedsUpdate || reason;
×
272
  }
×
273

×
274
  _updatePipeline(): ComputePipeline {
×
275
    if (this._pipelineNeedsUpdate) {
×
276
      let prevShader: Shader | null = null;
×
277
      if (this.pipeline) {
×
278
        log.log(
×
279
          1,
×
280
          `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`
×
281
        )();
×
282
        prevShader = this.shader;
×
283
      }
×
284

×
285
      this._pipelineNeedsUpdate = false;
×
286

×
287
      this.shader = this.shaderFactory.createShader({
×
288
        id: `${this.id}-fragment`,
×
289
        stage: 'compute',
×
290
        source: this.source,
×
291
        debugShaders: this.props.debugShaders
×
292
      });
×
293

×
294
      this.pipeline = this.pipelineFactory.createComputePipeline({
×
295
        ...this.props,
×
296
        shader: this.shader
×
297
      });
×
298

×
299
      if (prevShader) {
×
300
        this.shaderFactory.release(prevShader);
×
301
      }
×
302
    }
×
303
    return this.pipeline;
×
304
  }
×
305

×
306
  /** Throttle draw call logging */
×
307
  _lastLogTime = 0;
×
308
  _logOpen = false;
×
309

×
310
  _logDrawCallStart(): void {
×
311
    // IF level is 4 or higher, log every frame.
×
312
    const logDrawTimeout = log.level > 3 ? 0 : LOG_DRAW_TIMEOUT;
×
313
    if (log.level < 2 || Date.now() - this._lastLogTime < logDrawTimeout) {
×
314
      return;
×
315
    }
×
316

×
317
    this._lastLogTime = Date.now();
×
318
    this._logOpen = true;
×
319

×
320
    log.group(LOG_DRAW_PRIORITY, `>>> DRAWING MODEL ${this.id}`, {collapsed: log.level <= 2})();
×
321
  }
×
322

×
323
  _logDrawCallEnd(): void {
×
324
    if (this._logOpen) {
×
325
      // const shaderLayoutTable = getDebugTableForShaderLayout(this.pipeline.props.shaderLayout, this.id);
×
326

×
327
      // log.table(logLevel, attributeTable)();
×
328
      // log.table(logLevel, uniformTable)();
×
329
      // log.table(LOG_DRAW_PRIORITY, shaderLayoutTable)();
×
330

×
331
      const uniformTable = this.shaderInputs.getDebugTable();
×
332
      log.table(LOG_DRAW_PRIORITY, uniformTable)();
×
333

×
334
      log.groupEnd(LOG_DRAW_PRIORITY)();
×
335
      this._logOpen = false;
×
336
    }
×
337
  }
×
338

×
339
  protected _drawCount = 0;
×
340

×
341
  // TODO - fix typing of luma data types
×
342
  _getBufferOrConstantValues(attribute: Buffer | TypedArray, dataType: any): string {
×
NEW
343
    const TypedArrayConstructor = getTypedArrayConstructor(dataType);
×
344
    const typedArray =
×
345
      attribute instanceof Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
×
346
    return typedArray.toString();
×
347
  }
×
348
}
×
349

1✔
350
/** Create a shadertools platform info from the Device */
1✔
351
export function getPlatformInfo(device: Device): PlatformInfo {
1✔
352
  return {
×
353
    type: device.type,
×
354
    shaderLanguage: device.info.shadingLanguage,
×
355
    shaderLanguageVersion: device.info.shadingLanguageVersion as 100 | 300,
×
356
    gpu: device.info.gpu,
×
357
    // HACK - we pretend that the DeviceFeatures is a Set, it has a similar API
×
358
    features: device.features as unknown as Set<DeviceFeature>
×
359
  };
×
360
}
×
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