Skip to content

Commit e65e384

Browse files
committed
WIP
1 parent 1e3c2d3 commit e65e384

File tree

4 files changed

+201
-20
lines changed

4 files changed

+201
-20
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Database of alpha-to-coverage patterns from different devices.
3+
*
4+
* Name of device ->
5+
* Array of patterns from a=0.0 to a=1.0, evenly spaced, excluding endpoints ->
6+
* Array of N*N masks depending on the block size of the pattern used
7+
* (in row-major order)
8+
*/
9+
export const alphaToCoverageDatabase: { [k: string]: PatternSequence } = {
10+
'NVIDIA GeForce RTX 3070': [[0b1000], [0b1001], [0b1011]],
11+
'Intel HD Graphics 4400': [[0b0001], [0b0011], [0b0111]],
12+
};
13+
14+
type PatternSequence = ReadonlyArray<Pattern>;
15+
type Pattern = ReadonlyArray<Mask>;
16+
type Mask = number;
17+
18+
/**
19+
* For each device name, provides the source for a WGSL function which emulates
20+
* the alpha-to-coverage algorithm of that device by mapping (alpha, x, y) to
21+
* a sample mask.
22+
*/
23+
export const kEmulatedAlphaToCoverage = {
24+
'Apple M1 Pro': `\
25+
fn emulatedAlphaToCoverage(alpha: f32, x: u32, y: u32) -> u32 {
26+
let u = x % 2;
27+
let v = y % 2;
28+
if (alpha < 0.5 / 16) { return ${0b0000}; }
29+
// FIXME returning values out of an array is not working, always returns 0
30+
if (alpha < 1.5 / 16) { return array(array(${0b0001}u, ${0b0000}), array(${0b0000}, ${0b0000}))[v][u]; }
31+
if (alpha < 2.5 / 16) { return array(array(${0b0001}u, ${0b0000}), array(${0b0000}, ${0b0001}))[v][u]; }
32+
if (alpha < 3.5 / 16) { return array(array(${0b0001}u, ${0b0001}), array(${0b0000}, ${0b0001}))[v][u]; }
33+
if (alpha < 4.5 / 16) { return array(array(${0b0001}u, ${0b0001}), array(${0b0001}, ${0b0001}))[v][u]; }
34+
if (alpha < 5.5 / 16) { return array(array(${0b1001}u, ${0b0001}), array(${0b0001}, ${0b0001}))[v][u]; }
35+
if (alpha < 6.5 / 16) { return array(array(${0b1001}u, ${0b0001}), array(${0b0001}, ${0b1001}))[v][u]; }
36+
if (alpha < 7.5 / 16) { return array(array(${0b1001}u, ${0b1001}), array(${0b0001}, ${0b1001}))[v][u]; }
37+
if (alpha < 8.5 / 16) { return array(array(${0b1001}u, ${0b1001}), array(${0b1001}, ${0b1001}))[v][u]; }
38+
if (alpha < 9.5 / 16) { return array(array(${0b1011}u, ${0b1001}), array(${0b1001}, ${0b1001}))[v][u]; }
39+
if (alpha < 10.5 / 16) { return array(array(${0b1011}u, ${0b1001}), array(${0b1001}, ${0b1011}))[v][u]; }
40+
if (alpha < 11.5 / 16) { return array(array(${0b1011}u, ${0b1011}), array(${0b1001}, ${0b1011}))[v][u]; }
41+
if (alpha < 12.5 / 16) { return array(array(${0b1011}u, ${0b1011}), array(${0b1011}, ${0b1011}))[v][u]; }
42+
if (alpha < 13.5 / 16) { return array(array(${0b1111}u, ${0b1011}), array(${0b1011}, ${0b1011}))[v][u]; }
43+
if (alpha < 14.5 / 16) { return array(array(${0b1111}u, ${0b1011}), array(${0b1011}, ${0b1111}))[v][u]; }
44+
if (alpha < 15.5 / 16) { return array(array(${0b1111}u, ${0b1111}), array(${0b1011}, ${0b1111}))[v][u]; }
45+
return ${0b1111};
46+
}
47+
`.trimEnd(),
48+
'NVIDIA GeForce RTX 3070': `\
49+
fn emulatedAlphaToCoverage(alpha: f32, x: u32, y: u32) -> u32 {
50+
if (alpha < 0.5 / 4) { return ${0b0000}; }
51+
if (alpha < 1.5 / 4) { return ${0b1000}; }
52+
if (alpha < 2.5 / 4) { return ${0b1001}; }
53+
if (alpha < 3.5 / 4) { return ${0b1011}; }
54+
return ${0b1111};
55+
}
56+
`.trimEnd(),
57+
};

