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; +}