From c716d30808f13b0cc6859084675c0bd470db3e11 Mon Sep 17 00:00:00 2001 From: Rezmason Date: Wed, 27 Oct 2021 18:16:07 -0700 Subject: [PATCH] Merged the vertex and fragment shader WGSL files, because their bindings can't collide anyhow. The rain render shader now accepts a bind group of time uniforms, which change on every frame, as well as MSDF uniforms, along with an MSDF sampler and texture that's loaded in from the PNG. The shader currently renders the correct grid of quads for volumetric mode, displays the first glyph raw in each one, and every sixty frames, turns on and off the blue channel. --- js/webgpu_main.js | 118 +++++++++++++++++++++++++++++------- shaders/rainPass.frag.wgsl | 3 - shaders/rainPass.vert.wgsl | 39 ------------ shaders/rainRenderPass.wgsl | 69 +++++++++++++++++++++ 4 files changed, 166 insertions(+), 63 deletions(-) delete mode 100644 shaders/rainPass.frag.wgsl delete mode 100644 shaders/rainPass.vert.wgsl create mode 100644 shaders/rainRenderPass.wgsl diff --git a/js/webgpu_main.js b/js/webgpu_main.js index f555f2c..c057ea6 100644 --- a/js/webgpu_main.js +++ b/js/webgpu_main.js @@ -3,6 +3,32 @@ const getCanvasSize = (canvas) => { return [canvas.clientWidth * devicePixelRatio, canvas.clientHeight * devicePixelRatio]; }; +const loadTexture = async (device, url) => { + const image = new Image(); + image.crossOrigin = "anonymous"; + image.src = url; + await image.decode(); + const imageBitmap = await createImageBitmap(image); + + const texture = device.createTexture({ + size: [imageBitmap.width, imageBitmap.height, 1], + format: "rgba8unorm", + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, // Which of these are necessary? + }); + + device.queue.copyExternalImageToTexture( + { + source: imageBitmap, + }, + { + texture: texture, + }, + [imageBitmap.width, imageBitmap.height] + ); + + return texture; +}; + export default async (canvas, config) => { console.log(config); @@ -37,9 +63,11 @@ export default async (canvas, config) => { ], }; - // TODO: create buffers, uniforms, textures, samplers + const sampler = device.createSampler(); - const uniformBufferSize = 4 * (1 + 1); + const msdfTexture = await loadTexture(device, config.glyphTexURL); + + const uniformBufferSize = 4 * (1 * 1 + 1 * 1); const uniformBuffer = device.createBuffer({ size: uniformBufferSize, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, // Which of these are necessary? @@ -48,9 +76,24 @@ export default async (canvas, config) => { new Int32Array(uniformBuffer.getMappedRange()).set([numColumns, numRows]); uniformBuffer.unmap(); - // TODO: create pipelines, bind groups, shaders + const msdfUniformBufferSize = 4 * (1 * 1); + const msdfUniformBuffer = device.createBuffer({ + size: msdfUniformBufferSize, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.FRAGMENT | GPUBufferUsage.COPY_DST, // Which of these are necessary? + mappedAtCreation: true, + }); + new Int32Array(msdfUniformBuffer.getMappedRange()).set([config.glyphTextureColumns]); + msdfUniformBuffer.unmap(); - const [vert, frag] = await Promise.all(["shaders/rainPass.vert.wgsl", "shaders/rainPass.frag.wgsl"].map(async (path) => (await fetch(path)).text())); + const timeBufferSize = 4 * (1 * 1 + 1 * 1); + const timeBuffer = device.createBuffer({ + size: uniformBufferSize, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.VERTEX | GPUBufferUsage.FRAGMENT | GPUBufferUsage.COMPUTE | GPUBufferUsage.COPY_DST, // Which of these are necessary? + }); + + const [rainRenderShader] = await Promise.all(["shaders/rainRenderPass.wgsl"].map(async (path) => (await fetch(path)).text())); + + const rainRenderShaderModule = device.createShaderModule({ code: rainRenderShader }); const additiveBlendComponent = { operation: "add", @@ -65,16 +108,12 @@ export default async (canvas, config) => { const rainRenderPipeline = device.createRenderPipeline({ vertex: { - module: device.createShaderModule({ - code: vert, - }), - entryPoint: "main", + module: rainRenderShaderModule, + entryPoint: "vertMain", }, fragment: { - module: device.createShaderModule({ - code: frag, - }), - entryPoint: "main", + module: rainRenderShaderModule, + entryPoint: "fragMain", targets: [ { format: presentationFormat, @@ -85,7 +124,7 @@ export default async (canvas, config) => { primitive: { // What happens if this isn't here? topology: "triangle-list", // What happens if this isn't here? - cullMode: "none", // What happens if this isn't here? + cullMode: "back", // What happens if this isn't here? }, }); @@ -101,18 +140,55 @@ export default async (canvas, config) => { ], }); + const msdfBindGroup = device.createBindGroup({ + layout: rainRenderPipeline.getBindGroupLayout(1), + entries: [ + { + binding: 0, + resource: { + buffer: msdfUniformBuffer, + }, + }, + { + binding: 1, + resource: sampler, + }, + { + binding: 2, + resource: msdfTexture.createView(), + }, + ], + }); + + const timeBindGroup = device.createBindGroup({ + layout: rainRenderPipeline.getBindGroupLayout(2), + entries: [ + { + binding: 0, + resource: { + buffer: timeBuffer, + }, + }, + ], + }); + + const rainRenderPipelineBindGroups = [uniformBindGroup, msdfBindGroup, timeBindGroup]; + const bundleEncoder = device.createRenderBundleEncoder({ colorFormats: [presentationFormat], }); bundleEncoder.setPipeline(rainRenderPipeline); - bundleEncoder.setBindGroup(0, uniformBindGroup); - bundleEncoder.draw(6 * numColumns * numRows, 1, 0, 0); + rainRenderPipelineBindGroups.forEach((bindGroup, index) => { + bundleEncoder.setBindGroup(index, bindGroup); + }); + const numQuads = numColumns * numRows; + bundleEncoder.draw(6 * numQuads, 1, 0, 0); const renderBundles = [bundleEncoder.finish()]; - // queue.writeBuffer(uniformBuffer, 0, new Int32Array([numColumns, numRows])); + let frame = 0; - const frame = (now) => { + const renderLoop = (now) => { const canvasSize = getCanvasSize(canvas); if (canvasSize[0] !== canvasConfig.size[0] || canvasSize[1] !== canvasConfig.size[1]) { canvasConfig.size = canvasSize; @@ -123,9 +199,9 @@ export default async (canvas, config) => { // TODO: update camera matrix, screen size, write to queue } - // TODO: update the uniforms that change, write to queue + queue.writeBuffer(timeBuffer, 0, new Int32Array([now, frame])); + frame++; - renderPassConfig.colorAttachments[0].loadValue.r = Math.sin((now / 1000) * 2) / 2 + 0.5; renderPassConfig.colorAttachments[0].view = canvasContext.getCurrentTexture().createView(); const encoder = device.createCommandEncoder(); @@ -135,8 +211,8 @@ export default async (canvas, config) => { const commandBuffer = encoder.finish(); queue.submit([commandBuffer]); - requestAnimationFrame(frame); + requestAnimationFrame(renderLoop); }; - requestAnimationFrame(frame); + requestAnimationFrame(renderLoop); }; diff --git a/shaders/rainPass.frag.wgsl b/shaders/rainPass.frag.wgsl deleted file mode 100644 index f9b3092..0000000 --- a/shaders/rainPass.frag.wgsl +++ /dev/null @@ -1,3 +0,0 @@ -[[stage(fragment)]] fn main([[location(0)]] UV : vec2) -> [[location(0)]] vec4 { - return vec4(0.0, UV, 1.0); -} diff --git a/shaders/rainPass.vert.wgsl b/shaders/rainPass.vert.wgsl deleted file mode 100644 index a589d2f..0000000 --- a/shaders/rainPass.vert.wgsl +++ /dev/null @@ -1,39 +0,0 @@ -[[block]] struct Uniforms { - numColumns: i32; - numRows: i32; -}; -[[binding(0), group(0)]] var uniforms : Uniforms; - -struct VertexOutput { - [[builtin(position)]] Position : vec4; - [[location(0)]] UV : vec2; -}; - -[[stage(vertex)]] fn main([[builtin(vertex_index)]] VertexIndex : u32) -> VertexOutput { - - var i = i32(VertexIndex); - var quadIndex = i / 6; - - var cornerPosition = vec2( - f32(i % 2), - f32(((i + 1) % 6 / 3)) - ); - - var x = uniforms.numColumns; - - var position = cornerPosition; - position = position + vec2( - f32(quadIndex % uniforms.numColumns), - f32(quadIndex / uniforms.numColumns) - ); - position = position / vec2( - f32(uniforms.numColumns), - f32(uniforms.numRows) - ); - position = position * 2.0 - 1.0; - - return VertexOutput( - vec4(position, 1.0, 1.0), - cornerPosition - ); -} diff --git a/shaders/rainRenderPass.wgsl b/shaders/rainRenderPass.wgsl new file mode 100644 index 0000000..995c62f --- /dev/null +++ b/shaders/rainRenderPass.wgsl @@ -0,0 +1,69 @@ +let PI:f32 = 3.14159265359; +let TWO_PI:f32 = 6.28318530718; + +[[block]] struct Uniforms { + numColumns: i32; + numRows: i32; +}; +[[group(0), binding(0)]] var uniforms:Uniforms; + +[[block]] struct MSDFUniforms { + numColumns: i32; +}; +[[group(1), binding(0)]] var msdfUniforms:MSDFUniforms; +[[group(1), binding(1)]] var msdfSampler: sampler; +[[group(1), binding(2)]] var msdfTexture: texture_2d; + +[[block]] struct TimeUniforms { + time: i32; + frame: i32; +}; +[[group(2), binding(0)]] var timeUniforms:TimeUniforms; + +// Vertex shader + +struct VertexOutput { + [[builtin(position)]] Position:vec4; + [[location(0)]] UV:vec2; +}; + +[[stage(vertex)]] fn vertMain([[builtin(vertex_index)]] VertexIndex:u32) -> VertexOutput { + + var i = i32(VertexIndex); + var quadIndex = i / 6; + + var cornerPosition = vec2( + f32(i % 2), + f32(((i + 1) % 6 / 3)) + ); + + var x = uniforms.numColumns; + + var position = cornerPosition; + position = position + vec2( + f32(quadIndex % uniforms.numColumns), + f32(quadIndex / uniforms.numColumns) + ); + position = position / vec2( + f32(uniforms.numColumns), + f32(uniforms.numRows) + ); + position = 1.0 - position * 2.0; + // position.x = position.x + f32(quadIndex) * 0.01; + + return VertexOutput( + vec4(position, 1.0, 1.0), + cornerPosition + ); +} + +// Fragment shader + +[[stage(fragment)]] fn fragMain([[location(0)]] UV:vec2) -> [[location(0)]] vec4 { + var msdf:vec4 = textureSample(msdfTexture, msdfSampler, UV / f32(msdfUniforms.numColumns)); + // msdf.b = msdf.b * (sin(f32(timeUniforms.time) / 1000.0 * TWO_PI) * 0.5 + 0.5); + msdf.b = msdf.b * f32(timeUniforms.frame / 60 % 2); + var time = timeUniforms.time; + + return msdf; +}