Public
Edited
Feb 27
Paused
1 star
Insert cell
Insert cell
Insert cell
Insert cell
state = {
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const context = util.ctx(256, 256);
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format });
const module = device.createShaderModule({
code: `
struct VertexOut {
@builtin(position) position: vec4f,
@location(0) texcoord: vec2f,
};

@vertex
fn vs(@builtin(vertex_index) vertexIndex : u32) -> VertexOut {
// shouldn't hardcode these; should come from storage or vertex buffer
let pos = array(
// 1st triangle
vec2f(0.0, 0.0), // center
vec2f(1.0, 0.0), // right, center
vec2f(0.0, 1.0), // center, top

// 2st triangle
vec2f(0.0, 1.0), // center, top
vec2f(1.0, 0.0), // right, center
vec2f(1.0, 1.0), // right, top
);

var out: VertexOut;
let xy = pos[vertexIndex];
out.position = vec4f((xy - 0.5) * 2, 0.0, 1.0);
out.texcoord = xy;
return out;
}

@group(0) @binding(0) var texSampler: sampler;
@group(0) @binding(1) var tex: texture_2d<f32>;
@group(0) @binding(2) var<uniform> color: vec4f;

@fragment
fn fs(fsInput: VertexOut) -> @location(0) vec4f {
return textureSample(tex, texSampler, fsInput.texcoord) * color;
}
`,
});

const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
},
fragment: {
module,
entryPoint: 'fs',
targets: [{ format }],
},
});

// TODO having some trouble getting this to work with uint8
const uColor = new Float32Array([1, 1, 1, 1]);
const uniformBuffer = device.createBuffer({
size: uColor.byteLength,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
const source = await FileAttachment('791-2x.png').image();
const texture = device.createTexture({
format: 'rgba8unorm',
size: [source.width, source.height],
usage:
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT
})
device.queue.copyExternalImageToTexture(
{ source, flipY: true },
{ texture },
{ width: source.width, height: source.height }
);

const sampler = device.createSampler({
addressModeU: 'clamp-to-edge',
addressMoveV: 'clamp-to-edge',
magFilter: 'nearest'
});

const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: sampler },
{ binding: 1, resource: texture.createView() },
{ binding: 2, resource: { buffer: uniformBuffer }},
]
});

const renderPassDescriptor = {
colorAttachments: [
{
clearValue: [0.3, 0.3, 0.3, 1],
loadOp: 'clear',
storeOp: 'store',
},
],
};

function render(color = '#ffffff') {
uColor.set([...hexToRgb(color).map(d => d / 255), 1])
device.queue.writeBuffer(uniformBuffer, 0, uColor);

renderPassDescriptor.colorAttachments[0].view = context.getCurrentTexture().createView();

const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass(renderPassDescriptor);
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.draw(6); // call our vertex shader 6 times
pass.end();

device.queue.submit([encoder.finish()]);
}

render();

return { canvas: context.canvas, render };
}
Insert cell
Insert cell
Insert cell
async function loadImageBitmap(url) {
const res = await fetch(url);
const blob = await res.blob();
return await createImageBitmap(blob, { colorSpaceConversion: 'none' });
}
Insert cell
util = ({
rand: (min = 0, max = 1) => min + Math.random() * (max - min),
arr: (size, callback) => {
const arr = Array(size).fill(null)
return callback ? arr.map(callback) : arr;
},
ctx: (width = 512, height = 512) => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('webgpu');
return context;
},
})
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