From a143e3fc03e0f2173ff029f573ee4413edd9409c Mon Sep 17 00:00:00 2001 From: Rezmason Date: Mon, 8 Nov 2021 22:56:00 -0800 Subject: [PATCH] Renamed gpu-uniforms to gpu-buffer, and messed around with its API. I believe all the align, size, stride and byteOffset values are now in the proper units, ie. bytes. --- TODO.txt | 22 +++----- js/webgpu/imagePass.js | 3 +- js/webgpu/main.js | 6 +-- js/webgpu/palettePass.js | 4 +- js/webgpu/rainPass.js | 8 +-- js/webgpu/resurrectionPass.js | 4 +- js/webgpu/stripePass.js | 4 +- js/webgpu/utils.js | 2 +- lib/{gpu-uniforms.js => gpu-buffer.js} | 72 +++++++++++++------------- 9 files changed, 59 insertions(+), 66 deletions(-) rename lib/{gpu-uniforms.js => gpu-buffer.js} (79%) diff --git a/TODO.txt b/TODO.txt index c97c769..eb36fee 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,24 +1,18 @@ TODO: +gpu-buffer, working title + 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 + WebGPU blur pass - Update links in issues - Try to change post processing to compute shaders once they're easier to support - gpu-uniforms, working title - Is this an adequate name for it? Can't it be useful for non-uniform-related things? - gpu-buffer maybe? - 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 - - Write an explanation of the rain pass (and include images) Compute Volumetric quads diff --git a/js/webgpu/imagePass.js b/js/webgpu/imagePass.js index 3cbd147..a754899 100644 --- a/js/webgpu/imagePass.js +++ b/js/webgpu/imagePass.js @@ -1,5 +1,4 @@ -import uniforms from "/lib/gpu-uniforms.js"; -import { loadTexture, loadShader, makeUniformBuffer, makeBindGroup, makePassFBO, makePass } from "./utils.js"; +import { loadTexture, loadShader, makeBindGroup, makePassFBO, makePass } from "./utils.js"; // Multiplies the rendered rain and bloom by a loaded in image diff --git a/js/webgpu/main.js b/js/webgpu/main.js index 016cfac..a5c1dfc 100644 --- a/js/webgpu/main.js +++ b/js/webgpu/main.js @@ -1,4 +1,4 @@ -import uniforms from "/lib/gpu-uniforms.js"; +import { structs } from "/lib/gpu-buffer.js"; import { getCanvasSize, makeUniformBuffer, makePipeline } from "./utils.js"; import makeRain from "./rainPass.js"; @@ -38,7 +38,7 @@ export default async (canvas, config) => { GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST, }; - const timeUniforms = uniforms.read(`[[block]] struct Time { seconds : f32; frames : i32; };`).Time; + const timeUniforms = structs.from(`[[block]] struct Time { seconds : f32; frames : i32; };`).Time; const timeBuffer = makeUniformBuffer(device, timeUniforms); const context = { @@ -68,7 +68,7 @@ export default async (canvas, config) => { pipeline.forEach((step) => step.setSize(...canvasSize)); } - device.queue.writeBuffer(timeBuffer, 0, timeUniforms.write({ seconds: (now - start) / 1000, frames })); + device.queue.writeBuffer(timeBuffer, 0, timeUniforms.toBuffer({ seconds: (now - start) / 1000, frames })); frames++; const encoder = device.createCommandEncoder(); diff --git a/js/webgpu/palettePass.js b/js/webgpu/palettePass.js index 5c8d494..c9c2b33 100644 --- a/js/webgpu/palettePass.js +++ b/js/webgpu/palettePass.js @@ -1,4 +1,4 @@ -import uniforms from "/lib/gpu-uniforms.js"; +import { structs } from "/lib/gpu-buffer.js"; import { loadShader, makeUniformBuffer, makeBindGroup, makePassFBO, makePass } from "./utils.js"; // Maps the brightness of the rendered rain and bloom to colors @@ -123,7 +123,7 @@ export default (context, getInputs) => { }, }); - const paletteShaderUniforms = uniforms.read(paletteShader.code); + const paletteShaderUniforms = structs.from(paletteShader.code); const configUniforms = paletteShaderUniforms.Config; configBuffer = makeUniformBuffer(device, configUniforms, { ditherMagnitude: 0.05, backgroundColor: config.backgroundColor }); diff --git a/js/webgpu/rainPass.js b/js/webgpu/rainPass.js index c133566..2e6aae7 100644 --- a/js/webgpu/rainPass.js +++ b/js/webgpu/rainPass.js @@ -1,4 +1,4 @@ -import uniforms from "/lib/gpu-uniforms.js"; +import { structs, byteSizeOf } from "/lib/gpu-buffer.js"; import { makePassFBO, loadTexture, loadShader, makeUniformBuffer, makeBindGroup, makePass } from "./utils.js"; const { mat4, vec3 } = glMatrix; @@ -47,7 +47,7 @@ export default (context, getInputs) => { const numQuads = config.volumetric ? numCells : 1; const cellsBuffer = device.createBuffer({ - size: numCells * uniforms.byteSizeOf("vec4"), + size: numCells * byteSizeOf("vec4"), usage: GPUBufferUsage.STORAGE, }); @@ -91,7 +91,7 @@ export default (context, getInputs) => { const ready = (async () => { const [msdfTexture, rainShader] = await Promise.all(assets); - const rainShaderUniforms = uniforms.read(rainShader.code); + const rainShaderUniforms = structs.from(rainShader.code); configBuffer = makeConfigBuffer(device, rainShaderUniforms.Config, config, density, gridSize); sceneUniforms = rainShaderUniforms.Scene; @@ -147,7 +147,7 @@ export default (context, getInputs) => { mat4.perspectiveZO(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000); } const screenSize = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1]; - device.queue.writeBuffer(sceneBuffer, 0, sceneUniforms.write({ screenSize, camera, transform })); + device.queue.writeBuffer(sceneBuffer, 0, sceneUniforms.toBuffer({ screenSize, camera, transform })); // Update output?.destroy(); diff --git a/js/webgpu/resurrectionPass.js b/js/webgpu/resurrectionPass.js index ab56da4..27b2c8f 100644 --- a/js/webgpu/resurrectionPass.js +++ b/js/webgpu/resurrectionPass.js @@ -1,4 +1,4 @@ -import uniforms from "/lib/gpu-uniforms.js"; +import { structs } from "/lib/gpu-buffer.js"; import { loadShader, makeUniformBuffer, makePassFBO, makePass } from "./utils.js"; // Matrix Resurrections isn't in theaters yet, @@ -56,7 +56,7 @@ export default (context, getInputs) => { }, }); - const configUniforms = uniforms.read(resurrectionShader.code).Config; + const configUniforms = structs.from(resurrectionShader.code).Config; configBuffer = makeUniformBuffer(device, configUniforms, { ditherMagnitude: 0.05, backgroundColor: config.backgroundColor }); })(); diff --git a/js/webgpu/stripePass.js b/js/webgpu/stripePass.js index 2009da2..a8d282f 100644 --- a/js/webgpu/stripePass.js +++ b/js/webgpu/stripePass.js @@ -1,4 +1,4 @@ -import uniforms from "/lib/gpu-uniforms.js"; +import { structs } from "/lib/gpu-buffer.js"; import { loadShader, make1DTexture, makeUniformBuffer, makeBindGroup, makePassFBO, makePass } from "./utils.js"; // Multiplies the rendered rain and bloom by a 1D gradient texture @@ -91,7 +91,7 @@ export default (context, getInputs) => { }, }); - const configUniforms = uniforms.read(stripeShader.code).Config; + const configUniforms = structs.from(stripeShader.code).Config; configBuffer = makeUniformBuffer(device, configUniforms, { ditherMagnitude: 0.05, backgroundColor: config.backgroundColor }); })(); diff --git a/js/webgpu/utils.js b/js/webgpu/utils.js index 0d6a398..e3df22c 100644 --- a/js/webgpu/utils.js +++ b/js/webgpu/utils.js @@ -50,7 +50,7 @@ const makeUniformBuffer = (device, uniforms, data = null) => { mappedAtCreation: data != null, }); if (data != null) { - uniforms.write(data, buffer.getMappedRange()); + uniforms.toBuffer(data, buffer.getMappedRange()); buffer.unmap(); } return buffer; diff --git a/lib/gpu-uniforms.js b/lib/gpu-buffer.js similarity index 79% rename from lib/gpu-uniforms.js rename to lib/gpu-buffer.js index 4d07d12..657dc69 100644 --- a/lib/gpu-uniforms.js +++ b/lib/gpu-buffer.js @@ -12,50 +12,49 @@ * **/ -const BYTES_PER_ELEMENT = 4; const zero = () => 0; const array = (n) => () => Array(n).fill(0); const simpleTypes = { - ["i32"]: [1, 1, "i32", zero], - ["u32"]: [1, 1, "u32", zero], - ["f32"]: [1, 1, "f32", zero], + ["i32"]: [4, 4, "i32", zero], + ["u32"]: [4, 4, "u32", zero], + ["f32"]: [4, 4, "f32", zero], - ["atomic"]: [1, 1, "i32", zero], - ["atomic"]: [1, 1, "u32", zero], - ["atomic"]: [1, 1, "f32", zero], + ["atomic"]: [4, 4, "i32", zero], + ["atomic"]: [4, 4, "u32", zero], + ["atomic"]: [4, 4, "f32", zero], - ["vec2"]: [2, 2, "i32", array(2)], - ["vec2"]: [2, 2, "u32", array(2)], - ["vec2"]: [2, 2, "f32", array(2)], + ["vec2"]: [8, 8, "i32", array(2)], + ["vec2"]: [8, 8, "u32", array(2)], + ["vec2"]: [8, 8, "f32", array(2)], - ["vec3"]: [4, 3, "i32", array(3)], - ["vec3"]: [4, 3, "u32", array(3)], - ["vec3"]: [4, 3, "f32", array(3)], + ["vec3"]: [16, 12, "i32", array(3)], + ["vec3"]: [16, 12, "u32", array(3)], + ["vec3"]: [16, 12, "f32", array(3)], - ["vec4"]: [4, 4, "i32", array(4)], - ["vec4"]: [4, 4, "u32", array(4)], - ["vec4"]: [4, 4, "f32", array(4)], + ["vec4"]: [16, 16, "i32", array(4)], + ["vec4"]: [16, 16, "u32", array(4)], + ["vec4"]: [16, 16, "f32", array(4)], - ["mat2x2"]: [2, 4, "f32", array(2 * 2)], - ["mat3x2"]: [2, 6, "f32", array(3 * 2)], - ["mat4x2"]: [2, 8, "f32", array(4 * 2)], - ["mat2x3"]: [4, 8, "f32", array(2 * 3)], - ["mat3x3"]: [4, 12, "f32", array(3 * 3)], - ["mat4x3"]: [4, 16, "f32", array(4 * 3)], - ["mat2x4"]: [4, 8, "f32", array(2 * 4)], - ["mat3x4"]: [4, 12, "f32", array(3 * 4)], - ["mat4x4"]: [4, 16, "f32", array(4 * 4)], + ["mat2x2"]: [8, 16, "f32", array(2 * 2)], + ["mat3x2"]: [8, 24, "f32", array(3 * 2)], + ["mat4x2"]: [8, 32, "f32", array(4 * 2)], + ["mat2x3"]: [16, 32, "f32", array(2 * 3)], + ["mat3x3"]: [16, 48, "f32", array(3 * 3)], + ["mat4x3"]: [16, 64, "f32", array(4 * 3)], + ["mat2x4"]: [16, 32, "f32", array(2 * 4)], + ["mat3x4"]: [16, 48, "f32", array(3 * 4)], + ["mat4x4"]: [16, 64, "f32", array(4 * 4)], }; const getTypeData = (type, attributes, otherStructLayouts) => { if (simpleTypes[type] != null) { let [align, size, baseType, defaultValue] = simpleTypes[type]; if (attributes.align != null) { - align = parseInt(attributes.align) / 4; + align = parseInt(attributes.align); } if (attributes.size != null) { - size = parseInt(attributes.size) / 4; + size = parseInt(attributes.size); } return { baseType, @@ -67,10 +66,10 @@ const getTypeData = (type, attributes, otherStructLayouts) => { const innerLayout = otherStructLayouts[type]; let { align, size } = innerLayout; if (attributes.align != null) { - align = parseInt(attributes.align) / 4; + align = parseInt(attributes.align); } if (attributes.size != null) { - size = parseInt(attributes.size) / 4; + size = parseInt(attributes.size); } return { isStruct: true, @@ -153,7 +152,7 @@ const parseStruct = (str, structLayouts) => { byteOffset += typeData.size; } - const minSizeInBytes = byteOffset * BYTES_PER_ELEMENT; + const minSizeInBytes = byteOffset; const align = Math.max(...fields.map((field) => field.align)); const size = Math.ceil(minSizeInBytes / align) * align; return { name, fields, size, align }; @@ -199,7 +198,7 @@ const writeField = (allLayouts, field, value, views, byteOffset, warnMissingFiel } else { const view = views[field.baseType]; const array = value[Symbol.iterator] == null ? [Number(value)] : value; - view.set(array, byteOffset + field.byteOffset); + view.set(array, (byteOffset + field.byteOffset) / 4); } }; @@ -208,7 +207,7 @@ const makeGenerator = (layout, structLayouts) => { return Object.freeze({ minSize, create: () => makeDataForLayout(structLayouts, layout), - write: (object, destination, warnMissingFields = false) => { + toBuffer: (object, destination, warnMissingFields = false) => { if (destination == null) { let size = layout.size; const lastField = layout.fields[layout.fields.length - 1]; @@ -233,15 +232,16 @@ const makeGenerator = (layout, structLayouts) => { }); }; -const api = Object.freeze({ - read: (wgsl) => { +const byteSizeOf = (simpleType) => simpleTypes[simpleType]?.[1]; + +const structs = Object.freeze({ + from: (wgsl) => { if (typeof wgsl !== "string") { throw new Error("Input is not a string."); } const structLayouts = parseStructLayoutsFromShader(wgsl); return Object.fromEntries(Object.entries(structLayouts).map(([name, layout]) => [name, makeGenerator(layout, structLayouts)])); }, - byteSizeOf: (simpleType) => simpleTypes[simpleType][1] * BYTES_PER_ELEMENT, }); -export default api; +export { structs, byteSizeOf };