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
gpu-uniforms
gpu-uniforms, working title
Is this an adequate name for it? Can't it be useful for non-uniform-related things?
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
Test it
Demo it to others
Make improvements
Capture expected requirements down the road, make roadmap
License it and put it somewhere else

View File

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