Skip to content

Instantly share code, notes, and snippets.

@greggman
Created June 5, 2025 05:22
Show Gist options
  • Save greggman/ed328538bc8735f9c4a4646a39c4b30a to your computer and use it in GitHub Desktop.
Save greggman/ed328538bc8735f9c4a4646a39c4b30a to your computer and use it in GitHub Desktop.
WebGPU vs Canvas transparency
html {
background-color: #404040;
background-image:
linear-gradient(45deg, #808080 25%, transparent 25%),
linear-gradient(-45deg, #808080 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #808080 75%),
linear-gradient(-45deg, transparent 75%, #808080 75%);
background-size: 32px 32px;
background-position: 0 0, 0 16px, 16px -16px, -16px 0px;
}
<canvas id="twod"></canvas>
<canvas id="webgpu"></canvas>
const ctx = document.querySelector('#twod').getContext('2d');
ctx.fillStyle = 'rgba(255, 0, 0, 0.2)';
ctx.fillRect(10, 10, 100, 50);
ctx.fillStyle = 'rgba(255, 255, 0, 0.2)';
ctx.fillRect(20, 15, 100, 50);
ctx.fillStyle = 'rgba(0, 255, 0, 0.2)';
ctx.fillRect(30, 20, 100, 50);
ctx.fillStyle = 'rgba(0, 255, 255, 0.2)';
ctx.fillRect(40, 25, 100, 50);
ctx.fillStyle = 'rgba(0, 0, 255, 0.2)';
ctx.fillRect(50, 30, 100, 50);
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const context = document.querySelector('#webgpu').getContext('webgpu');
context.configure({
device,
alphaMode: 'premultiplied',
format: 'rgba8unorm',
});
const module = device.createShaderModule({
code: `
struct Uniforms {
xy: vec2f,
scale: vec2f,
color: vec4f,
};
@group(0) @binding(0) var<uniform> uni: Uniforms;
@vertex fn vs(@builtin(vertex_index) ndx: u32) -> @builtin(position) vec4f {
let pos = array(
vec2f(0, 0),
vec2f(1, 0),
vec2f(0, 1),
vec2f(0, 1),
vec2f(1, 0),
vec2f(1, 1),
);
return vec4f(uni.xy + pos[ndx] * uni.scale, 0, 1);
}
@fragment fn fs() -> @location(0) vec4f {
return uni.color;
}
`,
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: { module },
fragment: {
module,
targets: [
{
format: 'rgba8unorm',
blend: {
color: {
srcFactor: "src-alpha",
dstFactor: "one-minus-src-alpha"
},
alpha: {
srcFactor: "one",
dstFactor: "one-minus-src-alpha"
},
},
},
]
},
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: context.getCurrentTexture().createView(),
loadOp: 'clear',
storeOp: 'store',
},
],
});
pass.setPipeline(pipeline);
const { width, height } = context.getCurrentTexture();
const colors = [
[1, 0, 0, 0.2],
[1, 1, 0, 0.2],
[0, 1, 0, 0.2],
[0, 1, 1, 0.2],
[0, 0, 1, 0.2],
];
for (let i = 0; i < 5; ++i) {
const buffer = device.createBuffer({
size: 32,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM,
mappedAtCreation: true,
});
const f32 = new Float32Array(buffer.getMappedRange());
f32.set([
(10 + i * 10) / width * 2 - 1,
((10 + i * 5) / height * 2 - 1) * -1,
100 / width * 2,
-50 / height * 2,
...colors[i],
]);
buffer.unmap();
const bg = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer } },
],
});
pass.setBindGroup(0, bg);
pass.draw(6);
}
pass.end();
device.queue.submit([encoder.finish()]);
{"name":"WebGPU vs Canvas transparency","settings":{},"filenames":["index.html","index.css","index.js"]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment