mirror of
https://github.com/Noratrieb/webgpu-mandelbrot.git
synced 2026-01-14 17:05:02 +01:00
mandelbrot
This commit is contained in:
parent
1a07df64ca
commit
c6fd528af8
3 changed files with 150 additions and 80 deletions
15
index.html
15
index.html
|
|
@ -18,7 +18,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="unsupported-browser">
|
<div id="unsupported-browser">
|
||||||
<p class="bold">You are using an unsupported browser.</p>
|
<p class="bold">You are using an unsupported browser or platform.</p>
|
||||||
<p>
|
<p>
|
||||||
WebGPU is still experimental and does not have wide browser support.
|
WebGPU is still experimental and does not have wide browser support.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -31,14 +31,23 @@
|
||||||
the time of writing. Firefox may have one at the time of reading, in
|
the time of writing. Firefox may have one at the time of reading, in
|
||||||
which case I recommend it instead.
|
which case I recommend it instead.
|
||||||
</p>
|
</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>
|
<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>
|
</div>
|
||||||
<noscript>
|
<noscript>
|
||||||
This website uses WebGPU, which requires JavaScript to operate.
|
This website uses WebGPU, which requires JavaScript to operate.
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="error" class="hidden error"></div>
|
<div id="error" class="hidden error"></div>
|
||||||
|
|
||||||
<button id="render-me">Render Me</button>
|
<div>
|
||||||
|
<canvas id="result" height="800" width="1200"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script type="module" src="./index.js"></script>
|
<script type="module" src="./index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
144
index.js
144
index.js
|
|
@ -44,78 +44,104 @@ async function init() {
|
||||||
code: shader,
|
code: shader,
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("render-me").addEventListener("click", doStuff);
|
// document.getElementById("render-me").addEventListener("click", doStuff);
|
||||||
}
|
|
||||||
|
|
||||||
async function doStuff() {
|
const canvas = document.getElementById("result");
|
||||||
const output = device.createBuffer({
|
const context = canvas.getContext("webgpu");
|
||||||
size: 1000,
|
context.configure({
|
||||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
device: device,
|
||||||
});
|
format: navigator.gpu.getPreferredCanvasFormat(),
|
||||||
const stagingBuffer = device.createBuffer({
|
alphaMode: "premultiplied",
|
||||||
size: 1000,
|
|
||||||
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
|
||||||
});
|
});
|
||||||
|
const vertices = new Float32Array([
|
||||||
|
// Triangle 1 (Blue)
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
1,
|
||||||
|
-1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
|
||||||
const bindGroupLayout = device.createBindGroupLayout({
|
-1, // Triangle 2 (Red)
|
||||||
entries: [
|
-1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
-1,
|
||||||
|
1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const vertexBuffer = device.createBuffer({
|
||||||
|
label: "Vertices",
|
||||||
|
size: vertices.byteLength, // make it big enough to store vertices in
|
||||||
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
||||||
|
});
|
||||||
|
device.queue.writeBuffer(vertexBuffer, 0, vertices);
|
||||||
|
|
||||||
|
const GRID_SIZE = 4;
|
||||||
|
const uniformArray = new Float32Array([GRID_SIZE, GRID_SIZE]);
|
||||||
|
const uniformBuffer = device.createBuffer({
|
||||||
|
label: "Grid uniforms",
|
||||||
|
size: uniformArray.byteLength,
|
||||||
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||||
|
});
|
||||||
|
device.queue.writeBuffer(uniformBuffer, 0, uniformArray);
|
||||||
|
|
||||||
|
const vertexBuffers = [
|
||||||
{
|
{
|
||||||
binding: 0,
|
attributes: [
|
||||||
visibility: GPUShaderStage.COMPUTE,
|
{
|
||||||
buffer: {
|
shaderLocation: 0,
|
||||||
type: "storage",
|
offset: 0,
|
||||||
},
|
format: "float32x2",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
arrayStride: 8,
|
||||||
|
stepMode: "vertex",
|
||||||
const bindGroup = device.createBindGroup({
|
|
||||||
layout: bindGroupLayout,
|
|
||||||
entries: [
|
|
||||||
{
|
|
||||||
binding: 0,
|
|
||||||
resource: {
|
|
||||||
buffer: output,
|
|
||||||
},
|
},
|
||||||
},
|
];
|
||||||
],
|
const pipelineDescriptor = {
|
||||||
});
|
label: "Render Pipeline",
|
||||||
|
vertex: {
|
||||||
const computePipeline = device.createComputePipeline({
|
|
||||||
layout: device.createPipelineLayout({
|
|
||||||
bindGroupLayouts: [bindGroupLayout],
|
|
||||||
}),
|
|
||||||
compute: {
|
|
||||||
module: shaderModule,
|
module: shaderModule,
|
||||||
entryPoint: "main",
|
entryPoint: "vertex_main",
|
||||||
|
buffers: vertexBuffers,
|
||||||
},
|
},
|
||||||
});
|
fragment: {
|
||||||
|
module: shaderModule,
|
||||||
|
entryPoint: "fragment_main",
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
format: navigator.gpu.getPreferredCanvasFormat(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
primitive: {
|
||||||
|
topology: "triangle-list",
|
||||||
|
},
|
||||||
|
layout: "auto",
|
||||||
|
};
|
||||||
|
const renderPipeline = device.createRenderPipeline(pipelineDescriptor);
|
||||||
const commandEncoder = device.createCommandEncoder();
|
const commandEncoder = device.createCommandEncoder();
|
||||||
const passEncoder = commandEncoder.beginComputePass();
|
const clearColor = { r: 0.0, g: 0.5, b: 1.0, a: 1.0 };
|
||||||
passEncoder.setPipeline(computePipeline);
|
|
||||||
passEncoder.setBindGroup(0, bindGroup);
|
const renderPassDescriptor = {
|
||||||
passEncoder.dispatchWorkgroups(Math.ceil(1000 / 64));
|
colorAttachments: [
|
||||||
|
{
|
||||||
|
clearValue: clearColor,
|
||||||
|
loadOp: "clear",
|
||||||
|
storeOp: "store",
|
||||||
|
view: context.getCurrentTexture().createView(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
|
||||||
|
passEncoder.setPipeline(renderPipeline);
|
||||||
|
passEncoder.setVertexBuffer(0, vertexBuffer);
|
||||||
|
passEncoder.draw(vertices.length / 2);
|
||||||
passEncoder.end();
|
passEncoder.end();
|
||||||
commandEncoder.copyBufferToBuffer(
|
|
||||||
output,
|
|
||||||
0, // source offset
|
|
||||||
stagingBuffer,
|
|
||||||
0, // destination offest
|
|
||||||
1000
|
|
||||||
);
|
|
||||||
|
|
||||||
device.queue.submit([commandEncoder.finish()]);
|
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();
|
init();
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,54 @@
|
||||||
@group(0) @binding(0)
|
const CANVAS: vec2f = vec2f(1200, 800);
|
||||||
var<storage, read_write> output: array<f32>;
|
|
||||||
|
|
||||||
@compute @workgroup_size(64)
|
@vertex
|
||||||
fn main(
|
fn vertex_main(@location(0) pos: vec2f) ->
|
||||||
@builtin(global_invocation_id)
|
@builtin(position) vec4f {
|
||||||
global_id : vec3u,
|
return vec4f(pos, 0, 1);
|
||||||
|
|
||||||
@builtin(local_invocation_id)
|
|
||||||
local_id : vec3u,
|
|
||||||
) {
|
|
||||||
// Avoid accessing the buffer out of bounds
|
|
||||||
if (global_id.x >= 1000) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output[global_id.x] =
|
@fragment
|
||||||
f32(local_id.x);
|
fn fragment_main(
|
||||||
|
@builtin(position) pos: vec4f
|
||||||
|
) -> @location(0) vec4f {
|
||||||
|
let step_size = vec2f(3.0, 2.0) / CANVAS;
|
||||||
|
let center = vec2f(-0.75, 0.0);
|
||||||
|
let offset = vec2f(
|
||||||
|
center.x - CANVAS.x / 2 * step_size.x,
|
||||||
|
-(center.y - CANVAS.y / 2.0 * step_size.y) - 2.0,
|
||||||
|
);
|
||||||
|
let sample_pos = offset + (step_size * vec2f(pos.x, pos.y));
|
||||||
|
|
||||||
|
let value = mandelbrot_pixel(sample_pos);
|
||||||
|
|
||||||
|
let multiplier = 1.0 - (value * value) / f32(ITERATIONS);
|
||||||
|
let i = 255.0 * multiplier;
|
||||||
|
|
||||||
|
return vec4f(i, i, i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ITERATIONS = 50000;
|
||||||
|
const THRESHOLD: f32 = 100;
|
||||||
|
|
||||||
|
fn mandelbrot_pixel(position: vec2f) -> f32 {
|
||||||
|
var n = vec2f(0, 0);
|
||||||
|
let c = position;
|
||||||
|
n = n + c;
|
||||||
|
|
||||||
|
var passed_threshold_after: i32 = ITERATIONS;
|
||||||
|
|
||||||
|
for (var i = 0; i < ITERATIONS; i += 1) {
|
||||||
|
n = cmul(n, n) + c;
|
||||||
|
if n.x > THRESHOLD || n.y > THRESHOLD && passed_threshold_after == ITERATIONS {
|
||||||
|
passed_threshold_after = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return f32(passed_threshold_after);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmul(a: vec2f, b: vec2f) -> vec2f {
|
||||||
|
return vec2f(
|
||||||
|
a.x * b.x - a.y * b.y,
|
||||||
|
a.x * b.y + a.y * b.x,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue