commit 1a07df64cab20468a9fbfec556d66fa5e15b74aa Author: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Wed Oct 30 20:12:31 2024 +0100 compute diff --git a/index.html b/index.html new file mode 100644 index 0000000..bd3a7b0 --- /dev/null +++ b/index.html @@ -0,0 +1,45 @@ + + + + + + WebGPU Mandelbrot + + + +
+

You are using an unsupported browser.

+

+ WebGPU is still experimental and does not have wide browser support. +

+

+ Check out caniuse.com/webgpu to + see which browsers support it. +

+

+ I recommend using Chrome, which has working implementation on stable at + the time of writing. Firefox may have one at the time of reading, in + which case I recommend it instead. +

+

Also check out the Chrome Dev guide when using Chrome

+
+ + + + + + + + diff --git a/index.js b/index.js new file mode 100644 index 0000000..b8ff60c --- /dev/null +++ b/index.js @@ -0,0 +1,121 @@ +function error(msg) { + const elem = document.getElementById("error"); + elem.innerText = msg; + elem.classList.remove("hidden"); +} + +let device; +let shaderModule; + +async function init() { + const unsupportedBrowser = document.getElementById("unsupported-browser"); + if (navigator.gpu) { + unsupportedBrowser.classList.add("hidden"); + } else { + return; + } + + const adapter = await navigator.gpu.requestAdapter(); + if (!adapter) { + error( + "failed to get adapter from navigator.gpu. it looks like your environment does not have a GPU or is not supported." + ); + unsupportedBrowser.classList.remove("hidden"); + return; + } + + device = await adapter.requestDevice(); + if (!device) { + error( + "failed to get device from WebGPU adapter. it looks like your environment does not have a GPU or is not supported." + ); + unsupportedBrowser.classList.remove("hidden"); + return; + } + + const shaderResp = await fetch("mandelbrot.wgsl"); + if (!shaderResp.ok) { + error("failed to load shader"); + return; + } + const shader = await shaderResp.text(); + + shaderModule = device.createShaderModule({ + code: shader, + }); + + document.getElementById("render-me").addEventListener("click", doStuff); +} + +async function doStuff() { + const output = device.createBuffer({ + size: 1000, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + const stagingBuffer = device.createBuffer({ + size: 1000, + usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, + }); + + const bindGroupLayout = device.createBindGroupLayout({ + entries: [ + { + binding: 0, + visibility: GPUShaderStage.COMPUTE, + buffer: { + type: "storage", + }, + }, + ], + }); + + const bindGroup = device.createBindGroup({ + layout: bindGroupLayout, + entries: [ + { + binding: 0, + resource: { + buffer: output, + }, + }, + ], + }); + + const computePipeline = device.createComputePipeline({ + layout: device.createPipelineLayout({ + bindGroupLayouts: [bindGroupLayout], + }), + compute: { + module: shaderModule, + entryPoint: "main", + }, + }); + + const commandEncoder = device.createCommandEncoder(); + const passEncoder = commandEncoder.beginComputePass(); + passEncoder.setPipeline(computePipeline); + passEncoder.setBindGroup(0, bindGroup); + passEncoder.dispatchWorkgroups(Math.ceil(1000 / 64)); + passEncoder.end(); + commandEncoder.copyBufferToBuffer( + output, + 0, // source offset + stagingBuffer, + 0, // destination offest + 1000 + ); + + device.queue.submit([commandEncoder.finish()]); + + await stagingBuffer.mapAsync( + GPUMapMode.READ, + 0, // offset + 1000 // length + ); + const copyArrayBuffer = stagingBuffer.getMappedRange(0, 1000); + const data = copyArrayBuffer.slice(); + stagingBuffer.unmap(); + console.log(new Float32Array(data)); +} + +init(); diff --git a/mandelbrot.wgsl b/mandelbrot.wgsl new file mode 100644 index 0000000..6d53156 --- /dev/null +++ b/mandelbrot.wgsl @@ -0,0 +1,19 @@ +@group(0) @binding(0) +var output: array; + +@compute @workgroup_size(64) +fn main( + @builtin(global_invocation_id) + global_id : vec3u, + + @builtin(local_invocation_id) + local_id : vec3u, +) { + // Avoid accessing the buffer out of bounds + if (global_id.x >= 1000) { + return; + } + + output[global_id.x] = + f32(local_id.x); +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..4feb36b --- /dev/null +++ b/shell.nix @@ -0,0 +1,7 @@ +# doesn't work. +let moz_overlay = import (builtins.fetchTarball "https://github.com/mozilla/nixpkgs-mozilla/archive/9b11a87c0cc54e308fa83aac5b4ee1816d5418a2.tar.gz"); +in + +{ pkgs ? import { overlays = [ moz_overlay ]; } }: pkgs.mkShell { + packages = [ pkgs.latest.firefox-nightly-bin ]; +}