Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
kainino0x committed Oct 22, 2024
1 parent 1e3c2d3 commit e65e384
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 20 deletions.
57 changes: 57 additions & 0 deletions sample/alphaToCoverage/emulatedAlphaToCoverage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Database of alpha-to-coverage patterns from different devices.
*
* Name of device ->
* Array of patterns from a=0.0 to a=1.0, evenly spaced, excluding endpoints ->
* Array of N*N masks depending on the block size of the pattern used
* (in row-major order)
*/
export const alphaToCoverageDatabase: { [k: string]: PatternSequence } = {
'NVIDIA GeForce RTX 3070': [[0b1000], [0b1001], [0b1011]],
'Intel HD Graphics 4400': [[0b0001], [0b0011], [0b0111]],
};

type PatternSequence = ReadonlyArray<Pattern>;
type Pattern = ReadonlyArray<Mask>;
type Mask = number;

/**
* For each device name, provides the source for a WGSL function which emulates
* the alpha-to-coverage algorithm of that device by mapping (alpha, x, y) to
* a sample mask.
*/
export const kEmulatedAlphaToCoverage = {
'Apple M1 Pro': `\
fn emulatedAlphaToCoverage(alpha: f32, x: u32, y: u32) -> u32 {
let u = x % 2;
let v = y % 2;
if (alpha < 0.5 / 16) { return ${0b0000}; }
// FIXME returning values out of an array is not working, always returns 0
if (alpha < 1.5 / 16) { return array(array(${0b0001}u, ${0b0000}), array(${0b0000}, ${0b0000}))[v][u]; }
if (alpha < 2.5 / 16) { return array(array(${0b0001}u, ${0b0000}), array(${0b0000}, ${0b0001}))[v][u]; }
if (alpha < 3.5 / 16) { return array(array(${0b0001}u, ${0b0001}), array(${0b0000}, ${0b0001}))[v][u]; }
if (alpha < 4.5 / 16) { return array(array(${0b0001}u, ${0b0001}), array(${0b0001}, ${0b0001}))[v][u]; }
if (alpha < 5.5 / 16) { return array(array(${0b1001}u, ${0b0001}), array(${0b0001}, ${0b0001}))[v][u]; }
if (alpha < 6.5 / 16) { return array(array(${0b1001}u, ${0b0001}), array(${0b0001}, ${0b1001}))[v][u]; }
if (alpha < 7.5 / 16) { return array(array(${0b1001}u, ${0b1001}), array(${0b0001}, ${0b1001}))[v][u]; }
if (alpha < 8.5 / 16) { return array(array(${0b1001}u, ${0b1001}), array(${0b1001}, ${0b1001}))[v][u]; }
if (alpha < 9.5 / 16) { return array(array(${0b1011}u, ${0b1001}), array(${0b1001}, ${0b1001}))[v][u]; }
if (alpha < 10.5 / 16) { return array(array(${0b1011}u, ${0b1001}), array(${0b1001}, ${0b1011}))[v][u]; }
if (alpha < 11.5 / 16) { return array(array(${0b1011}u, ${0b1011}), array(${0b1001}, ${0b1011}))[v][u]; }
if (alpha < 12.5 / 16) { return array(array(${0b1011}u, ${0b1011}), array(${0b1011}, ${0b1011}))[v][u]; }
if (alpha < 13.5 / 16) { return array(array(${0b1111}u, ${0b1011}), array(${0b1011}, ${0b1011}))[v][u]; }
if (alpha < 14.5 / 16) { return array(array(${0b1111}u, ${0b1011}), array(${0b1011}, ${0b1111}))[v][u]; }
if (alpha < 15.5 / 16) { return array(array(${0b1111}u, ${0b1111}), array(${0b1011}, ${0b1111}))[v][u]; }
return ${0b1111};
}
`.trimEnd(),
'NVIDIA GeForce RTX 3070': `\
fn emulatedAlphaToCoverage(alpha: f32, x: u32, y: u32) -> u32 {
if (alpha < 0.5 / 4) { return ${0b0000}; }
if (alpha < 1.5 / 4) { return ${0b1000}; }
if (alpha < 2.5 / 4) { return ${0b1001}; }
if (alpha < 3.5 / 4) { return ${0b1011}; }
return ${0b1111};
}
`.trimEnd(),
};
118 changes: 103 additions & 15 deletions sample/alphaToCoverage/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { GUI } from 'dat.gui';

import showMultisampleTextureWGSL from './showMultisampleTexture.wgsl';
import renderWithAlphaToCoverageWGSL from './renderWithAlphaToCoverage.wgsl';
import renderWithEmulatedAlphaToCoverageWGSL from './renderWithEmulatedAlphaToCoverage.wgsl';
import { quitIfWebGPUNotAvailable } from '../util';
import { kEmulatedAlphaToCoverage } from './emulatedAlphaToCoverage';

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

