@luma.gl/webgl 9.0.0-alpha.26 → 9.0.0-alpha.29

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 (56) hide show
  1. package/dist/adapter/helpers/get-shader-layout.d.ts +8 -5
  2. package/dist/adapter/helpers/get-shader-layout.d.ts.map +1 -1
  3. package/dist/adapter/helpers/get-shader-layout.js +59 -16
  4. package/dist/adapter/helpers/get-shader-layout.js.map +1 -1
  5. package/dist/adapter/helpers/get-vertex-buffer-layout.d.ts +12 -0
  6. package/dist/adapter/helpers/get-vertex-buffer-layout.d.ts.map +1 -0
  7. package/dist/adapter/helpers/get-vertex-buffer-layout.js +84 -0
  8. package/dist/adapter/helpers/get-vertex-buffer-layout.js.map +1 -0
  9. package/dist/adapter/objects/webgl-vertex-array-object.d.ts +35 -8
  10. package/dist/adapter/objects/webgl-vertex-array-object.d.ts.map +1 -1
  11. package/dist/adapter/objects/webgl-vertex-array-object.js +116 -16
  12. package/dist/adapter/objects/webgl-vertex-array-object.js.map +1 -1
  13. package/dist/adapter/resources/webgl-buffer.d.ts +13 -4
  14. package/dist/adapter/resources/webgl-buffer.d.ts.map +1 -1
  15. package/dist/adapter/resources/webgl-buffer.js +23 -21
  16. package/dist/adapter/resources/webgl-buffer.js.map +1 -1
  17. package/dist/adapter/resources/webgl-external-texture.js.map +1 -1
  18. package/dist/adapter/resources/webgl-render-pipeline.d.ts +38 -5
  19. package/dist/adapter/resources/webgl-render-pipeline.d.ts.map +1 -1
  20. package/dist/adapter/resources/webgl-render-pipeline.js +35 -7
  21. package/dist/adapter/resources/webgl-render-pipeline.js.map +1 -1
  22. package/dist/adapter/webgl-device.d.ts.map +1 -1
  23. package/dist/adapter/webgl-device.js +5 -2
  24. package/dist/adapter/webgl-device.js.map +1 -1
  25. package/dist/classic/buffer-with-accessor.d.ts +5 -15
  26. package/dist/classic/buffer-with-accessor.d.ts.map +1 -1
  27. package/dist/classic/buffer-with-accessor.js +25 -30
  28. package/dist/classic/buffer-with-accessor.js.map +1 -1
  29. package/dist/classic/copy-and-blit.d.ts.map +1 -1
  30. package/dist/classic/copy-and-blit.js +2 -2
  31. package/dist/classic/copy-and-blit.js.map +1 -1
  32. package/dist/context/debug/webgl-developer-tools.d.ts.map +1 -1
  33. package/dist/context/debug/webgl-developer-tools.js +2 -1
  34. package/dist/context/debug/webgl-developer-tools.js.map +1 -1
  35. package/dist/context/parameters/webgl-parameter-tables.js +2 -2
  36. package/dist/context/parameters/webgl-parameter-tables.js.map +1 -1
  37. package/dist/dist.dev.js +350 -99
  38. package/dist/index.cjs +343 -157
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +1 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist.min.js +22 -22
  44. package/package.json +5 -5
  45. package/src/adapter/helpers/get-shader-layout.ts +93 -33
  46. package/src/adapter/helpers/get-vertex-buffer-layout.ts +123 -0
  47. package/src/adapter/objects/webgl-vertex-array-object.ts +192 -28
  48. package/src/adapter/resources/webgl-buffer.ts +40 -41
  49. package/src/adapter/resources/webgl-external-texture.ts +1 -1
  50. package/src/adapter/resources/webgl-render-pipeline.ts +73 -36
  51. package/src/adapter/webgl-device.ts +4 -2
  52. package/src/classic/buffer-with-accessor.ts +42 -43
  53. package/src/classic/copy-and-blit.ts +2 -3
  54. package/src/context/debug/webgl-developer-tools.ts +8 -2
  55. package/src/context/parameters/webgl-parameter-tables.ts +2 -2
  56. package/src/index.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luma.gl/webgl",
