Fixed a struct layout bug. Struct layout and builder now support mixing integer and float data in a struct, and output an ArrayBuffer.

This commit is contained in:
Rezmason
2021-10-29 07:43:39 -07:00
parent 81f77c70ae
commit e79e741fcf
3 changed files with 68 additions and 67 deletions

View File

@@ -2,13 +2,6 @@ TODO:
WebGPU WebGPU
std140 std140
Right now, buildStruct packs an array, which is changed to a typedarray and fed to a GPUBuffer
That limits the type to whatever the typedarray is
Instead, we need to represent the type of each value, and write it as such
Create an array buffer
Create a Float32Array view into it, and an Int32Array view
Transcribe the values into these views
Return the array buffer
Write an explanation of the rain pass (and include images) Write an explanation of the rain pass (and include images)

View File

@@ -32,74 +32,80 @@ const loadTexture = async (device, url) => {
}; };
const supportedLayoutTypes = { const supportedLayoutTypes = {
i32: { alignAtByte: 1, sizeInBytes: 1 }, ["i32"]: { alignAtByte: 1, sizeInBytes: 1, baseType: "i32" },
u32: { alignAtByte: 1, sizeInBytes: 1 }, ["u32"]: { alignAtByte: 1, sizeInBytes: 1, baseType: "u32" },
f32: { alignAtByte: 1, sizeInBytes: 1 }, ["f32"]: { alignAtByte: 1, sizeInBytes: 1, baseType: "f32" },
atomic: { alignAtByte: 1, sizeInBytes: 1 },
vec2: { alignAtByte: 2, sizeInBytes: 2 }, ["atomic<i32>"]: { alignAtByte: 1, sizeInBytes: 1, baseType: "i32" },
vec3: { alignAtByte: 4, sizeInBytes: 3 }, ["vec2<i32>"]: { alignAtByte: 2, sizeInBytes: 2, baseType: "i32" },
vec4: { alignAtByte: 4, sizeInBytes: 4 }, ["vec3<i32>"]: { alignAtByte: 4, sizeInBytes: 3, baseType: "i32" },
mat2x2: { alignAtByte: 2, sizeInBytes: 4 }, ["vec4<i32>"]: { alignAtByte: 4, sizeInBytes: 4, baseType: "i32" },
mat3x2: { alignAtByte: 2, sizeInBytes: 6 },
mat4x2: { alignAtByte: 2, sizeInBytes: 8 }, ["atomic<u32>"]: { alignAtByte: 1, sizeInBytes: 1, baseType: "u32" },
mat2x3: { alignAtByte: 4, sizeInBytes: 8 }, ["vec2<u32>"]: { alignAtByte: 2, sizeInBytes: 2, baseType: "u32" },
mat3x3: { alignAtByte: 4, sizeInBytes: 12 }, ["vec3<u32>"]: { alignAtByte: 4, sizeInBytes: 3, baseType: "u32" },
mat4x3: { alignAtByte: 4, sizeInBytes: 16 }, ["vec4<u32>"]: { alignAtByte: 4, sizeInBytes: 4, baseType: "u32" },
mat2x4: { alignAtByte: 4, sizeInBytes: 8 },
mat3x4: { alignAtByte: 4, sizeInBytes: 12 }, ["atomic<f32>"]: { alignAtByte: 1, sizeInBytes: 1, baseType: "f32" },
mat4x4: { alignAtByte: 4, sizeInBytes: 16 }, ["vec2<f32>"]: { alignAtByte: 2, sizeInBytes: 2, baseType: "f32" },
["vec3<f32>"]: { alignAtByte: 4, sizeInBytes: 3, baseType: "f32" },
["vec4<f32>"]: { alignAtByte: 4, sizeInBytes: 4, baseType: "f32" },
["mat2x2<f32>"]: { alignAtByte: 2, sizeInBytes: 4, baseType: "f32" },
["mat3x2<f32>"]: { alignAtByte: 2, sizeInBytes: 6, baseType: "f32" },
["mat4x2<f32>"]: { alignAtByte: 2, sizeInBytes: 8, baseType: "f32" },
["mat2x3<f32>"]: { alignAtByte: 4, sizeInBytes: 8, baseType: "f32" },
["mat3x3<f32>"]: { alignAtByte: 4, sizeInBytes: 12, baseType: "f32" },
["mat4x3<f32>"]: { alignAtByte: 4, sizeInBytes: 16, baseType: "f32" },
["mat2x4<f32>"]: { alignAtByte: 4, sizeInBytes: 8, baseType: "f32" },
["mat3x4<f32>"]: { alignAtByte: 4, sizeInBytes: 12, baseType: "f32" },
["mat4x4<f32>"]: { alignAtByte: 4, sizeInBytes: 16, baseType: "f32" },
}; };
const computeStructLayout = (types) => { const computeStructLayout = (types) => {
const byteOffsets = []; const entries = [];
let sizeInBytes = 0; let byteOffset = 0;
for (const type of types) { for (const type of types) {
const layout = supportedLayoutTypes[type.split("<")[0]]; if (supportedLayoutTypes[type] == null) {
if (layout == null) {
throw new Error(`Unsupported type: ${type}`); throw new Error(`Unsupported type: ${type}`);
} }
sizeInBytes += sizeInBytes % layout.alignAtByte; const { alignAtByte, sizeInBytes, baseType } = supportedLayoutTypes[type];
byteOffsets.push(sizeInBytes); byteOffset = Math.ceil(byteOffset / alignAtByte) * alignAtByte;
sizeInBytes += layout.sizeInBytes; entries.push({ baseType, byteOffset });
byteOffset += sizeInBytes;
} }
// console.log(types);
// console.log(entries);
return { return {
byteOffsets, entries,
sizeInBytes, size: byteOffset * Float32Array.BYTES_PER_ELEMENT,
size: sizeInBytes * Float32Array.BYTES_PER_ELEMENT,
}; };
}; };
const buildStruct = (layout, values) => { const buildStruct = (buffer, layout, values) => {
const { byteOffsets, sizeInBytes } = layout; const { entries } = layout;
if (values.length !== byteOffsets.length) {
throw new Error(`This struct contains ${byteOffsets.length} values, and you supplied only ${values.length}.`); if (values.length !== entries.length) {
throw new Error(`This struct contains ${entries.length} values, and you supplied ${values.length}.`);
} }
let buffer = [];
let count = 0; buffer ??= new ArrayBuffer(layout.size);
const views = {
i32: new Int32Array(buffer),
u32: new Uint32Array(buffer),
f32: new Float32Array(buffer),
};
for (let i = 0; i < values.length; i++) { for (let i = 0; i < values.length; i++) {
const diff = byteOffsets[i] - count; const view = views[entries[i].baseType];
if (diff > 0) {
buffer.push(Array(diff).fill());
}
const value = values[i]; const value = values[i];
let array; const array = value[Symbol.iterator] == null ? [value] : value;
if (Array.isArray(value)) { view.set(array, entries[i].byteOffset);
array = value;
} else if (value[Symbol.iterator] != null) {
array = Array.from(value);
} else {
array = [value];
}
buffer.push(array);
count += array.length + diff;
} }
{ return buffer;
const diff = sizeInBytes - count;
if (diff > 0) {
buffer.push(Array(diff).fill());
}
}
return buffer.flat();
}; };
export default async (canvas, config) => { export default async (canvas, config) => {
@@ -141,14 +147,14 @@ export default async (canvas, config) => {
const sampler = device.createSampler(); const sampler = device.createSampler();
const msdfTexture = await loadTexture(device, config.glyphTexURL); const msdfTexture = await loadTexture(device, config.glyphTexURL);
const configStructLayout = computeStructLayout(["i32", "i32"]); const configStructLayout = computeStructLayout(["i32", "i32", "f32"]);
const configBufferSize = configStructLayout.size; const configBufferSize = configStructLayout.size;
const configBuffer = device.createBuffer({ const configBuffer = device.createBuffer({
size: configBufferSize, size: configBufferSize,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.VERTEX | GPUBufferUsage.FRAGMENT, // Which of these are necessary? usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.VERTEX | GPUBufferUsage.FRAGMENT, // Which of these are necessary?
mappedAtCreation: true, mappedAtCreation: true,
}); });
new Int32Array(configBuffer.getMappedRange()).set(buildStruct(configStructLayout, [numColumns, numRows])); buildStruct(configBuffer.getMappedRange(), configStructLayout, [numColumns, numRows, config.glyphHeightToWidth]);
configBuffer.unmap(); configBuffer.unmap();
// prettier-ignore // prettier-ignore
@@ -158,7 +164,7 @@ export default async (canvas, config) => {
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.FRAGMENT, // Which of these are necessary? usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.FRAGMENT, // Which of these are necessary?
mappedAtCreation: true, mappedAtCreation: true,
}); });
new Int32Array(msdfBuffer.getMappedRange()).set(buildStruct(msdfStructLayout, [config.glyphTextureColumns, config.glyphSequenceLength])); buildStruct(msdfBuffer.getMappedRange(), msdfStructLayout, [config.glyphTextureColumns, config.glyphSequenceLength]);
msdfBuffer.unmap(); msdfBuffer.unmap();
// prettier-ignore // prettier-ignore
@@ -187,7 +193,7 @@ export default async (canvas, config) => {
const aspectRatio = canvasSize[0] / canvasSize[1]; const aspectRatio = canvasSize[0] / canvasSize[1];
mat4.perspectiveZO(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000); mat4.perspectiveZO(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
const screenSize = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1]; const screenSize = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
queue.writeBuffer(sceneBuffer, 0, new Float32Array(buildStruct(sceneStructLayout, [screenSize, camera, transform]))); queue.writeBuffer(sceneBuffer, 0, buildStruct(null, sceneStructLayout, [screenSize, camera, transform]));
}; };
updateCameraBuffer(); updateCameraBuffer();
@@ -291,7 +297,7 @@ export default async (canvas, config) => {
updateCameraBuffer(); updateCameraBuffer();
} }
queue.writeBuffer(timeBuffer, 0, new Int32Array(buildStruct(timeStructLayout, [now, frame]))); queue.writeBuffer(timeBuffer, 0, buildStruct(null, timeStructLayout, [now, frame]));
frame++; frame++;
renderPassConfig.colorAttachments[0].view = canvasContext.getCurrentTexture().createView(); renderPassConfig.colorAttachments[0].view = canvasContext.getCurrentTexture().createView();
@@ -303,7 +309,7 @@ export default async (canvas, config) => {
const commandBuffer = encoder.finish(); const commandBuffer = encoder.finish();
queue.submit([commandBuffer]); queue.submit([commandBuffer]);
requestAnimationFrame(renderLoop); // requestAnimationFrame(renderLoop);
}; };
requestAnimationFrame(renderLoop); requestAnimationFrame(renderLoop);

View File

@@ -5,6 +5,7 @@ let TWO_PI:f32 = 6.28318530718;
[[block]] struct Config { [[block]] struct Config {
numColumns: i32; numColumns: i32;
numRows: i32; numRows: i32;
glyphHeightToWidth: f32;
}; };
[[group(0), binding(0)]] var<uniform> config:Config; [[group(0), binding(0)]] var<uniform> config:Config;
@@ -58,11 +59,12 @@ struct VertexOutput {
f32(config.numRows) f32(config.numRows)
); );
position = 1.0 - position * 2.0; position = 1.0 - position * 2.0;
// position = position * scene.screenSize; // position = position * scene.screenSize;
var depth:f32 = 0.0; var depth:f32 = 0.0;
var pos: vec4<f32> = vec4<f32>(position, depth, 1.0); var pos: vec4<f32> = vec4<f32>(position, depth, 1.0);
// pos.x = pos.x / glyphHeightToWidth; pos.x = pos.x / config.glyphHeightToWidth;
pos = scene.camera * scene.transform * pos; pos = scene.camera * scene.transform * pos;
return VertexOutput( return VertexOutput(