@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.
Files changed (63) hide show
  1. package/dist/adapter/device.d.ts +1 -1
  2. package/dist/adapter/device.d.ts.map +1 -1
  3. package/dist/adapter/device.js +3 -2
  4. package/dist/adapter/device.js.map +1 -1
  5. package/dist/adapter/luma.d.ts.map +1 -1
  6. package/dist/adapter/luma.js +2 -1
  7. package/dist/adapter/luma.js.map +1 -1
  8. package/dist/dist.dev.js +328 -173
  9. package/dist/dist.min.js +5 -5
  10. package/dist/factories/bind-group-factory.d.ts.map +1 -1
  11. package/dist/factories/bind-group-factory.js +14 -5
  12. package/dist/factories/bind-group-factory.js.map +1 -1
  13. package/dist/index.cjs +317 -173
  14. package/dist/index.cjs.map +4 -4
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +2 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/portable/shader-block-writer.d.ts +51 -0
  20. package/dist/portable/shader-block-writer.d.ts.map +1 -0
  21. package/dist/portable/shader-block-writer.js +185 -0
  22. package/dist/portable/shader-block-writer.js.map +1 -0
  23. package/dist/portable/uniform-store.d.ts +52 -20
  24. package/dist/portable/uniform-store.d.ts.map +1 -1
  25. package/dist/portable/uniform-store.js +71 -26
  26. package/dist/portable/uniform-store.js.map +1 -1
  27. package/dist/shadertypes/data-types/data-type-decoder.js +2 -2
  28. package/dist/shadertypes/data-types/data-type-decoder.js.map +1 -1
  29. package/dist/shadertypes/data-types/decode-data-types.js +2 -2
  30. package/dist/shadertypes/data-types/decode-data-types.js.map +1 -1
  31. package/dist/shadertypes/shader-types/shader-block-layout.d.ts +72 -0
  32. package/dist/shadertypes/shader-types/shader-block-layout.d.ts.map +1 -0
  33. package/dist/shadertypes/shader-types/shader-block-layout.js +209 -0
  34. package/dist/shadertypes/shader-types/shader-block-layout.js.map +1 -0
  35. package/dist/shadertypes/texture-types/texture-format-decoder.js +1 -1
  36. package/dist/shadertypes/texture-types/texture-format-decoder.js.map +1 -1
  37. package/dist/shadertypes/texture-types/texture-format-table.js +2 -2
  38. package/dist/shadertypes/texture-types/texture-format-table.js.map +1 -1
  39. package/dist/shadertypes/vertex-types/vertex-format-decoder.d.ts.map +1 -1
  40. package/dist/shadertypes/vertex-types/vertex-format-decoder.js +41 -3
  41. package/dist/shadertypes/vertex-types/vertex-format-decoder.js.map +1 -1
  42. package/dist/shadertypes/vertex-types/vertex-formats.d.ts +6 -6
  43. package/dist/shadertypes/vertex-types/vertex-formats.d.ts.map +1 -1
  44. package/package.json +2 -2
  45. package/src/adapter/device.ts +4 -2
  46. package/src/adapter/luma.ts +1 -0
  47. package/src/factories/bind-group-factory.ts +23 -5
  48. package/src/index.ts +7 -1
  49. package/src/portable/shader-block-writer.ts +254 -0
  50. package/src/portable/uniform-store.ts +92 -37
  51. package/src/shadertypes/data-types/data-type-decoder.ts +2 -2
  52. package/src/shadertypes/data-types/decode-data-types.ts +2 -2
  53. package/src/shadertypes/shader-types/shader-block-layout.ts +340 -0
  54. package/src/shadertypes/shader-types/shader-types.ts +5 -5
  55. package/src/shadertypes/texture-types/texture-format-decoder.ts +1 -1
  56. package/src/shadertypes/texture-types/texture-format-table.ts +2 -2
  57. package/src/shadertypes/vertex-types/vertex-format-decoder.ts +47 -3
  58. package/src/shadertypes/vertex-types/vertex-formats.ts +18 -5
  59. package/dist/portable/uniform-buffer-layout.d.ts +0 -42
  60. package/dist/portable/uniform-buffer-layout.d.ts.map +0 -1
  61. package/dist/portable/uniform-buffer-layout.js +0 -274
  62. package/dist/portable/uniform-buffer-layout.js.map +0 -1
  63. 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-alpha.9",
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": "737b0f752b3f8a6ae920b394d5ca028427275b37"
49
+ "gitHead": "c5e44faacd07b315a8622b5927e724f15a3d40ae"
50
50
  }
@@ -696,7 +696,8 @@ export abstract class Device {
696
696
  debug(): void {
697
697
  if (this.props.debug) {
698
698
  // @ts-ignore
699
- debugger; // eslint-disable-line
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
  }
@@ -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 {UniformBufferLayout} from './portable/uniform-buffer-layout';
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 {UniformBufferLayout} from './uniform-buffer-layout';
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
- /** Can generate data for a uniform buffer for each block from data */
29
- uniformBufferLayouts = new Map<keyof TPropGroups, UniformBufferLayout>();
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
- * Create a new UniformStore instance
35
- * @param blocks
69
+ * Creates a new {@link UniformStore} for the supplied device and block definitions.
36
70
  */
37
- constructor(
38
- blocks: Record<
39
- keyof TPropGroups,
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 uniformBufferLayout = new UniformBufferLayout(block.uniformTypes ?? {});
52
- this.uniformBufferLayouts.set(uniformBufferName, uniformBufferLayout);
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
- * Makes all properties partial
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 uniformBufferLayout = this.uniformBufferLayouts.get(uniformBufferName);
80
- const flattenedUniforms = uniformBufferLayout?.getFlatUniformValues(
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
- /** Get the required minimum length of the uniform buffer */
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
- return this.uniformBufferLayouts.get(uniformBufferName)?.byteLength || 0;
129
+ const packedByteLength = this.shaderBlockLayouts.get(uniformBufferName)?.byteLength || 0;
130
+ return Math.max(packedByteLength, minUniformBufferSize);
94
131
  }
95
132
 
96
- /** Get formatted binary memory that can be uploaded to a buffer */
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
- // @ts-ignore
100
- return this.uniformBufferLayouts.get(uniformBufferName)?.getData(uniformValues);
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 (umnanaged means that application is responsible for destroying it)
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
- /** Get the managed uniform buffer. "managed" resources are destroyed when the uniformStore is destroyed. */
127
- getManagedUniformBuffer(device: Device, uniformBufferName: keyof TPropGroups): Buffer {
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
- /** Updates all uniform buffers where values have changed */
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
- /** Update one uniform buffer. Only updates if values have changed */
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
- // prettier-ignore
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
- // prettier-ignore
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
- // prettier-ignore
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
- // prettier-ignore
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