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 1 commit
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
230 changes: 113 additions & 117 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,63 +124,56 @@ 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, multiplied by 8 so the pixel ranges from -8 to 8 in
// each dimension (as in the D3D documentation).
// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels
var sampleCentersX8 = array(
// sampleCount = 1
vec2f(0, 0),
// sampleCount = 4
vec2f(-2, 6),
vec2f(6, 2),
vec2f(-6, -2),
vec2f(2, -6),
);
// A tiny quad to draw around the sample center to ensure we hit only the expected point.
var tinyQuadX8 = array(
kainino0x marked this conversation as resolved.
Show resolved Hide resolved
vec2( 0.25, 0.25),
vec2( 0.25, -0.25),
vec2(-0.25, -0.25),
vec2( 0.25, 0.25),
vec2(-0.25, -0.25),
vec2(-0.25, 0.25),
);

var uv = array<vec2<f32>, 30>(
var uvsFlat = array(
// sampleCount = 1
// Note: avoids hitting the point between the 4 texels.
vec2(0.51, 0.51),
// sampleCount = 4
vec2(0.25, 0.25),
vec2<f32>(0.75, 0.25),
kainino0x marked this conversation as resolved.
Show resolved Hide resolved
vec2<f32>(0.25, 0.75),
vec2<f32>(0.75, 0.75),
);
var uvsInterpolated = array(
// center quad
// 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.
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 1.0),
vec2<f32>(0.0, 1.0),
Expand Down Expand Up @@ -218,11 +214,51 @@ fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
vec2<f32>(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 = (sampleCentersX8[InstanceIndex] + tinyQuadX8[VertexIndex]) / 8.0;
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 +394,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 +506,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 +552,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 +641,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 +663,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