Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slightly expand sample_mask tests #2466

Merged
merged 2 commits into from
Mar 28, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
291 changes: 144 additions & 147 deletions src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
export const description = `
Tests that the final sample mask is the logical AND of all the relevant masks, including
the rasterization mask, sample mask, fragment output mask, and alpha to coverage mask (when alphaToCoverageEnabled === true)
the rasterization mask, sample mask, fragment output mask, and alpha to coverage mask (when alphaToCoverageEnabled === true).

Also tested:
- The positions of samples in the standard sample patterns.
- Per-sample interpolation sampling: @interpolate(perspective, sample).

TODO: add a test without a 0th color attachment (sparse color attachment), with different color attachments and alpha value output.
The cross-platform behavior is unknown. could be any of:
- coverage is always 100%
- coverage is always 0%
- it uses the first non-null attachment
- it's an error

Details could be found at: https://github.com/gpuweb/cts/issues/2201
`;

Expand Down Expand Up @@ -121,108 +124,142 @@ function getExpectedStencilData(
return expectedData;
}

const kSampleMaskTestVertexShader = `
struct VertexOutput {
const kSampleMaskTestShader = `
struct Varyings {
@builtin(position) Position : vec4<f32>,
@location(0) @interpolate(perspective, sample) fragUV : vec2<f32>,
@location(0) @interpolate(flat) uvFlat : vec2<f32>,
@location(1) @interpolate(perspective, sample) uvInterpolated : vec2<f32>,
}

//
// Vertex shader
//

