This commit is contained in:
nora 2024-10-30 20:12:31 +01:00
commit 1a07df64ca
4 changed files with 192 additions and 0 deletions

45
index.html Normal file
View file

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebGPU Mandelbrot</title>
<style>
.hidden {
display: none;
}
.error {
color: red;
}
.bold {
font-weight: bold;
}
</style>
</head>
<body>
<div id="unsupported-browser">
<p class="bold">You are using an unsupported browser.</p>
<p>
WebGPU is still experimental and does not have wide browser support.
</p>
<p>
Check out <a href="https://caniuse.com/webgpu">caniuse.com/webgpu</a> to
see which browsers support it.
</p>
<p>
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.
</p>
<p>Also check out the <a href="https://developer.chrome.com/docs/web-platform/webgpu/troubleshooting-tips">Chrome Dev guide</a> when using Chrome</p>
</div>
<noscript>
This website uses WebGPU, which requires JavaScript to operate.
</noscript>
<div id="error" class="hidden error"></div>
<button id="render-me">Render Me</button>
<script type="module" src="./index.js"></script>
</body>
</html>

121
index.js Normal file
View file

@ -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();

19
mandelbrot.wgsl Normal file
View file

@ -0,0 +1,19 @@
@group(0) @binding(0)
var<storage, read_write> output: array<f32>;
@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);
}

7
shell.nix Normal file
View file

@ -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 <nixpkgs> { overlays = [ moz_overlay ]; } }: pkgs.mkShell {
packages = [ pkgs.latest.firefox-nightly-bin ];
}