Last active
May 7, 2022 05:53
-
-
Save bellbind/5dfee59a71be2c6cc9b93065dad59280 to your computer and use it in GitHub Desktop.
[WebGPU] Image texture example for WebGPU API for Chrome-100
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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
demo: https://gist.githack.com/bellbind/5dfee59a71be2c6cc9b93065dad59280/raw/index.html