@luma.gl/webgl 9.3.0-alpha.2 → 9.3.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/adapter/converters/webgl-texture-table.d.ts +7 -1
  2. package/dist/adapter/converters/webgl-texture-table.d.ts.map +1 -1
  3. package/dist/adapter/converters/webgl-texture-table.js +121 -43
  4. package/dist/adapter/converters/webgl-texture-table.js.map +1 -1
  5. package/dist/adapter/device-helpers/webgl-device-features.d.ts.map +1 -1
  6. package/dist/adapter/device-helpers/webgl-device-features.js +1 -2
  7. package/dist/adapter/device-helpers/webgl-device-features.js.map +1 -1
  8. package/dist/adapter/device-helpers/webgl-device-info.js +5 -0
  9. package/dist/adapter/device-helpers/webgl-device-info.js.map +1 -1
  10. package/dist/adapter/helpers/get-shader-layout-from-glsl.js +23 -21
  11. package/dist/adapter/helpers/get-shader-layout-from-glsl.js.map +1 -1
  12. package/dist/adapter/helpers/parse-shader-compiler-log.d.ts +1 -1
  13. package/dist/adapter/helpers/parse-shader-compiler-log.d.ts.map +1 -1
  14. package/dist/adapter/helpers/parse-shader-compiler-log.js +20 -0
  15. package/dist/adapter/helpers/parse-shader-compiler-log.js.map +1 -1
  16. package/dist/adapter/resources/webgl-buffer.d.ts.map +1 -1
  17. package/dist/adapter/resources/webgl-buffer.js +19 -4
  18. package/dist/adapter/resources/webgl-buffer.js.map +1 -1
  19. package/dist/adapter/resources/webgl-command-buffer.d.ts +3 -4
  20. package/dist/adapter/resources/webgl-command-buffer.d.ts.map +1 -1
  21. package/dist/adapter/resources/webgl-command-buffer.js +11 -7
  22. package/dist/adapter/resources/webgl-command-buffer.js.map +1 -1
  23. package/dist/adapter/resources/webgl-command-encoder.d.ts +5 -4
  24. package/dist/adapter/resources/webgl-command-encoder.d.ts.map +1 -1
  25. package/dist/adapter/resources/webgl-command-encoder.js +20 -7
  26. package/dist/adapter/resources/webgl-command-encoder.js.map +1 -1
  27. package/dist/adapter/resources/webgl-query-set.d.ts +29 -31
  28. package/dist/adapter/resources/webgl-query-set.d.ts.map +1 -1
  29. package/dist/adapter/resources/webgl-query-set.js +193 -97
  30. package/dist/adapter/resources/webgl-query-set.js.map +1 -1
  31. package/dist/adapter/resources/webgl-render-pass.d.ts.map +1 -1
  32. package/dist/adapter/resources/webgl-render-pass.js +17 -0
  33. package/dist/adapter/resources/webgl-render-pass.js.map +1 -1
  34. package/dist/adapter/resources/webgl-render-pipeline.d.ts +13 -19
  35. package/dist/adapter/resources/webgl-render-pipeline.d.ts.map +1 -1
  36. package/dist/adapter/resources/webgl-render-pipeline.js +36 -154
  37. package/dist/adapter/resources/webgl-render-pipeline.js.map +1 -1
  38. package/dist/adapter/resources/webgl-shared-render-pipeline.d.ts +24 -0
  39. package/dist/adapter/resources/webgl-shared-render-pipeline.d.ts.map +1 -0
  40. package/dist/adapter/resources/webgl-shared-render-pipeline.js +152 -0
  41. package/dist/adapter/resources/webgl-shared-render-pipeline.js.map +1 -0
  42. package/dist/adapter/resources/webgl-texture.d.ts +23 -4
  43. package/dist/adapter/resources/webgl-texture.d.ts.map +1 -1
  44. package/dist/adapter/resources/webgl-texture.js +203 -100
  45. package/dist/adapter/resources/webgl-texture.js.map +1 -1
  46. package/dist/adapter/resources/webgl-transform-feedback.js +5 -5
  47. package/dist/adapter/resources/webgl-transform-feedback.js.map +1 -1
  48. package/dist/adapter/webgl-adapter.d.ts.map +1 -1
  49. package/dist/adapter/webgl-adapter.js +3 -4
  50. package/dist/adapter/webgl-adapter.js.map +1 -1
  51. package/dist/adapter/webgl-device.d.ts +6 -3
  52. package/dist/adapter/webgl-device.d.ts.map +1 -1
  53. package/dist/adapter/webgl-device.js +56 -14
  54. package/dist/adapter/webgl-device.js.map +1 -1
  55. package/dist/adapter/webgl-presentation-context.d.ts +21 -0
  56. package/dist/adapter/webgl-presentation-context.d.ts.map +1 -0
  57. package/dist/adapter/webgl-presentation-context.js +64 -0
  58. package/dist/adapter/webgl-presentation-context.js.map +1 -0
  59. package/dist/context/debug/spector.d.ts.map +1 -1
  60. package/dist/context/debug/spector.js +4 -4
  61. package/dist/context/debug/spector.js.map +1 -1
  62. package/dist/context/debug/webgl-developer-tools.js +2 -0
  63. package/dist/context/debug/webgl-developer-tools.js.map +1 -1
  64. package/dist/context/helpers/create-browser-context.d.ts.map +1 -1
  65. package/dist/context/helpers/create-browser-context.js +6 -8
  66. package/dist/context/helpers/create-browser-context.js.map +1 -1
  67. package/dist/context/helpers/webgl-context-data.d.ts +5 -1
  68. package/dist/context/helpers/webgl-context-data.d.ts.map +1 -1
  69. package/dist/context/helpers/webgl-context-data.js +9 -10
  70. package/dist/context/helpers/webgl-context-data.js.map +1 -1
  71. package/dist/context/parameters/unified-parameter-api.d.ts +1 -1
  72. package/dist/context/parameters/unified-parameter-api.js +2 -2
  73. package/dist/context/parameters/unified-parameter-api.js.map +1 -1
  74. package/dist/context/state-tracker/webgl-state-tracker.js +2 -2
  75. package/dist/context/state-tracker/webgl-state-tracker.js.map +1 -1
  76. package/dist/dist.dev.js +1427 -828
  77. package/dist/dist.min.js +2 -2
  78. package/dist/index.cjs +1325 -811
  79. package/dist/index.cjs.map +4 -4
  80. package/dist/utils/fill-array.js +1 -1
  81. package/dist/utils/fill-array.js.map +1 -1
  82. package/package.json +4 -4
  83. package/src/adapter/converters/webgl-texture-table.ts +159 -47
  84. package/src/adapter/device-helpers/webgl-device-features.ts +1 -2
  85. package/src/adapter/device-helpers/webgl-device-info.ts +6 -0
  86. package/src/adapter/helpers/get-shader-layout-from-glsl.ts +25 -24
  87. package/src/adapter/helpers/parse-shader-compiler-log.ts +23 -1
  88. package/src/adapter/resources/webgl-buffer.ts +16 -4
  89. package/src/adapter/resources/webgl-command-buffer.ts +21 -24
  90. package/src/adapter/resources/webgl-command-encoder.ts +22 -7
  91. package/src/adapter/resources/webgl-query-set.ts +229 -102
  92. package/src/adapter/resources/webgl-render-pass.ts +19 -0
  93. package/src/adapter/resources/webgl-render-pipeline.ts +46 -181
  94. package/src/adapter/resources/webgl-shared-render-pipeline.ts +208 -0
  95. package/src/adapter/resources/webgl-texture.ts +326 -121
  96. package/src/adapter/resources/webgl-transform-feedback.ts +5 -5
  97. package/src/adapter/webgl-adapter.ts +3 -4
  98. package/src/adapter/webgl-device.ts +66 -19
  99. package/src/adapter/webgl-presentation-context.ts +93 -0
  100. package/src/context/debug/spector.ts +4 -4
  101. package/src/context/debug/webgl-developer-tools.ts +2 -0
  102. package/src/context/helpers/create-browser-context.ts +8 -8
  103. package/src/context/helpers/webgl-context-data.ts +17 -11
  104. package/src/context/parameters/unified-parameter-api.ts +2 -2
  105. package/src/context/state-tracker/webgl-state-tracker.ts +2 -2
  106. package/src/utils/fill-array.ts +1 -1