const kInitConfig = {
scene: 'solid_colors',
emulatedDevice: 'none',
sizeLog2: 3,
showResolvedColor: true,
color1: 0x0000ff,
Expand All @@ -34,17 +38,23 @@ gui.width = 300;
},
};

gui.add(config, 'scene', ['solid_colors']);
gui.add(config, 'emulatedDevice', [
'none',
...Object.keys(kEmulatedAlphaToCoverage),
]);

const settings = gui.addFolder('Settings');
settings.open();
settings.add(config, 'sizeLog2', 0, 8, 1).name('size = 2**');
settings.add(config, 'showResolvedColor', true);

const draw1Panel = gui.addFolder('Draw 1');
const draw1Panel = gui.addFolder('solid_colors Draw 1');
draw1Panel.open();
draw1Panel.addColor(config, 'color1').name('color');
draw1Panel.add(config, 'alpha1', 0, 255).name('alpha');

const draw2Panel = gui.addFolder('Draw 2');
const draw2Panel = gui.addFolder('solid_colors Draw 2');
draw2Panel.open();
draw2Panel.addColor(config, 'color2').name('color');
draw2Panel.add(config, 'alpha2', 0, 255).name('alpha');
Expand Down Expand Up @@ -80,23 +90,32 @@ const bufInstanceColors = device.createBuffer({
size: 8,
});

