Skip to content

Commit

Permalink
Test fragment_input @Builtin(sample_mask) (#3404)
Browse files Browse the repository at this point in the history
* Test fragment_input @Builtin(sample_mask)
  • Loading branch information
greggman authored Feb 15, 2024
1 parent ec9a1bc commit b03d6d4
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 7 deletions.
2 changes: 1 addition & 1 deletion src/webgpu/listing_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -1705,7 +1705,7 @@
"webgpu:shader,execution,shader_io,fragment_builtins:inputs,interStage,centroid:*": { "subcaseMS": 1.001 },
"webgpu:shader,execution,shader_io,fragment_builtins:inputs,position:*": { "subcaseMS": 1.001 },
"webgpu:shader,execution,shader_io,fragment_builtins:inputs,sample_index:*": { "subcaseMS": 1.001 },
"webgpu:shader,execution,shader_io,fragment_builtins:inputs,sample_mask:*": { "subcaseMS": 0.050 },
"webgpu:shader,execution,shader_io,fragment_builtins:inputs,sample_mask:*": { "subcaseMS": 1.001 },
"webgpu:shader,execution,shader_io,shared_structs:shared_between_stages:*": { "subcaseMS": 9.601 },
"webgpu:shader,execution,shader_io,shared_structs:shared_with_buffer:*": { "subcaseMS": 20.701 },
"webgpu:shader,execution,shader_io,shared_structs:shared_with_non_entry_point_function:*": { "subcaseMS": 6.801 },
Expand Down
202 changes: 196 additions & 6 deletions src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const description = `Test fragment shader builtin variables and inter-sta
* test @interpolate
* test builtin(sample_index)
* test builtin(front_facing)
* test builtin(sample_mask)
Note: @interpolate settings and sample_index affect whether or not the fragment shader
is evaluated per-fragment or per-sample. With @interpolate(, sample) or usage of
Expand Down Expand Up @@ -305,6 +306,7 @@ type FragData = {
ndcPoints: readonly number[][];
windowPoints: readonly number[][];
sampleIndex: number;
sampleMask: number;
frontFacing: boolean;
};

Expand Down Expand Up @@ -349,6 +351,22 @@ function generateFragmentInputs({

for (let y = 0; y < height; ++y) {
for (let x = 0; x < width; ++x) {
let sampleMask = 0;
for (let sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) {
const localSampleMask = 1 << sampleIndex;
const multisampleOffset = fragmentOffsets[sampleIndex];
const sampleFragmentPoint = [x + multisampleOffset[0], y + multisampleOffset[1]];
const sampleBarycentricCoords = calcBarycentricCoordinates(
windowPoints2D,
sampleFragmentPoint
);

const inside = isInsideTriangle(sampleBarycentricCoords);
if (inside) {
sampleMask |= localSampleMask;
}
}

for (let sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) {
const fragmentPoint = [x + 0.5, y + 0.5];
const multisampleOffset = fragmentOffsets[sampleIndex];
Expand All @@ -373,6 +391,7 @@ function generateFragmentInputs({
ndcPoints,
windowPoints,
sampleIndex,
sampleMask,
frontFacing,
});

Expand Down Expand Up @@ -481,6 +500,13 @@ function computeFragmentFrontFacing({ frontFacing }: FragData) {
return [frontFacing ? 1 : 0, 0, 0, 0];
}

/**
* Computes 'builtin(sample_mask)'
*/
function computeSampleMask({ sampleMask }: FragData) {
return [sampleMask, 0, 0, 0];
}

/**
* Renders float32 fragment shader inputs values to 4 rgba8unorm textures that
* can be multisampled textures. It stores each of the channels, r, g, b, a of
Expand Down Expand Up @@ -1203,8 +1229,8 @@ g.test('inputs,front_facing')
interpolateFn: computeFragmentFrontFacing,
});

// Double check, first corner should be different than last based on the triangles we are drawing.
assert(expected[0] !== expected[expected.length - 4]);
assert(expected.indexOf(0) >= 0, 'expect some values to be 0');
assert(expected.findIndex(v => v !== 0) >= 0, 'expect some values to be non 0');

t.expectOK(
checkSampleRectsApproximatelyEqual({
Expand All @@ -1218,7 +1244,171 @@ g.test('inputs,front_facing')
);
});

// To test sample_mask as a fragment shader output, consider
// extending the tests in
// src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts
g.test('inputs,sample_mask').unimplemented();
g.test('inputs,sample_mask')
.desc(
`
Test fragment shader builtin(sample_mask) values.
Draws various triangles that should trigger different sample_mask values.
Checks that sample_mask matches what's expected. Note: the triangles
are selected so they do not intersect sample points as we don't want
to test precision issues on whether or not a sample point is inside
or outside the triangle when right on the edge.
Example: x=-1, y=2, it draws the following triangle
[ -0.8, -2 ]
[ 1.2, 2 ]
[ -0.8, 2 ]
On to a 4x4 pixel texture
-0.8, 2
.----------------------. 1.2 2
|...................../
|..................../
|.................../
|................../
|................./
+-----+-----+-----+-----+ ---
| |...|.....|..../| | ^
| |...|.....|.../ | | |
+-----+-----+-----+-----+ |
| |...|.....|../ | | |
| |...|.....|./ | | |
+-----+-----+-----+-----+ texture / clip space
| |...|.....| | | |
| |...|..../| | | |
+-----+-----+-----+-----+ |
| |...|../ | | | |
| |...|./ | | | V
+-----+-----+-----+-----+ ---
|.../
|../
|./
|/
/
.
-0.8, -2
Inside an individual pixel you might see this situation
+-------------+
|....s1|/ |
|......| |
|...../| s2 |
+------C------+
|s3./ | |
|../ | |
|./ |s4 |
+-------------+
where s1, s2, s3, s4, are sample points and C is the center. For a sampleCount = 4 texture
the sample_mask is expected to emit sample_mask = 0b0101
ref: https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels
`
)
.params(u =>
u //
.combine('nearFar', [[0, 1] as const, [0.25, 0.75] as const] as const)
.combine('sampleCount', [1, 4] as const)
.combine('interpolation', [
// given that 'sample' effects whether things are run per-sample or per-fragment
// we test all of these to make sure they don't affect the result differently than expected.
{ type: 'perspective', sampling: 'center' },
{ type: 'perspective', sampling: 'centroid' },
{ type: 'perspective', sampling: 'sample' },
{ type: 'linear', sampling: 'center' },
{ type: 'linear', sampling: 'centroid' },
{ type: 'linear', sampling: 'sample' },
{ type: 'flat' },
] as const)
.beginSubcases()
.combineWithParams([
{ x: -1, y: -1 },
{ x: -1, y: -2 },
{ x: -1, y: 1 },
{ x: -1, y: 3 },
{ x: -2, y: -1 },
{ x: -2, y: 3 },
{ x: -3, y: -1 },
{ x: -3, y: -2 },
{ x: -3, y: 1 },
{ x: 1, y: -1 },
{ x: 1, y: -3 },
{ x: 1, y: 1 },
{ x: 1, y: 2 },
{ x: 2, y: -2 },
{ x: 2, y: -3 },
{ x: 2, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: -1 },
{ x: 3, y: -3 },
{ x: 3, y: 1 },
{ x: 3, y: 2 },
{ x: 3, y: 3 },
])
)
.beforeAllSubcases(t => {
const {
interpolation: { type, sampling },
} = t.params;
t.skipIfInterpolationTypeOrSamplingNotSupported({ type, sampling });
})
.fn(async t => {
const {
x,
y,
nearFar,
sampleCount,
interpolation: { type, sampling },
} = t.params;
// prettier-ignore
const clipSpacePoints = [
[ x + 0.2, -y, 0, 1],
[-x + 0.2, y, 0, 1],
[ x + 0.2, y, 0, 1],
];

const interStagePoints = [
[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24],
];

const width = 4;
const height = 4;
const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, {
interpolationType: type,
interpolationSampling: sampling,
sampleCount,
width,
height,
nearFar,
clipSpacePoints,
interStagePoints,
fragInCode: '@builtin(sample_mask) sample_mask: u32,',
outputCode: 'vec4f(f32(fin.sample_mask), 0, 0, 0)',
});

const expected = generateFragmentInputs({
width,
height,
nearFar,
sampleCount,
clipSpacePoints,
interpolateFn: computeSampleMask,
});

t.expectOK(
checkSampleRectsApproximatelyEqual({
width,
height,
sampleCount,
actual,
expected,
maxDiffULPsForFloatFormat: 0,
})
);
});

0 comments on commit b03d6d4

Please sign in to comment.