mirror of
https://github.com/Noratrieb/webgpu-mandelbrot.git
synced 2026-01-14 08:55:01 +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>
|
||||
<body>
|
||||
<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>
|
||||
WebGPU is still experimental and does not have wide browser support.
|
||||
</p>
|
||||
|
|
@ -31,14 +31,23 @@
|
|||
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>
|
||||
<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>
|
||||
<div>
|
||||
<canvas id="result" height="800" width="1200"></canvas>
|
||||
</div>
|
||||
|
||||
<script type="module" src="./index.js"></script>
|
||||
</body>
|
||||
|
|
|
|||
144
index.js
144
index.js
|
|
@ -44,78 +44,104 @@ async function init() {
|
|||
code: shader,
|
||||
});
|
||||
|
||||
document.getElementById("render-me").addEventListener("click", doStuff);
|
||||
}
|
||||
// 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 canvas = document.getElementById("result");
|
||||
const context = canvas.getContext("webgpu");
|
||||
context.configure({
|
||||
device: device,
|
||||
format: navigator.gpu.getPreferredCanvasFormat(),
|
||||
alphaMode: "premultiplied",
|
||||
});
|
||||
const vertices = new Float32Array([
|
||||
// Triangle 1 (Blue)
|
||||
-1,
|
||||
-1,
|
||||
1,
|
||||
-1,
|
||||
1,
|
||||
1,
|
||||
|
||||
const bindGroupLayout = device.createBindGroupLayout({
|
||||
entries: [
|
||||
-1, // Triangle 2 (Red)
|
||||
-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,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: "storage",
|
||||
},
|
||||
attributes: [
|
||||
{
|
||||
shaderLocation: 0,
|
||||
offset: 0,
|
||||
format: "float32x2",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const bindGroup = device.createBindGroup({
|
||||
layout: bindGroupLayout,
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer: output,
|
||||
arrayStride: 8,
|
||||
stepMode: "vertex",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const computePipeline = device.createComputePipeline({
|
||||
layout: device.createPipelineLayout({
|
||||
bindGroupLayouts: [bindGroupLayout],
|
||||
}),
|
||||
compute: {
|
||||
];
|
||||
const pipelineDescriptor = {
|
||||
label: "Render Pipeline",
|
||||
vertex: {
|
||||
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 passEncoder = commandEncoder.beginComputePass();
|
||||
passEncoder.setPipeline(computePipeline);
|
||||
passEncoder.setBindGroup(0, bindGroup);
|
||||
passEncoder.dispatchWorkgroups(Math.ceil(1000 / 64));
|
||||
const clearColor = { r: 0.0, g: 0.5, b: 1.0, a: 1.0 };
|
||||
|
||||
const renderPassDescriptor = {
|
||||
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();
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -1,19 +1,54 @@
|
|||
@group(0) @binding(0)
|
||||
var<storage, read_write> output: array<f32>;
|
||||
const CANVAS: vec2f = vec2f(1200, 800);
|
||||
|
||||
@compute @workgroup_size(64)
|
||||
fn main(
|
||||
@builtin(global_invocation_id)
|
||||
global_id : vec3u,
|
||||
@vertex
|
||||
fn vertex_main(@location(0) pos: vec2f) ->
|
||||
@builtin(position) vec4f {
|
||||
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;
|
||||
@fragment
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
output[global_id.x] =
|
||||
f32(local_id.x);
|
||||
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