@@ -16,7 +16,6 @@ import {RenderPipeline, log} from '@luma.gl/core';
16
16
  // import {getAttributeInfosFromLayouts} from '@luma.gl/core';
17
17
  import {GL} from '@luma.gl/constants';
18
18
 
19
- import {getShaderLayoutFromGLSL} from '../helpers/get-shader-layout-from-glsl';
20
19
  import {withDeviceAndGLParameters} from '../converters/device-parameters';
21
20
  import {setUniform} from '../helpers/set-uniform';
22
21
  // import {copyUniform, checkUniformValues} from '../../classes/uniforms';
@@ -30,8 +29,7 @@ import {WEBGLTextureView} from './webgl-texture-view';
30
29
  import {WEBGLRenderPass} from './webgl-render-pass';
31
30
  import {WEBGLTransformFeedback} from './webgl-transform-feedback';
32
31
  import {getGLDrawMode} from '../helpers/webgl-topology-utils';
33
-
34
- const LOG_PROGRAM_PERF_PRIORITY = 4;
32
+ import {WEBGLSharedRenderPipeline} from './webgl-shared-render-pipeline';
35
33
 
36
34
  /** Creates a new render pipeline */
37
35
  export class WEBGLRenderPipeline extends RenderPipeline {
@@ -46,10 +44,10 @@ export class WEBGLRenderPipeline extends RenderPipeline {
46
44
  /** The layout extracted from shader by WebGL introspection APIs */
47
45
  introspectedLayout: ShaderLayout;
48
46
 
49
- /** Uniforms set on this model */
50
- uniforms: Record<string, UniformValue> = {};
51
- /** Bindings set on this model */
47
+ /** Compatibility path for direct pipeline.setBindings() usage */
52
48
  bindings: Record<string, Binding> = {};
49
+ /** Compatibility path for direct pipeline.uniforms usage */
50
+ uniforms: Record<string, UniformValue> = {};
53
51
  /** WebGL varyings */
54
52
  varyings: string[] | null = null;
55
53
 
@@ -63,28 +61,18 @@ export class WEBGLRenderPipeline extends RenderPipeline {
63
61
  constructor(device: WebGLDevice, props: RenderPipelineProps) {
64
62
  super(device, props);
65
63
  this.device = device;
66
- this.handle = this.props.handle || this.device.gl.createProgram();
64
+ const webglSharedRenderPipeline =
65
+ (this.sharedRenderPipeline as WEBGLSharedRenderPipeline | null) ||
66
+ (this.device._createSharedRenderPipelineWebGL(props) as WEBGLSharedRenderPipeline);
67
+
68
+ this.sharedRenderPipeline = webglSharedRenderPipeline;
69
+ this.handle = webglSharedRenderPipeline.handle;
70
+ this.vs = webglSharedRenderPipeline.vs;
71
+ this.fs = webglSharedRenderPipeline.fs;
72
+ this.linkStatus = webglSharedRenderPipeline.linkStatus;
73
+ this.introspectedLayout = webglSharedRenderPipeline.introspectedLayout;
67
74
  this.device._setWebGLDebugMetadata(this.handle, this, {spector: {id: this.props.id}});
68
75
 
69
- // Create shaders if needed
70
- this.vs = props.vs as WEBGLShader;
71
- this.fs = props.fs as WEBGLShader;
72
- // assert(this.vs.stage === 'vertex');
73
- // assert(this.fs.stage === 'fragment');
74
-
75
- // Setup varyings if supplied
76
- // @ts-expect-error WebGL only
77
- const {varyings, bufferMode = GL.SEPARATE_ATTRIBS} = props;
78
- if (varyings && varyings.length > 0) {
79
- this.varyings = varyings;
80
- this.device.gl.transformFeedbackVaryings(this.handle, varyings, bufferMode);
81
- }
82
-
83
- this._linkShaders();
84
- log.time(3, `RenderPipeline ${this.id} - shaderLayout introspection`)();
85
- this.introspectedLayout = getShaderLayoutFromGLSL(this.device.gl, this.handle);
86
- log.timeEnd(3, `RenderPipeline ${this.id} - shaderLayout introspection`)();
87
-
88
76
  // Merge provided layout with introspected layout
89
77
  this.shaderLayout = props.shaderLayout
90
78
  ? mergeShaderLayout(this.introspectedLayout, props.shaderLayout)
@@ -92,32 +80,21 @@ export class WEBGLRenderPipeline extends RenderPipeline {
92
80
  }
93
81
 
94
82
  override destroy(): void {
95
- if (this.handle) {
96
- // log.error(`Deleting program ${this.id}`)();
97
- this.device.gl.useProgram(null);
98
- this.device.gl.deleteProgram(this.handle);
99
- this.destroyed = true;
100
- // @ts-expect-error
101
- this.handle.destroyed = true;
102
- // @ts-ignore
103
- this.handle = null;
83
+ if (this.destroyed) {
84
+ return;
104
85
  }
86
+ if (this.sharedRenderPipeline && !this.props._sharedRenderPipeline) {
87
+ this.sharedRenderPipeline.destroy();
88
+ }
89
+ this.destroyResource();
105
90
  }
106
91
 
107
92
  /**
108
- * Bindings include: textures, samplers and uniform buffers
109
- * @todo needed for portable model
93
+ * Compatibility shim for code paths that still set bindings on the pipeline.
94
+ * Shared-model draws pass bindings per draw and do not rely on this state.
110
95
  */
111
96
  setBindings(bindings: Record<string, Binding>, options?: {disableWarnings?: boolean}): void {
112
- // if (log.priority >= 2) {
113
- // checkUniformValues(uniforms, this.id, this._uniformSetters);
114
- // }
115
-
116
97
  for (const [name, value] of Object.entries(bindings)) {
117
- // Accept both `xyz` and `xyzUniforms` as valid names for `xyzUniforms` uniform block
118
- // This convention allows shaders to name uniform blocks as `uniform appUniforms {} app;`
119
- // and reference them as `app` from both GLSL and JS.
120
- // TODO - this is rather hacky - we could also remap the name directly in the shader layout.
121
98
  const binding =
122
99
  this.shaderLayout.bindings.find(binding_ => binding_.name === name) ||
123
100
  this.shaderLayout.bindings.find(binding_ => binding_.name === `${name}Uniforms`);
@@ -132,7 +109,7 @@ export class WEBGLRenderPipeline extends RenderPipeline {
132
109
  value
133
110
  )();
134
111
  }
135
- continue; // eslint-disable-line no-continue
112
+ continue;
136
113
  }
137
114
  if (!value) {
138
115
  log.warn(`Unsetting binding "${name}" in render pipeline "${this.id}"`)();
@@ -184,7 +161,11 @@ export class WEBGLRenderPipeline extends RenderPipeline {
184
161
  firstInstance?: number;
185
162
  baseVertex?: number;
186
163
  transformFeedback?: WEBGLTransformFeedback;
164
+ bindings?: Record<string, Binding>;
165
+ uniforms?: Record<string, UniformValue>;
187
166
  }): boolean {
167
+ this._syncLinkStatus();
168
+
188
169
  const {
189
170
  renderPass,
190
171
  parameters = this.props.parameters,
@@ -198,7 +179,9 @@ export class WEBGLRenderPipeline extends RenderPipeline {
198
179
  // firstIndex,
199
180
  // firstInstance,
200
181
  // baseVertex,
201
- transformFeedback
182
+ transformFeedback,
183
+ bindings = this.bindings,
184
+ uniforms = this.uniforms
202
185
  } = options;
203
186
 
204
187
  const glDrawMode = getGLDrawMode(topology);
@@ -216,7 +199,7 @@ export class WEBGLRenderPipeline extends RenderPipeline {
216
199
  // Note: async textures set as uniforms might still be loading.
217
200
  // Now that all uniforms have been updated, check if any texture
218
201
  // in the uniforms is not yet initialized, then we don't draw
219
- if (!this._areTexturesRenderable()) {
202
+ if (!this._areTexturesRenderable(bindings)) {
220
203
  log.info(2, `RenderPipeline:${this.id}.draw() aborted - textures not yet loaded`)();
221
204
  // Note: false means that the app needs to redraw the pipeline again.
222
205
  return false;
@@ -239,8 +222,8 @@ export class WEBGLRenderPipeline extends RenderPipeline {
239
222
  }
240
223
 
241
224
  // We have to apply bindings before every draw call since other draw calls will overwrite
242
- this._applyBindings();
243
- this._applyUniforms();
225
+ this._applyBindings(bindings, {disableWarnings: this.props.disableWarnings});
226
+ this._applyUniforms(uniforms);
244
227
 
245
228
  const webglRenderPass = renderPass as WEBGLRenderPass;
246
229
 
@@ -278,138 +261,16 @@ export class WEBGLRenderPipeline extends RenderPipeline {
278
261
  return true;
279
262
  }
280
263
 
281
- // PRIVATE METHODS
282
-
283
- // setAttributes(attributes: Record<string, Buffer>): void {}
284
- // setBindings(bindings: Record<string, Binding>): void {}
285
-
286
- protected async _linkShaders() {
287
- const {gl} = this.device;
288
- gl.attachShader(this.handle, this.vs.handle);
289
- gl.attachShader(this.handle, this.fs.handle);
290
- log.time(LOG_PROGRAM_PERF_PRIORITY, `linkProgram for ${this.id}`)();
291
- gl.linkProgram(this.handle);
292
- log.timeEnd(LOG_PROGRAM_PERF_PRIORITY, `linkProgram for ${this.id}`)();
293
-
294
- // TODO Avoid checking program linking error in production
295
- if (log.level === 0) {
296
- // return;
297
- }
298
-
299
- if (!this.device.features.has('compilation-status-async-webgl')) {
300
- const status = this._getLinkStatus();
301
- this._reportLinkStatus(status);
302
- return;
303
- }
304
-
305
- // async case
306
- log.once(1, 'RenderPipeline linking is asynchronous')();
307
- await this._waitForLinkComplete();
308
- log.info(2, `RenderPipeline ${this.id} - async linking complete: ${this.linkStatus}`)();
309
- const status = this._getLinkStatus();
310
- this._reportLinkStatus(status);
311
- }
312
-
313
- /** Report link status. First, check for shader compilation failures if linking fails */
314
- async _reportLinkStatus(status: 'success' | 'link-error' | 'validation-error'): Promise<void> {
315
- switch (status) {
316
- case 'success':
317
- return;
318
-
319
- default:
320
- const errorType = status === 'link-error' ? 'Link error' : 'Validation error';
321
- // First check for shader compilation failures if linking fails
322
- switch (this.vs.compilationStatus) {
323
- case 'error':
324
- this.vs.debugShader();
325
- throw new Error(`${this} ${errorType} during compilation of ${this.vs}`);
326
- case 'pending':
327
- await this.vs.asyncCompilationStatus;
328
- this.vs.debugShader();
329
- break;
330
- case 'success':
331
- break;
332
- }
333
-
334
- switch (this.fs?.compilationStatus) {
335
- case 'error':
336
- this.fs.debugShader();
337
- throw new Error(`${this} ${errorType} during compilation of ${this.fs}`);
338
- case 'pending':
339
- await this.fs.asyncCompilationStatus;
340
- this.fs.debugShader();
341
- break;
342
- case 'success':
343
- break;
344
- }
345
-
346
- const linkErrorLog = this.device.gl.getProgramInfoLog(this.handle);
347
- this.device.reportError(
348
- new Error(`${errorType} during ${status}: ${linkErrorLog}`),
349
- this
350
- )();
351
- this.device.debug();
352
- }
353
- }
354
-
355
- /**
356
- * Get the shader compilation status
357
- * TODO - Load log even when no error reported, to catch warnings?
358
- * https://gamedev.stackexchange.com/questions/30429/how-to-detect-glsl-warnings
359
- */
360
- _getLinkStatus(): 'success' | 'link-error' | 'validation-error' {
361
- const {gl} = this.device;
362
- const linked = gl.getProgramParameter(this.handle, GL.LINK_STATUS);
363
- if (!linked) {
364
- this.linkStatus = 'error';
365
- return 'link-error';
366
- }
367
-
368
- gl.validateProgram(this.handle);
369
- const validated = gl.getProgramParameter(this.handle, GL.VALIDATE_STATUS);
370
- if (!validated) {
371
- this.linkStatus = 'error';
372
- return 'validation-error';
373
- }
374
-
375
- this.linkStatus = 'success';
376
- return 'success';
377
- }
378
-
379
- /** Use KHR_parallel_shader_compile extension if available */
380
- async _waitForLinkComplete(): Promise<void> {
381
- const waitMs = async (ms: number) => await new Promise(resolve => setTimeout(resolve, ms));
382
- const DELAY_MS = 10; // Shader compilation is typically quite fast (with some exceptions)
383
-
384
- // If status polling is not available, we can't wait for completion. Just wait a little to minimize blocking
385
- if (!this.device.features.has('compilation-status-async-webgl')) {
386
- await waitMs(DELAY_MS);
387
- return;
388
- }
389
-
390
- const {gl} = this.device;
391
- for (;;) {
392
- const complete = gl.getProgramParameter(this.handle, GL.COMPLETION_STATUS_KHR);
393
- if (complete) {
394
- return;
395
- }
396
- await waitMs(DELAY_MS);
397
- }
398
- }
399
-
400
264
  /**
401
265
  * Checks if all texture-values uniforms are renderable (i.e. loaded)
402
266
  * Update a texture if needed (e.g. from video)
403
267
  * Note: This is currently done before every draw call
404
268
  */
405
- _areTexturesRenderable() {
269
+ _areTexturesRenderable(bindings: Record<string, Binding>) {
406
270
  let texturesRenderable = true;
407
271
 
408
272
  for (const bindingInfo of this.shaderLayout.bindings) {
409
- if (
410
- !this.bindings[bindingInfo.name] &&
411
- !this.bindings[bindingInfo.name.replace(/Uniforms$/, '')]
412
- ) {
273
+ if (!bindings[bindingInfo.name] && !bindings[bindingInfo.name.replace(/Uniforms$/, '')]) {
413
274
  log.warn(`Binding ${bindingInfo.name} not found in ${this.id}`)();
414
275
  texturesRenderable = false;
415
276
  }
@@ -426,7 +287,9 @@ export class WEBGLRenderPipeline extends RenderPipeline {
426
287
  }
427
288
 
428
289
  /** Apply any bindings (before each draw call) */
429
- _applyBindings() {
290
+ _applyBindings(bindings: Record<string, Binding>, _options?: {disableWarnings?: boolean}) {
291
+ this._syncLinkStatus();
292
+
430
293
  // If we are using async linking, we need to wait until linking completes
431
294
  if (this.linkStatus !== 'success') {
432
295
  return;
@@ -439,8 +302,7 @@ export class WEBGLRenderPipeline extends RenderPipeline {
439
302
  let uniformBufferIndex = 0;
440
303
  for (const binding of this.shaderLayout.bindings) {
441
304
  // Accept both `xyz` and `xyzUniforms` as valid names for `xyzUniforms` uniform block
442
- const value =
443
- this.bindings[binding.name] || this.bindings[binding.name.replace(/Uniforms$/, '')];
305
+ const value = bindings[binding.name] || bindings[binding.name.replace(/Uniforms$/, '')];
444
306
  if (!value) {
445
307
  throw new Error(`No value for binding ${binding.name} in ${this.id}`);
446
308
  }
@@ -452,8 +314,7 @@ export class WEBGLRenderPipeline extends RenderPipeline {
452
314
  if ((location as GL) === GL.INVALID_INDEX) {
453
315
  throw new Error(`Invalid uniform block name ${name}`);
454
316
  }
455
- gl.uniformBlockBinding(this.handle, uniformBufferIndex, location);
456
- // console.debug(binding, location);
317
+ gl.uniformBlockBinding(this.handle, location, uniformBufferIndex);
457
318
  if (value instanceof WEBGLBuffer) {
458
319
  gl.bindBufferBase(GL.UNIFORM_BUFFER, uniformBufferIndex, value.handle);
459
320
  } else {
@@ -519,15 +380,19 @@ export class WEBGLRenderPipeline extends RenderPipeline {
519
380
  * Due to program sharing, uniforms need to be reset before every draw call
520
381
  * (though caching will avoid redundant WebGL calls)
521
382
  */
522
- _applyUniforms() {
383
+ _applyUniforms(uniforms: Record<string, UniformValue>) {
523
384
  for (const uniformLayout of this.shaderLayout.uniforms || []) {
524
385
  const {name, location, type, textureUnit} = uniformLayout;
525
- const value = this.uniforms[name] ?? textureUnit;
386
+ const value = uniforms[name] ?? textureUnit;
526
387
  if (value !== undefined) {
527
388
  setUniform(this.device.gl, location, type, value);
528
389
  }
529
390
  }
530
391
  }
392
+
393
+ private _syncLinkStatus(): void {
394
+ this.linkStatus = (this.sharedRenderPipeline as WEBGLSharedRenderPipeline).linkStatus;
395
+ }
531
396
  }
532
397
 
533
398
  /**
@@ -0,0 +1,208 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {GL} from '@luma.gl/constants';
6
+ import {
7
+ SharedRenderPipeline,
8
+ log,
9
+ type ShaderLayout,
10
+ type SharedRenderPipelineProps
11
+ } from '@luma.gl/core';
12
+
13
+ import {getShaderLayoutFromGLSL} from '../helpers/get-shader-layout-from-glsl';
14
+ import {isGLSamplerType} from '../converters/webgl-shadertypes';
15
+ import type {WebGLDevice} from '../webgl-device';
16
+ import type {WEBGLShader} from './webgl-shader';
17
+
18
+ const LOG_PROGRAM_PERF_PRIORITY = 4;
19
+
20
+ export class WEBGLSharedRenderPipeline extends SharedRenderPipeline {
21
+ readonly device: WebGLDevice;
22
+ readonly handle: WebGLProgram;
23
+ readonly vs: WEBGLShader;
24
+ readonly fs: WEBGLShader;
25
+ introspectedLayout: ShaderLayout = {attributes: [], bindings: [], uniforms: []};
26
+ linkStatus: 'pending' | 'success' | 'error' = 'pending';
27
+
28
+ constructor(
29
+ device: WebGLDevice,
30
+ props: SharedRenderPipelineProps & {
31
+ handle?: WebGLProgram;
32
+ vs: WEBGLShader;
33
+ fs: WEBGLShader;
34
+ }
35
+ ) {
36
+ super(device, props);
37
+ this.device = device;
38
+ this.handle = props.handle || this.device.gl.createProgram();
39
+ this.vs = props.vs;
40
+ this.fs = props.fs;
41
+
42
+ if (props.varyings && props.varyings.length > 0) {
43
+ this.device.gl.transformFeedbackVaryings(
44
+ this.handle,
45
+ props.varyings,
46
+ props.bufferMode || GL.SEPARATE_ATTRIBS
47
+ );
48
+ }
49
+
50
+ this._linkShaders();
51
+ log.time(3, `RenderPipeline ${this.id} - shaderLayout introspection`)();
52
+ this.introspectedLayout = getShaderLayoutFromGLSL(this.device.gl, this.handle);
53
+ log.timeEnd(3, `RenderPipeline ${this.id} - shaderLayout introspection`)();
54
+ }
55
+
56
+ override destroy(): void {
57
+ if (this.destroyed) {
58
+ return;
59
+ }
60
+
61
+ this.device.gl.useProgram(null);
62
+ this.device.gl.deleteProgram(this.handle);
63
+ // @ts-expect-error
64
+ this.handle.destroyed = true;
65
+ this.destroyResource();
66
+ }
67
+
68
+ protected async _linkShaders() {
69
+ const {gl} = this.device;
70
+ gl.attachShader(this.handle, this.vs.handle);
71
+ gl.attachShader(this.handle, this.fs.handle);
72
+ log.time(LOG_PROGRAM_PERF_PRIORITY, `linkProgram for ${this.id}`)();
73
+ gl.linkProgram(this.handle);
74
+ log.timeEnd(LOG_PROGRAM_PERF_PRIORITY, `linkProgram for ${this.id}`)();
75
+
76
+ if (!this.device.features.has('compilation-status-async-webgl')) {
77
+ const status = this._getLinkStatus();
78
+ this._reportLinkStatus(status);
79
+ return;
80
+ }
81
+
82
+ log.once(1, 'RenderPipeline linking is asynchronous')();
83
+ await this._waitForLinkComplete();
84
+ log.info(2, `RenderPipeline ${this.id} - async linking complete: ${this.linkStatus}`)();
85
+ const status = this._getLinkStatus();
86
+ this._reportLinkStatus(status);
87
+ }
88
+
89
+ async _reportLinkStatus(status: 'success' | 'link-error' | 'validation-error'): Promise<void> {
90
+ switch (status) {
91
+ case 'success':
92
+ return;
93
+
94
+ default:
95
+ const errorType = status === 'link-error' ? 'Link error' : 'Validation error';
96
+ switch (this.vs.compilationStatus) {
97
+ case 'error':
98
+ this.vs.debugShader();
99
+ throw new Error(`${this} ${errorType} during compilation of ${this.vs}`);
100
+ case 'pending':
101
+ await this.vs.asyncCompilationStatus;
102
+ this.vs.debugShader();
103
+ break;
104
+ case 'success':
105
+ break;
106
+ }
107
+
108
+ switch (this.fs?.compilationStatus) {
109
+ case 'error':
110
+ this.fs.debugShader();
111
+ throw new Error(`${this} ${errorType} during compilation of ${this.fs}`);
112
+ case 'pending':
113
+ await this.fs.asyncCompilationStatus;
114
+ this.fs.debugShader();
115
+ break;
116
+ case 'success':
117
+ break;
118
+ }
119
+
120
+ const linkErrorLog = this.device.gl.getProgramInfoLog(this.handle);
121
+ this.device.reportError(
122
+ new Error(`${errorType} during ${status}: ${linkErrorLog}`),
123
+ this
124
+ )();
125
+ this.device.debug();
126
+ }
127
+ }
128
+
129
+ _getLinkStatus(): 'success' | 'link-error' | 'validation-error' {
130
+ const {gl} = this.device;
131
+ const linked = gl.getProgramParameter(this.handle, GL.LINK_STATUS);
132
+ if (!linked) {
133
+ this.linkStatus = 'error';
134
+ return 'link-error';
135
+ }
136
+
137
+ this._initializeSamplerUniforms();
138
+ gl.validateProgram(this.handle);
139
+ const validated = gl.getProgramParameter(this.handle, GL.VALIDATE_STATUS);
140
+ if (!validated) {
141
+ this.linkStatus = 'error';
142
+ return 'validation-error';
143
+ }
144
+
145
+ this.linkStatus = 'success';
146
+ return 'success';
147
+ }
148
+
149
+ _initializeSamplerUniforms(): void {
150
+ const {gl} = this.device;
151
+ gl.useProgram(this.handle);
152
+
153
+ let textureUnit = 0;
154
+ const uniformCount = gl.getProgramParameter(this.handle, GL.ACTIVE_UNIFORMS);
155
+ for (let uniformIndex = 0; uniformIndex < uniformCount; uniformIndex++) {
156
+ const activeInfo = gl.getActiveUniform(this.handle, uniformIndex);
157
+ if (activeInfo && isGLSamplerType(activeInfo.type)) {
158
+ const isArray = activeInfo.name.endsWith('[0]');
159
+ const uniformName = isArray ? activeInfo.name.slice(0, -3) : activeInfo.name;
160
+ const location = gl.getUniformLocation(this.handle, uniformName);
161
+
162
+ if (location !== null) {
163
+ textureUnit = this._assignSamplerUniform(location, activeInfo, isArray, textureUnit);
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ _assignSamplerUniform(
170
+ location: WebGLUniformLocation,
171
+ activeInfo: WebGLActiveInfo,
172
+ isArray: boolean,
173
+ textureUnit: number
174
+ ): number {
175
+ const {gl} = this.device;
176
+
177
+ if (isArray && activeInfo.size > 1) {
178
+ const textureUnits = Int32Array.from(
179
+ {length: activeInfo.size},
180
+ (_, arrayIndex) => textureUnit + arrayIndex
181
+ );
182
+ gl.uniform1iv(location, textureUnits);
183
+ return textureUnit + activeInfo.size;
184
+ }
185
+
186
+ gl.uniform1i(location, textureUnit);
187
+ return textureUnit + 1;
188
+ }
189
+
190
+ async _waitForLinkComplete(): Promise<void> {
191
+ const waitMs = async (ms: number) => await new Promise(resolve => setTimeout(resolve, ms));
192
+ const DELAY_MS = 10;
193
+
194
+ if (!this.device.features.has('compilation-status-async-webgl')) {
195
+ await waitMs(DELAY_MS);
196
+ return;
197
+ }
198
+
199
+ const {gl} = this.device;
200
+ for (;;) {
201
+ const complete = gl.getProgramParameter(this.handle, GL.COMPLETION_STATUS_KHR);
202
+ if (complete) {
203
+ return;
204
+ }
205
+ await waitMs(DELAY_MS);
206
+ }
207
+ }
208
+ }