diff --git a/TODO.txt b/TODO.txt index b6b783f..cd3d9d2 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,8 +4,11 @@ WebGPU First decent rainRender Port compute pass 100% Compute entry point can live in the same wgsl file, I think - use textureLoad for texel access in render pipeline + ping-pong buffers + Bind both ping-pong buffers in render pass + The frame integer can be used by the render code to reference the correct one Reorder the config fields + Reconsider how the bind groups are organized? Render target Resize accordingly Put in its own module @@ -13,6 +16,7 @@ WebGPU The other passes should be a breeze Just add them to the render bundle + Update links in issues std140 diff --git a/js/webgpu/main.js b/js/webgpu/main.js index b487192..e9e0118 100644 --- a/js/webgpu/main.js +++ b/js/webgpu/main.js @@ -53,6 +53,8 @@ export default async (canvas, config) => { const configData = [ // common { name: "animationSpeed", type: "f32", value: config.animationSpeed }, + { name: "glyphSequenceLength", type: "i32", value: config.glyphSequenceLength }, + { name: "glyphTextureColumns", type: "i32", value: config.glyphTextureColumns }, { name: "glyphHeightToWidth", type: "f32", value: config.glyphHeightToWidth }, { name: "resurrectingCodeRatio", type: "f32", value: config.resurrectingCodeRatio }, { name: "gridSize", type: "vec2", value: gridSize }, @@ -94,19 +96,6 @@ export default async (canvas, config) => { configData.map((field) => field.value) ); - const msdfData = [ - { name: "glyphSequenceLength", type: "i32", value: config.glyphSequenceLength }, - { name: "glyphTextureColumns", type: "i32", value: config.glyphTextureColumns }, - ]; - console.table(msdfData); - - const msdfLayout = std140(msdfData.map((field) => field.type)); - const msdfBuffer = makeUniformBuffer( - device, - msdfLayout, - msdfData.map((field) => field.value) - ); - const timeLayout = std140(["f32", "i32"]); const timeBuffer = makeUniformBuffer(device, timeLayout); @@ -135,6 +124,13 @@ export default async (canvas, config) => { const rainRenderShaderModule = device.createShaderModule({ code: rainRenderShader }); + const rainComputePipeline = device.createComputePipeline({ + compute: { + module: rainRenderShaderModule, + entryPoint: "computeMain", + }, + }); + const additiveBlendComponent = { operation: "add", srcFactor: "one", @@ -161,9 +157,19 @@ export default async (canvas, config) => { }, }); - const bindGroup = device.createBindGroup({ + const renderBindGroup = device.createBindGroup({ layout: rainRenderPipeline.getBindGroupLayout(0), - entries: [configBuffer, msdfBuffer, msdfSampler, msdfTexture.createView(), timeBuffer, sceneBuffer] + entries: [configBuffer, timeBuffer, sceneBuffer, msdfSampler, msdfTexture.createView()] + .map((resource) => (resource instanceof GPUBuffer ? { buffer: resource } : resource)) + .map((resource, binding) => ({ + binding, + resource, + })), + }); + + const computeBindGroup = device.createBindGroup({ + layout: rainComputePipeline.getBindGroupLayout(0), + entries: [configBuffer, timeBuffer] .map((resource) => (resource instanceof GPUBuffer ? { buffer: resource } : resource)) .map((resource, binding) => ({ binding, @@ -176,7 +182,7 @@ export default async (canvas, config) => { }); bundleEncoder.setPipeline(rainRenderPipeline); - bundleEncoder.setBindGroup(0, bindGroup); + bundleEncoder.setBindGroup(0, renderBindGroup); bundleEncoder.draw(numVerticesPerQuad * numQuads, 1, 0, 0); const renderBundles = [bundleEncoder.finish()]; @@ -209,6 +215,13 @@ export default async (canvas, config) => { renderPassConfig.colorAttachments[0].view = canvasContext.getCurrentTexture().createView(); const encoder = device.createCommandEncoder(); + + const computePass = encoder.beginComputePass(); + computePass.setPipeline(rainComputePipeline); + computePass.setBindGroup(0, computeBindGroup); + computePass.dispatch(...gridSize, 1); + computePass.endPass(); + const renderPass = encoder.beginRenderPass(renderPassConfig); renderPass.executeBundles(renderBundles); renderPass.endPass(); diff --git a/shaders/wgsl/rainRenderPass.wgsl b/shaders/wgsl/rainRenderPass.wgsl index a0ea19a..f14a9f6 100644 --- a/shaders/wgsl/rainRenderPass.wgsl +++ b/shaders/wgsl/rainRenderPass.wgsl @@ -9,6 +9,8 @@ let SQRT_5 : f32 = 2.23606797749979; [[block]] struct Config { // common animationSpeed : f32; + glyphSequenceLength : i32; + glyphTextureColumns : i32; glyphHeightToWidth : f32; resurrectingCodeRatio : f32; gridSize : vec2; @@ -43,29 +45,30 @@ let SQRT_5 : f32 = 2.23606797749979; }; [[group(0), binding(0)]] var config : Config; -[[block]] struct MSDF { - glyphSequenceLength : i32; - glyphTextureColumns : i32; -}; -[[group(0), binding(1)]] var msdf : MSDF; -[[group(0), binding(2)]] var msdfSampler : sampler; -[[group(0), binding(3)]] var msdfTexture : texture_2d; - [[block]] struct Time { seconds : f32; frames : i32; }; -[[group(0), binding(4)]] var time : Time; +[[group(0), binding(1)]] var time : Time; [[block]] struct Scene { screenSize : vec2; camera : mat4x4; transform : mat4x4; }; -[[group(0), binding(5)]] var scene : Scene; +[[group(0), binding(2)]] var scene : Scene; + +[[group(0), binding(3)]] var msdfSampler : sampler; +[[group(0), binding(4)]] var msdfTexture : texture_2d; + + // Shader params +struct ComputeInput { + [[builtin(global_invocation_id)]] id : vec3; +}; + struct VertInput { [[builtin(vertex_index)]] index : u32; }; @@ -100,6 +103,13 @@ fn wobble(x : f32) -> f32 { return x + 0.3 * sin(SQRT_2 * x) + 0.2 * sin(SQRT_5 * x); } +// Compute shader + +[[stage(compute), workgroup_size(1, 1, 1)]] fn computeMain(input : ComputeInput) { + var hasSun = bool(config.hasSun); // TODO: remove + var seconds = time.seconds; // TODO: remove +} + // Vertex shader [[stage(vertex)]] fn vertMain(input : VertInput) -> VertOutput { @@ -179,9 +189,9 @@ fn median3(i : vec3) -> f32 { } fn getSymbolUV(glyphCycle : f32) -> vec2 { - var symbol = i32(f32(msdf.glyphSequenceLength) * glyphCycle); - var symbolX = symbol % msdf.glyphTextureColumns; - var symbolY = symbol / msdf.glyphTextureColumns; + var symbol = i32(f32(config.glyphSequenceLength) * glyphCycle); + var symbolX = symbol % config.glyphTextureColumns; + var symbolY = symbol / config.glyphTextureColumns; return vec2(f32(symbolX), f32(symbolY)); } @@ -233,7 +243,7 @@ fn getSymbolUV(glyphCycle : f32) -> vec2 { glyphUV = glyphUV - 0.5; glyphUV = glyphUV * clamp(1.0 - config.glyphEdgeCrop, 0.0, 1.0); glyphUV = glyphUV + 0.5; - var msdfUV = (glyphUV + symbolUV) / f32(msdf.glyphTextureColumns); + var msdfUV = (glyphUV + symbolUV) / f32(config.glyphTextureColumns); // MSDF : calculate brightness of fragment based on distance to shape var dist = textureSample(msdfTexture, msdfSampler, msdfUV).rgb;