@luma.gl/webgl 9.3.0-alpha.4 → 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.
- package/dist/adapter/converters/webgl-texture-table.d.ts +7 -1
- package/dist/adapter/converters/webgl-texture-table.d.ts.map +1 -1
- package/dist/adapter/converters/webgl-texture-table.js +121 -43
- package/dist/adapter/converters/webgl-texture-table.js.map +1 -1
- package/dist/adapter/device-helpers/webgl-device-features.d.ts.map +1 -1
- package/dist/adapter/device-helpers/webgl-device-features.js +1 -2
- package/dist/adapter/device-helpers/webgl-device-features.js.map +1 -1
- package/dist/adapter/device-helpers/webgl-device-info.js +5 -0
- package/dist/adapter/device-helpers/webgl-device-info.js.map +1 -1
- package/dist/adapter/helpers/get-shader-layout-from-glsl.js +16 -17
- package/dist/adapter/helpers/get-shader-layout-from-glsl.js.map +1 -1
- package/dist/adapter/resources/webgl-buffer.d.ts.map +1 -1
- package/dist/adapter/resources/webgl-buffer.js +19 -4
- package/dist/adapter/resources/webgl-buffer.js.map +1 -1
- package/dist/adapter/resources/webgl-command-buffer.d.ts +2 -2
- package/dist/adapter/resources/webgl-command-buffer.d.ts.map +1 -1
- package/dist/adapter/resources/webgl-command-buffer.js +2 -2
- package/dist/adapter/resources/webgl-command-buffer.js.map +1 -1
- package/dist/adapter/resources/webgl-command-encoder.d.ts +5 -4
- package/dist/adapter/resources/webgl-command-encoder.d.ts.map +1 -1
- package/dist/adapter/resources/webgl-command-encoder.js +20 -7
- package/dist/adapter/resources/webgl-command-encoder.js.map +1 -1
- package/dist/adapter/resources/webgl-query-set.d.ts +29 -31
- package/dist/adapter/resources/webgl-query-set.d.ts.map +1 -1
- package/dist/adapter/resources/webgl-query-set.js +193 -97
- package/dist/adapter/resources/webgl-query-set.js.map +1 -1
- package/dist/adapter/resources/webgl-render-pass.d.ts.map +1 -1
- package/dist/adapter/resources/webgl-render-pass.js +12 -0
- package/dist/adapter/resources/webgl-render-pass.js.map +1 -1
- package/dist/adapter/resources/webgl-render-pipeline.d.ts +13 -19
- package/dist/adapter/resources/webgl-render-pipeline.d.ts.map +1 -1
- package/dist/adapter/resources/webgl-render-pipeline.js +35 -152
- package/dist/adapter/resources/webgl-render-pipeline.js.map +1 -1
- package/dist/adapter/resources/webgl-shared-render-pipeline.d.ts +24 -0
- package/dist/adapter/resources/webgl-shared-render-pipeline.d.ts.map +1 -0
- package/dist/adapter/resources/webgl-shared-render-pipeline.js +152 -0
- package/dist/adapter/resources/webgl-shared-render-pipeline.js.map +1 -0
- package/dist/adapter/resources/webgl-texture.d.ts +23 -4
- package/dist/adapter/resources/webgl-texture.d.ts.map +1 -1
- package/dist/adapter/resources/webgl-texture.js +203 -100
- package/dist/adapter/resources/webgl-texture.js.map +1 -1
- package/dist/adapter/webgl-device.d.ts +4 -2
- package/dist/adapter/webgl-device.d.ts.map +1 -1
- package/dist/adapter/webgl-device.js +31 -3
- package/dist/adapter/webgl-device.js.map +1 -1
- package/dist/adapter/webgl-presentation-context.d.ts +21 -0
- package/dist/adapter/webgl-presentation-context.d.ts.map +1 -0
- package/dist/adapter/webgl-presentation-context.js +64 -0
- package/dist/adapter/webgl-presentation-context.js.map +1 -0
- package/dist/dist.dev.js +1332 -788
- package/dist/dist.min.js +2 -2
- package/dist/index.cjs +1247 -797
- package/dist/index.cjs.map +4 -4
- package/package.json +3 -3
- package/src/adapter/converters/webgl-texture-table.ts +159 -47
- package/src/adapter/device-helpers/webgl-device-features.ts +1 -2
- package/src/adapter/device-helpers/webgl-device-info.ts +6 -0
- package/src/adapter/helpers/get-shader-layout-from-glsl.ts +18 -19
- package/src/adapter/resources/webgl-buffer.ts +16 -4
- package/src/adapter/resources/webgl-command-buffer.ts +3 -2
- package/src/adapter/resources/webgl-command-encoder.ts +22 -7
- package/src/adapter/resources/webgl-query-set.ts +229 -102
- package/src/adapter/resources/webgl-render-pass.ts +13 -0
- package/src/adapter/resources/webgl-render-pipeline.ts +45 -179
- package/src/adapter/resources/webgl-shared-render-pipeline.ts +208 -0
- package/src/adapter/resources/webgl-texture.ts +326 -121
- package/src/adapter/webgl-device.ts +40 -4
- package/src/adapter/webgl-presentation-context.ts +93 -0
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
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.
|
|
96
|
-
|
|
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
|
-
*
|
|
109
|
-
*
|
|
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;
|
|
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
|
}
|
|
@@ -518,15 +380,19 @@ export class WEBGLRenderPipeline extends RenderPipeline {
|
|
|
518
380
|
* Due to program sharing, uniforms need to be reset before every draw call
|
|
519
381
|
* (though caching will avoid redundant WebGL calls)
|
|
520
382
|
*/
|
|
521
|
-
_applyUniforms() {
|
|
383
|
+
_applyUniforms(uniforms: Record<string, UniformValue>) {
|
|
522
384
|
for (const uniformLayout of this.shaderLayout.uniforms || []) {
|
|
523
385
|
const {name, location, type, textureUnit} = uniformLayout;
|
|
524
|
-
const value =
|
|
386
|
+
const value = uniforms[name] ?? textureUnit;
|
|
525
387
|
if (value !== undefined) {
|
|
526
388
|
setUniform(this.device.gl, location, type, value);
|
|
527
389
|
}
|
|
528
390
|
}
|
|
529
391
|
}
|
|
392
|
+
|
|
393
|
+
private _syncLinkStatus(): void {
|
|
394
|
+
this.linkStatus = (this.sharedRenderPipeline as WEBGLSharedRenderPipeline).linkStatus;
|
|
395
|
+
}
|
|
530
396
|
}
|
|
531
397
|
|
|
532
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
|
+
}
|