mirror of
https://github.com/Noratrieb/webgpu-mandelbrot.git
synced 2026-01-14 08:55:01 +01:00
compute
This commit is contained in:
commit
1a07df64ca
4 changed files with 192 additions and 0 deletions
45
index.html
Normal file
45
index.html
Normal 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
121
index.js
Normal 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
19
mandelbrot.wgsl
Normal 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
7
shell.nix
Normal 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 ];
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue