{
let canvas = Object.assign(document.createElement("canvas"), {
width: w,
height: h
});
let context = canvas.getContext("webgpu");
let adapter = await navigator.gpu?.requestAdapter();
let device = await adapter?.requestDevice();
let format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format });
let [x_buffer, y_buffer] = [x_values, y_values].map((arr) => {
let buffer = device.createBuffer({
size: arr.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
mappedAtCreation: true
});
new Float32Array(buffer.getMappedRange()).set(arr);
buffer.unmap();
return buffer;
});
let xylayout = device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.VERTEX,
buffer: { type: "read-only-storage" }
},
{
binding: 1,
visibility: GPUShaderStage.VERTEX,
buffer: { type: "read-only-storage" }
}
]
});
let uniforms = new Float32Array(50);
let u_zoom = uniforms.subarray(0, 16);
let u_window_scale = uniforms.subarray(16, 32);
let u_untransform = uniforms.subarray(32, 48);
let ubuffer = device.createBuffer({
size: uniforms.byteLength,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});
{
let mats = window_transform(scales.x, scales.y, w, h);
u_window_scale.set(mats[0]);
u_untransform.set(mats[1]);
}
let ulayout = device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.VERTEX,
buffer: { type: "uniform" }
}
]
});
let code = `
@group(0) @binding(0) var<storage, read> x_values : array<f32>;
@group(0) @binding(1) var<storage, read> y_values : array<f32>;
@group(1) @binding(0) var<uniform> uni: Uniforms;
struct Uniforms {
zoom: mat4x4<f32>,
window_scale: mat4x4<f32>,
untransform: mat4x4<f32>,
};
@vertex fn vert(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4<f32> {
let x = x_values[vertex_index];
let y = y_values[vertex_index];
let t = uni.untransform * uni.zoom * uni.window_scale;
let pos = t * vec4(x, y, 1.0, 1.0);
return pos;
}
@fragment fn frag() -> @location(0) vec4<f32> {
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}
`;
let module = device.createShaderModule({ code });
let pipeline = device.createRenderPipeline({
layout: device.createPipelineLayout({
bindGroupLayouts: [xylayout, ulayout]
}),
vertex: {
module,
entryPoint: "vert",
buffers: []
},
fragment: {
module,
entryPoint: "frag",
targets: [{ format }]
},
primitive: { topology: "point-list" }
});
let xygroup = device.createBindGroup({
layout: xylayout,
entries: [
{ binding: 0, resource: { buffer: x_buffer } },
{ binding: 1, resource: { buffer: y_buffer } }
]
});
let ugroup = device.createBindGroup({
layout: ulayout,
entries: [{ binding: 0, resource: { buffer: ubuffer } }]
});
function render() {
let encoder = device.createCommandEncoder();
let pass = encoder.beginRenderPass({
colorAttachments: [
{
clearValue: [1, 1, 1, 1],
loadOp: "clear",
storeOp: "store",
view: context.getCurrentTexture().createView()
}
]
});
pass.setPipeline(pipeline);
pass.setBindGroup(0, xygroup);
pass.setBindGroup(1, ugroup);
pass.draw(x_values.length);
pass.end();
device.queue.submit([encoder.finish()]);
}
function zoomed({ k, x, y }) {
let mat = [
[k, 0, 0, 0],
[0, k, 0, 0],
[0, 0, 1, 0],
[x, y, 0, 1]
];
u_zoom.set(mat.flat());
device.queue.writeBuffer(ubuffer, 0, uniforms);
render();
}
d3.select(context.canvas).call(
d3
.zoom()
.scaleExtent([0.1, 1000])
.on("zoom", ({ transform }) => zoomed(transform))
);
zoomed(d3.zoomIdentity);
return context.canvas;
}