sample/alphaToCoverage/main.ts

Lines changed: 103 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { GUI } from 'dat.gui';
22

33
import showMultisampleTextureWGSL from './showMultisampleTexture.wgsl';
44
import renderWithAlphaToCoverageWGSL from './renderWithAlphaToCoverage.wgsl';
5+
import renderWithEmulatedAlphaToCoverageWGSL from './renderWithEmulatedAlphaToCoverage.wgsl';
56
import { quitIfWebGPUNotAvailable } from '../util';
7+
import { kEmulatedAlphaToCoverage } from './emulatedAlphaToCoverage';
68

79
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
810
const adapter = await navigator.gpu?.requestAdapter();
@@ -14,6 +16,8 @@ quitIfWebGPUNotAvailable(adapter, device);
1416
//
1517

1618
const kInitConfig = {
19+
scene: 'solid_colors',
20+
emulatedDevice: 'none',
1721
sizeLog2: 3,
1822
showResolvedColor: true,
1923
color1: 0x0000ff,
@@ -34,17 +38,23 @@ gui.width = 300;
3438
},
3539
};
3640

41+
gui.add(config, 'scene', ['solid_colors']);
42+
gui.add(config, 'emulatedDevice', [
43+
'none',
44+
...Object.keys(kEmulatedAlphaToCoverage),
45+
]);
46+
3747
const settings = gui.addFolder('Settings');
3848
settings.open();
3949
settings.add(config, 'sizeLog2', 0, 8, 1).name('size = 2**');
4050
settings.add(config, 'showResolvedColor', true);
4151

42-
const draw1Panel = gui.addFolder('Draw 1');
52+
const draw1Panel = gui.addFolder('solid_colors Draw 1');
4353
draw1Panel.open();
4454
draw1Panel.addColor(config, 'color1').name('color');
4555
draw1Panel.add(config, 'alpha1', 0, 255).name('alpha');
4656

47-
const draw2Panel = gui.addFolder('Draw 2');
57+
const draw2Panel = gui.addFolder('solid_colors Draw 2');
4858
draw2Panel.open();
4959
draw2Panel.addColor(config, 'color2').name('color');
5060
draw2Panel.add(config, 'alpha2', 0, 255).name('alpha');
@@ -80,23 +90,32 @@ const bufInstanceColors = device.createBuffer({
8090
size: 8,
8191
});
8292

