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 };
}