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

Test fragment_input @builtin(sample_mask) #3404

Merged
merged 7 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
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
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', [
dneto0 marked this conversation as resolved.
Show resolved Hide resolved
// 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 = [
greggman marked this conversation as resolved.
Show resolved Hide resolved
[ 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,
})
);
});
Loading