From b03d6d4ace7bfbb5e0398c5d79ddb142b0372020 Mon Sep 17 00:00:00 2001 From: Greggman Date: Thu, 15 Feb 2024 09:18:12 +0900 Subject: [PATCH] Test fragment_input @builtin(sample_mask) (#3404) * Test fragment_input @builtin(sample_mask) --- src/webgpu/listing_meta.json | 2 +- .../shader_io/fragment_builtins.spec.ts | 202 +++++++++++++++++- 2 files changed, 197 insertions(+), 7 deletions(-) diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index a2bc8ac6adf3..3ef1fbca734f 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -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 }, diff --git a/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts b/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts index 333ced6272f7..883456c05101 100644 --- a/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts +++ b/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts @@ -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 @@ -305,6 +306,7 @@ type FragData = { ndcPoints: readonly number[][]; windowPoints: readonly number[][]; sampleIndex: number; + sampleMask: number; frontFacing: boolean; }; @@ -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]; @@ -373,6 +391,7 @@ function generateFragmentInputs({ ndcPoints, windowPoints, sampleIndex, + sampleMask, frontFacing, }); @@ -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 @@ -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({ @@ -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, + }) + ); + });