diff --git a/sample/alphaToCoverage/index.html b/sample/alphaToCoverage/index.html
new file mode 100644
index 00000000..2839973c
--- /dev/null
+++ b/sample/alphaToCoverage/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+ webgpu-samples: alphaToCoverage
+
+
+
+
+
+
+
+
+
diff --git a/sample/alphaToCoverage/main.ts b/sample/alphaToCoverage/main.ts
new file mode 100644
index 00000000..5a8dd324
--- /dev/null
+++ b/sample/alphaToCoverage/main.ts
@@ -0,0 +1,196 @@
+import { GUI } from 'dat.gui';
+
+import showMultisampleTextureWGSL from './showMultisampleTexture.wgsl';
+import renderWithAlphaToCoverageWGSL from './renderWithAlphaToCoverage.wgsl';
+import { quitIfWebGPUNotAvailable } from '../util';
+
+const canvas = document.querySelector('canvas') as HTMLCanvasElement;
+const adapter = await navigator.gpu?.requestAdapter();
+const device = await adapter?.requestDevice();
+quitIfWebGPUNotAvailable(adapter, device);
+
+//
+// GUI controls
+//
+
+const kAlphaSteps = 64;
+
+const kInitConfig = {
+ width: 8,
+ height: 8,
+ alpha: 4,
+ pause: false,
+};
+const config = { ...kInitConfig };
+const updateConfig = () => {
+ const data = new Float32Array([config.alpha / kAlphaSteps]);
+ device.queue.writeBuffer(bufConfig, 0, data);
+};
+
+const gui = new GUI();
+{
+ const buttons = {
+ initial() {
+ Object.assign(config, kInitConfig);
+ gui.updateDisplay();
+ },
+ };
+
+ const settings = gui.addFolder('Settings');
+ settings.open();
+ settings.add(config, 'width', 1, 16, 1);
+ settings.add(config, 'height', 1, 16, 1);
+
+ const alphaPanel = gui.addFolder('Alpha');
+ alphaPanel.open();
+ alphaPanel
+ .add(config, 'alpha', -2, kAlphaSteps + 2, 1)
+ .name(`alpha (of ${kAlphaSteps})`);
+ alphaPanel.add(config, 'pause', false);
+
+ gui.add(buttons, 'initial').name('reset to initial');
+}
+
+//
+// Canvas setup
+//
+
+function updateCanvasSize() {
+ const devicePixelRatio = window.devicePixelRatio;
+ canvas.width = canvas.clientWidth * devicePixelRatio;
+ canvas.height = canvas.clientHeight * devicePixelRatio;
+}
+updateCanvasSize();
+const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
+
+const context = canvas.getContext('webgpu') as GPUCanvasContext;
+context.configure({
+ device,
+ format: presentationFormat,
+ alphaMode: 'premultiplied',
+});
+
+//
+// Config buffer
+//
+
+const bufConfig = device.createBuffer({
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM,
+ size: 128,
+});
+
+//
+// Pipeline to render to a multisampled texture using alpha-to-coverage
+//
+
+const renderWithAlphaToCoverageModule = device.createShaderModule({
+ code: renderWithAlphaToCoverageWGSL,
+});
+const renderWithAlphaToCoveragePipeline = device.createRenderPipeline({
+ label: 'renderWithAlphaToCoveragePipeline',
+ layout: 'auto',
+ vertex: { module: renderWithAlphaToCoverageModule },
+ fragment: {
+ module: renderWithAlphaToCoverageModule,
+ targets: [{ format: 'rgba16float' }],
+ },
+ multisample: { count: 4, alphaToCoverageEnabled: true },
+ primitive: { topology: 'triangle-list' },
+});
+const renderWithAlphaToCoverageBGL =
+ renderWithAlphaToCoveragePipeline.getBindGroupLayout(0);
+
+const renderWithAlphaToCoverageBG = device.createBindGroup({
+ layout: renderWithAlphaToCoverageBGL,
+ entries: [{ binding: 0, resource: { buffer: bufConfig } }],
+});
+
+//
+// "Debug" view of the actual texture contents
+//
+
+const showMultisampleTextureModule = device.createShaderModule({
+ code: showMultisampleTextureWGSL,
+});
+const showMultisampleTexturePipeline = device.createRenderPipeline({
+ label: 'showMultisampleTexturePipeline',
+ layout: 'auto',
+ vertex: { module: showMultisampleTextureModule },
+ fragment: {
+ module: showMultisampleTextureModule,
+ targets: [{ format: presentationFormat }],
+ },
+ primitive: { topology: 'triangle-list' },
+});
+const showMultisampleTextureBGL =
+ showMultisampleTexturePipeline.getBindGroupLayout(0);
+
+function render() {
+ updateConfig();
+
+ const multisampleTexture = device.createTexture({
+ format: 'rgba16float',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
+ size: [config.width, config.height],
+ sampleCount: 4,
+ });
+ const multisampleTextureView = multisampleTexture.createView();
+
+ const showMultisampleTextureBG = device.createBindGroup({
+ layout: showMultisampleTextureBGL,
+ entries: [{ binding: 0, resource: multisampleTextureView }],
+ });
+
+ const commandEncoder = device.createCommandEncoder();
+ // renderWithAlphaToCoverage pass
+ {
+ const pass = commandEncoder.beginRenderPass({
+ label: 'renderWithAlphaToCoverage pass',
+ colorAttachments: [
+ {
+ view: multisampleTextureView,
+ clearValue: [0, 0, 0, 1], // will be overwritten
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.setPipeline(renderWithAlphaToCoveragePipeline);
+ pass.setBindGroup(0, renderWithAlphaToCoverageBG);
+ pass.draw(6);
+ pass.end();
+ }
+ // showMultisampleTexture pass
+ {
+ const pass = commandEncoder.beginRenderPass({
+ label: 'showMulitsampleTexture pass',
+ colorAttachments: [
+ {
+ view: context.getCurrentTexture().createView(),
+ clearValue: [1, 0, 1, 1], // error color, will be overwritten
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.setPipeline(showMultisampleTexturePipeline);
+ pass.setBindGroup(0, showMultisampleTextureBG);
+ pass.draw(6);
+ pass.end();
+ }
+ device.queue.submit([commandEncoder.finish()]);
+
+ multisampleTexture.destroy();
+}
+
+function frame() {
+ if (!config.pause) {
+ config.alpha = ((performance.now() / 10000) % 1) * (kAlphaSteps + 4) - 2;
+ gui.updateDisplay();
+ }
+ updateCanvasSize();
+ render();
+ requestAnimationFrame(frame);
+}
+
+requestAnimationFrame(frame);
diff --git a/sample/alphaToCoverage/meta.ts b/sample/alphaToCoverage/meta.ts
new file mode 100644
index 00000000..f41498af
--- /dev/null
+++ b/sample/alphaToCoverage/meta.ts
@@ -0,0 +1,11 @@
+export default {
+ name: 'Alpha-to-Coverage',
+ description:
+ 'Visualizes how alpha-to-coverage translates alpha values into sample coverage on your device. This varies per device; for example, not all devices guarantee that once a sample pops in, it will stay; some devices repeat at 2x2 pixels, others at 4x4; etc. The circles show the 4 samples of each pixel; the background checkerboard shows where the pixels are.',
+ filename: __DIRNAME__,
+ sources: [
+ { path: 'main.ts' },
+ { path: './renderWithAlphaToCoverage.wgsl' },
+ { path: './showMultisampleTexture.wgsl' },
+ ],
+};
diff --git a/sample/alphaToCoverage/renderWithAlphaToCoverage.wgsl b/sample/alphaToCoverage/renderWithAlphaToCoverage.wgsl
new file mode 100644
index 00000000..d0791268
--- /dev/null
+++ b/sample/alphaToCoverage/renderWithAlphaToCoverage.wgsl
@@ -0,0 +1,26 @@
+struct Config {
+ alpha: f32,
+};
+@group(0) @binding(0) var config: Config;
+
+struct Varying {
+ @builtin(position) pos: vec4f,
+}
+
+@vertex
+fn vmain(
+ @builtin(vertex_index) vertex_index: u32,
+) -> Varying {
+ var square = array(
+ vec2f(-1, -1), vec2f(-1, 1), vec2f( 1, -1),
+ vec2f( 1, -1), vec2f(-1, 1), vec2f( 1, 1),
+ );
+
+ return Varying(vec4(square[vertex_index], 0, 1));
+}
+
+@fragment
+fn fmain(vary: Varying) -> @location(0) vec4f {
+ return vec4f(1, 1, 1, config.alpha);
+}
+
diff --git a/sample/alphaToCoverage/showMultisampleTexture.wgsl b/sample/alphaToCoverage/showMultisampleTexture.wgsl
new file mode 100644
index 00000000..b7195a98
--- /dev/null
+++ b/sample/alphaToCoverage/showMultisampleTexture.wgsl
@@ -0,0 +1,58 @@
+@group(0) @binding(0) var tex: texture_multisampled_2d;
+
+struct Varying {
+ @builtin(position) pos: vec4f,
+ @location(0) uv: vec2f,
+}
+
+const kMipLevels = 4;
+const baseMipSize: u32 = 16;
+
+const kSquare = array(
+ vec2f(0, 0), vec2f(0, 1), vec2f(1, 0),
+ vec2f(1, 0), vec2f(0, 1), vec2f(1, 1),
+);
+
+@vertex
+fn vmain(
+ @builtin(vertex_index) vertex_index: u32,
+) -> Varying {
+
+ let uv = kSquare[vertex_index];
+ let pos = vec4(uv * vec2(2, -2) + vec2(-1, 1), 0.0, 1.0);
+
+ return Varying(pos, uv);
+}
+
+const kSampleCount = 4;
+const kSamplePositions: array = array(
+ vec2f(0.375, 0.125),
+ vec2f(0.875, 0.375),
+ vec2f(0.125, 0.625),
+ vec2f(0.625, 0.875),
+);
+
+@fragment
+fn fmain(vary: Varying) -> @location(0) vec4f {
+ let dim = textureDimensions(tex);
+ let dimMax = max(dim.x, dim.y);
+
+ let xy = vary.uv * f32(dimMax);
+ let xyInt = vec2u(xy);
+ let xyFrac = xy % vec2f(1, 1);
+
+ if xyInt.x >= dim.x || xyInt.y >= dim.y {
+ return vec4f(0, 0, 0, 1);
+ }
+
+ // check if we're close to a sample, and return it
+ for (var sampleIndex = 0; sampleIndex < kSampleCount; sampleIndex += 1) {
+ if distance(xyFrac, kSamplePositions[sampleIndex]) < 0.1 {
+ let val = textureLoad(tex, xyInt, sampleIndex).rgb;
+ return vec4f(val, 1);
+ }
+ }
+
+ // if not close to a sample, render background checkerboard
+ return vec4f(f32(xyInt.x % 4 + 1) / 8, 0, f32(xyInt.y % 4 + 1) / 8, 1);
+}
diff --git a/sample/multipleCanvases/index.html b/sample/multipleCanvases/index.html
index ae3ea671..517e02bc 100644
--- a/sample/multipleCanvases/index.html
+++ b/sample/multipleCanvases/index.html
@@ -3,7 +3,7 @@
- webgpu-samples: multiple canvases
+ webgpu-samples: multipleCanvases