Skip to content

Commit

Permalink
Add alphaToCoverage sample (#444)
Browse files Browse the repository at this point in the history
  • Loading branch information
kainino0x authored Sep 20, 2024
1 parent 8065282 commit 389827c
Show file tree
Hide file tree
Showing 11 changed files with 329 additions and 6 deletions.
30 changes: 30 additions & 0 deletions sample/alphaToCoverage/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>webgpu-samples: alphaToCoverage</title>
<style>
:root {
color-scheme: light dark;
}
html, body {
margin: 0; /* remove default margin */
height: 100%; /* make body fill the browser window */
display: flex;
place-content: center center;
}
canvas {
width: min(100vw, 100vh);
height: min(100vw, 100vh);
display: block;
}
</style>
<script src="../../third_party/gif.js/gif.js"></script>
<script defer type="module" src="main.js"></script>
<script defer type="module" src="../../js/iframe-helper.js"></script>
</head>
<body>
<canvas></canvas>
</body>
</html>
196 changes: 196 additions & 0 deletions sample/alphaToCoverage/main.ts
Original file line number Diff line number Diff line change
@@ -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);
11 changes: 11 additions & 0 deletions sample/alphaToCoverage/meta.ts
Original file line number Diff line number Diff line change
@@ -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' },
],
};
26 changes: 26 additions & 0 deletions sample/alphaToCoverage/renderWithAlphaToCoverage.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
struct Config {
alpha: f32,
};
@group(0) @binding(0) var<uniform> 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);
}

58 changes: 58 additions & 0 deletions sample/alphaToCoverage/showMultisampleTexture.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
@group(0) @binding(0) var tex: texture_multisampled_2d<f32>;

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<vec2f, kSampleCount> = 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);
}
2 changes: 1 addition & 1 deletion sample/multipleCanvases/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>webgpu-samples: multiple canvases</title>
<title>webgpu-samples: multipleCanvases</title>
<style>
:root {
color-scheme: light dark;
Expand Down
2 changes: 1 addition & 1 deletion sample/occlusionQuery/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>webgpu-samples: wireframe</title>
<title>webgpu-samples: occlusionQuery</title>
<style>
:root {
color-scheme: light dark;
Expand Down
2 changes: 1 addition & 1 deletion sample/reversedZ/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>webgpu-samples: samplerParameters</title>
<title>webgpu-samples: reversedZ</title>
<style>
:root {
color-scheme: light dark;
Expand Down
2 changes: 1 addition & 1 deletion sample/rotatingCube/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>webgpu-samples: a-buffer</title>
<title>webgpu-samples: rotatingCube</title>
<style>
:root {
color-scheme: light dark;
Expand Down
2 changes: 1 addition & 1 deletion sample/samplerParameters/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>webgpu-samples: a-buffer</title>
<title>webgpu-samples: samplerParameters</title>
<style>
:root {
color-scheme: light dark;
Expand Down
4 changes: 3 additions & 1 deletion src/samples.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import aBuffer from '../sample/a-buffer/meta';
import alphaToCoverage from '../sample/alphaToCoverage/meta';
import animometer from '../sample/animometer/meta';
import bitonicSort from '../sample/bitonicSort/meta';
import bundleCulling from '../sample/bundleCulling/meta';
Expand Down Expand Up @@ -86,10 +87,11 @@ export const pageCategories: PageCategory[] = [
title: 'WebGPU Features',
description: 'Highlights of important WebGPU features.',
samples: {
samplerParameters,
reversedZ,
renderBundles,
occlusionQuery,
samplerParameters,
alphaToCoverage,
},
},

Expand Down

0 comments on commit 389827c

Please sign in to comment.