Public
Edited
Apr 23, 2024
1 fork
Insert cell
Insert cell
{
const { device, context, format } = await gpu.init(768, 768)
const module = device.createShaderModule({
code: `
struct Uniforms {
size: f32,
count: f32,
}

struct Data {
position: vec2f,
}

struct VertexOut {
@builtin(position) position: vec4f,
@location(1) norm_index: f32,
@location(2) pos: vec2f,
};

@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@group(0) @binding(1) var<storage, read> data: array<Data>;

@vertex
fn vs(
@builtin(instance_index) instance_index : u32,
@builtin(vertex_index) vertex_index : u32,
) -> VertexOut {
let p = array(
vec2f(0.0, 0.0),
vec2f(1.0, 0.0),
vec2f(0.0, 1.0),
vec2f(0.0, 1.0),
vec2f(1.0, 0.0),
vec2f(1.0, 1.0),
);
let pos = data[instance_index].position;
let xy = (p[vertex_index] - 0.5) * uniforms.size + pos;

return VertexOut(
vec4f(xy, 1, 1),
f32(instance_index) / uniforms.count,
pos
);
}

@fragment
fn fs(vout: VertexOut) -> @location(0) vec4f {
let pn = (vout.pos + 1) / 2;
return vec4f(pn, 1 - pn.x, 1) * 0.5;
}
`,
});

const pipeline = device.createRenderPipeline({
label: 'pipeline',
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
},
fragment: {
module,
entryPoint: 'fs',
targets: [{
format,
blend: {
color: {
srcFactor: 'one',
dstFactor: 'one-minus-src-alpha'
},
alpha: {
srcFactor: 'one',
dstFactor: 'one-minus-src-alpha'
},
},
}],
},
})

const n = 3_000
const particleSize = 0.02
const uniforms = new Float32Array([particleSize, n])
const uniformBuffer = device.createBuffer({
size: uniforms.byteLength,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
})
device.queue.writeBuffer(uniformBuffer, 0, uniforms);

// const points = new Float32Array(util.arr(n, () => [util.rands(), util.rands()]).flat())
const points = new Float32Array(util.arr(n, () => [util.randn(0, 0.2), util.randn(0, 0.2)]).flat())
const pointsBuffer = device.createBuffer({
label: 'points storage buffer',
size: points.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
})
device.queue.writeBuffer(pointsBuffer, 0, points);

const bindGroup = device.createBindGroup({
label: 'bind group layout',
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: uniformBuffer }},
{ binding: 1, resource: { buffer: pointsBuffer }},
]
});

function render() {
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
clearValue: [0, 0, 0, 1],
loadOp: 'clear',
storeOp: 'store',
view: context.getCurrentTexture().createView()
},
],
})
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.draw(6, n);
pass.end();
device.queue.submit([encoder.finish()]);
}

render()

return context.canvas
}

Insert cell
gpu = ({
init: async (width = 512, height = 512) => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('webgpu');

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format, alphaMode: 'premultiplied', });
return { context, adapter, device, format }
},
})
Insert cell
util = ({
// random signed
rands: () => (Math.random() - 0.5) * 2,
arr: (size, callback) => {
const arr = new Array(size)
if (typeof callback !== 'function') {
return arr.fill(callback)
}
return arr.fill(null).map((_, i) => callback(i))
},
shuffle: arr => {
return arr.sort((a, b) => Math.random() - 0.5)
},
// from karpathy:
randn: (mean, variance) => {
let V1, V2, S;
do {
const U1 = Math.random();
const U2 = Math.random();
V1 = 2 * U1 - 1;
V2 = 2 * U2 - 1;
S = V1 * V1 + V2 * V2;
} while (S > 1);
let X = Math.sqrt(-2 * Math.log(S) / S) * V1;
X = mean + Math.sqrt(variance) * X;
return X;
},
})
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more