Trying to implement the spec for the alignment and size of struct and array fields in gpu-uniforms.

This commit is contained in:
Rezmason
2021-11-08 08:58:55 -08:00
parent bbe3d62331
commit 6ab16b3091
2 changed files with 47 additions and 31 deletions

View File

@@ -7,14 +7,15 @@ WebGPU
Try to change post processing to compute shaders once they're easier to support Try to change post processing to compute shaders once they're easier to support
gpu-uniforms gpu-uniforms, working title
Is this an adequate name for it? Can't it be useful for non-uniform-related things? Is this an adequate name for it? Can't it be useful for non-uniform-related things?
gpu-buffer maybe? gpu-buffer maybe?
Resolve the remaining to-dos Get all the units to be the same
Try and use it for the palette color buffer Try and use it for the palette color buffer
Test it Test it
Demo it to others Demo it to others
Make improvements Make improvements
Capture expected requirements down the road, make roadmap
License it and put it somewhere else License it and put it somewhere else

View File

@@ -24,18 +24,19 @@ const simpleTypes = {
["f32"]: [1, 1, "f32", zero], ["f32"]: [1, 1, "f32", zero],
["atomic<i32>"]: [1, 1, "i32", zero], ["atomic<i32>"]: [1, 1, "i32", zero],
["vec2<i32>"]: [2, 2, "i32", array(2)],
["vec3<i32>"]: [4, 3, "i32", array(3)],
["vec4<i32>"]: [4, 4, "i32", array(4)],
["atomic<u32>"]: [1, 1, "u32", zero], ["atomic<u32>"]: [1, 1, "u32", zero],
["vec2<u32>"]: [2, 2, "u32", array(2)],
["vec3<u32>"]: [4, 3, "u32", array(3)],
["vec4<u32>"]: [4, 4, "u32", array(4)],
["atomic<f32>"]: [1, 1, "f32", zero], ["atomic<f32>"]: [1, 1, "f32", zero],
["vec2<i32>"]: [2, 2, "i32", array(2)],
["vec2<u32>"]: [2, 2, "u32", array(2)],
["vec2<f32>"]: [2, 2, "f32", array(2)], ["vec2<f32>"]: [2, 2, "f32", array(2)],
["vec3<i32>"]: [4, 3, "i32", array(3)],
["vec3<u32>"]: [4, 3, "u32", array(3)],
["vec3<f32>"]: [4, 3, "f32", array(3)], ["vec3<f32>"]: [4, 3, "f32", array(3)],
["vec4<i32>"]: [4, 4, "i32", array(4)],
["vec4<u32>"]: [4, 4, "u32", array(4)],
["vec4<f32>"]: [4, 4, "f32", array(4)], ["vec4<f32>"]: [4, 4, "f32", array(4)],
["mat2x2<f32>"]: [2, 4, "f32", array(2 * 2)], ["mat2x2<f32>"]: [2, 4, "f32", array(2 * 2)],
@@ -51,21 +52,33 @@ const simpleTypes = {
const getTypeData = (type, attributes, otherStructLayouts) => { const getTypeData = (type, attributes, otherStructLayouts) => {
if (simpleTypes[type] != null) { if (simpleTypes[type] != null) {
const [alignAtByte, sizeInBytes, baseType, defaultValue] = simpleTypes[type]; let [align, size, baseType, defaultValue] = simpleTypes[type];
if (attributes.align != null) {
align = parseInt(attributes.align) / 4;
}
if (attributes.size != null) {
size = parseInt(attributes.size) / 4;
}
return { return {
baseType, baseType,
alignAtByte, align,
sizeInBytes, size,
defaultValue, defaultValue,
}; };
} else if (type in otherStructLayouts) { } else if (type in otherStructLayouts) {
const innerLayout = otherStructLayouts[type]; const innerLayout = otherStructLayouts[type];
const { alignAtByte, sizeInBytes } = innerLayout; let { align, size } = innerLayout;
if (attributes.align != null) {
align = parseInt(attributes.align) / 4;
}
if (attributes.size != null) {
size = parseInt(attributes.size) / 4;
}
return { return {
isStruct: true, isStruct: true,
innerLayout, innerLayout,
sizeInBytes, size,
alignAtByte, align,
defaultValue: () => makeDataForLayout(otherStructLayouts, innerLayout), defaultValue: () => makeDataForLayout(otherStructLayouts, innerLayout),
}; };
} else if (type.startsWith("array<")) { } else if (type.startsWith("array<")) {
@@ -77,16 +90,19 @@ const getTypeData = (type, attributes, otherStructLayouts) => {
const innerTypeData = getTypeData(innerType, [], otherStructLayouts); const innerTypeData = getTypeData(innerType, [], otherStructLayouts);
const mult = parseInt(fixedSize ?? "0"); const mult = parseInt(fixedSize ?? "0");
const alignAtByte = 1; // TODO: calculate based on align rule of arrays let align = innerTypeData.align;
const sizeInBytes = 1; // TODO: calculate based on size rule of arrays let size = Math.ceil(innerTypeData.size / align) * align * mult;
// TODO: support stride attribute if (attributes.stride != null) {
size = parseInt(attributes.stride) * mult;
}
return { return {
isArray: true, isArray: true,
isFixedSize: mult > 0, isFixedSize: mult > 0,
innerTypeData, innerTypeData,
mult, mult,
sizeInBytes, size,
alignAtByte, align,
defaultValue: () => defaultValue: () =>
Array(mult) Array(mult)
.fill() .fill()
@@ -99,11 +115,11 @@ const getTypeData = (type, attributes, otherStructLayouts) => {
}; };
const parseAttributes = (str) => { const parseAttributes = (str) => {
const attributes = []; const attributes = {};
for (const attr of str.split(",").filter((attr) => attr.length > 0)) { for (const attr of str.split(",").filter((attr) => attr.length > 0)) {
const match = attr.match(/(\w+)(\((.*)\))?/); // foo(bar) const match = attr.match(/(\w+)(\((.*)\))?/); // foo(bar)
const [_, name, __, value] = match; const [_, name, __, value] = match;
attributes.push({ name, value }); attributes[name] = value;
} }
return attributes; return attributes;
}; };
@@ -126,8 +142,7 @@ const parseStruct = (str, structLayouts) => {
return null; return null;
} }
byteOffset = Math.ceil(byteOffset / typeData.alignAtByte) * typeData.alignAtByte; byteOffset = Math.ceil(byteOffset / typeData.align) * typeData.align;
// TODO: support align and size attributes
fields.push({ fields.push({
attributes: parseAttributes(leftAttributes ?? ""), attributes: parseAttributes(leftAttributes ?? ""),
identifier, identifier,
@@ -135,13 +150,13 @@ const parseStruct = (str, structLayouts) => {
...typeData, ...typeData,
byteOffset, byteOffset,
}); });
byteOffset += typeData.sizeInBytes; byteOffset += typeData.size;
} }
const minSizeInBytes = byteOffset * BYTES_PER_ELEMENT; const minSizeInBytes = byteOffset * BYTES_PER_ELEMENT;
const sizeInBytes = minSizeInBytes; // TODO: support runtime-sized arrays const align = Math.max(...fields.map((field) => field.align));
const alignAtByte = 1; // TODO: calculate based on align rule of structs const size = Math.ceil(minSizeInBytes / align) * align; // TODO: support runtime-sized arrays
return { name, fields, sizeInBytes, alignAtByte }; return { name, fields, size, align };
}; };
const parseStructLayoutsFromShader = (wgsl) => { const parseStructLayoutsFromShader = (wgsl) => {
@@ -189,12 +204,12 @@ const writeField = (allLayouts, field, value, views, byteOffset, warnMissingFiel
}; };
const makeGenerator = (layout, structLayouts) => { const makeGenerator = (layout, structLayouts) => {
const minSize = layout.sizeInBytes; const minSize = layout.size;
return Object.freeze({ return Object.freeze({
minSize, minSize,
create: () => makeDataForLayout(structLayouts, layout), create: () => makeDataForLayout(structLayouts, layout),
write: (object, destination, warnMissingFields = false) => { write: (object, destination, warnMissingFields = false) => {
destination ??= new ArrayBuffer(layout.sizeInBytes); // TODO: expand to support runtime-sized arrays, via the length of the array on the data object destination ??= new ArrayBuffer(layout.size); // TODO: expand to support runtime-sized arrays, via the length of the array on the data object
const views = { const views = {
i32: new Int32Array(destination), i32: new Int32Array(destination),