Skip to content

Instantly share code, notes, and snippets.

@bellbind
Last active May 7, 2022 05:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bellbind/5dfee59a71be2c6cc9b93065dad59280 to your computer and use it in GitHub Desktop.
Save bellbind/5dfee59a71be2c6cc9b93065dad59280 to your computer and use it in GitHub Desktop.
[WebGPU] Image texture example for WebGPU API for Chrome-100
<!doctype html>
<html>
<head>
<!-- IMPORTANT: The current Chrome requires some origin-trial token in <meta>.
To register origins at the last "WebGPU REGISTER" in https://developer.chrome.com/origintrials/
This token is for a Web Origin "http://localhost:8000" (maybe expired at Mar 31, 2022)
-->
<meta http-equiv="origin-trial"
content="AkIL+/THBoi1QEsWbX5SOuMpL6+KGAXKrZE5Bz6yHTuijzvKz2MznuLqE+MH4YSqRi/v1fDK/6JyFzgibTTeNAsAAABJeyJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjgwMDAiLCJmZWF0dXJlIjoiV2ViR1BVIiwiZXhwaXJ5IjoxNjUyODMxOTk5fQ==" />
<meta http-equiv="origin-trial"
content="Akv07qcAop5MFaZYxJtHHjUuM8eV3GpbHkTeuhZo/4wsNjYnQ7GSGJyo7hRVZvpvyjYwilbJ8KbFVchI4O1DpA0AAABQeyJvcmlnaW4iOiJodHRwczovL2dpc3QuZ2l0aGFjay5jb206NDQzIiwiZmVhdHVyZSI6IldlYkdQVSIsImV4cGlyeSI6MTY1MjgzMTk5OX0=" />
<script src="./main.js" type="module"></script>
<style>@media(prefers-color-scheme: dark){:root {color-scheme: dark;}}</style>
<link rel="icon" href="data:image/x-icon;," />
</head>
<body>
<h1>(Notice: The origin-trial token in this page will be expired at May 15, 2022)</h1>
<canvas style="width: 80vmin; height: 80vmin; border: solid;" id="canvas"></canvas>
</body>
</html>
// Image texture example for WebGPU API for Chrome-100: https://www.w3.org/TR/webgpu/
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
// prepare image
const img = new Image();
img.src = "data:image/svg+xml," + encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="600">
<rect fill="#ffffff" width="900" height="600" />
<circle fill="#bc002d" cx="450" cy="300" r="180" />
</svg>
`);
await img.decode();
const bitmap = await createImageBitmap(img);
const max = Math.max(bitmap.width, bitmap.height);
const [w, h] = [bitmap.width / max, bitmap.height / max];
// triangle-strip square: 4-(x,y, u, v); top-left: (u,v)=(0,0)
const square = new Float32Array([
-w, -h, 0, 1,
-w, +h, 0, 0,
+w, -h, 1, 1,
+w, +h, 1, 0,
]);
const vertexBuffer = device.createBuffer({size: square.byteLength, usage: GPUBufferUsage.VERTEX, mappedAtCreation: true});
new Float32Array(vertexBuffer.getMappedRange()).set(square);
vertexBuffer.unmap();
const stride = {arrayStride: 4 * square.BYTES_PER_ELEMENT, attributes: [
{shaderLocation: 0, offset: 0, format: "float32x2"},
{shaderLocation: 1, offset: 2 * square.BYTES_PER_ELEMENT, format: "float32x2"},
]};
// WGSL shaders: https://www.w3.org/TR/WGSL/
const vertexWgsl = `
struct Out {
@builtin(position) pos: vec4<f32>;
@location(0) uv: vec2<f32>;
};
@stage(vertex) fn main(@location(0) xy: vec2<f32>, @location(1) uv: vec2<f32>) -> Out {
return Out(vec4<f32>(xy, 0.0, 1.0), uv);
}
`;
const vertexShader = device.createShaderModule({code: vertexWgsl});
const fragmentWgsl = `
@group(0) @binding(0) var samp: sampler;
@group(0) @binding(1) var tex: texture_2d<f32>;
@stage(fragment) fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
return textureSample(tex, samp, uv);
}
`;
const fragmentShader = device.createShaderModule({code: fragmentWgsl});
// gpu config for canvas
const canvas = document.getElementById("canvas");
const gpu = canvas.getContext("webgpu");
const format = gpu.getPreferredFormat(adapter);
gpu.configure({device, format, size: [canvas.width, canvas.height]});
// texture and sampler
const samp = device.createSampler({minFilter: "linear", magFilter: "linear"});
const tex = device.createTexture({
format: "rgba8unorm", size: [bitmap.width, bitmap.height],
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
});
device.queue.copyExternalImageToTexture({source: bitmap}, {texture: tex}, [bitmap.width, bitmap.height]);
// pipeline
const pipeline = device.createRenderPipeline({
primitive: {topology: "triangle-strip"},
vertex: {module: vertexShader, entryPoint: "main", buffers: [stride]},
fragment: {module: fragmentShader, entryPoint: "main", targets: [{format}]},
});
// bind group
const bindGroupLayout = pipeline.getBindGroupLayout(0);
const bindGroup = device.createBindGroup({
layout: bindGroupLayout,
entries: [
{binding: 0, resource: samp},
{binding: 1, resource: tex.createView()},
]
});
// render
const render = () => {
const view = gpu.getCurrentTexture().createView();
const renderPass = {colorAttachments: [{view, loadOp: "clear", clearValue: {r: 0, g: 0, b: 0, a: 1}, storeOp: "store"}]};
const commandEncoder = device.createCommandEncoder();
const passEncoder = commandEncoder.beginRenderPass(renderPass);
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.setVertexBuffer(0, vertexBuffer);
passEncoder.draw(4, 1);
passEncoder.end();
device.queue.submit([commandEncoder.finish()]);
};
(function loop() {
render();
requestAnimationFrame(loop);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment