@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.
- package/dist/adapter/helpers/get-shader-layout.d.ts +8 -5
- package/dist/adapter/helpers/get-shader-layout.d.ts.map +1 -1
- package/dist/adapter/helpers/get-shader-layout.js +59 -16
- package/dist/adapter/helpers/get-shader-layout.js.map +1 -1
- package/dist/adapter/helpers/get-vertex-buffer-layout.d.ts +12 -0
- package/dist/adapter/helpers/get-vertex-buffer-layout.d.ts.map +1 -0
- package/dist/adapter/helpers/get-vertex-buffer-layout.js +84 -0
- package/dist/adapter/helpers/get-vertex-buffer-layout.js.map +1 -0
- package/dist/adapter/objects/webgl-vertex-array-object.d.ts +35 -8
- package/dist/adapter/objects/webgl-vertex-array-object.d.ts.map +1 -1
- package/dist/adapter/objects/webgl-vertex-array-object.js +116 -16
- package/dist/adapter/objects/webgl-vertex-array-object.js.map +1 -1
- package/dist/adapter/resources/webgl-buffer.d.ts +13 -4
- package/dist/adapter/resources/webgl-buffer.d.ts.map +1 -1
- package/dist/adapter/resources/webgl-buffer.js +23 -21
- package/dist/adapter/resources/webgl-buffer.js.map +1 -1
- package/dist/adapter/resources/webgl-external-texture.js.map +1 -1
- package/dist/adapter/resources/webgl-render-pipeline.d.ts +38 -5
- package/dist/adapter/resources/webgl-render-pipeline.d.ts.map +1 -1
- package/dist/adapter/resources/webgl-render-pipeline.js +35 -7
- package/dist/adapter/resources/webgl-render-pipeline.js.map +1 -1
- package/dist/adapter/webgl-device.d.ts.map +1 -1
- package/dist/adapter/webgl-device.js +5 -2
- package/dist/adapter/webgl-device.js.map +1 -1
- package/dist/classic/buffer-with-accessor.d.ts +5 -15
- package/dist/classic/buffer-with-accessor.d.ts.map +1 -1
- package/dist/classic/buffer-with-accessor.js +25 -30
- package/dist/classic/buffer-with-accessor.js.map +1 -1
- package/dist/classic/copy-and-blit.d.ts.map +1 -1
- package/dist/classic/copy-and-blit.js +2 -2
- package/dist/classic/copy-and-blit.js.map +1 -1
- package/dist/context/debug/webgl-developer-tools.d.ts.map +1 -1
- package/dist/context/debug/webgl-developer-tools.js +2 -1
- package/dist/context/debug/webgl-developer-tools.js.map +1 -1
- package/dist/context/parameters/webgl-parameter-tables.js +2 -2
- package/dist/context/parameters/webgl-parameter-tables.js.map +1 -1
- package/dist/dist.dev.js +350 -99
- package/dist/index.cjs +343 -157
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist.min.js +22 -22
- package/package.json +5 -5
- package/src/adapter/helpers/get-shader-layout.ts +93 -33
- package/src/adapter/helpers/get-vertex-buffer-layout.ts +123 -0
- package/src/adapter/objects/webgl-vertex-array-object.ts +192 -28
- package/src/adapter/resources/webgl-buffer.ts +40 -41
- package/src/adapter/resources/webgl-external-texture.ts +1 -1
- package/src/adapter/resources/webgl-render-pipeline.ts +73 -36
- package/src/adapter/webgl-device.ts +4 -2
- package/src/classic/buffer-with-accessor.ts +42 -43
- package/src/classic/copy-and-blit.ts +2 -3
- package/src/context/debug/webgl-developer-tools.ts +8 -2
- package/src/context/parameters/webgl-parameter-tables.ts +2 -2
- 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.
|
|
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.
|
|
48
|
-
"@luma.gl/constants": "9.0.0-alpha.
|
|
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.
|
|
52
|
+
"@luma.gl/test-utils": "9.0.0-alpha.29"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
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
|
-
|
|
5
|
+
AttributeLayout,
|
|
6
6
|
UniformBinding,
|
|
7
7
|
UniformBlockBinding,
|
|
8
|
-
ProgramBindings,
|
|
9
8
|
AttributeBinding,
|
|
10
9
|
VaryingBinding,
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
):
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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 =
|
|
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 {
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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.
|
|
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):
|
|
96
|
+
setBuffer(location: number, buffer: WEBGLBuffer, accessor: any): void {
|
|
59
97
|
// Check target
|
|
60
|
-
if (buffer.
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
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
|
-
|
|
97
|
-
//
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
}
|