Skip to content

Commit

Permalink
Slightly expand sample_mask tests (gpuweb#2466)
Browse files Browse the repository at this point in the history
* Slightly expand sample_mask tests

- Tighten the rasterized primitives around the standard sample locations
  as a test that they're in the exact places we expect.
- Parameterize by whether UV is interpolated or not. The
  non-interpolated fragment_output_mask tests now pass on Mali, but
  alpha_to_coverage tests still all fail, but we don't know why.

* address comments
  • Loading branch information
kainino0x authored and ErichDonGubler committed Aug 16, 2023
1 parent cf5f9a4 commit 4bd1220
Showing 1 changed file with 144 additions and 147 deletions.
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

0 comments on commit 4bd1220

Please sign in to comment.