3
- "version": "9.0.0-alpha.26",
3
+ "version": "9.0.0-alpha.29",
4
4
  "description": "WebGL2 adapter for the luma.gl API",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -44,12 +44,12 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@babel/runtime": "^7.0.0",
47
- "@luma.gl/api": "9.0.0-alpha.26",
48
- "@luma.gl/constants": "9.0.0-alpha.26",
47
+ "@luma.gl/api": "9.0.0-alpha.29",
48
+ "@luma.gl/constants": "9.0.0-alpha.29",
49
49
  "@probe.gl/env": "^4.0.2"
50
50
  },
51
51
  "devDependencies": {
52
- "@luma.gl/test-utils": "9.0.0-alpha.26"
52
+ "@luma.gl/test-utils": "9.0.0-alpha.29"
53
53
  },
54
- "gitHead": "e255f78c1c0a4555808c6e9282bcecb6accd147b"
54
+ "gitHead": "1aee1ad82fd09d441172f5ef490ebc083a7fa239"
55
55
  }
@@ -1,16 +1,17 @@
1
1
  // luma.gl, MIT license
2
2
 
3
- import {
3
+ import type {
4
4
  ShaderLayout,
5
- // BindingLayout,
5
+ AttributeLayout,
6
6
  UniformBinding,
7
7
  UniformBlockBinding,
8
- ProgramBindings,
9
8
  AttributeBinding,
10
9
  VaryingBinding,
11
- // AttributeLayout,
12
- AccessorObject
10
+ AccessorObject,
11
+ BufferMapping,
12
+ VertexFormat
13
13
  } from '@luma.gl/api';
14
+ import {log} from '@luma.gl/api';
14
15
  import {GL} from '@luma.gl/constants';
15
16
  import {isWebGL2} from '../../context/context/webgl-checks';
16
17
  import {Accessor} from '../../classic/accessor'; // TODO - should NOT depend on classic API
@@ -24,14 +25,13 @@ import {isSamplerUniform} from './uniforms';
24
25
  * (although linking does not need to have been successful).
25
26
  */
26
27
  export function getShaderLayout(gl: WebGLRenderingContext, program: WebGLProgram): ShaderLayout {
27
- const programBindings = getProgramBindings(gl, program);
28
-
29
28
  const shaderLayout: ShaderLayout = {
30
29
  attributes: [],
31
30
  bindings: []
32
31
  };
33
32
 
34
- for (const attribute of programBindings.attributes) {
33
+ const attributes: AttributeBinding[] = readAttributeBindings(gl, program);
34
+ for (const attribute of attributes) {
35
35
  // TODO - multicolumn attributes like a matrix4 can be up to 16 elts...
36
36
  const size = Math.min(attribute.accessor.size, 4);
37
37
  const format =
@@ -46,8 +46,9 @@ export function getShaderLayout(gl: WebGLRenderingContext, program: WebGLProgram
46
46
  }
47
47
 
48
48
  // Uniform blocks
49
- for (const uniformBlock of programBindings.uniformBlocks) {
50
- const uniforms = uniformBlock.uniforms.map((uniform) => ({
49
+ const uniformBlocks: UniformBlockBinding[] = readUniformBlocks(gl, program);
50
+ for (const uniformBlock of uniformBlocks) {
51
+ const uniforms = uniformBlock.uniforms.map(uniform => ({
51
52
  name: uniform.name,
52
53
  format: uniform.format,
53
54
  byteOffset: uniform.byteOffset,
@@ -64,8 +65,9 @@ export function getShaderLayout(gl: WebGLRenderingContext, program: WebGLProgram
64
65
  });
65
66
  }
66
67
 
68
+ const uniforms: UniformBinding[] = readUniformBindings(gl, program);
67
69
  let textureUnit = 0;
68
- for (const uniform of programBindings.uniforms) {
70
+ for (const uniform of uniforms) {
69
71
  if (isSamplerUniform(uniform.type)) {
70
72
  const {viewDimension, sampleType} = getSamplerInfo(uniform.type);
71
73
  shaderLayout.bindings.push({
@@ -82,38 +84,95 @@ export function getShaderLayout(gl: WebGLRenderingContext, program: WebGLProgram
82
84
  }
83
85
  }
84
86
 
85
- const uniforms = programBindings.uniforms?.filter((uniform) => uniform.location !== null) || [];
86
87
  if (uniforms.length) {
87
88
  shaderLayout.uniforms = uniforms;
88
89
  }
89
- if (programBindings.varyings?.length) {
90
- shaderLayout.varyings = programBindings.varyings;
90
+
91
+ // Varyings
92
+ const varyings: VaryingBinding[] = readVaryings(gl, program);
93
+ // Note - samplers are always in unform bindings, even if uniform blocks are used
94
+ if (varyings?.length) {
95
+ shaderLayout.varyings = varyings;
91
96
  }
92
97
 
93
98
  return shaderLayout;
94
99
  }
95
100
 
96
101
  /**
97
- * Extract metadata describing binding information for a program's shaders
98
- * Note: `linkProgram()` needs to have been called
99
- * (although linking does not need to have been successful).
102
+ * Merges an provided shader layout into a base shader layout
103
+ * In WebGL, this allows the auto generated shader layout to be overridden by the application
104
+ * Typically to change the format of the vertex attributes (from float32x4 to uint8x4 etc).
105
+ * @todo Drop this? This could also be done more clearly with bufferMapping
100
106
  */
101
- export function getProgramBindings(
102
- gl: WebGLRenderingContext,
103
- program: WebGLProgram
104
- ): ProgramBindings {
105
- const config: ProgramBindings = {
106
- attributes: readAttributeBindings(gl, program),
107
- uniforms: readUniformBindings(gl, program),
108
- uniformBlocks: readUniformBlocks(gl, program),
109
- varyings: readVaryings(gl, program)
107
+ export function mergeShaderLayout(
108
+ baseLayout: ShaderLayout,
109
+ overrideLayout: ShaderLayout
110
+ ): ShaderLayout {
111
+ // Deep clone the base layout
112
+ const mergedLayout: ShaderLayout = {
113
+ ...baseLayout,
114
+ attributes: baseLayout.attributes.map(attribute => ({...attribute}))
115
+ };
116
+ // Merge the attributes
117
+ for (const attribute of overrideLayout?.attributes || []) {
118
+ const baseAttribute = mergedLayout.attributes.find(attr => attr.name === attribute.name);
119
+ if (!baseAttribute) {
120
+ log.warn(`shader layout attribute ${attribute.name} not present in shader`);
121
+ } else {
122
+ baseAttribute.format = attribute.format || baseAttribute.format;
123
+ baseAttribute.stepMode = attribute.stepMode || baseAttribute.stepMode;
124
+ }
125
+ }
126
+ return mergedLayout;
127
+ }
128
+
129
+ export function mergeBufferMap(baseLayout: ShaderLayout, bufferMap: BufferMapping[]): ShaderLayout {
130
+ // Deep clone the base layout
131
+ const mergedLayout: ShaderLayout = {
132
+ ...baseLayout,
133
+ attributes: baseLayout.attributes.map(attribute => ({...attribute}))
110
134
  };
135
+ for (const bufferMapping of bufferMap) {
136
+ // Handle interleave
137
+ switch (bufferMapping.type) {
138
+ case 'interleave':
139
+ // Handle interleaved buffer mapping
140
+ for (const attributeOverride of bufferMapping.attributes) {
141
+ overrideShaderLayoutAttribute(mergedLayout, attributeOverride);
142
+ }
143
+ break;
144
+
145
+ default:
146
+ // Handle simple attribute overrides
147
+ overrideShaderLayoutAttribute(mergedLayout, bufferMapping);
148
+ }
149
+ }
150
+ return mergedLayout;
151
+ }
111
152
 
112
- Object.seal(config);
113
- return config;
114
- // generateWebGPUStyleBindings(bindings);
153
+ function overrideShaderLayoutAttribute(
154
+ layout: ShaderLayout,
155
+ attributeOverride: {name: string; format?: VertexFormat}
156
+ ): void {
157
+ const attribute = getAttributeFromLayout(layout, attributeOverride.name);
158
+ if (attribute && attributeOverride.format) {
159
+ attribute.format = attributeOverride.format;
160
+ }
115
161
  }
116
162
 
163
+ export function getAttributeFromLayout(
164
+ shaderLayout: ShaderLayout,
165
+ name: string
166
+ ): AttributeLayout | null {
167
+ const attribute = shaderLayout.attributes.find(attr => attr.name === name);
168
+ if (!attribute) {
169
+ log.warn(`shader layout attribute "${name}" not present in shader`);
170
+ }
171
+ return attribute || null;
172
+ }
173
+
174
+ // HELPERS
175
+
117
176
  /**
118
177
  * Extract info about all transform feedback varyings
119
178
  *
@@ -261,7 +320,8 @@ function readUniformBlocks(
261
320
  uniforms: [] as any[]
262
321
  };
263
322
 
264
- const uniformIndices = getBlockParameter(blockIndex, GL.UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES) as number[] || [];
323
+ const uniformIndices =
324
+ (getBlockParameter(blockIndex, GL.UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES) as number[]) || [];
265
325
 
266
326
  const uniformType = gl2.getActiveUniforms(program, uniformIndices, GL.UNIFORM_TYPE); // Array of GLenum indicating the types of the uniforms.
267
327
  const uniformArrayLength = gl2.getActiveUniforms(program, uniformIndices, GL.UNIFORM_SIZE); // Array of GLuint indicating the sizes of the uniforms.
@@ -283,14 +343,14 @@ function readUniformBlocks(
283
343
  if (!activeInfo) {
284
344
  throw new Error('activeInfo');
285
345
  }
286
-
346
+
287
347
  blockInfo.uniforms.push({
288
348
  name: activeInfo.name,
289
349
  format: decodeUniformType(uniformType[i]).format,
290
350
  type: uniformType[i],
291
351
  arrayLength: uniformArrayLength[i],
292
352
  byteOffset: uniformOffset[i],
293
- byteStride: uniformStride[i],
353
+ byteStride: uniformStride[i]
294
354
  // matrixStride: uniformStride[i],
295
355
  // rowMajor: uniformRowMajor[i]
296
356
  });
@@ -327,7 +387,7 @@ const SAMPLER_UNIFORMS_GL_TO_GPU: Record<
327
387
  [GL.UNSIGNED_INT_SAMPLER_2D_ARRAY]: ['2d-array', 'uint']
328
388
  };
329
389
 
330
- type SamplerInfo = | {
390
+ type SamplerInfo = {
331
391
  viewDimension: '1d' | '2d' | '2d-array' | 'cube' | 'cube-array' | '3d';
332
392
  sampleType: 'float' | 'unfilterable-float' | 'depth' | 'sint' | 'uint';
333
393
  };
@@ -0,0 +1,123 @@
1
+ import type {ShaderLayout, BufferMapping, AttributeLayout} from '@luma.gl/api';
2
+ import {decodeVertexFormat} from '@luma.gl/api';
3
+
4
+ /**
5
+ * Build a WebGPU vertex buffer layout intended for use in a GPURenderPassDescriptor.
6
+ * Converts luma.gl attribute definitions to a WebGPU GPUVertexBufferLayout[] array
7
+ * @param layout
8
+ * @param bufferMap The buffer map is optional
9
+ * @returns WebGPU layout intended for a GPURenderPassDescriptor.
10
+ */
11
+ export function getVertexBufferLayout(layout: ShaderLayout, bufferMap: BufferMapping[]): GPUVertexBufferLayout[] {
12
+ const vertexBufferLayouts: GPUVertexBufferLayout[] = [];
13
+ const usedAttributes = new Set<string>();
14
+
15
+ // First handle any buffers mentioned in `bufferMapping`
16
+ for (const mapping of bufferMap) {
17
+ // Build vertex attributes for one buffer
18
+ const vertexAttributes: GPUVertexAttribute[] = [];
19
+
20
+ // TODO verify that all stepModes for one buffer are the same
21
+ let stepMode: 'vertex' | 'instance' = 'vertex';
22
+ let byteStride = 0;
23
+ const byteOffset = mapping.byteOffset || 0;
24
+
25
+ // interleaved mapping {..., attributes: [{...}, ...]}
26
+ if ('attributes' in mapping) {
27
+ // const arrayStride = mapping.byteStride; TODO
28
+ for (const interleaved of mapping.attributes) {
29
+ const attributeLayout = findAttributeLayout(layout, interleaved.name, usedAttributes);
30
+
31
+ stepMode = attributeLayout.stepMode || 'vertex';
32
+ vertexAttributes.push({
33
+ format: attributeLayout.format,
34
+ offset: byteOffset + byteStride,
35
+ shaderLocation: attributeLayout.location
36
+ });
37
+
38
+ byteStride += decodeVertexFormat(attributeLayout.format).byteLength;
39
+ }
40
+ // non-interleaved mapping (just set offset and stride)
41
+ } else {
42
+ const attributeLayout = findAttributeLayout(layout, mapping.name, usedAttributes);
43
+ byteStride = decodeVertexFormat(attributeLayout.format).byteLength;
44
+
45
+ stepMode = attributeLayout.stepMode || 'vertex';
46
+ vertexAttributes.push({
47
+ format: attributeLayout.format,
48
+ offset: byteOffset,
49
+ shaderLocation: attributeLayout.location
50
+ });
51
+ }
52
+
53
+ // Store all the attribute bindings for one buffer
54
+ vertexBufferLayouts.push({
55
+ arrayStride: mapping.byteStride || byteStride,
56
+ stepMode: stepMode || 'vertex',
57
+ attributes: vertexAttributes
58
+ });
59
+ }
60
+
61
+ // Add any non-mapped attributes
62
+ for (const attribute of layout.attributes) {
63
+ if (!usedAttributes.has(attribute.name)) {
64
+ vertexBufferLayouts.push({
65
+ arrayStride: decodeVertexFormat(attribute.format).byteLength,
66
+ stepMode: attribute.stepMode || 'vertex',
67
+ attributes: [{
68
+ format: attribute.format,
69
+ offset: 0,
70
+ shaderLocation: attribute.location
71
+ }]
72
+ });
73
+ }
74
+ }
75
+
76
+ return vertexBufferLayouts;
77
+ }
78
+
79
+ export function getBufferSlots(layout: ShaderLayout, bufferMap: BufferMapping[]): Record<string, number> {
80
+ const usedAttributes = new Set<string>();
81
+ let bufferSlot = 0;
82
+ const bufferSlots: Record<string, number> = {};
83
+
84
+ // First handle any buffers mentioned in `bufferMapping`
85
+ for (const mapping of bufferMap) {
86
+ // interleaved mapping {..., attributes: [{...}, ...]}
87
+ if ('attributes' in mapping) {
88
+ for (const interleaved of mapping.attributes) {
89
+ usedAttributes.add(interleaved.name);
90
+ }
91
+ // non-interleaved mapping (just set offset and stride)
92
+ } else {
93
+ usedAttributes.add(mapping.name);
94
+ }
95
+ bufferSlots[mapping.name] = bufferSlot++;
96
+ }
97
+
98
+ // Add any non-mapped attributes
99
+ for (const attribute of layout.attributes) {
100
+ if (!usedAttributes.has(attribute.name)) {
101
+ bufferSlots[attribute.name] = bufferSlot++;
102
+ }
103
+ }
104
+
105
+ return bufferSlots;
106
+ }
107
+
108
+ /**
109
+ * Looks up an attribute in the ShaderLayout.
110
+ * @throws if name is not in ShaderLayout
111
+ * @throws if name has already been referenced
112
+ */
113
+ function findAttributeLayout(layout: ShaderLayout, name: string, attributeNames: Set<string>): AttributeLayout {
114
+ const attribute = layout.attributes.find(attribute => attribute.name === name);
115
+ if (!attribute) {
116
+ throw new Error(`Unknown attribute ${name}`);
117
+ }
118
+ if (attributeNames.has(name)) {
119
+ throw new Error(`Duplicate attribute ${name}`);
120
+ }
121
+ attributeNames.add(name);
122
+ return attribute;
123
+ }
@@ -1,18 +1,23 @@
1
- import {assert, ResourceProps} from '@luma.gl/api';
1
+ import type {Device, Buffer, ResourceProps, TypedArray, NumericArray} from '@luma.gl/api';
2
+ import {Resource, assert, getScratchArray, fillArray} from '@luma.gl/api';
2
3
  import {GL} from '@luma.gl/constants';
3
4
  import {getBrowser} from '@probe.gl/env';
4
5
 
5
6
  import {WebGLDevice} from '../webgl-device';
6
7
  import {WebGLResource} from './webgl-resource';
7
-
8
8
  import {WEBGLBuffer} from '../resources/webgl-buffer';
9
9
 
10
+ import {BufferWithAccessor} from '../../classic/buffer-with-accessor';
11
+
10
12
  const ERR_ELEMENTS = 'elements must be GL.ELEMENT_ARRAY_BUFFER';
11
13
 
12
14
  /**
13
15
  * VertexArrayObject properties
16
+ * @param constantAttributeZero Attribute 0 can not be disable on most desktop OpenGL based browsers
17
+ * and on iOS Safari browser.
14
18
  */
15
19
  export type VertexArrayObjectProps = ResourceProps & {
20
+ constantAttributeZero?: boolean;
16
21
  };
17
22
 
18
23
  /** VertexArrayObject wrapper */
@@ -21,11 +26,26 @@ export class WEBGLVertexArrayObject extends WebGLResource<VertexArrayObjectProps
21
26
  return 'BaseVertexArrayObject';
22
27
  }
23
28
 
24
- constructor(device: WebGLDevice, props?: VertexArrayObjectProps) {
25
- // @ts-expect-error
26
- super(device, props, {});
29
+ /** Buffer constant */
30
+ private buffer: BufferWithAccessor | null = null;
31
+ private bufferValue = null;
32
+
33
+ static isConstantAttributeZeroSupported(device: Device): boolean {
34
+ return device.info.type === 'webgl2' || getBrowser() === 'Chrome';
27
35
  }
28
36
 
37
+ // Create a VertexArray
38
+ constructor(device: Device, props?: VertexArrayObjectProps) {
39
+ super(device, props, {...Resource.defaultProps, constantAttributeZero: false});
40
+ Object.seal(this);
41
+ }
42
+
43
+ override destroy(): void {
44
+ super.destroy();
45
+ if (this.buffer) {
46
+ this.buffer?.destroy();
47
+ } }
48
+
29
49
  override _createHandle() {
30
50
  return this.gl2.createVertexArray();
31
51
  }
@@ -41,24 +61,43 @@ export class WEBGLVertexArrayObject extends WebGLResource<VertexArrayObjectProps
41
61
  this.gl2.bindVertexArray(handle);
42
62
  }
43
63
 
64
+ /**
65
+ * Enabling an attribute location makes it reference the currently bound buffer
66
+ * Disabling an attribute location makes it reference the global constant value
67
+ * TODO - handle single values for size 1 attributes?
68
+ * TODO - convert classic arrays based on known type?
69
+ */
70
+ enable(location: number, enable = true): void {
71
+ // Attribute 0 cannot be disabled in most desktop OpenGL based browsers...
72
+ const canDisableAttributeZero = this.device.isWebGL2 || getBrowser() === 'Chrome';
73
+ const canDisableAttribute = canDisableAttributeZero || location !== 0;
74
+
75
+ if (enable || canDisableAttribute) {
76
+ location = Number(location);
77
+ this.bind(() =>
78
+ enable
79
+ ? this.gl.enableVertexAttribArray(location)
80
+ : this.gl.disableVertexAttribArray(location)
81
+ );
82
+ } }
83
+
44
84
  // Set (bind) an elements buffer, for indexed rendering.
45
85
  // Must be a Buffer bound to GL.ELEMENT_ARRAY_BUFFER. Constants not supported
46
86
  setElementBuffer(elementBuffer: WEBGLBuffer | null = null, opts = {}) {
47
- assert(!elementBuffer || elementBuffer.target === GL.ELEMENT_ARRAY_BUFFER, ERR_ELEMENTS);
87
+ assert(!elementBuffer || elementBuffer.glTarget === GL.ELEMENT_ARRAY_BUFFER, ERR_ELEMENTS);
48
88
 
49
89
  // The GL.ELEMENT_ARRAY_BUFFER_BINDING is stored on the VertexArrayObject...
50
90
  this.bind(() => {
51
91
  this.gl.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, elementBuffer ? elementBuffer.handle : null);
52
92
  });
53
-
54
- return this;
55
93
  }
56
94
 
57
95
  /** Set a location in vertex attributes array to a buffer, enables the location, sets divisor */
58
- setBuffer(location: number, buffer: WEBGLBuffer, accessor: any): this {
96
+ setBuffer(location: number, buffer: WEBGLBuffer, accessor: any): void {
59
97
  // Check target
60
- if (buffer.target === GL.ELEMENT_ARRAY_BUFFER) {
61
- return this.setElementBuffer(buffer, accessor);
98
+ if (buffer.glTarget === GL.ELEMENT_ARRAY_BUFFER) {
99
+ this.setElementBuffer(buffer, accessor);
100
+ return;
62
101
  }
63
102
 
64
103
  const {size, type, stride, offset, normalized, integer, divisor} = accessor;
@@ -83,29 +122,154 @@ export class WEBGLVertexArrayObject extends WebGLResource<VertexArrayObjectProps
83
122
 
84
123
  // NOTE We don't unbind buffer here, typically another buffer will be bound just after
85
124
  });
125
+ }
86
126
 
87
- return this;
127
+ /**
128
+ * Set an attribute to a constant value
129
+ * @param device
130
+ * @param location
131
+ * @param array
132
+ *
133
+ * @note Constants are stored globally on the WebGL context, not the VAO
134
+ * so they need to be updated before every render
135
+ * @todo - use known type (in configuration or passed in) to allow non-typed arrays?
136
+ * @todo - remember/cache values to avoid setting them unnecessarily?
137
+ */
138
+ setConstant(location: any, array: TypedArray): void {
139
+ switch (array.constructor) {
140
+ case Float32Array:
141
+ setConstantFloatArray(this.device, location, array as Float32Array);
142
+ break;
143
+ case Int32Array:
144
+ setConstantIntArray(this.device, location, array as Int32Array);
145
+ break;
146
+ case Uint32Array:
147
+ setConstantUintArray(this.device, location, array as Uint32Array);
148
+ break;
149
+ default:
150
+ assert(false);
151
+ }
88
152
  }
89
153
 
90
154
  /**
91
- * Enabling an attribute location makes it reference the currently bound buffer
92
- * Disabling an attribute location makes it reference the global constant value
93
- * TODO - handle single values for size 1 attributes?
94
- * TODO - convert classic arrays based on known type?
155
+ * Provide a means to create a buffer that is equivalent to a constant.
156
+ * NOTE: Desktop OpenGL cannot disable attribute 0.
157
+ * https://stackoverflow.com/questions/20305231/webgl-warning-attribute-0-is-disabled-
158
+ * this-has-significant-performance-penalty
95
159
  */
96
- enable(location: number, enable = true): this {
97
- // Attribute 0 cannot be disabled in most desktop OpenGL based browsers...
98
- const canDisableAttributeZero = this.device.isWebGL2 || getBrowser() === 'Chrome';
99
- const canDisableAttribute = canDisableAttributeZero || location !== 0;
160
+ getConstantBuffer(elementCount: number, value): Buffer {
161
+ // Create buffer only when needed, and reuse it (avoids inflating buffer creation statistics)
100
162
 
101
- if (enable || canDisableAttribute) {
102
- location = Number(location);
103
- this.bind(() =>
104
- enable
105
- ? this.gl.enableVertexAttribArray(location)
106
- : this.gl.disableVertexAttribArray(location)
107
- );
163
+ const constantValue = normalizeConstantArrayValue(value);
164
+
165
+ const byteLength = constantValue.byteLength * elementCount;
166
+ const length = constantValue.length * elementCount;
167
+
168
+ let updateNeeded = !this.buffer;
169
+
170
+ this.buffer = this.buffer || this.device.createBuffer({byteLength}) as BufferWithAccessor;
171
+ updateNeeded = updateNeeded || this.buffer.reallocate(byteLength);
172
+
173
+ // Reallocate and update contents if needed
174
+ updateNeeded =
175
+ updateNeeded || !compareConstantArrayValues(constantValue, this.bufferValue);
176
+
177
+ if (updateNeeded) {
178
+ // Create a typed array that is big enough, and fill it with the required data
179
+ const typedArray = getScratchArray(value.constructor, length);
180
+ fillArray({target: typedArray, source: constantValue, start: 0, count: length});
181
+ this.buffer.subData(typedArray);
182
+ this.bufferValue = value;
183
+ }
184
+
185
+ return this.buffer;
186
+ }
187
+ }
188
+
189
+ function setConstantFloatArray(device: WebGLDevice, location: number, array: Float32Array): void {
190
+ switch (array.length) {
191
+ case 1:
192
+ device.gl.vertexAttrib1fv(location, array);
193
+ break;
194
+ case 2:
195
+ device.gl.vertexAttrib2fv(location, array);
196
+ break;
197
+ case 3:
198
+ device.gl.vertexAttrib3fv(location, array);
199
+ break;
200
+ case 4:
201
+ device.gl.vertexAttrib4fv(location, array);
202
+ break;
203
+ default:
204
+ assert(false);
205
+ }
206
+ }
207
+
208
+ function setConstantIntArray(device: WebGLDevice, location: number, array: Int32Array): void {
209
+ device.assertWebGL2();
210
+ device.gl2?.vertexAttribI4iv(location, array);
211
+ // switch (array.length) {
212
+ // case 1:
213
+ // gl.vertexAttribI1iv(location, array);
214
+ // break;
215
+ // case 2:
216
+ // gl.vertexAttribI2iv(location, array);
217
+ // break;
218
+ // case 3:
219
+ // gl.vertexAttribI3iv(location, array);
220
+ // break;
221
+ // case 4:
222
+ // break;
223
+ // default:
224
+ // assert(false);
225
+ // }
226
+ }
227
+
228
+ function setConstantUintArray(device: WebGLDevice, location: number, array: Uint32Array) {
229
+ device.assertWebGL2();
230
+ device.gl2?.vertexAttribI4uiv(location, array);
231
+ // switch (array.length) {
232
+ // case 1:
233
+ // gl.vertexAttribI1uiv(location, array);
234
+ // break;
235
+ // case 2:
236
+ // gl.vertexAttribI2uiv(location, array);
237
+ // break;
238
+ // case 3:
239
+ // gl.vertexAttribI3uiv(location, array);
240
+ // break;
241
+ // case 4:
242
+ // gl.vertexAttribI4uiv(location, array);
243
+ // break;
244
+ // default:
245
+ // assert(false);
246
+ // }
247
+ }
248
+
249
+ // HELPERS
250
+
251
+ /**
252
+ * TODO - convert Arrays based on known type? (read type from accessor, don't assume Float32Array)
253
+ * TODO - handle single values for size 1 attributes?
254
+ */
255
+ function normalizeConstantArrayValue(arrayValue: NumericArray) {
256
+ if (Array.isArray(arrayValue)) {
257
+ return new Float32Array(arrayValue);
258
+ }
259
+ return arrayValue;
260
+ }
261
+
262
+ /**
263
+ *
264
+ */
265
+ function compareConstantArrayValues(v1: NumericArray, v2: NumericArray): boolean {
266
+ if (!v1 || !v2 || v1.length !== v2.length || v1.constructor !== v2.constructor) {
267
+ return false;
268
+ }
269
+ for (let i = 0; i < v1.length; ++i) {
270
+ if (v1[i] !== v2[i]) {
271
+ return false;
108
272
  }
109
- return this;
110
273
  }
274
+ return true;
111
275
  }