From 70426a05f56708f4e21a2f59f5081cfefc4df5dd Mon Sep 17 00:00:00 2001 From: Gregg Tavares Date: Wed, 3 Jan 2024 16:33:10 -0800 Subject: [PATCH] Test fragment @builtin(sample_index) --- src/webgpu/listing_meta.json | 1 + .../shader_io/fragment_builtins.spec.ts | 108 ++++++++++++++++-- 2 files changed, 102 insertions(+), 7 deletions(-) diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index a8368dd7efef..213ca1d5b245 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -1642,6 +1642,7 @@ "webgpu:shader,execution,shader_io,compute_builtins:inputs:*": { "subcaseMS": 19.342 }, "webgpu:shader,execution,shader_io,fragment_builtins:inputs,interStage:*": { "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,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 c93f0337c01f..76c9b85ff6d4 100644 --- a/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts +++ b/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts @@ -8,10 +8,9 @@ This means they render to all pixels in the textures. To fully test centroid int probably requires drawing various triangles that only cover certain samples. That is TBD. TODO: -* test sample interpolation -* test centroid interpolation +* test sample interpolation (see MAINTENANCE_TODOs below) +* test centroid interpolation (see MAINTENANCE_TODOs below) * test front_facing -* test sample_index * test frag_depth `; @@ -271,6 +270,7 @@ type FragData = { clipSpacePoints: readonly number[][]; ndcPoints: readonly number[][]; windowPoints: readonly number[][]; + sampleIndex: number; }; /** @@ -324,6 +324,7 @@ function generateFragmentInputs({ clipSpacePoints, ndcPoints, windowPoints, + sampleIndex: s, }); const offset = ((y * width + x) * sampleCount + s) * 4; @@ -395,6 +396,13 @@ function createInterStageInterpolationFn( }; } +/** + * Computes 'builtin(sample_index)' + */ +function computeFragmentSampleIndex({ sampleIndex }: FragData) { + return [sampleIndex, 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 @@ -448,19 +456,25 @@ async function renderFragmentShaderInputsTo4TexturesAndReadbackValues( @group(0) @binding(0) var uni: Uniforms; - struct Vertex { + struct VertexOut { + @builtin(position) position: vec4f, + @location(0) @interpolate(${interpolate}) interpolatedValue: vec4f, + }; + + struct VertexIn { @builtin(position) position: vec4f, @location(0) @interpolate(${interpolate}) interpolatedValue: vec4f, + @builtin(sample_index) sampleIndex: u32, }; - @vertex fn vs(@builtin(vertex_index) vNdx: u32) -> Vertex { + @vertex fn vs(@builtin(vertex_index) vNdx: u32) -> VertexOut { let pos = array( ${clipSpacePoints.map(p => `vec4f(${p.join(', ')})`).join(', ')} ); let interStage = array( ${interStagePoints.map(p => `vec4f(${p.join(', ')})`).join(', ')} ); - var v: Vertex; + var v: VertexOut; v.position = pos[vNdx]; v.interpolatedValue = interStage[vNdx]; _ = uni; @@ -483,7 +497,7 @@ async function renderFragmentShaderInputsTo4TexturesAndReadbackValues( ); } - @fragment fn fs(vin: Vertex) -> FragOut { + @fragment fn fs(vin: VertexIn) -> FragOut { var f: FragOut; let v = ${outputCode}; let u = bitcast(v); @@ -779,3 +793,83 @@ g.test('inputs,interStage') }) ); }); + +g.test('inputs,sample_index') + .desc( + ` + Test fragment shader builtin(sample_index) values. + ` + ) + .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', [ + { 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) + ) + .beforeAllSubcases(t => { + const { + interpolation: { type, sampling }, + } = t.params; + t.skipIfInterpolationTypeOrSamplingNotSupported({ type, sampling }); + }) + .fn(async t => { + const { + nearFar, + sampleCount, + interpolation: { type, sampling }, + } = t.params; + // prettier-ignore + const clipSpacePoints = [ // ndc values + [0.333, 0.333, 0.333, 0.333], // 1, 1, 1 + [ 1.0, -3.0, 0.25, 1.0 ], // 1, -3, 0.25 + [-1.5, 0.5, 0.25, 0.5 ], // -3, 1, 0.5 + ]; + + const interStagePoints = [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + ]; + + const width = 4; + const height = 4; + const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, { + interpolationType: type, + interpolationSampling: sampling, + sampleCount, + width, + height, + nearFar, + clipSpacePoints, + interStagePoints, + outputCode: 'vec4f(f32(vin.sampleIndex), 0, 0, 0)', + }); + + const expected = generateFragmentInputs({ + width, + height, + nearFar, + sampleCount, + clipSpacePoints, + interpolateFn: computeFragmentSampleIndex, + }); + + t.expectOK( + checkSampleRectsApproximatelyEqual({ + width, + height, + sampleCount, + actual, + expected, + maxFractionalDiff: 0.00001, + }) + ); + });