let multisampleTexture: GPUTexture, multisampleTextureView: GPUTextureView;
let actualMSTexture: GPUTexture, actualMSTextureView: GPUTextureView;
let emulatedMSTexture: GPUTexture, emulatedMSTextureView: GPUTextureView;
let resolveTexture: GPUTexture, resolveTextureView: GPUTextureView;
let lastSize = 0;
function resetMultisampleTexture() {
let renderWithEmulatedAlphaToCoveragePipeline: GPURenderPipeline | null;
let lastEmulatedDevice = 'none';
function resetConfiguredObjects() {
const size = 2 ** config.sizeLog2;
if (lastSize !== size) {
if (multisampleTexture) {
multisampleTexture.destroy();
if (actualMSTexture) {
actualMSTexture.destroy();
}
multisampleTexture = device.createTexture({
format: 'rgba8unorm',
if (emulatedMSTexture) {
emulatedMSTexture.destroy();
}
const msTextureDesc = {
format: 'rgba8unorm' as const,
usage:
GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
size: [size, size],
sampleCount: 4,
});
multisampleTextureView = multisampleTexture.createView();
};
actualMSTexture = device.createTexture(msTextureDesc);
actualMSTextureView = actualMSTexture.createView();
emulatedMSTexture = device.createTexture(msTextureDesc);
emulatedMSTextureView = emulatedMSTexture.createView();

if (resolveTexture) {
resolveTexture.destroy();
Expand All @@ -111,6 +130,40 @@ function resetMultisampleTexture() {

lastSize = size;
}

if (
config.emulatedDevice !== 'none' &&
lastEmulatedDevice !== config.emulatedDevice
) {
// Pipeline to render to a multisampled texture using *emulated* alpha-to-coverage
const renderWithEmulatedAlphaToCoverageModule = device.createShaderModule({
code:
renderWithEmulatedAlphaToCoverageWGSL +
kEmulatedAlphaToCoverage[config.emulatedDevice],
});
renderWithEmulatedAlphaToCoveragePipeline = device.createRenderPipeline({
label: 'renderWithEmulatedAlphaToCoveragePipeline',
layout: 'auto',
vertex: {
module: renderWithEmulatedAlphaToCoverageModule,
buffers: [
{
stepMode: 'instance',
arrayStride: 4,
attributes: [{ shaderLocation: 0, format: 'unorm8x4', offset: 0 }],
},
],
},
fragment: {
module: renderWithEmulatedAlphaToCoverageModule,
targets: [{ format: 'rgba8unorm' }],
},
multisample: { count: 4, alphaToCoverageEnabled: false },
primitive: { topology: 'triangle-list' },
});
} else {
renderWithEmulatedAlphaToCoveragePipeline = null;
}
}

function applyConfig() {
Expand All @@ -129,7 +182,7 @@ function applyConfig() {
]);
device.queue.writeBuffer(bufInstanceColors, 0, data);

resetMultisampleTexture();
resetConfiguredObjects();
}

//
Expand Down Expand Up @@ -170,7 +223,16 @@ const showMultisampleTextureModule = device.createShaderModule({
const showMultisampleTexturePipeline = device.createRenderPipeline({
label: 'showMultisampleTexturePipeline',
layout: 'auto',
vertex: { module: showMultisampleTextureModule },
vertex: {
module: showMultisampleTextureModule,
buffers: [
{
stepMode: 'instance',
arrayStride: 4,
attributes: [{ shaderLocation: 0, format: 'unorm8x4', offset: 0 }],
},
],
},
fragment: {
module: showMultisampleTextureModule,
targets: [{ format: presentationFormat }],
Expand All @@ -186,8 +248,15 @@ function render() {
const showMultisampleTextureBG = device.createBindGroup({
layout: showMultisampleTextureBGL,
entries: [
{ binding: 0, resource: multisampleTextureView },
{ binding: 1, resource: resolveTextureView },
{ binding: 0, resource: actualMSTextureView },
{
binding: 1,
resource:
config.emulatedDevice === 'none'
? actualMSTextureView
: emulatedMSTextureView,
},
{ binding: 2, resource: resolveTextureView },
],
});

Expand All @@ -212,7 +281,7 @@ function render() {
label: 'renderWithAlphaToCoverage pass',
colorAttachments: [
{
view: multisampleTextureView,
view: actualMSTextureView,
resolveTarget: config.showResolvedColor
? resolveTextureView
: undefined,
Expand All @@ -227,6 +296,24 @@ function render() {
pass.draw(6, 2);
pass.end();
}
// renderWithEmulatedAlphaToCoverage pass
if (renderWithEmulatedAlphaToCoveragePipeline) {
const pass = commandEncoder.beginRenderPass({
label: 'renderWithEmulatedAlphaToCoverage pass',
colorAttachments: [
{
view: emulatedMSTextureView,
clearValue: [0, 0, 0, 1], // black background
loadOp: 'clear',
storeOp: 'store',
},
],
});
pass.setPipeline(renderWithEmulatedAlphaToCoveragePipeline);
pass.setVertexBuffer(0, bufInstanceColors);
pass.draw(6, 2);
pass.end();
}
// showMultisampleTexture pass
{
const pass = commandEncoder.beginRenderPass({
Expand All @@ -242,6 +329,7 @@ function render() {
});
pass.setPipeline(showMultisampleTexturePipeline);
pass.setBindGroup(0, showMultisampleTextureBG);
pass.setVertexBuffer(0, bufInstanceColors);
pass.draw(6);
pass.end();
}
Expand Down
30 changes: 30 additions & 0 deletions sample/alphaToCoverage/renderWithEmulatedAlphaToCoverage.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
struct Varying {
@builtin(position) pos: vec4f,
// Color from instance-step-mode vertex buffer
@location(0) color: vec4f,
}

@vertex
fn vmain(
@builtin(vertex_index) vertex_index: u32,
@location(0) color: vec4f,
) -> 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), color);
}

struct FragOut {
@location(0) color: vec4f,
@builtin(sample_mask) mask: u32,
}

@fragment
fn fmain(vary: Varying) -> FragOut {
let mask = emulatedAlphaToCoverage(vary.color.a, u32(vary.pos.x), u32(vary.pos.y));
return FragOut(vec4f(vary.color.rgb, 1.0), mask);
}

16 changes: 11 additions & 5 deletions sample/alphaToCoverage/showMultisampleTexture.wgsl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@group(0) @binding(0) var tex: texture_multisampled_2d<f32>;
@group(0) @binding(1) var resolved: texture_2d<f32>;
@group(0) @binding(0) var tex_left: texture_multisampled_2d<f32>;
@group(0) @binding(1) var tex_right: texture_multisampled_2d<f32>;
@group(0) @binding(2) var resolved: texture_2d<f32>;

struct Varying {
@builtin(position) pos: vec4f,
Expand Down Expand Up @@ -42,7 +43,7 @@ const kSampleOuterRadius = kSampleDistanceFromCloseEdge + kGridEdgeHalfWidth;

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

let xy = vary.uv * f32(dimMax);
Expand All @@ -56,8 +57,13 @@ fn fmain(vary: Varying) -> @location(0) vec4f {
let distanceFromSample = distance(xyFrac, kSamplePositions[sampleIndex]);
if distanceFromSample < kSampleInnerRadius {
// Draw a circle for the sample value
let val = textureLoad(tex, xyInt, sampleIndex).rgb;
return vec4f(val, 1);
if xyFrac.x < kSamplePositions[sampleIndex].x {
let val = textureLoad(tex_left, xyInt, sampleIndex).rgb;
return vec4f(val, 1);
} else {
let val = textureLoad(tex_right, xyInt, sampleIndex).rgb;
return vec4f(val, 1);
}
} else if distanceFromSample < kSampleOuterRadius {
// Draw a ring around the circle
return vec4f(0, 0, 0, 1);
Expand Down

0 comments on commit e65e384

Please sign in to comment.