83-
let multisampleTexture: GPUTexture, multisampleTextureView: GPUTextureView;
93+
let actualMSTexture: GPUTexture, actualMSTextureView: GPUTextureView;
94+
let emulatedMSTexture: GPUTexture, emulatedMSTextureView: GPUTextureView;
8495
let resolveTexture: GPUTexture, resolveTextureView: GPUTextureView;
8596
let lastSize = 0;
86-
function resetMultisampleTexture() {
97+
let renderWithEmulatedAlphaToCoveragePipeline: GPURenderPipeline | null;
98+
let lastEmulatedDevice = 'none';
99+
function resetConfiguredObjects() {
87100
const size = 2 ** config.sizeLog2;
88101
if (lastSize !== size) {
89-
if (multisampleTexture) {
90-
multisampleTexture.destroy();
102+
if (actualMSTexture) {
103+
actualMSTexture.destroy();
91104
}
92-
multisampleTexture = device.createTexture({
93-
format: 'rgba8unorm',
105+
if (emulatedMSTexture) {
106+
emulatedMSTexture.destroy();
107+
}
108+
const msTextureDesc = {
109+
format: 'rgba8unorm' as const,
94110
usage:
95111
GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
96112
size: [size, size],
97113
sampleCount: 4,
98-
});
99-
multisampleTextureView = multisampleTexture.createView();
114+
};
115+
actualMSTexture = device.createTexture(msTextureDesc);
116+
actualMSTextureView = actualMSTexture.createView();
117+
emulatedMSTexture = device.createTexture(msTextureDesc);
118+
emulatedMSTextureView = emulatedMSTexture.createView();
100119

101120
if (resolveTexture) {
102121
resolveTexture.destroy();
@@ -111,6 +130,40 @@ function resetMultisampleTexture() {
111130

112131
lastSize = size;
113132
}
133+
134+
if (
135+
config.emulatedDevice !== 'none' &&
136+
lastEmulatedDevice !== config.emulatedDevice
137+
) {
138+
// Pipeline to render to a multisampled texture using *emulated* alpha-to-coverage
139+
const renderWithEmulatedAlphaToCoverageModule = device.createShaderModule({
140+
code:
141+
renderWithEmulatedAlphaToCoverageWGSL +
142+
kEmulatedAlphaToCoverage[config.emulatedDevice],
143+
});
144+
renderWithEmulatedAlphaToCoveragePipeline = device.createRenderPipeline({
145+
label: 'renderWithEmulatedAlphaToCoveragePipeline',
146+
layout: 'auto',
147+
vertex: {
148+
module: renderWithEmulatedAlphaToCoverageModule,
149+
buffers: [
150+
{
151+
stepMode: 'instance',
152+
arrayStride: 4,
153+
attributes: [{ shaderLocation: 0, format: 'unorm8x4', offset: 0 }],
154+
},
155+
],
156+
},
157+
fragment: {
158+
module: renderWithEmulatedAlphaToCoverageModule,
159+
targets: [{ format: 'rgba8unorm' }],
160+
},
161+
multisample: { count: 4, alphaToCoverageEnabled: false },
162+
primitive: { topology: 'triangle-list' },
163+
});
164+
} else {
165+
renderWithEmulatedAlphaToCoveragePipeline = null;
166+
}
114167
}
115168

116169
function applyConfig() {
@@ -129,7 +182,7 @@ function applyConfig() {
129182
]);
130183
device.queue.writeBuffer(bufInstanceColors, 0, data);
131184

132-
resetMultisampleTexture();
185+
resetConfiguredObjects();
133186
}
134187

135188
//
@@ -170,7 +223,16 @@ const showMultisampleTextureModule = device.createShaderModule({
170223
const showMultisampleTexturePipeline = device.createRenderPipeline({
171224
label: 'showMultisampleTexturePipeline',
172225
layout: 'auto',
173-
vertex: { module: showMultisampleTextureModule },
226+
vertex: {
227+
module: showMultisampleTextureModule,
228+
buffers: [
229+
{
230+
stepMode: 'instance',
231+
arrayStride: 4,
232+
attributes: [{ shaderLocation: 0, format: 'unorm8x4', offset: 0 }],
233+
},
234+
],
235+
},
174236
fragment: {
175237
module: showMultisampleTextureModule,
176238
targets: [{ format: presentationFormat }],
@@ -186,8 +248,15 @@ function render() {
186248
const showMultisampleTextureBG = device.createBindGroup({
187249
layout: showMultisampleTextureBGL,
188250
entries: [
189-
{ binding: 0, resource: multisampleTextureView },
190-
{ binding: 1, resource: resolveTextureView },
251+
{ binding: 0, resource: actualMSTextureView },
252+
{
253+
binding: 1,
254+
resource:
255+
config.emulatedDevice === 'none'
256+
? actualMSTextureView
257+
: emulatedMSTextureView,
258+
},
259+
{ binding: 2, resource: resolveTextureView },
191260
],
192261
});
193262

@@ -212,7 +281,7 @@ function render() {
212281
label: 'renderWithAlphaToCoverage pass',
213282
colorAttachments: [
214283
{
215-
view: multisampleTextureView,
284+
view: actualMSTextureView,
216285
resolveTarget: config.showResolvedColor
217286
? resolveTextureView
218287
: undefined,
@@ -227,6 +296,24 @@ function render() {
227296
pass.draw(6, 2);
228297
pass.end();
229298
}
299+
// renderWithEmulatedAlphaToCoverage pass
300+
if (renderWithEmulatedAlphaToCoveragePipeline) {
301+
const pass = commandEncoder.beginRenderPass({
302+
label: 'renderWithEmulatedAlphaToCoverage pass',
303+
colorAttachments: [
304+
{
305+
view: emulatedMSTextureView,
306+
clearValue: [0, 0, 0, 1], // black background
307+
loadOp: 'clear',
308+
storeOp: 'store',
309+
},
310+
],
311+
});
312+
pass.setPipeline(renderWithEmulatedAlphaToCoveragePipeline);
313+
pass.setVertexBuffer(0, bufInstanceColors);
314+
pass.draw(6, 2);
315+
pass.end();
316+
}
230317
// showMultisampleTexture pass
231318
{
232319
const pass = commandEncoder.beginRenderPass({
@@ -242,6 +329,7 @@ function render() {
242329
});
243330
pass.setPipeline(showMultisampleTexturePipeline);
244331
pass.setBindGroup(0, showMultisampleTextureBG);
332+
pass.setVertexBuffer(0, bufInstanceColors);
245333
pass.draw(6);
246334
pass.end();
247335
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
struct Varying {
2+
@builtin(position) pos: vec4f,
3+
// Color from instance-step-mode vertex buffer
4+
@location(0) color: vec4f,
5+
}
6+
7+
@vertex
8+
fn vmain(
9+
@builtin(vertex_index) vertex_index: u32,
10+
@location(0) color: vec4f,
11+
) -> Varying {
12+
var square = array(
13+
vec2f(-1, -1), vec2f(-1, 1), vec2f( 1, -1),
14+
vec2f( 1, -1), vec2f(-1, 1), vec2f( 1, 1),
15+
);
16+
17+
return Varying(vec4(square[vertex_index], 0, 1), color);
18+
}
19+
20+
struct FragOut {
21+
@location(0) color: vec4f,
22+
@builtin(sample_mask) mask: u32,
23+
}
24+
25+
@fragment
26+
fn fmain(vary: Varying) -> FragOut {
27+
let mask = emulatedAlphaToCoverage(vary.color.a, u32(vary.pos.x), u32(vary.pos.y));
28+
return FragOut(vec4f(vary.color.rgb, 1.0), mask);
29+
}
30+

sample/alphaToCoverage/showMultisampleTexture.wgsl

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
@group(0) @binding(0) var tex: texture_multisampled_2d<f32>;
2-
@group(0) @binding(1) var resolved: texture_2d<f32>;
1+
@group(0) @binding(0) var tex_left: texture_multisampled_2d<f32>;
2+
@group(0) @binding(1) var tex_right: texture_multisampled_2d<f32>;
3+
@group(0) @binding(2) var resolved: texture_2d<f32>;
34

45
struct Varying {
56
@builtin(position) pos: vec4f,
@@ -42,7 +43,7 @@ const kSampleOuterRadius = kSampleDistanceFromCloseEdge + kGridEdgeHalfWidth;
4243

4344
@fragment
4445
fn fmain(vary: Varying) -> @location(0) vec4f {
45-
let dim = textureDimensions(tex);
46+
let dim = textureDimensions(tex_left);
4647
let dimMax = max(dim.x, dim.y);
4748

4849
let xy = vary.uv * f32(dimMax);
@@ -56,8 +57,13 @@ fn fmain(vary: Varying) -> @location(0) vec4f {
5657
let distanceFromSample = distance(xyFrac, kSamplePositions[sampleIndex]);
5758
if distanceFromSample < kSampleInnerRadius {
5859
// Draw a circle for the sample value
59-
let val = textureLoad(tex, xyInt, sampleIndex).rgb;
60-
return vec4f(val, 1);
60+
if xyFrac.x < kSamplePositions[sampleIndex].x {
61+
let val = textureLoad(tex_left, xyInt, sampleIndex).rgb;
62+
return vec4f(val, 1);
63+
} else {
64+
let val = textureLoad(tex_right, xyInt, sampleIndex).rgb;
65+
return vec4f(val, 1);
66+
}
6167
} else if distanceFromSample < kSampleOuterRadius {
6268
// Draw a ring around the circle
6369
return vec4f(0, 0, 0, 1);

0 commit comments

Comments
 (0)