From e79e741fcfbf6b30ec4c2187f7ce48f3e1dfb696 Mon Sep 17 00:00:00 2001 From: Rezmason Date: Fri, 29 Oct 2021 07:43:39 -0700 Subject: [PATCH] Fixed a struct layout bug. Struct layout and builder now support mixing integer and float data in a struct, and output an ArrayBuffer. --- TODO.txt | 7 -- js/webgpu_main.js | 124 +++++++++++++++++++----------------- shaders/rainRenderPass.wgsl | 4 +- 3 files changed, 68 insertions(+), 67 deletions(-) diff --git a/TODO.txt b/TODO.txt index 76c4f07..54922c3 100644 --- a/TODO.txt +++ b/TODO.txt @@ -2,13 +2,6 @@ TODO: WebGPU 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) diff --git a/js/webgpu_main.js b/js/webgpu_main.js index 1320846..753a811 100644 --- a/js/webgpu_main.js +++ b/js/webgpu_main.js @@ -32,74 +32,80 @@ const loadTexture = async (device, url) => { }; const supportedLayoutTypes = { - i32: { alignAtByte: 1, sizeInBytes: 1 }, - u32: { alignAtByte: 1, sizeInBytes: 1 }, - f32: { alignAtByte: 1, sizeInBytes: 1 }, - atomic: { alignAtByte: 1, sizeInBytes: 1 }, - vec2: { alignAtByte: 2, sizeInBytes: 2 }, - vec3: { alignAtByte: 4, sizeInBytes: 3 }, - vec4: { alignAtByte: 4, sizeInBytes: 4 }, - mat2x2: { alignAtByte: 2, sizeInBytes: 4 }, - mat3x2: { alignAtByte: 2, sizeInBytes: 6 }, - mat4x2: { alignAtByte: 2, sizeInBytes: 8 }, - mat2x3: { alignAtByte: 4, sizeInBytes: 8 }, - mat3x3: { alignAtByte: 4, sizeInBytes: 12 }, - mat4x3: { alignAtByte: 4, sizeInBytes: 16 }, - mat2x4: { alignAtByte: 4, sizeInBytes: 8 }, - mat3x4: { alignAtByte: 4, sizeInBytes: 12 }, - mat4x4: { alignAtByte: 4, sizeInBytes: 16 }, + ["i32"]: { alignAtByte: 1, sizeInBytes: 1, baseType: "i32" }, + ["u32"]: { alignAtByte: 1, sizeInBytes: 1, baseType: "u32" }, + ["f32"]: { alignAtByte: 1, sizeInBytes: 1, baseType: "f32" }, + + ["atomic"]: { alignAtByte: 1, sizeInBytes: 1, baseType: "i32" }, + ["vec2"]: { alignAtByte: 2, sizeInBytes: 2, baseType: "i32" }, + ["vec3"]: { alignAtByte: 4, sizeInBytes: 3, baseType: "i32" }, + ["vec4"]: { alignAtByte: 4, sizeInBytes: 4, baseType: "i32" }, + + ["atomic"]: { alignAtByte: 1, sizeInBytes: 1, baseType: "u32" }, + ["vec2"]: { alignAtByte: 2, sizeInBytes: 2, baseType: "u32" }, + ["vec3"]: { alignAtByte: 4, sizeInBytes: 3, baseType: "u32" }, + ["vec4"]: { alignAtByte: 4, sizeInBytes: 4, baseType: "u32" }, + + ["atomic"]: { alignAtByte: 1, sizeInBytes: 1, baseType: "f32" }, + ["vec2"]: { alignAtByte: 2, sizeInBytes: 2, baseType: "f32" }, + ["vec3"]: { alignAtByte: 4, sizeInBytes: 3, baseType: "f32" }, + ["vec4"]: { alignAtByte: 4, sizeInBytes: 4, baseType: "f32" }, + + ["mat2x2"]: { alignAtByte: 2, sizeInBytes: 4, baseType: "f32" }, + ["mat3x2"]: { alignAtByte: 2, sizeInBytes: 6, baseType: "f32" }, + ["mat4x2"]: { alignAtByte: 2, sizeInBytes: 8, baseType: "f32" }, + ["mat2x3"]: { alignAtByte: 4, sizeInBytes: 8, baseType: "f32" }, + ["mat3x3"]: { alignAtByte: 4, sizeInBytes: 12, baseType: "f32" }, + ["mat4x3"]: { alignAtByte: 4, sizeInBytes: 16, baseType: "f32" }, + ["mat2x4"]: { alignAtByte: 4, sizeInBytes: 8, baseType: "f32" }, + ["mat3x4"]: { alignAtByte: 4, sizeInBytes: 12, baseType: "f32" }, + ["mat4x4"]: { alignAtByte: 4, sizeInBytes: 16, baseType: "f32" }, }; const computeStructLayout = (types) => { - const byteOffsets = []; - let sizeInBytes = 0; + const entries = []; + let byteOffset = 0; for (const type of types) { - const layout = supportedLayoutTypes[type.split("<")[0]]; - if (layout == null) { + if (supportedLayoutTypes[type] == null) { throw new Error(`Unsupported type: ${type}`); } - sizeInBytes += sizeInBytes % layout.alignAtByte; - byteOffsets.push(sizeInBytes); - sizeInBytes += layout.sizeInBytes; + const { alignAtByte, sizeInBytes, baseType } = supportedLayoutTypes[type]; + byteOffset = Math.ceil(byteOffset / alignAtByte) * alignAtByte; + entries.push({ baseType, byteOffset }); + byteOffset += sizeInBytes; } + + // console.log(types); + // console.log(entries); + return { - byteOffsets, - sizeInBytes, - size: sizeInBytes * Float32Array.BYTES_PER_ELEMENT, + entries, + size: byteOffset * Float32Array.BYTES_PER_ELEMENT, }; }; -const buildStruct = (layout, values) => { - const { byteOffsets, sizeInBytes } = layout; - if (values.length !== byteOffsets.length) { - throw new Error(`This struct contains ${byteOffsets.length} values, and you supplied only ${values.length}.`); +const buildStruct = (buffer, layout, values) => { + const { entries } = layout; + + 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++) { - const diff = byteOffsets[i] - count; - if (diff > 0) { - buffer.push(Array(diff).fill()); - } + const view = views[entries[i].baseType]; const value = values[i]; - let array; - if (Array.isArray(value)) { - array = value; - } else if (value[Symbol.iterator] != null) { - array = Array.from(value); - } else { - array = [value]; - } - buffer.push(array); - count += array.length + diff; + const array = value[Symbol.iterator] == null ? [value] : value; + view.set(array, entries[i].byteOffset); } - { - const diff = sizeInBytes - count; - if (diff > 0) { - buffer.push(Array(diff).fill()); - } - } - return buffer.flat(); + return buffer; }; export default async (canvas, config) => { @@ -141,14 +147,14 @@ export default async (canvas, config) => { const sampler = device.createSampler(); const msdfTexture = await loadTexture(device, config.glyphTexURL); - const configStructLayout = computeStructLayout(["i32", "i32"]); + const configStructLayout = computeStructLayout(["i32", "i32", "f32"]); const configBufferSize = configStructLayout.size; const configBuffer = device.createBuffer({ size: configBufferSize, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.VERTEX | GPUBufferUsage.FRAGMENT, // Which of these are necessary? mappedAtCreation: true, }); - new Int32Array(configBuffer.getMappedRange()).set(buildStruct(configStructLayout, [numColumns, numRows])); + buildStruct(configBuffer.getMappedRange(), configStructLayout, [numColumns, numRows, config.glyphHeightToWidth]); configBuffer.unmap(); // prettier-ignore @@ -158,7 +164,7 @@ export default async (canvas, config) => { usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.FRAGMENT, // Which of these are necessary? mappedAtCreation: true, }); - new Int32Array(msdfBuffer.getMappedRange()).set(buildStruct(msdfStructLayout, [config.glyphTextureColumns, config.glyphSequenceLength])); + buildStruct(msdfBuffer.getMappedRange(), msdfStructLayout, [config.glyphTextureColumns, config.glyphSequenceLength]); msdfBuffer.unmap(); // prettier-ignore @@ -187,7 +193,7 @@ export default async (canvas, config) => { const aspectRatio = canvasSize[0] / canvasSize[1]; mat4.perspectiveZO(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000); 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(); @@ -291,7 +297,7 @@ export default async (canvas, config) => { updateCameraBuffer(); } - queue.writeBuffer(timeBuffer, 0, new Int32Array(buildStruct(timeStructLayout, [now, frame]))); + queue.writeBuffer(timeBuffer, 0, buildStruct(null, timeStructLayout, [now, frame])); frame++; renderPassConfig.colorAttachments[0].view = canvasContext.getCurrentTexture().createView(); @@ -303,7 +309,7 @@ export default async (canvas, config) => { const commandBuffer = encoder.finish(); queue.submit([commandBuffer]); - requestAnimationFrame(renderLoop); + // requestAnimationFrame(renderLoop); }; requestAnimationFrame(renderLoop); diff --git a/shaders/rainRenderPass.wgsl b/shaders/rainRenderPass.wgsl index 74b9557..98a9f3e 100644 --- a/shaders/rainRenderPass.wgsl +++ b/shaders/rainRenderPass.wgsl @@ -5,6 +5,7 @@ let TWO_PI:f32 = 6.28318530718; [[block]] struct Config { numColumns: i32; numRows: i32; + glyphHeightToWidth: f32; }; [[group(0), binding(0)]] var config:Config; @@ -58,11 +59,12 @@ struct VertexOutput { f32(config.numRows) ); position = 1.0 - position * 2.0; + // position = position * scene.screenSize; var depth:f32 = 0.0; var pos: vec4 = vec4(position, depth, 1.0); - // pos.x = pos.x / glyphHeightToWidth; + pos.x = pos.x / config.glyphHeightToWidth; pos = scene.camera * scene.transform * pos; return VertexOutput(