From 181f837c6bb77dcdc76e540d0e52948c4c36fea9 Mon Sep 17 00:00:00 2001 From: Gregg Tavares Date: Fri, 13 Oct 2023 17:50:31 -0700 Subject: [PATCH] use new wgsl_reflect --- package-lock.json | 10 +- package.json | 4 +- src/buffer-views.ts | 177 +++++++++++++++----- src/data-definitions.ts | 255 +++++++++++++++++++---------- test/tests/buffer-views-test.js | 105 ++++++++---- test/tests/data-definition-test.js | 115 ++++++++++++- 6 files changed, 488 insertions(+), 178 deletions(-) diff --git a/package-lock.json b/package-lock.json index a33393f..c9a2a53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "tslib": "^2.6.1", "typedoc": "^0.24.8", "typescript": "^5.1.6", - "wgsl_reflect": "github:brendan-duncan/wgsl_reflect#083d726a36fa495626566b37bfebefb06ad41574" + "wgsl_reflect": "github:brendan-duncan/wgsl_reflect#ffdb63c" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -4120,8 +4120,7 @@ }, "node_modules/wgsl_reflect": { "version": "0.1.1", - "resolved": "git+ssh://git@github.com/brendan-duncan/wgsl_reflect.git#083d726a36fa495626566b37bfebefb06ad41574", - "integrity": "sha512-qMZHrZnYfFhizU4raSJSFT5XqfXNIdiaGyrorg8aCj5e4yad4q2a7LcCiPbmtKlZJmAXxadFwpIFuCx5zKe/sw==", + "resolved": "git+ssh://git@github.com/brendan-duncan/wgsl_reflect.git#ffdb63c775db34c3f5e9d51fd4d5c3c0beadd784", "dev": true, "license": "MIT" }, @@ -7292,10 +7291,9 @@ "dev": true }, "wgsl_reflect": { - "version": "git+ssh://git@github.com/brendan-duncan/wgsl_reflect.git#083d726a36fa495626566b37bfebefb06ad41574", - "integrity": "sha512-qMZHrZnYfFhizU4raSJSFT5XqfXNIdiaGyrorg8aCj5e4yad4q2a7LcCiPbmtKlZJmAXxadFwpIFuCx5zKe/sw==", + "version": "git+ssh://git@github.com/brendan-duncan/wgsl_reflect.git#ffdb63c775db34c3f5e9d51fd4d5c3c0beadd784", "dev": true, - "from": "wgsl_reflect@github:brendan-duncan/wgsl_reflect#083d726a36fa495626566b37bfebefb06ad41574" + "from": "wgsl_reflect@github:brendan-duncan/wgsl_reflect#ffdb63c" }, "whatwg-url": { "version": "5.0.0", diff --git a/package.json b/package.json index 1e587d9..b999509 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "makeindex": "node build/tools/makeindex.js", "lint": "eslint \"src/**/*.{js,ts,tsx}\"", "pre-push": "npm run lint && npm run build && npm run test", - "rollup-watch": "rollup -c -w", + "watch": "rollup -c -w", "start": "rollup -c rollup.config.js -w", "test": "node test/puppeteer.js" }, @@ -63,6 +63,6 @@ "tslib": "^2.6.1", "typedoc": "^0.24.8", "typescript": "^5.1.6", - "wgsl_reflect": "github:brendan-duncan/wgsl_reflect#083d726a36fa495626566b37bfebefb06ad41574" + "wgsl_reflect": "github:brendan-duncan/wgsl_reflect#ffdb63c" } } diff --git a/src/buffer-views.ts b/src/buffer-views.ts index da5a5cb..bb81559 100644 --- a/src/buffer-views.ts +++ b/src/buffer-views.ts @@ -1,13 +1,16 @@ import { - FieldDefinition, IntrinsicDefinition, StructDefinition, + ArrayDefinition, + TypeDefinition, + VariableDefinition, } from './data-definitions.js'; import { isTypedArray, TypedArrayConstructor, TypedArray, } from './typed-arrays.js'; +import { roundUpToMultipleOf } from './utils.js'; type TypeDef = { numElements: number; @@ -104,51 +107,106 @@ export type ArrayBufferViews = { } // This needs to be fixed! 😱 -function getSizeOfStructDef(fieldDef: FieldDefinition): number { - if (Array.isArray(fieldDef)) { - return fieldDef.length * getSizeOfStructDef(fieldDef[0]); +function getSizeOfTypeDef(typeDef: TypeDefinition): number { + const asArrayDef = typeDef as ArrayDefinition; + const elementType = asArrayDef.elementType; + if (elementType) { + if (isIntrinsic(elementType)) { + const asIntrinsicDef = elementType as IntrinsicDefinition; + const { align } = typeInfo[asIntrinsicDef.type]; + return roundUpToMultipleOf(typeDef.size, align) * asArrayDef.numElements; + } else { + return asArrayDef.numElements * getSizeOfTypeDef(elementType); + } } else { - return fieldDef.size; + const asStructDef = typeDef as StructDefinition; + const numElements = asArrayDef.numElements || 1; + if (asStructDef.fields) { + return typeDef.size * numElements; + } else { + const asIntrinsicDef = typeDef as IntrinsicDefinition; + const { align } = typeInfo[asIntrinsicDef.type]; + return numElements > 1 + ? roundUpToMultipleOf(typeDef.size, align) * numElements + : typeDef.size; + } } } +function range(count: number, fn: (i: number) => T) { + return new Array(count).fill(0).map((_, i) => fn(i)); +} + +// If numElements is undefined this is NOT an array. If it is defined then it IS an array +// Sizes for arrays are different than sizes for non-arrays. Example +// a vec3f non array is Float32Array(3) +// a vec3f array of 2 is Float32Array(4 * 2) +// a vec3f array of 1 is Float32Array(4 * 1) +function makeIntrinsicTypedArrayView(typeDef: TypeDefinition, buffer: ArrayBuffer, baseOffset: number, numElements?: number): TypedArray { + const { size, type } = typeDef as IntrinsicDefinition; + try { + const { View, align } = typeInfo[type]; + const isArray = numElements !== undefined; + const sizeInBytes = isArray + ? roundUpToMultipleOf(size, align) + : size; + const baseNumElements = sizeInBytes / View.BYTES_PER_ELEMENT; + + return new View(buffer, baseOffset, baseNumElements * (numElements || 1)); + } catch { + throw new Error(`unknown type: ${type}`); + } + +} + +function isIntrinsic(typeDef: TypeDefinition) { + return !(typeDef as StructDefinition).fields && + !(typeDef as ArrayDefinition).elementType; +} + /** * Creates a set of named TypedArray views on an ArrayBuffer - * @param structDef Definition of the various types of views. + * @param typeDef Definition of the various types of views. * @param arrayBuffer Optional ArrayBuffer to use (if one provided one will be created) * @param offset Optional offset in existing ArrayBuffer to start the views. * @returns A bunch of named TypedArray views and the ArrayBuffer */ -export function makeTypedArrayViews(structDef: StructDefinition, arrayBuffer?: ArrayBuffer, offset?: number): ArrayBufferViews { +export function makeTypedArrayViews(typeDef: TypeDefinition, arrayBuffer?: ArrayBuffer, offset?: number): ArrayBufferViews { const baseOffset = offset || 0; - const buffer = arrayBuffer || new ArrayBuffer(getSizeOfStructDef(structDef)); + const buffer = arrayBuffer || new ArrayBuffer(getSizeOfTypeDef(typeDef)); - const makeViews = (structDef: FieldDefinition): TypedArrayOrViews => { - if (Array.isArray(structDef)) { - return (structDef as StructDefinition[]).map(elemDef => makeViews(elemDef)) as Views[]; - } else if (typeof structDef === 'string') { + const makeViews = (typeDef: TypeDefinition, baseOffset: number): TypedArrayOrViews => { + const asArrayDef = typeDef as ArrayDefinition; + const elementType = asArrayDef.elementType; + if (elementType) { + // TODO: Should be optional? Per Type? Depth set? Per field? + // The issue is, if we have `array` we don't likely + // want 1000 `Float32Array(4)` views. We want 1 `Float32Array(1000 * 4)` view. + // On the other hand, if we have `array` the maybe we do want + // 10 `Float32Array(16)` views since you might want to do + // `mat4.perspective(fov, aspect, near, far, foo.bar.arrayOf10Mat4s[3])`; + if (isIntrinsic(elementType)) { + return makeIntrinsicTypedArrayView(elementType, buffer, baseOffset, asArrayDef.numElements); + } else { + const elementSize = getSizeOfTypeDef(elementType); + return range(asArrayDef.numElements, i => makeViews(elementType, baseOffset + elementSize * i)) as Views[]; + } + } else if (typeof typeDef === 'string') { throw Error('unreachable'); } else { - const fields = (structDef as StructDefinition).fields; + const fields = (typeDef as StructDefinition).fields; if (fields) { const views: Views = {}; - for (const [name, def] of Object.entries(fields)) { - views[name] = makeViews(def as StructDefinition); + for (const [name, {type, offset}] of Object.entries(fields)) { + views[name] = makeViews(type, baseOffset + offset); } return views; } else { - const { size, offset, type } = structDef as IntrinsicDefinition; - try { - const { View } = typeInfo[type]; - const numElements = size / View.BYTES_PER_ELEMENT; - return new View(buffer, baseOffset + offset, numElements); - } catch { - throw new Error(`unknown type: ${type}`); - } + return makeIntrinsicTypedArrayView(typeDef, buffer, baseOffset); } } }; - return { views: makeViews(structDef), arrayBuffer: buffer }; + return { views: makeViews(typeDef, baseOffset), arrayBuffer: buffer }; } /** @@ -239,15 +297,17 @@ export type StructuredView = ArrayBufferViews & { } /** - * Given a StructDefinition, create matching TypedArray views - * @param structDef A StructDefinition as returned from {@link makeShaderDataDefinitions} + * Given a VariableDefinition, create matching TypedArray views + * @param varDef A VariableDefinition as returned from {@link makeShaderDataDefinitions} * @param arrayBuffer Optional ArrayBuffer for the views * @param offset Optional offset into the ArrayBuffer for the views * @returns TypedArray views for the various named fields of the structure as well * as a `set` function to make them easy to set, and the arrayBuffer */ -export function makeStructuredView(structDef: StructDefinition, arrayBuffer?: ArrayBuffer, offset = 0): StructuredView { - const views = makeTypedArrayViews(structDef, arrayBuffer, offset); +export function makeStructuredView(varDef: VariableDefinition | StructDefinition, arrayBuffer?: ArrayBuffer, offset = 0): StructuredView { + const asVarDef = varDef as VariableDefinition; + const typeDef = asVarDef.group === undefined ? varDef as StructDefinition : asVarDef.typeDefinition; + const views = makeTypedArrayViews(typeDef, arrayBuffer, offset); return { ...views, set(data: any) { @@ -278,30 +338,57 @@ function getView(arrayBuffer: ArrayBuffer, Ctor: TypedArra return view as T; } -export function setStructuredValues(fieldDef: FieldDefinition, data: any, arrayBuffer: ArrayBuffer, offset = 0) { - const asIntrinsicDefinition = fieldDef as IntrinsicDefinition; - if (asIntrinsicDefinition.type) { - const type = typeInfo[asIntrinsicDefinition.type]; - const view = getView(arrayBuffer, type.View); - const index = (offset + asIntrinsicDefinition.offset) / view.BYTES_PER_ELEMENT; - if (typeof data === 'number') { - view[index] = data; - } else { - view.set(data, index); +// Is this something like [1,2,3]? +function isArrayLikeOfNumber(data: any) { + return isTypedArray(data) || Array.isArray(data) && typeof data[0] === 'number'; +} + +function setIntrinsicFromArrayLikeOfNumber(typeDef: IntrinsicDefinition, data: any, arrayBuffer: ArrayBuffer, offset: number) { + const asIntrinsicDefinition = typeDef as IntrinsicDefinition; + const type = typeInfo[asIntrinsicDefinition.type]; + const view = getView(arrayBuffer, type.View); + const index = offset / view.BYTES_PER_ELEMENT; + if (typeof data === 'number') { + view[index] = data; + } else { + view.set(data, index); + } +} + +export function setTypedValues(typeDef: TypeDefinition, data: any, arrayBuffer: ArrayBuffer, offset = 0) { + const asArrayDef = typeDef as ArrayDefinition; + const elementType = asArrayDef.elementType; + if (elementType) { + // It's ArrayDefinition + if (isIntrinsic(elementType)) { + const asIntrinsicDef = elementType as IntrinsicDefinition; + if (isArrayLikeOfNumber(data)) { + setIntrinsicFromArrayLikeOfNumber(asIntrinsicDef, data, arrayBuffer, offset); + return; + } } - } else if (Array.isArray(fieldDef)) { - // It's IntrinsicDefinition[] or StructDefinition[] data.forEach((newValue: any, ndx: number) => { - setStructuredValues(fieldDef[ndx], newValue, arrayBuffer, offset); + setTypedValues(elementType, newValue, arrayBuffer, offset + elementType.size * ndx); }); - } else { + return; + } + + const asStructDef = typeDef as StructDefinition; + const fields = asStructDef.fields; + if (fields) { // It's StructDefinition - const asStructDefinition = fieldDef as StructDefinition; for (const [key, newValue] of Object.entries(data)) { - const fieldDef = asStructDefinition.fields[key]; + const fieldDef = fields[key]; if (fieldDef) { - setStructuredValues(fieldDef, newValue, arrayBuffer, offset); + setTypedValues(fieldDef.type, newValue, arrayBuffer, offset + fieldDef.offset); } } + } else { + // It's IntrinsicDefinition + setIntrinsicFromArrayLikeOfNumber(typeDef as IntrinsicDefinition, data, arrayBuffer, offset); } +} + +export function setStructuredValues(varDef: VariableDefinition, data: any, arrayBuffer: ArrayBuffer, offset = 0) { + setTypedValues(varDef.typeDefinition, data, arrayBuffer, offset); } \ No newline at end of file diff --git a/src/data-definitions.ts b/src/data-definitions.ts index a601cbd..c8c503b 100644 --- a/src/data-definitions.ts +++ b/src/data-definitions.ts @@ -1,42 +1,105 @@ -import { WgslReflect, MemberInfo } from 'wgsl_reflect'; +import { + WgslReflect, + ArrayInfo, + StructInfo, + TemplateInfo, + TypeInfo, + VariableInfo, +} from 'wgsl_reflect'; + +export { WgslReflect }; + +export type FieldDefinition = { + offset: number; + type: TypeDefinition; +}; -export interface StructDefinition { - fields: FieldDefinitions; - size: number; -} +export type FieldDefinitions = { + [x: string]: FieldDefinition; +}; -export interface StorageDefinition extends StructDefinition { - binding: number; - group: number; -} +export type TypeDefinition = { + size: number; +}; -export type IntrinsicDefinition = { - offset: number; +// These 3 types are wonky. Maybe we should make them inherit from a common +// type with a `type` field. I wanted this to be a plain object though, not an object +// with a constructor. In any case, right now, the way you tell them apart is +// If it's got `elementType` then it's an ArrayDefinition +// If it's got `fields` then it's a StructDefinition +// else it's an IntrinsicDefinition +export type StructDefinition = TypeDefinition & { + fields: FieldDefinitions; size: number; +}; + +export type IntrinsicDefinition = TypeDefinition & { type: string; numElements?: number; }; -export type FieldDefinition = IntrinsicDefinition | StructDefinition | IntrinsicDefinition[] | StructDefinition[]; - -export type FieldDefinitions = { - [x: string]: FieldDefinition; +export type ArrayDefinition = TypeDefinition & { + elementType: TypeDefinition, + numElements: number, }; +/** + * @group(x) @binding(y) var<...> definition + */ +export interface VariableDefinition { + binding: number; + group: number; + size: number; + typeDefinition: TypeDefinition; +} + export type StructDefinitions = { [x: string]: StructDefinition; -} +}; -export type StorageDefinitions = { - [x: string]: StorageDefinition; -} +export type VariableDefinitions = { + [x: string]: VariableDefinition; +}; type ShaderDataDefinitions = { - uniforms: StorageDefinitions, - storages: StorageDefinitions, + uniforms: VariableDefinitions, + storages: VariableDefinitions, structs: StructDefinitions, }; +function getNamedVariables(reflect: WgslReflect, variables: VariableInfo[]): VariableDefinitions { + return Object.fromEntries(variables.map(v => { + const typeDefinition = addType(reflect, v.type, 0); + return [ + v.name, + { + typeDefinition, + group: v.group, + binding: v.binding, + size: typeDefinition.size, + }, + ]; + })) as VariableDefinitions; +} + +function makeStructDefinition(reflect: WgslReflect, structInfo: StructInfo, offset: number) { + // StructDefinition + const fields: FieldDefinitions = Object.fromEntries(structInfo.members.map(m => { + return [ + m.name, + { + offset: m.offset, + type: addType(reflect, m.type, 0), + }, + ]; + })); + return { + fields, + size: structInfo.size, + offset, + }; +} + /** * Given a WGSL shader, returns data definitions for structures, * uniforms, and storage buffers @@ -73,26 +136,12 @@ type ShaderDataDefinitions = { export function makeShaderDataDefinitions(code: string): ShaderDataDefinitions { const reflect = new WgslReflect(code); - const structs = Object.fromEntries(reflect.structs.map(struct => { - const info = reflect.getStructInfo(struct); - return [struct.name, addMembers(reflect, info!.members!, info!.size)]; + const structs = Object.fromEntries(reflect.structs.map(structInfo => { + return [structInfo.name, makeStructDefinition(reflect, structInfo, 0)]; })); - const uniforms = Object.fromEntries(reflect.uniforms.map(uniform => { - const info = reflect.getUniformBufferInfo(uniform); - const member = addMember(reflect, info as unknown as MemberInfo, 0)[1] as StorageDefinition; - member.binding = info!.binding; - member.group = info!.group; - return [uniform.name, member]; - })); - - const storages = Object.fromEntries(reflect.storage.map(uniform => { - const info = reflect.getStorageBufferInfo(uniform); - const member = addMember(reflect, info as unknown as MemberInfo, 0)[1] as StorageDefinition; - member.binding = info!.binding; - member.group = info!.group; - return [uniform.name, member]; - })); + const uniforms = getNamedVariables(reflect, reflect.uniforms); + const storages = getNamedVariables(reflect, reflect.storage); return { structs, @@ -101,54 +150,92 @@ export function makeShaderDataDefinitions(code: string): ShaderDataDefinitions { }; } -function addMember(reflect: WgslReflect, m: MemberInfo, offset: number): [string, StructDefinition | IntrinsicDefinition | IntrinsicDefinition[] | StructDefinition[]] { - if (m.isArray) { - if (m.isStruct) { - return [ - m.name, - new Array(m.arrayCount).fill(0).map((_, ndx) => { - return addMembers(reflect, m.members!, m.size / m.arrayCount, offset + (m.offset || 0) + m.size / m.arrayCount * ndx); - }), - ]; - } else { - return [ - m.name, - { - offset: offset + (m.offset || 0), - size: m.size, - type: (m.type as any).format!.format - ? `${(m.type as any).format!.name!}<${(m.type as any).format!.format!.name}>` - : (m.type as any).format!.name!, - numElements: m.arrayCount, - }, - ]; - } - } else if (m.isStruct) { - return [ - m.name, - addMembers(reflect, m.members!, m.size, offset + (m.offset || 0)), - ]; - } else { - return [ - m.name, - { - offset: offset + (m.offset || 0), - size: m.size, - type: (m.type as any)?.format - ? `${m.type.name}<${(m.type as any).format.name}>` - : m.type?.name || m.name, - }, - ]; +function assert(cond: boolean, msg = '') { + if (!cond) { + throw new Error(msg); } } -function addMembers(reflect: WgslReflect, members: MemberInfo[], size: number, offset = 0): StructDefinition { - const fields: FieldDefinitions = Object.fromEntries(members.map(m => { - return addMember(reflect, m, offset); - })); +/* + write down what I want for a given type - return { - fields, - size, + struct VSUniforms { + foo: u32, }; + @group(4) @binding(1) var uni1: f32; + @group(3) @binding(2) var uni2: array; + @group(2) @binding(3) var uni3: VSUniforms; + @group(1) @binding(4) var uni4: array; + + uni1: { + type: 'f32', + numElements: undefined + }, + uni2: { + type: 'array', + elementType: 'f32' + numElements: 5, + }, + uni3: { + type: 'struct', + fields: { + foo: { + type: 'f32', + numElements: undefined + } + }, + }, + uni4: { + type: 'array', + elementType: + fields: { + foo: { + type: 'f32', + numElements: undefined + } + }, + fields: { + foo: { + type: 'f32', + numElements: undefined + } + }, + ... + ] + + */ + + + +function addType(reflect: WgslReflect, typeInfo: TypeInfo, offset: number): + StructDefinition | + IntrinsicDefinition | + ArrayDefinition { + if (typeInfo.isArray) { + assert(!typeInfo.isStruct, 'struct array is invalid'); + assert(!typeInfo.isStruct, 'template array is invalid'); + const arrayInfo = typeInfo as ArrayInfo; + // ArrayDefinition + return { + size: arrayInfo.size * arrayInfo.count, + elementType: addType(reflect, arrayInfo.format, offset), + numElements: arrayInfo.count, + }; + } else if (typeInfo.isStruct) { + assert(!typeInfo.isTemplate, 'template struct is invalid'); + const structInfo = typeInfo as StructInfo; + return makeStructDefinition(reflect, structInfo, offset); + } else { + // template is like vec4 or mat4x4 + const asTemplateInfo = typeInfo as TemplateInfo; + const type = typeInfo.isTemplate + ? `${asTemplateInfo.name}<${asTemplateInfo.format!.name}>` + : typeInfo.name; + // IntrinsicDefinition + return { + size: typeInfo.size, + type, + }; + } } + diff --git a/test/tests/buffer-views-test.js b/test/tests/buffer-views-test.js index c91f063..6bb7070 100644 --- a/test/tests/buffer-views-test.js +++ b/test/tests/buffer-views-test.js @@ -9,6 +9,18 @@ import { assertArrayEqual, assertEqual, assertTruthy } from '../assert.js'; describe('buffer-views-tests', () => { + it('handles intrinsics', () => { + const shader = ` + @group(12) @binding(13) var uni1: f32; + `; + const defs = makeShaderDataDefinitions(shader); + const {views, arrayBuffer} = makeStructuredView(defs.uniforms.uni1); + const asF32 = new Float32Array(arrayBuffer); + views[0] = 123; + assertTruthy(views instanceof Float32Array); + assertEqual(asF32[0], 123); + }); + it('generates handles built-in type aliases', () => { const shader = ` struct VertexDesc { @@ -227,15 +239,15 @@ describe('buffer-views-tests', () => { }; struct VSUniforms { - worldViewProjection: mat4x4, - position: array, - lineInfo: array, - color: vec4, - lightDirection: array, 6>, - kernel: vec4, - colorMult: vec4f, - colorMultU: vec4u, - colorMultI: vec4i, + worldViewProjection: mat4x4, // 0-> 63 + position: array, // 64->111 3 * 4 * 4 + lineInfo: array, // 112->159 6 * 4 * 5 + color: vec4, // 240 + lightDirection: array, 6>, // 256 + kernel: vec4, // 352 + colorMult: vec4f, // 368 + colorMultU: vec4u, // 384 + colorMultI: vec4i, // 400 }; `; const defs = makeShaderDataDefinitions(shader).structs; @@ -651,29 +663,6 @@ describe('buffer-views-tests', () => { assertEqual(views[2].position.byteOffset, 16); }); - it('works with alias', () => { - const code = ` - alias material_index = u32; - alias color = vec3f; - - struct Material { - index: material_index, - diffuse: color, - }; - - @group(0) @binding(1) var material: Material; - `; - const d = makeShaderDataDefinitions(code); - const defs = d.storages; - assertTruthy(defs); - assertTruthy(defs.material ); - assertEqual(defs.material.size, 32); - assertEqual(defs.material.fields.index.offset, 0); - assertEqual(defs.material.fields.index.size, 4); - assertEqual(defs.material.fields.diffuse.offset, 16); - assertEqual(defs.material.fields.diffuse.size, 12); - }); - /* it('works with const', () => { const code = ` @@ -797,4 +786,56 @@ describe('buffer-views-tests', () => { }); + it('sets arrays of arrays', () => { + const code = ` + struct InnerUniforms { + bar: u32, + }; + + struct VSUniforms { + foo: u32, + moo: InnerUniforms, + }; + @group(0) @binding(0) var foo0: vec3f; + @group(0) @binding(1) var foo1: array; + @group(0) @binding(2) var foo2: array, 6>; + @group(0) @binding(3) var foo3: array, 6>, 7>; + + @group(0) @binding(4) var foo4: VSUniforms; + @group(0) @binding(5) var foo5: array; + @group(0) @binding(6) var foo6: array, 6>; + @group(0) @binding(7) var foo7: array, 6>, 7>; + `; + const defs = makeShaderDataDefinitions(code).uniforms; + const {views, set, arrayBuffer} = makeStructuredView(defs.foo7); + // 2 * 4 * 5 * 6 * 7 + assertEqual(arrayBuffer.byteLength, 2 * 4 * 5 * 6 * 7); // 1680 + assertTruthy(views[6][5][4].foo instanceof Uint32Array); + assertTruthy(views[6][5][4].moo.bar instanceof Uint32Array); + set([ + , // 0 + , // 1 + , // 2 + , // 3 + , // 4 + , // 5 + [ + , // 0 + , // 1 + , // 2 + , // 3 + , // 4 + [ + , // 0 + , // 1 + , // 2 + , // 3 + { foo: 123, moo: { bar: 456 }}, + ], + ], + ]); + assertEqual(views[6][5][4].foo[0], 123); + assertEqual(views[6][5][4].moo.bar[0], 456); + }); + }); \ No newline at end of file diff --git a/test/tests/data-definition-test.js b/test/tests/data-definition-test.js index 44d8dc2..b5295da 100644 --- a/test/tests/data-definition-test.js +++ b/test/tests/data-definition-test.js @@ -2,7 +2,7 @@ import { describe, it } from '../mocha-support.js'; import { makeShaderDataDefinitions, } from '../../dist/0.x/webgpu-utils.module.js'; -import { assertEqual, assertFalsy } from '../assert.js'; +import { assertEqual, assertFalsy, assertTruthy } from '../assert.js'; describe('data-definition-tests', () => { @@ -30,14 +30,14 @@ describe('data-definition-tests', () => { `; const d = makeShaderDataDefinitions(shader); const defs = d.uniforms; - assertEqual(defs.uni1.type, 'f32'); - assertFalsy(defs.uni1.numElements); - assertEqual(defs.uni2.type, 'f32'); - assertEqual(defs.uni2.numElements, 5); - assertEqual(defs.uni3.fields.foo.type, 'u32'); - assertFalsy(defs.uni3.fields.foo.numElements); - assertEqual(defs.uni4.length, 6); - assertEqual(defs.uni4[0].fields.foo.type, 'u32'); + assertEqual(defs.uni1.typeDefinition.type, 'f32'); + assertFalsy(defs.uni1.typeDefinition.numElements); + assertEqual(defs.uni2.typeDefinition.elementType.type, 'f32'); + assertEqual(defs.uni2.typeDefinition.numElements, 5); + assertEqual(defs.uni3.typeDefinition.fields.foo.type.type, 'u32'); + assertFalsy(defs.uni3.typeDefinition.fields.foo.numElements); + assertEqual(defs.uni4.typeDefinition.numElements, 6); + assertEqual(defs.uni4.typeDefinition.elementType.fields.foo.type.type, 'u32'); assertEqual(defs.uni1.binding, 1); assertEqual(defs.uni2.binding, 2); @@ -50,5 +50,102 @@ describe('data-definition-tests', () => { assertEqual(defs.uni4.group, 1); }); + it('generates expected offsets', () => { + const code = ` + struct VSUniforms { + foo: u32, + bar: f32, + moo: vec3f, + mrp: i32, + }; + @group(4) @binding(1) var uni1: VSUniforms; + `; + const d = makeShaderDataDefinitions(code); + const def = d.uniforms.uni1; + assertTruthy(def); + assertEqual(def.typeDefinition.size, 32); + assertEqual(def.typeDefinition.fields.foo.offset, 0); + assertEqual(def.typeDefinition.fields.bar.offset, 4); + assertEqual(def.typeDefinition.fields.moo.offset, 16); + assertEqual(def.typeDefinition.fields.mrp.offset, 28); + }); + + it('works with alias', () => { + const code = ` + alias material_index = u32; + alias color = vec3f; + + struct Material { + index: material_index, + diffuse: color, + }; + + @group(0) @binding(1) var material: Material; + `; + const d = makeShaderDataDefinitions(code); + const defs = d.storages; + assertTruthy(defs); + assertTruthy(defs.material); + assertEqual(defs.material.size, 32); + assertEqual(defs.material.typeDefinition.fields.index.offset, 0); + assertEqual(defs.material.typeDefinition.fields.index.type.size, 4); + assertEqual(defs.material.typeDefinition.fields.diffuse.offset, 16); + assertEqual(defs.material.typeDefinition.fields.diffuse.type.size, 12); + }); + + it('works with arrays of arrays', () => { + const code = ` + struct InnerUniforms { + bar: u32, + }; + + struct VSUniforms { + foo: u32, + moo: InnerUniforms, + }; + @group(0) @binding(0) var foo0: vec3f; + @group(0) @binding(1) var foo1: array; + @group(0) @binding(2) var foo2: array, 6>; + @group(0) @binding(3) var foo3: array, 6>, 7>; + + @group(0) @binding(4) var foo4: VSUniforms; + @group(0) @binding(5) var foo5: array; + @group(0) @binding(6) var foo6: array, 6>; + @group(0) @binding(7) var foo7: array, 6>, 7>; + `; + + const d = makeShaderDataDefinitions(code); + assertTruthy(d); + assertEqual(d.uniforms.foo0.typeDefinition.numElements, undefined); + assertEqual(d.uniforms.foo0.typeDefinition.type, 'vec3f'); + + assertEqual(d.uniforms.foo1.typeDefinition.numElements, 5); + assertEqual(d.uniforms.foo1.typeDefinition.elementType.type, 'vec3f'); + + assertEqual(d.uniforms.foo2.typeDefinition.numElements, 6); + assertEqual(d.uniforms.foo2.typeDefinition.elementType.numElements, 5); + assertEqual(d.uniforms.foo2.typeDefinition.elementType.elementType.type, 'vec3f'); + + assertEqual(d.uniforms.foo3.typeDefinition.numElements, 7); + assertEqual(d.uniforms.foo3.typeDefinition.elementType.numElements, 6); + assertEqual(d.uniforms.foo3.typeDefinition.elementType.elementType.numElements, 5); + assertEqual(d.uniforms.foo3.typeDefinition.elementType.elementType.elementType.type, 'vec3f'); + + assertEqual(d.uniforms.foo4.typeDefinition.numElements, undefined); + assertEqual(d.uniforms.foo4.typeDefinition.fields.foo.type.type, 'u32'); + + assertEqual(d.uniforms.foo5.typeDefinition.numElements, 5); + assertEqual(d.uniforms.foo5.typeDefinition.elementType.fields.foo.type.type, 'u32'); + + assertEqual(d.uniforms.foo6.typeDefinition.numElements, 6); + assertEqual(d.uniforms.foo6.typeDefinition.elementType.numElements, 5); + assertEqual(d.uniforms.foo6.typeDefinition.elementType.elementType.fields.foo.type.type, 'u32'); + + assertEqual(d.uniforms.foo7.typeDefinition.numElements, 7); + assertEqual(d.uniforms.foo7.typeDefinition.elementType.numElements, 6); + assertEqual(d.uniforms.foo7.typeDefinition.elementType.elementType.numElements, 5); + assertEqual(d.uniforms.foo7.typeDefinition.elementType.elementType.elementType.fields.foo.type.type, 'u32'); + + }); });