@luma.gl/core 9.3.0-alpha.9 → 9.3.0
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/device.d.ts +1 -1
- package/dist/adapter/device.d.ts.map +1 -1
- package/dist/adapter/device.js +3 -2
- package/dist/adapter/device.js.map +1 -1
- package/dist/adapter/luma.d.ts.map +1 -1
- package/dist/adapter/luma.js +2 -1
- package/dist/adapter/luma.js.map +1 -1
- package/dist/dist.dev.js +328 -173
- package/dist/dist.min.js +5 -5
- package/dist/factories/bind-group-factory.d.ts.map +1 -1
- package/dist/factories/bind-group-factory.js +14 -5
- package/dist/factories/bind-group-factory.js.map +1 -1
- package/dist/index.cjs +317 -173
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/portable/shader-block-writer.d.ts +51 -0
- package/dist/portable/shader-block-writer.d.ts.map +1 -0
- package/dist/portable/shader-block-writer.js +185 -0
- package/dist/portable/shader-block-writer.js.map +1 -0
- package/dist/portable/uniform-store.d.ts +52 -20
- package/dist/portable/uniform-store.d.ts.map +1 -1
- package/dist/portable/uniform-store.js +71 -26
- package/dist/portable/uniform-store.js.map +1 -1
- package/dist/shadertypes/data-types/data-type-decoder.js +2 -2
- package/dist/shadertypes/data-types/data-type-decoder.js.map +1 -1
- package/dist/shadertypes/data-types/decode-data-types.js +2 -2
- package/dist/shadertypes/data-types/decode-data-types.js.map +1 -1
- package/dist/shadertypes/shader-types/shader-block-layout.d.ts +72 -0
- package/dist/shadertypes/shader-types/shader-block-layout.d.ts.map +1 -0
- package/dist/shadertypes/shader-types/shader-block-layout.js +209 -0
- package/dist/shadertypes/shader-types/shader-block-layout.js.map +1 -0
- package/dist/shadertypes/texture-types/texture-format-decoder.js +1 -1
- package/dist/shadertypes/texture-types/texture-format-decoder.js.map +1 -1
- package/dist/shadertypes/texture-types/texture-format-table.js +2 -2
- package/dist/shadertypes/texture-types/texture-format-table.js.map +1 -1
- package/dist/shadertypes/vertex-types/vertex-format-decoder.d.ts.map +1 -1
- package/dist/shadertypes/vertex-types/vertex-format-decoder.js +41 -3
- package/dist/shadertypes/vertex-types/vertex-format-decoder.js.map +1 -1
- package/dist/shadertypes/vertex-types/vertex-formats.d.ts +6 -6
- package/dist/shadertypes/vertex-types/vertex-formats.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/adapter/device.ts +4 -2
- package/src/adapter/luma.ts +1 -0
- package/src/factories/bind-group-factory.ts +23 -5
- package/src/index.ts +7 -1
- package/src/portable/shader-block-writer.ts +254 -0
- package/src/portable/uniform-store.ts +92 -37
- package/src/shadertypes/data-types/data-type-decoder.ts +2 -2
- package/src/shadertypes/data-types/decode-data-types.ts +2 -2
- package/src/shadertypes/shader-types/shader-block-layout.ts +340 -0
- package/src/shadertypes/shader-types/shader-types.ts +5 -5
- package/src/shadertypes/texture-types/texture-format-decoder.ts +1 -1
- package/src/shadertypes/texture-types/texture-format-table.ts +2 -2
- package/src/shadertypes/vertex-types/vertex-format-decoder.ts +47 -3
- package/src/shadertypes/vertex-types/vertex-formats.ts +18 -5
- package/dist/portable/uniform-buffer-layout.d.ts +0 -42
- package/dist/portable/uniform-buffer-layout.d.ts.map +0 -1
- package/dist/portable/uniform-buffer-layout.js +0 -274
- package/dist/portable/uniform-buffer-layout.js.map +0 -1
- package/src/portable/uniform-buffer-layout.ts +0 -384
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luma.gl/core",
|
|
3
|
-
"version": "9.3.0
|
|
3
|
+
"version": "9.3.0",
|
|
4
4
|
"description": "The luma.gl core Device API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"@probe.gl/stats": "^4.1.1",
|
|
47
47
|
"@types/offscreencanvas": "^2019.7.3"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "c5e44faacd07b315a8622b5927e724f15a3d40ae"
|
|
50
50
|
}
|
package/src/adapter/device.ts
CHANGED
|
@@ -696,7 +696,8 @@ export abstract class Device {
|
|
|
696
696
|
debug(): void {
|
|
697
697
|
if (this.props.debug) {
|
|
698
698
|
// @ts-ignore
|
|
699
|
-
|
|
699
|
+
// biome-ignore lint/suspicious/noDebugger: explicit debug break when device debugging is enabled.
|
|
700
|
+
debugger;
|
|
700
701
|
} else {
|
|
701
702
|
// TODO(ibgreen): Does not appear to be printed in the console
|
|
702
703
|
const message = `\
|
|
@@ -806,7 +807,8 @@ or create a device with the 'debug: true' prop.`;
|
|
|
806
807
|
_bindGroupLayout: unknown,
|
|
807
808
|
_shaderLayout: ShaderLayout | ComputeShaderLayout,
|
|
808
809
|
_bindings: Bindings,
|
|
809
|
-
_group: number
|
|
810
|
+
_group: number,
|
|
811
|
+
_label?: string
|
|
810
812
|
): unknown {
|
|
811
813
|
throw new Error('_createBindGroupWebGPU() not implemented');
|
|
812
814
|
}
|
package/src/adapter/luma.ts
CHANGED
|
@@ -235,4 +235,5 @@ export class Luma {
|
|
|
235
235
|
* Register WebGPU and/or WebGL adapters (controls application bundle size)
|
|
236
236
|
* Run-time selection of the first available Device
|
|
237
237
|
*/
|
|
238
|
+
// biome-ignore lint/suspicious/noRedeclare: the exported singleton intentionally mirrors the global debug handle.
|
|
238
239
|
export const luma = new Luma();
|
|
@@ -48,13 +48,15 @@ export class BindGroupFactory {
|
|
|
48
48
|
for (const group of getBindGroupIndicesUpToMax(pipeline.shaderLayout.bindings)) {
|
|
49
49
|
const groupBindings = bindingsByGroup[group];
|
|
50
50
|
const bindGroupLayout = this._getBindGroupLayout(pipeline, group);
|
|
51
|
+
const bindGroupLabel = getBindGroupLabel(pipeline, pipeline.shaderLayout, group);
|
|
51
52
|
|
|
52
53
|
if (!groupBindings || Object.keys(groupBindings).length === 0) {
|
|
53
54
|
if (!hasBindingsInGroup(pipeline.shaderLayout.bindings, group)) {
|
|
54
55
|
resolvedBindGroups[group] = this._getEmptyBindGroup(
|
|
55
56
|
bindGroupLayout,
|
|
56
57
|
pipeline.shaderLayout,
|
|
57
|
-
group
|
|
58
|
+
group,
|
|
59
|
+
bindGroupLabel
|
|
58
60
|
);
|
|
59
61
|
}
|
|
60
62
|
continue;
|
|
@@ -72,7 +74,8 @@ export class BindGroupFactory {
|
|
|
72
74
|
bindGroupLayout,
|
|
73
75
|
pipeline.shaderLayout,
|
|
74
76
|
groupBindings,
|
|
75
|
-
group
|
|
77
|
+
group,
|
|
78
|
+
bindGroupLabel
|
|
76
79
|
);
|
|
77
80
|
layoutCache.bindGroupsBySource.set(bindGroupCacheKey, bindGroup);
|
|
78
81
|
resolvedBindGroups[group] = bindGroup;
|
|
@@ -81,7 +84,8 @@ export class BindGroupFactory {
|
|
|
81
84
|
bindGroupLayout,
|
|
82
85
|
pipeline.shaderLayout,
|
|
83
86
|
groupBindings,
|
|
84
|
-
group
|
|
87
|
+
group,
|
|
88
|
+
bindGroupLabel
|
|
85
89
|
);
|
|
86
90
|
}
|
|
87
91
|
}
|
|
@@ -103,11 +107,12 @@ export class BindGroupFactory {
|
|
|
103
107
|
private _getEmptyBindGroup(
|
|
104
108
|
bindGroupLayout: object,
|
|
105
109
|
shaderLayout: AnyShaderLayout,
|
|
106
|
-
group: number
|
|
110
|
+
group: number,
|
|
111
|
+
label: string
|
|
107
112
|
): unknown {
|
|
108
113
|
const layoutCache = this._getLayoutBindGroupCache(bindGroupLayout);
|
|
109
114
|
layoutCache.emptyBindGroup ||=
|
|
110
|
-
this.device._createBindGroupWebGPU(bindGroupLayout, shaderLayout, {}, group) || null;
|
|
115
|
+
this.device._createBindGroupWebGPU(bindGroupLayout, shaderLayout, {}, group, label) || null;
|
|
111
116
|
return layoutCache.emptyBindGroup;
|
|
112
117
|
}
|
|
113
118
|
|
|
@@ -137,3 +142,16 @@ function getBindGroupIndicesUpToMax(bindings: AnyShaderLayout['bindings']): numb
|
|
|
137
142
|
function hasBindingsInGroup(bindings: AnyShaderLayout['bindings'], group: number): boolean {
|
|
138
143
|
return bindings.some(binding => binding.group === group);
|
|
139
144
|
}
|
|
145
|
+
|
|
146
|
+
function getBindGroupLabel(
|
|
147
|
+
pipeline: AnyPipeline,
|
|
148
|
+
shaderLayout: AnyShaderLayout,
|
|
149
|
+
group: number
|
|
150
|
+
): string {
|
|
151
|
+
const bindingNames = shaderLayout.bindings
|
|
152
|
+
.filter(binding => binding.group === group)
|
|
153
|
+
.sort((left, right) => left.location - right.location)
|
|
154
|
+
.map(binding => binding.name);
|
|
155
|
+
const bindingSuffix = bindingNames.length > 0 ? bindingNames.join(',') : 'empty';
|
|
156
|
+
return `${pipeline.id}/group${group}[${bindingSuffix}]`;
|
|
157
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -84,7 +84,13 @@ export type {PipelineLayoutProps} from './adapter/resources/pipeline-layout';
|
|
|
84
84
|
export {PipelineLayout} from './adapter/resources/pipeline-layout';
|
|
85
85
|
|
|
86
86
|
// PORTABLE API - UNIFORM BUFFERS
|
|
87
|
-
export {
|
|
87
|
+
export {
|
|
88
|
+
makeShaderBlockLayout,
|
|
89
|
+
type ShaderBlockLayout,
|
|
90
|
+
type ShaderBlockLayoutEntry,
|
|
91
|
+
type ShaderBlockLayoutOptions
|
|
92
|
+
} from './shadertypes/shader-types/shader-block-layout';
|
|
93
|
+
export {ShaderBlockWriter} from './portable/shader-block-writer';
|
|
88
94
|
export {UniformBlock} from './portable/uniform-block';
|
|
89
95
|
export {UniformStore} from './portable/uniform-store';
|
|
90
96
|
// TEXTURE TYPES
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// luma.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type {CompositeUniformValue, UniformValue} from '../adapter/types/uniforms';
|
|
6
|
+
import {getScratchArrayBuffer} from '../utils/array-utils-flat';
|
|
7
|
+
import {isNumberArray} from '../utils/is-array';
|
|
8
|
+
import {log} from '../utils/log';
|
|
9
|
+
import type {
|
|
10
|
+
CompositeShaderType,
|
|
11
|
+
VariableShaderType
|
|
12
|
+
} from '../shadertypes/shader-types/shader-types';
|
|
13
|
+
import {
|
|
14
|
+
getLeafLayoutInfo,
|
|
15
|
+
isCompositeShaderTypeStruct,
|
|
16
|
+
type ShaderBlockLayout
|
|
17
|
+
} from '../shadertypes/shader-types/shader-block-layout';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Serializes nested JavaScript uniform values according to a {@link ShaderBlockLayout}.
|
|
21
|
+
*/
|
|
22
|
+
export class ShaderBlockWriter {
|
|
23
|
+
/** Layout metadata used to flatten and serialize values. */
|
|
24
|
+
readonly layout: ShaderBlockLayout;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a writer for a precomputed shader-block layout.
|
|
28
|
+
*/
|
|
29
|
+
constructor(layout: ShaderBlockLayout) {
|
|
30
|
+
this.layout = layout;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Returns `true` if the flattened layout contains the given field.
|
|
35
|
+
*/
|
|
36
|
+
has(name: string): boolean {
|
|
37
|
+
return Boolean(this.layout.fields[name]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Returns offset and size metadata for a flattened field.
|
|
42
|
+
*/
|
|
43
|
+
get(name: string): {offset: number; size: number} | undefined {
|
|
44
|
+
const entry = this.layout.fields[name];
|
|
45
|
+
return entry ? {offset: entry.offset, size: entry.size} : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Flattens nested composite values into leaf-path values understood by {@link UniformBlock}.
|
|
50
|
+
*
|
|
51
|
+
* Top-level values may be supplied either in nested object form matching the
|
|
52
|
+
* declared composite shader types or as already-flattened leaf-path values.
|
|
53
|
+
*/
|
|
54
|
+
getFlatUniformValues(
|
|
55
|
+
uniformValues: Readonly<Record<string, CompositeUniformValue>>
|
|
56
|
+
): Record<string, UniformValue> {
|
|
57
|
+
const flattenedUniformValues: Record<string, UniformValue> = {};
|
|
58
|
+
|
|
59
|
+
for (const [name, value] of Object.entries(uniformValues)) {
|
|
60
|
+
const uniformType = this.layout.uniformTypes[name];
|
|
61
|
+
if (uniformType) {
|
|
62
|
+
this._flattenCompositeValue(flattenedUniformValues, name, uniformType, value);
|
|
63
|
+
} else if (this.layout.fields[name]) {
|
|
64
|
+
flattenedUniformValues[name] = value as UniformValue;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return flattenedUniformValues;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Serializes the supplied values into buffer-backed binary data.
|
|
73
|
+
*
|
|
74
|
+
* The returned view length matches {@link ShaderBlockLayout.byteLength}, which
|
|
75
|
+
* is the exact packed size of the block.
|
|
76
|
+
*/
|
|
77
|
+
getData(uniformValues: Readonly<Record<string, CompositeUniformValue>>): Uint8Array {
|
|
78
|
+
const buffer = getScratchArrayBuffer(this.layout.byteLength);
|
|
79
|
+
new Uint8Array(buffer, 0, this.layout.byteLength).fill(0);
|
|
80
|
+
const typedArrays = {
|
|
81
|
+
i32: new Int32Array(buffer),
|
|
82
|
+
u32: new Uint32Array(buffer),
|
|
83
|
+
f32: new Float32Array(buffer),
|
|
84
|
+
f16: new Uint16Array(buffer)
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const flattenedUniformValues = this.getFlatUniformValues(uniformValues);
|
|
88
|
+
for (const [name, value] of Object.entries(flattenedUniformValues)) {
|
|
89
|
+
this._writeLeafValue(typedArrays, name, value);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return new Uint8Array(buffer, 0, this.layout.byteLength);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Recursively flattens nested values using the declared composite shader type.
|
|
97
|
+
*/
|
|
98
|
+
private _flattenCompositeValue(
|
|
99
|
+
flattenedUniformValues: Record<string, UniformValue>,
|
|
100
|
+
baseName: string,
|
|
101
|
+
uniformType: CompositeShaderType,
|
|
102
|
+
value: CompositeUniformValue | undefined
|
|
103
|
+
): void {
|
|
104
|
+
if (value === undefined) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (typeof uniformType === 'string' || this.layout.fields[baseName]) {
|
|
109
|
+
flattenedUniformValues[baseName] = value as UniformValue;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (Array.isArray(uniformType)) {
|
|
114
|
+
const elementType = uniformType[0] as CompositeShaderType;
|
|
115
|
+
const length = uniformType[1] as number;
|
|
116
|
+
|
|
117
|
+
if (Array.isArray(elementType)) {
|
|
118
|
+
throw new Error(`Nested arrays are not supported for ${baseName}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (typeof elementType === 'string' && isNumberArray(value)) {
|
|
122
|
+
this._flattenPackedArray(flattenedUniformValues, baseName, elementType, length, value);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!Array.isArray(value)) {
|
|
127
|
+
log.warn(`Unsupported uniform array value for ${baseName}:`, value)();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (let index = 0; index < Math.min(value.length, length); index++) {
|
|
132
|
+
const elementValue = value[index];
|
|
133
|
+
if (elementValue === undefined) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this._flattenCompositeValue(
|
|
138
|
+
flattenedUniformValues,
|
|
139
|
+
`${baseName}[${index}]`,
|
|
140
|
+
elementType,
|
|
141
|
+
elementValue
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (isCompositeShaderTypeStruct(uniformType) && isCompositeUniformObject(value)) {
|
|
148
|
+
for (const [key, subValue] of Object.entries(value)) {
|
|
149
|
+
if (subValue === undefined) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const nestedName = `${baseName}.${key}`;
|
|
154
|
+
this._flattenCompositeValue(flattenedUniformValues, nestedName, uniformType[key], subValue);
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
log.warn(`Unsupported uniform value for ${baseName}:`, value)();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Expands tightly packed numeric arrays into per-element leaf fields.
|
|
164
|
+
*/
|
|
165
|
+
private _flattenPackedArray(
|
|
166
|
+
flattenedUniformValues: Record<string, UniformValue>,
|
|
167
|
+
baseName: string,
|
|
168
|
+
elementType: VariableShaderType,
|
|
169
|
+
length: number,
|
|
170
|
+
value: UniformValue
|
|
171
|
+
): void {
|
|
172
|
+
const numericValue = value as Readonly<ArrayLike<number>>;
|
|
173
|
+
const elementLayout = getLeafLayoutInfo(elementType, this.layout.layout);
|
|
174
|
+
const packedElementLength = elementLayout.components;
|
|
175
|
+
|
|
176
|
+
for (let index = 0; index < length; index++) {
|
|
177
|
+
const start = index * packedElementLength;
|
|
178
|
+
if (start >= numericValue.length) {
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (packedElementLength === 1) {
|
|
183
|
+
flattenedUniformValues[`${baseName}[${index}]`] = Number(numericValue[start]);
|
|
184
|
+
} else {
|
|
185
|
+
flattenedUniformValues[`${baseName}[${index}]`] = sliceNumericArray(
|
|
186
|
+
value,
|
|
187
|
+
start,
|
|
188
|
+
start + packedElementLength
|
|
189
|
+
) as UniformValue;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Writes one flattened leaf value into its typed-array view.
|
|
196
|
+
*/
|
|
197
|
+
private _writeLeafValue(
|
|
198
|
+
typedArrays: Record<string, any>,
|
|
199
|
+
name: string,
|
|
200
|
+
value: UniformValue
|
|
201
|
+
): void {
|
|
202
|
+
const entry = this.layout.fields[name];
|
|
203
|
+
if (!entry) {
|
|
204
|
+
log.warn(`Uniform ${name} not found in layout`)();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const {type, components, columns, rows, offset, columnStride} = entry;
|
|
209
|
+
const array = typedArrays[type];
|
|
210
|
+
|
|
211
|
+
if (components === 1) {
|
|
212
|
+
array[offset] = Number(value);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const sourceValue = value as Readonly<ArrayLike<number>>;
|
|
217
|
+
|
|
218
|
+
if (columns === 1) {
|
|
219
|
+
for (let componentIndex = 0; componentIndex < components; componentIndex++) {
|
|
220
|
+
array[offset + componentIndex] = Number(sourceValue[componentIndex] ?? 0);
|
|
221
|
+
}
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let sourceIndex = 0;
|
|
226
|
+
for (let columnIndex = 0; columnIndex < columns; columnIndex++) {
|
|
227
|
+
const columnOffset = offset + columnIndex * columnStride;
|
|
228
|
+
for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
|
|
229
|
+
array[columnOffset + rowIndex] = Number(sourceValue[sourceIndex++] ?? 0);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Type guard for nested uniform objects.
|
|
237
|
+
*/
|
|
238
|
+
function isCompositeUniformObject(
|
|
239
|
+
value: CompositeUniformValue
|
|
240
|
+
): value is Record<string, CompositeUniformValue | undefined> {
|
|
241
|
+
return (
|
|
242
|
+
Boolean(value) &&
|
|
243
|
+
typeof value === 'object' &&
|
|
244
|
+
!Array.isArray(value) &&
|
|
245
|
+
!ArrayBuffer.isView(value)
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Slices a numeric array-like value without changing its numeric representation.
|
|
251
|
+
*/
|
|
252
|
+
function sliceNumericArray(value: UniformValue, start: number, end: number): number[] {
|
|
253
|
+
return Array.prototype.slice.call(value, start, end) as number[];
|
|
254
|
+
}
|
|
@@ -7,8 +7,39 @@ import type {CompositeUniformValue} from '../adapter/types/uniforms';
|
|
|
7
7
|
import type {Device} from '../adapter/device';
|
|
8
8
|
import {Buffer} from '../adapter/resources/buffer';
|
|
9
9
|
import {log} from '../utils/log';
|
|
10
|
+
import {
|
|
11
|
+
makeShaderBlockLayout,
|
|
12
|
+
type ShaderBlockLayout
|
|
13
|
+
} from '../shadertypes/shader-types/shader-block-layout';
|
|
10
14
|
import {UniformBlock} from './uniform-block';
|
|
11
|
-
import {
|
|
15
|
+
import {ShaderBlockWriter} from './shader-block-writer';
|
|
16
|
+
|
|
17
|
+
/** Definition of a single managed uniform block. */
|
|
18
|
+
export type UniformStoreBlockDefinition = {
|
|
19
|
+
/** Declared shader types for the block's uniforms. */
|
|
20
|
+
uniformTypes?: Record<string, CompositeShaderType>;
|
|
21
|
+
/** Reserved for future prop-level defaults. */
|
|
22
|
+
defaultProps?: Record<string, unknown>;
|
|
23
|
+
/** Initial uniform values written into the backing block. */
|
|
24
|
+
defaultUniforms?: Record<string, CompositeUniformValue>;
|
|
25
|
+
/** Explicit shader-block layout override. */
|
|
26
|
+
layout?: 'std140' | 'wgsl-uniform' | 'wgsl-storage';
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Uniform block definitions keyed by block name. */
|
|
30
|
+
export type UniformStoreBlocks<TPropGroups extends Record<string, Record<string, unknown>>> =
|
|
31
|
+
Record<keyof TPropGroups, UniformStoreBlockDefinition>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Smallest buffer size that can be used for uniform buffers.
|
|
35
|
+
*
|
|
36
|
+
* This is an allocation policy rather than part of {@link ShaderBlockLayout}.
|
|
37
|
+
* Layouts report the exact packed size, while the store applies any minimum
|
|
38
|
+
* buffer-size rule when allocating GPU buffers.
|
|
39
|
+
*
|
|
40
|
+
* TODO - does this depend on device?
|
|
41
|
+
*/
|
|
42
|
+
const minUniformBufferSize = 1024;
|
|
12
43
|
|
|
13
44
|
/**
|
|
14
45
|
* A uniform store holds a uniform values for one or more uniform blocks,
|
|
@@ -23,39 +54,37 @@ export class UniformStore<
|
|
|
23
54
|
Record<string, unknown>
|
|
24
55
|
>
|
|
25
56
|
> {
|
|
57
|
+
/** Device used to infer layout and allocate buffers. */
|
|
58
|
+
readonly device: Device;
|
|
26
59
|
/** Stores the uniform values for each uniform block */
|
|
27
60
|
uniformBlocks = new Map<keyof TPropGroups, UniformBlock>();
|
|
28
|
-
/**
|
|
29
|
-
|
|
61
|
+
/** Flattened layout metadata for each block. */
|
|
62
|
+
shaderBlockLayouts = new Map<keyof TPropGroups, ShaderBlockLayout>();
|
|
63
|
+
/** Serializers for block-backed uniform data. */
|
|
64
|
+
shaderBlockWriters = new Map<keyof TPropGroups, ShaderBlockWriter>();
|
|
30
65
|
/** Actual buffer for the blocks */
|
|
31
66
|
uniformBuffers = new Map<keyof TPropGroups, Buffer>();
|
|
32
67
|
|
|
33
68
|
/**
|
|
34
|
-
*
|
|
35
|
-
* @param blocks
|
|
69
|
+
* Creates a new {@link UniformStore} for the supplied device and block definitions.
|
|
36
70
|
*/
|
|
37
|
-
constructor(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{
|
|
41
|
-
uniformTypes?: Record<string, CompositeShaderType>;
|
|
42
|
-
defaultProps?: Record<string, unknown>;
|
|
43
|
-
defaultUniforms?: Record<string, CompositeUniformValue>;
|
|
44
|
-
}
|
|
45
|
-
>
|
|
46
|
-
) {
|
|
71
|
+
constructor(device: Device, blocks: UniformStoreBlocks<TPropGroups>) {
|
|
72
|
+
this.device = device;
|
|
73
|
+
|
|
47
74
|
for (const [bufferName, block] of Object.entries(blocks)) {
|
|
48
75
|
const uniformBufferName = bufferName as keyof TPropGroups;
|
|
49
76
|
|
|
50
77
|
// Create a layout object to help us generate correctly formatted binary uniform buffers
|
|
51
|
-
const
|
|
52
|
-
|
|
78
|
+
const shaderBlockLayout = makeShaderBlockLayout(block.uniformTypes ?? {}, {
|
|
79
|
+
layout: block.layout ?? getDefaultUniformBufferLayout(device)
|
|
80
|
+
});
|
|
81
|
+
const shaderBlockWriter = new ShaderBlockWriter(shaderBlockLayout);
|
|
82
|
+
this.shaderBlockLayouts.set(uniformBufferName, shaderBlockLayout);
|
|
83
|
+
this.shaderBlockWriters.set(uniformBufferName, shaderBlockWriter);
|
|
53
84
|
|
|
54
85
|
// Create a Uniform block to store the uniforms for each buffer.
|
|
55
86
|
const uniformBlock = new UniformBlock({name: bufferName});
|
|
56
|
-
uniformBlock.setUniforms(
|
|
57
|
-
uniformBufferLayout.getFlatUniformValues(block.defaultUniforms || {})
|
|
58
|
-
);
|
|
87
|
+
uniformBlock.setUniforms(shaderBlockWriter.getFlatUniformValues(block.defaultUniforms || {}));
|
|
59
88
|
this.uniformBlocks.set(uniformBufferName, uniformBlock);
|
|
60
89
|
}
|
|
61
90
|
}
|
|
@@ -69,15 +98,17 @@ export class UniformStore<
|
|
|
69
98
|
|
|
70
99
|
/**
|
|
71
100
|
* Set uniforms
|
|
72
|
-
*
|
|
101
|
+
*
|
|
102
|
+
* Makes all group properties partial and eagerly propagates changes to any
|
|
103
|
+
* managed GPU buffers.
|
|
73
104
|
*/
|
|
74
105
|
setUniforms(
|
|
75
106
|
uniforms: Partial<{[group in keyof TPropGroups]: Partial<TPropGroups[group]>}>
|
|
76
107
|
): void {
|
|
77
108
|
for (const [blockName, uniformValues] of Object.entries(uniforms)) {
|
|
78
109
|
const uniformBufferName = blockName as keyof TPropGroups;
|
|
79
|
-
const
|
|
80
|
-
const flattenedUniforms =
|
|
110
|
+
const shaderBlockWriter = this.shaderBlockWriters.get(uniformBufferName);
|
|
111
|
+
const flattenedUniforms = shaderBlockWriter?.getFlatUniformValues(
|
|
81
112
|
(uniformValues || {}) as Record<string, CompositeUniformValue>
|
|
82
113
|
);
|
|
83
114
|
this.uniformBlocks.get(uniformBufferName)?.setUniforms(flattenedUniforms || {});
|
|
@@ -88,24 +119,33 @@ export class UniformStore<
|
|
|
88
119
|
this.updateUniformBuffers();
|
|
89
120
|
}
|
|
90
121
|
|
|
91
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Returns the allocation size for the named uniform buffer.
|
|
124
|
+
*
|
|
125
|
+
* This may exceed the packed layout size because minimum buffer-size policy is
|
|
126
|
+
* applied at the store layer.
|
|
127
|
+
*/
|
|
92
128
|
getUniformBufferByteLength(uniformBufferName: keyof TPropGroups): number {
|
|
93
|
-
|
|
129
|
+
const packedByteLength = this.shaderBlockLayouts.get(uniformBufferName)?.byteLength || 0;
|
|
130
|
+
return Math.max(packedByteLength, minUniformBufferSize);
|
|
94
131
|
}
|
|
95
132
|
|
|
96
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* Returns packed binary data that can be uploaded to the named uniform buffer.
|
|
135
|
+
*
|
|
136
|
+
* The returned view length matches the packed block size and is not padded to
|
|
137
|
+
* the store's minimum allocation size.
|
|
138
|
+
*/
|
|
97
139
|
getUniformBufferData(uniformBufferName: keyof TPropGroups): Uint8Array {
|
|
98
140
|
const uniformValues = this.uniformBlocks.get(uniformBufferName)?.getAllUniforms() || {};
|
|
99
|
-
|
|
100
|
-
return
|
|
141
|
+
const shaderBlockWriter = this.shaderBlockWriters.get(uniformBufferName);
|
|
142
|
+
return shaderBlockWriter?.getData(uniformValues) || new Uint8Array(0);
|
|
101
143
|
}
|
|
102
144
|
|
|
103
145
|
/**
|
|
104
|
-
* Creates an unmanaged uniform buffer
|
|
105
|
-
* The new buffer is initialized with current / supplied values
|
|
146
|
+
* Creates an unmanaged uniform buffer initialized with the current or supplied values.
|
|
106
147
|
*/
|
|
107
148
|
createUniformBuffer(
|
|
108
|
-
device: Device,
|
|
109
149
|
uniformBufferName: keyof TPropGroups,
|
|
110
150
|
uniforms?: Partial<{[group in keyof TPropGroups]: Partial<TPropGroups[group]>}>
|
|
111
151
|
): Buffer {
|
|
@@ -113,7 +153,7 @@ export class UniformStore<
|
|
|
113
153
|
this.setUniforms(uniforms);
|
|
114
154
|
}
|
|
115
155
|
const byteLength = this.getUniformBufferByteLength(uniformBufferName);
|
|
116
|
-
const uniformBuffer = device.createBuffer({
|
|
156
|
+
const uniformBuffer = this.device.createBuffer({
|
|
117
157
|
usage: Buffer.UNIFORM | Buffer.COPY_DST,
|
|
118
158
|
byteLength
|
|
119
159
|
});
|
|
@@ -123,11 +163,11 @@ export class UniformStore<
|
|
|
123
163
|
return uniformBuffer;
|
|
124
164
|
}
|
|
125
165
|
|
|
126
|
-
/**
|
|
127
|
-
getManagedUniformBuffer(
|
|
166
|
+
/** Returns the managed uniform buffer for the named block. */
|
|
167
|
+
getManagedUniformBuffer(uniformBufferName: keyof TPropGroups): Buffer {
|
|
128
168
|
if (!this.uniformBuffers.get(uniformBufferName)) {
|
|
129
169
|
const byteLength = this.getUniformBufferByteLength(uniformBufferName);
|
|
130
|
-
const uniformBuffer = device.createBuffer({
|
|
170
|
+
const uniformBuffer = this.device.createBuffer({
|
|
131
171
|
usage: Buffer.UNIFORM | Buffer.COPY_DST,
|
|
132
172
|
byteLength
|
|
133
173
|
});
|
|
@@ -138,7 +178,11 @@ export class UniformStore<
|
|
|
138
178
|
return this.uniformBuffers.get(uniformBufferName);
|
|
139
179
|
}
|
|
140
180
|
|
|
141
|
-
/**
|
|
181
|
+
/**
|
|
182
|
+
* Updates every managed uniform buffer whose source uniforms have changed.
|
|
183
|
+
*
|
|
184
|
+
* @returns The first redraw reason encountered, or `false` if nothing changed.
|
|
185
|
+
*/
|
|
142
186
|
updateUniformBuffers(): false | string {
|
|
143
187
|
let reason: false | string = false;
|
|
144
188
|
for (const uniformBufferName of this.uniformBlocks.keys()) {
|
|
@@ -151,7 +195,11 @@ export class UniformStore<
|
|
|
151
195
|
return reason;
|
|
152
196
|
}
|
|
153
197
|
|
|
154
|
-
/**
|
|
198
|
+
/**
|
|
199
|
+
* Updates one managed uniform buffer if its corresponding block is dirty.
|
|
200
|
+
*
|
|
201
|
+
* @returns The redraw reason for the update, or `false` if no write occurred.
|
|
202
|
+
*/
|
|
155
203
|
updateUniformBuffer(uniformBufferName: keyof TPropGroups): false | string {
|
|
156
204
|
const uniformBlock = this.uniformBlocks.get(uniformBufferName);
|
|
157
205
|
let uniformBuffer = this.uniformBuffers.get(uniformBufferName);
|
|
@@ -177,3 +225,10 @@ export class UniformStore<
|
|
|
177
225
|
return reason;
|
|
178
226
|
}
|
|
179
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Returns the default uniform-buffer layout for the supplied device.
|
|
231
|
+
*/
|
|
232
|
+
function getDefaultUniformBufferLayout(device: Device): 'std140' | 'wgsl-uniform' | 'wgsl-storage' {
|
|
233
|
+
return device.type === 'webgpu' ? 'wgsl-uniform' : 'std140';
|
|
234
|
+
}
|
|
@@ -35,7 +35,7 @@ export class DataTypeDecoder {
|
|
|
35
35
|
/** Build a vertex format from a signed data type and a component */
|
|
36
36
|
getNormalizedDataType(signedDataType: SignedDataType): NormalizedDataType {
|
|
37
37
|
const dataType: NormalizedDataType = signedDataType;
|
|
38
|
-
//
|
|
38
|
+
// biome-ignore format: preserve layout
|
|
39
39
|
switch (dataType) {
|
|
40
40
|
case 'uint8': return 'unorm8';
|
|
41
41
|
case 'sint8': return 'snorm8';
|
|
@@ -47,7 +47,7 @@ export class DataTypeDecoder {
|
|
|
47
47
|
|
|
48
48
|
/** Align offset to 1, 2 or 4 elements (4, 8 or 16 bytes) */
|
|
49
49
|
alignTo(size: number, count: number): number {
|
|
50
|
-
//
|
|
50
|
+
// biome-ignore format: preserve layout
|
|
51
51
|
switch (count) {
|
|
52
52
|
case 1: return size; // Pad upwards to even multiple of 2
|
|
53
53
|
case 2: return size + (size % 2); // Pad upwards to even multiple of 2
|
|
@@ -28,7 +28,7 @@ export function getDataTypeInfo(type: NormalizedDataType): DataTypeInfo {
|
|
|
28
28
|
/** Build a vertex format from a signed data type and a component */
|
|
29
29
|
export function getNormalizedDataType(signedDataType: SignedDataType): NormalizedDataType {
|
|
30
30
|
const dataType: NormalizedDataType = signedDataType;
|
|
31
|
-
//
|
|
31
|
+
// biome-ignore format: preserve layout
|
|
32
32
|
switch (dataType) {
|
|
33
33
|
case 'uint8': return 'unorm8';
|
|
34
34
|
case 'sint8': return 'snorm8';
|
|
@@ -40,7 +40,7 @@ export function getNormalizedDataType(signedDataType: SignedDataType): Normalize
|
|
|
40
40
|
|
|
41
41
|
/** Align offset to 1, 2 or 4 elements (4, 8 or 16 bytes) */
|
|
42
42
|
export function alignTo(size: number, count: number): number {
|
|
43
|
-
//
|
|
43
|
+
// biome-ignore format: preserve layout
|
|
44
44
|
switch (count) {
|
|
45
45
|
case 1: return size; // Pad upwards to even multiple of 2
|
|
46
46
|
case 2: return size + (size % 2); // Pad upwards to even multiple of 2
|