@vertex
fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
var pos = array<vec2<f32>, 30>(
// center quad
// only covers pixel center which is sample point when sampleCount === 1
// small enough to avoid covering any multi sample points
vec2<f32>( 0.2, 0.2),
vec2<f32>( 0.2, -0.2),
vec2<f32>(-0.2, -0.2),
vec2<f32>( 0.2, 0.2),
vec2<f32>(-0.2, -0.2),
vec2<f32>(-0.2, 0.2),

// Sub quads are representing rasterization mask and
// are slightly scaled to avoid covering the pixel center

// top-left quad
vec2<f32>( -0.01, 1.0),
vec2<f32>( -0.01, 0.01),
vec2<f32>(-1.0, 0.01),
vec2<f32>( -0.01, 1.0),
vec2<f32>(-1.0, 0.01),
vec2<f32>(-1.0, 1.0),

// top-right quad
vec2<f32>(1.0, 1.0),
vec2<f32>(1.0, 0.01),
vec2<f32>(0.01, 0.01),
vec2<f32>(1.0, 1.0),
vec2<f32>(0.01, 0.01),
vec2<f32>(0.01, 1.0),

// bottom-left quad
vec2<f32>( -0.01, -0.01),
vec2<f32>( -0.01, -1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>( -0.01, -0.01),
vec2<f32>(-1.0, -1.0),
vec2<f32>(-1.0, -0.01),

// bottom-right quad
vec2<f32>(1.0, -0.01),
vec2<f32>(1.0, -1.0),
vec2<f32>(0.01, -1.0),
vec2<f32>(1.0, -0.01),
vec2<f32>(0.01, -1.0),
vec2<f32>(0.01, -0.01)
fn vmain(@builtin(vertex_index) VertexIndex : u32,
@builtin(instance_index) InstanceIndex : u32) -> Varyings {
// Standard sample locations within a pixel, where the pixel ranges from (-1,-1) to (1,1), and is
// centered at (0,0) (NDC - the test uses a 1x1 render target).
// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels
var sampleCenters = array(
// sampleCount = 1
vec2f(0, 0),
// sampleCount = 4
vec2f(-2, 6) / 8,
vec2f( 6, 2) / 8,
vec2f(-6, -2) / 8,
vec2f( 2, -6) / 8,
);
// A tiny quad to draw around the sample center to ensure we hit only the expected point.
let kTinyQuadRadius = 1.0 / 32;
var tinyQuad = array(
vec2f( kTinyQuadRadius, kTinyQuadRadius),
vec2f( kTinyQuadRadius, -kTinyQuadRadius),
vec2f(-kTinyQuadRadius, -kTinyQuadRadius),
vec2f( kTinyQuadRadius, kTinyQuadRadius),
vec2f(-kTinyQuadRadius, -kTinyQuadRadius),
vec2f(-kTinyQuadRadius, kTinyQuadRadius),
);

var uv = array<vec2<f32>, 30>(
var uvsFlat = array(
// sampleCount = 1
// Note: avoids hitting the point between the 4 texels.
vec2f(0.51, 0.51),
// sampleCount = 4
vec2f(0.25, 0.25),
vec2f(0.75, 0.25),
vec2f(0.25, 0.75),
vec2f(0.75, 0.75),
);
var uvsInterpolated = array(
// center quad
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 1.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(0.0, 0.0),
// Note: the interpolated point will be exactly in the middle of the 4 texels.
// The test expects to get texel 1,1 (the 3rd texel) in this case.
vec2f(1.0, 0.0),
vec2f(1.0, 1.0),
vec2f(0.0, 1.0),
vec2f(1.0, 0.0),
vec2f(0.0, 1.0),
vec2f(0.0, 0.0),

// top-left quad (texel 0)
vec2<f32>(0.5, 0.0),
vec2<f32>(0.5, 0.5),
vec2<f32>(0.0, 0.5),
vec2<f32>(0.5, 0.0),
vec2<f32>(0.0, 0.5),
vec2<f32>(0.0, 0.0),
vec2f(0.5, 0.0),
vec2f(0.5, 0.5),
vec2f(0.0, 0.5),
vec2f(0.5, 0.0),
vec2f(0.0, 0.5),
vec2f(0.0, 0.0),

// top-right quad (texel 1)
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 0.5),
vec2<f32>(0.5, 0.5),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.5, 0.5),
vec2<f32>(0.5, 0.0),
vec2f(1.0, 0.0),
vec2f(1.0, 0.5),
vec2f(0.5, 0.5),
vec2f(1.0, 0.0),
vec2f(0.5, 0.5),
vec2f(0.5, 0.0),

// bottom-left quad (texel 2)
vec2<f32>(0.5, 0.5),
vec2<f32>(0.5, 1.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(0.5, 0.5),
vec2<f32>(0.0, 1.0),
vec2<f32>(0.0, 0.5),
vec2f(0.5, 0.5),
vec2f(0.5, 1.0),
vec2f(0.0, 1.0),
vec2f(0.5, 0.5),
vec2f(0.0, 1.0),
vec2f(0.0, 0.5),

// bottom-right quad (texel 3)
vec2<f32>(1.0, 0.5),
vec2<f32>(1.0, 1.0),
vec2<f32>(0.5, 1.0),
vec2<f32>(1.0, 0.5),
vec2<f32>(0.5, 1.0),
vec2<f32>(0.5, 0.5)
vec2f(1.0, 0.5),
vec2f(1.0, 1.0),
vec2f(0.5, 1.0),
vec2f(1.0, 0.5),
vec2f(0.5, 1.0),
vec2f(0.5, 0.5)
);

var output : VertexOutput;
output.Position = vec4<f32>(pos[VertexIndex], ${kDepthWriteValue}, 1.0);
output.fragUV = uv[VertexIndex];
var output : Varyings;
let pos = sampleCenters[InstanceIndex] + tinyQuad[VertexIndex];
output.Position = vec4(pos, ${kDepthWriteValue}, 1.0);
output.uvFlat = uvsFlat[InstanceIndex];
output.uvInterpolated = uvsInterpolated[InstanceIndex * 6 + VertexIndex];
return output;
}`;
}

//
// Fragment shaders
//

@group(0) @binding(0) var mySampler: sampler;
@group(0) @binding(1) var myTexture: texture_2d<f32>;

// For test named 'fragment_output_mask'

@group(0) @binding(2) var<uniform> fragMask: u32;
struct FragmentOutput1 {
@builtin(sample_mask) mask : u32,
@location(0) color : vec4<f32>,
}
@fragment fn fmain__fragment_output_mask__flat(varyings: Varyings) -> FragmentOutput1 {
return FragmentOutput1(fragMask, textureSample(myTexture, mySampler, varyings.uvFlat));
}
@fragment fn fmain__fragment_output_mask__interp(varyings: Varyings) -> FragmentOutput1 {
return FragmentOutput1(fragMask, textureSample(myTexture, mySampler, varyings.uvInterpolated));
}

// For test named 'alpha_to_coverage_mask'

struct FragmentOutput2 {
@location(0) color0 : vec4<f32>,
@location(1) color1 : vec4<f32>,
}
@group(0) @binding(2) var<uniform> alpha: vec2<f32>;
@fragment fn fmain__alpha_to_coverage_mask__flat(varyings: Varyings) -> FragmentOutput2 {
var c = textureSample(myTexture, mySampler, varyings.uvFlat);
return FragmentOutput2(vec4(c.xyz, alpha[0]), vec4(c.xyz, alpha[1]));
}
@fragment fn fmain__alpha_to_coverage_mask__interp(varyings: Varyings) -> FragmentOutput2 {
var c = textureSample(myTexture, mySampler, varyings.uvInterpolated);
return FragmentOutput2(vec4(c.xyz, alpha[0]), vec4(c.xyz, alpha[1]));
}
`;

class F extends TextureTestMixin(GPUTest) {
private sampleTexture: GPUTexture | undefined;
Expand Down Expand Up @@ -358,25 +395,25 @@ class F extends TextureTestMixin(GPUTest) {
if (sampleCount === 1) {
if ((rasterizationMask & 1) !== 0) {
// draw center quad
passEncoder.draw(6);
passEncoder.draw(6, 1, 0, 0);
}
} else {
assert(sampleCount === 4);
if ((rasterizationMask & 1) !== 0) {
// draw top-left quad
passEncoder.draw(6, 1, 6);
passEncoder.draw(6, 1, 0, 1);
}
if ((rasterizationMask & 2) !== 0) {
// draw top-right quad
passEncoder.draw(6, 1, 12);
passEncoder.draw(6, 1, 0, 2);
}
if ((rasterizationMask & 4) !== 0) {
// draw bottom-left quad
passEncoder.draw(6, 1, 18);
passEncoder.draw(6, 1, 0, 3);
}
if ((rasterizationMask & 8) !== 0) {
// draw bottom-right quad
passEncoder.draw(6, 1, 24);
passEncoder.draw(6, 1, 0, 4);
}
}
passEncoder.end();
Expand Down Expand Up @@ -470,9 +507,11 @@ textureLoad each sample index from the texture and write to a storage buffer to
)
.params(u =>
u
.combine('interpolated', [false, true])
.combine('sampleCount', [1, 4] as const)
.expand('rasterizationMask', function* (p) {
for (let i = 0, len = 2 ** p.sampleCount - 1; i <= len; i++) {
const maxMask = 2 ** p.sampleCount - 1;
for (let i = 0; i <= maxMask; i++) {
yield i;
}
})
Expand Down Expand Up @@ -514,32 +553,13 @@ textureLoad each sample index from the texture and write to a storage buffer to
new Uint32Array([fragmentShaderOutputMask])
);

const module = t.device.createShaderModule({ code: kSampleMaskTestShader });
const pipeline = t.device.createRenderPipeline({
layout: 'auto',
vertex: {
module: t.device.createShaderModule({
code: kSampleMaskTestVertexShader,
}),
entryPoint: 'main',
},
vertex: { module, entryPoint: 'vmain' },
fragment: {
module: t.device.createShaderModule({
code: `
@group(0) @binding(0) var mySampler: sampler;
@group(0) @binding(1) var myTexture: texture_2d<f32>;
@group(0) @binding(2) var<uniform> fragMask: u32;

struct FragmentOutput {
@builtin(sample_mask) mask : u32,
@location(0) color : vec4<f32>,
}

@fragment
fn main(@location(0) @interpolate(perspective, sample) fragUV: vec2<f32>) -> FragmentOutput {
return FragmentOutput(fragMask, textureSample(myTexture, mySampler, fragUV));
}`,
}),
entryPoint: 'main',
module,
entryPoint: `fmain__fragment_output_mask__${t.params.interpolated ? 'interp' : 'flat'}`,
targets: [{ format }],
},
primitive: { topology: 'triangle-list' },
Expand Down Expand Up @@ -622,18 +642,20 @@ color' <= color.
)
.params(u =>
u
.combine('interpolated', [false, true])
.combine('sampleCount', [4] as const)
.expand('rasterizationMask', function* (p) {
for (let i = 0, len = 0xf; i <= len; i++) {
const maxMask = 2 ** p.sampleCount - 1;
for (let i = 0; i <= maxMask; i++) {
yield i;
}
})
.beginSubcases()
.combine('alpha1', [0.0, 0.5, 1.0] as const)
)
.fn(async t => {
const sampleCount = 4;
const { sampleCount, rasterizationMask, alpha1 } = t.params;
const sampleMask = 0xffffffff;
const { rasterizationMask, alpha1 } = t.params;

const alphaValues = new Float32Array(4); // [alpha0, alpha1, 0, 0]
const alphaValueUniformBuffer = t.device.createBuffer({
Expand All @@ -642,38 +664,13 @@ color' <= color.
});
t.trackForCleanup(alphaValueUniformBuffer);

const module = t.device.createShaderModule({ code: kSampleMaskTestShader });
const pipeline = t.device.createRenderPipeline({
layout: 'auto',
vertex: {
module: t.device.createShaderModule({
code: kSampleMaskTestVertexShader,
}),
entryPoint: 'main',
},
vertex: { module, entryPoint: 'vmain' },
fragment: {
module: t.device.createShaderModule({
code: `
@group(0) @binding(0) var mySampler: sampler;
@group(0) @binding(1) var myTexture: texture_2d<f32>;
@group(0) @binding(2) var<uniform> alpha: vec4<f32>;

struct FragmentOutput {
@location(0) color0 : vec4<f32>,
@location(1) color1 : vec4<f32>,
}

@fragment
fn main(@location(0) @interpolate(perspective, sample) fragUV: vec2<f32>) -> FragmentOutput {
var c = textureSample(myTexture, mySampler, fragUV);
var output: FragmentOutput;
output.color0 = c;
output.color0.a = alpha[0];
output.color1 = c;
output.color1.a = alpha[1];
return output;
}`,
}),
entryPoint: 'main',
module,
entryPoint: `fmain__alpha_to_coverage_mask__${t.params.interpolated ? 'interp' : 'flat'}`,
targets: [{ format }, { format }],
},
primitive: { topology: 'triangle-list' },
Expand Down