diff --git a/TODO.txt b/TODO.txt index 02c9389..c97c769 100644 --- a/TODO.txt +++ b/TODO.txt @@ -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 diff --git a/lib/gpu-uniforms.js b/lib/gpu-uniforms.js index 56786d8..a9b8814 100644 --- a/lib/gpu-uniforms.js +++ b/lib/gpu-uniforms.js @@ -24,18 +24,19 @@ const simpleTypes = { ["f32"]: [1, 1, "f32", zero], ["atomic"]: [1, 1, "i32", zero], - ["vec2"]: [2, 2, "i32", array(2)], - ["vec3"]: [4, 3, "i32", array(3)], - ["vec4"]: [4, 4, "i32", array(4)], - ["atomic"]: [1, 1, "u32", zero], - ["vec2"]: [2, 2, "u32", array(2)], - ["vec3"]: [4, 3, "u32", array(3)], - ["vec4"]: [4, 4, "u32", array(4)], - ["atomic"]: [1, 1, "f32", zero], + + ["vec2"]: [2, 2, "i32", array(2)], + ["vec2"]: [2, 2, "u32", array(2)], ["vec2"]: [2, 2, "f32", array(2)], + + ["vec3"]: [4, 3, "i32", array(3)], + ["vec3"]: [4, 3, "u32", array(3)], ["vec3"]: [4, 3, "f32", array(3)], + + ["vec4"]: [4, 4, "i32", array(4)], + ["vec4"]: [4, 4, "u32", array(4)], ["vec4"]: [4, 4, "f32", array(4)], ["mat2x2"]: [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),