Skip to content

Commit 8d64795

Browse files
dneto0alan-baker
andauthored
sample_mask input: allow either full coverage mask or (1<<sample_index) (#4671)
This allows the varying behaviour observed on different platforms. Generalize the "findFailedPixels" utility to allow two reference "expected" texel views. A pixel comparison succeeds if the "actual" texel matches the texel from either of the "expected" or the "alternate expected" texel views. See gpuweb/gpuweb#5457 Co-authored-by: alan-baker <alanbaker@google.com>
1 parent f529c5a commit 8d64795

2 files changed

Lines changed: 137 additions & 27 deletions

File tree

‎src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts‎

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -510,17 +510,25 @@ function computeFragmentFrontFacing({ frontFacing }: FragData) {
510510
}
511511

512512
/**
513-
* Computes 'builtin(sample_mask)'
513+
* Computes the full sample coverage mask.
514514
*/
515-
function computeSampleMask({ sampleMask }: FragData) {
515+
function computeFullSampleCoverageMask({ sampleMask }: FragData) {
516516
return [sampleMask, 0, 0, 0];
517517
}
518518

519+
/**
520+
* Computes (1 << sample_index)
521+
*/
522+
function computeSingleSampleMask({ sampleIndex }: FragData) {
523+
return [1 << sampleIndex, 0, 0, 0];
524+
}
525+
519526
/**
520527
* Renders float32 fragment shader inputs values to 4 rgba8unorm textures that
521528
* can be multisampled textures. It stores each of the channels, r, g, b, a of
522529
* the shader input to a separate texture, doing the math required to store the
523-
* float32 value into an rgba8unorm texel.
530+
* float32 value into an rgba8unorm texel (i.e. mapping each 8bit chunk in the
531+
* bit representation of a float32 value to a 8-bit norm float value).
524532
*
525533
* Note: We could try to store the output to an vec4f storage buffer.
526534
* Unfortunately, using a storage buffer has the issue that we need to compute
@@ -700,13 +708,15 @@ function checkSampleRectsApproximatelyEqual({
700708
actual,
701709
expected,
702710
maxDiffULPsForFloatFormat,
711+
alternativeExpected,
703712
}: {
704713
width: number;
705714
height: number;
706715
sampleCount: number;
707716
actual: Float32Array;
708717
expected: Float32Array;
709718
maxDiffULPsForFloatFormat: number;
719+
alternativeExpected?: Float32Array;
710720
}) {
711721
const subrectOrigin = [0, 0, 0];
712722
const subrectSize = [width * sampleCount, height, 1];
@@ -728,12 +738,19 @@ function checkSampleRectsApproximatelyEqual({
728738
new Uint8Array(expected.buffer),
729739
areaDesc
730740
);
741+
const altExpTexelView = alternativeExpected
742+
? TexelView.fromTextureDataByReference(
743+
format,
744+
new Uint8Array((alternativeExpected ?? new Float32Array()).buffer),
745+
areaDesc
746+
)
747+
: undefined;
731748

732749
const failedPixelsMessage = findFailedPixels(
733750
format,
734751
{ x: 0, y: 0, z: 0 },
735752
{ width: width * sampleCount, height, depthOrArrayLayers: 1 },
736-
{ actTexelView, expTexelView },
753+
{ actTexelView, expTexelView, altExpTexelView },
737754
{ maxDiffULPsForFloatFormat }
738755
);
739756

@@ -742,6 +759,7 @@ function checkSampleRectsApproximatelyEqual({
742759
return new ErrorWithExtra(msg, () => ({
743760
expTexelView,
744761
actTexelView,
762+
altExpTexelView,
745763
}));
746764
}
747765

@@ -1300,10 +1318,14 @@ g.test('inputs,sample_mask')
13001318
Test fragment shader builtin(sample_mask) values.
13011319
13021320
Draws various triangles that should trigger different sample_mask values.
1303-
Checks that sample_mask matches what's expected. Note: the triangles
1304-
are selected so they do not intersect sample points as we don't want
1305-
to test precision issues on whether or not a sample point is inside
1306-
or outside the triangle when right on the edge.
1321+
Checks that sample_mask matches what's expected. The expected sample mask
1322+
is either:
1323+
- the full coverage mask for the fragment, or
1324+
- only has 1 bit at the current sample index.
1325+
1326+
Note: the triangles are selected so they do not intersect sample points
1327+
as we don't want to test precision issues on whether or not a sample point
1328+
is inside or outside the triangle when right on the edge.
13071329
13081330
Example: x=-1, y=2, it draws the following triangle
13091331
@@ -1423,6 +1445,7 @@ g.test('inputs,sample_mask')
14231445
[ x + 0.2, y, 0, 1],
14241446
];
14251447

1448+
// This is not significant in this test, but is still needed by the helper.
14261449
const interStagePoints = [
14271450
[13, 14, 15, 16],
14281451
[17, 18, 19, 20],
@@ -1444,24 +1467,36 @@ g.test('inputs,sample_mask')
14441467
outputCode: 'vec4f(f32(fin.sample_mask), 0, 0, 0)',
14451468
});
14461469

1447-
const expected = generateFragmentInputs({
1470+
const expectedFullCoverageMask = generateFragmentInputs({
14481471
width,
14491472
height,
14501473
nearFar,
14511474
sampleCount,
14521475
clipSpacePoints,
1453-
interpolateFn: computeSampleMask,
1476+
interpolateFn: computeFullSampleCoverageMask,
14541477
});
14551478

1456-
showExpected(t, width, height, sampleCount, expected);
1479+
showExpected(t, width, height, sampleCount, expectedFullCoverageMask);
1480+
1481+
const expectedSingleSampleMask = generateFragmentInputs({
1482+
width,
1483+
height,
1484+
nearFar,
1485+
sampleCount,
1486+
clipSpacePoints,
1487+
interpolateFn: computeSingleSampleMask,
1488+
});
1489+
1490+
showExpected(t, width, height, sampleCount, expectedSingleSampleMask);
14571491

14581492
t.expectOK(
14591493
checkSampleRectsApproximatelyEqual({
14601494
width,
14611495
height,
14621496
sampleCount,
14631497
actual,
1464-
expected,
1498+
expected: expectedFullCoverageMask,
1499+
alternativeExpected: expectedSingleSampleMask,
14651500
maxDiffULPsForFloatFormat: 0,
14661501
})
14671502
);

‎src/webgpu/util/texture/texture_ok.ts‎

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,17 @@ type TexelViewComparer = {
4646
tableRows: (failedCoords: readonly Required<GPUOrigin3DDict>[]) => Iterable<string>[];
4747
};
4848

49+
// Creates a texel view comparer that compares an actual texel view against
50+
// an expected texel view and optionally a second, alternate, texel view.
51+
// The comparison for each texel succeeds if the actual texel is close enough
52+
// to either
4953
function makeTexelViewComparer(
5054
format: EncodableTextureFormat,
51-
{ actTexelView, expTexelView }: { actTexelView: TexelView; expTexelView: TexelView },
55+
{
56+
actTexelView,
57+
expTexelView,
58+
altExpTexelView,
59+
}: { actTexelView: TexelView; expTexelView: TexelView; altExpTexelView?: TexelView },
5260
opts: TexelCompareOptions
5361
): TexelViewComparer {
5462
const {
@@ -76,27 +84,35 @@ function makeTexelViewComparer(
7684
const tvc = {} as TexelViewComparer;
7785
if (fmtIsInt) {
7886
tvc.predicate = coords =>
79-
comparePerComponent(actTexelView.color(coords), expTexelView.color(coords), maxIntDiff);
87+
comparePerComponent(
88+
actTexelView.color(coords),
89+
expTexelView.color(coords),
90+
maxIntDiff,
91+
altExpTexelView?.color(coords)
92+
);
8093
} else if (fmtIsNorm && maxDiffULPsForNormFormat !== undefined) {
8194
tvc.predicate = coords =>
8295
comparePerComponent(
8396
actTexelView.ulpFromZero(coords),
8497
expTexelView.ulpFromZero(coords),
85-
maxDiffULPsForNormFormat
98+
maxDiffULPsForNormFormat,
99+
altExpTexelView?.ulpFromZero(coords)
86100
);
87101
} else if (fmtIsFloat && maxDiffULPsForFloatFormat !== undefined) {
88102
tvc.predicate = coords =>
89103
comparePerComponent(
90104
actTexelView.ulpFromZero(coords),
91105
expTexelView.ulpFromZero(coords),
92-
maxDiffULPsForFloatFormat
106+
maxDiffULPsForFloatFormat,
107+
altExpTexelView?.ulpFromZero(coords)
93108
);
94109
} else if (maxFractionalDiff !== undefined) {
95110
tvc.predicate = coords =>
96111
comparePerComponent(
97112
actTexelView.color(coords),
98113
expTexelView.color(coords),
99-
maxFractionalDiff
114+
maxFractionalDiff,
115+
altExpTexelView?.color(coords)
100116
);
101117
} else {
102118
if (fmtIsNorm) {
@@ -114,10 +130,17 @@ function makeTexelViewComparer(
114130
[`tolerance ± ${maxIntDiff}`],
115131
(function* () {
116132
yield* [` diff (act - exp)`, '==', ''];
133+
if (altExpTexelView) {
134+
yield* [` diff (act - alternateExp)`, '==', ''];
135+
}
117136
for (const coords of failedCoords) {
118137
const act = actTexelView.color(coords);
119138
const exp = expTexelView.color(coords);
120139
yield repr.componentOrder.map(ch => act[ch]! - exp[ch]!).join(',');
140+
if (altExpTexelView) {
141+
const altExp = altExpTexelView.color(coords);
142+
yield repr.componentOrder.map(ch => act[ch]! - altExp[ch]!).join(',');
143+
}
121144
}
122145
})(),
123146
];
@@ -130,10 +153,17 @@ function makeTexelViewComparer(
130153
[`tolerance ± ${toleranceULPs} normal-ULPs`],
131154
(function* () {
132155
yield* [` diff (act - exp) in normal-ULPs`, '==', ''];
156+
if (altExpTexelView) {
157+
yield* [` diff (act - alternateExp) in normal-ULPs`, '==', ''];
158+
}
133159
for (const coords of failedCoords) {
134160
const act = actTexelView.ulpFromZero(coords);
135161
const exp = expTexelView.ulpFromZero(coords);
136162
yield repr.componentOrder.map(ch => act[ch]! - exp[ch]!).join(',');
163+
if (altExpTexelView) {
164+
const altExp = altExpTexelView.ulpFromZero(coords);
165+
yield repr.componentOrder.map(ch => act[ch]! - altExp[ch]!).join(',');
166+
}
137167
}
138168
})(),
139169
];
@@ -143,10 +173,17 @@ function makeTexelViewComparer(
143173
[`tolerance ± ${maxFractionalDiff}`],
144174
(function* () {
145175
yield* [` diff (act - exp)`, '==', ''];
176+
if (altExpTexelView) {
177+
yield* [` diff (act - alternateExp)`, '==', ''];
178+
}
146179
for (const coords of failedCoords) {
147180
const act = actTexelView.color(coords);
148181
const exp = expTexelView.color(coords);
149182
yield repr.componentOrder.map(ch => (act[ch]! - exp[ch]!).toPrecision(4)).join(',');
183+
if (altExpTexelView) {
184+
const altExp = altExpTexelView.color(coords);
185+
yield repr.componentOrder.map(ch => (act[ch]! - altExp[ch]!).toPrecision(4)).join(',');
186+
}
150187
}
151188
})(),
152189
];
@@ -155,18 +192,32 @@ function makeTexelViewComparer(
155192
return tvc;
156193
}
157194

195+
/** Returns true if all components of an "actual" texel compare as
196+
* nearly equal (with tolerance) against either a (required) expected
197+
* texel or against an optional alternate texel.
198+
*/
158199
function comparePerComponent(
159200
actual: PerTexelComponent<number>,
160201
expected: PerTexelComponent<number>,
161-
maxDiff: number
202+
maxDiff: number,
203+
alternateExpected?: PerTexelComponent<number>
162204
) {
163-
return Object.keys(actual).every(key => {
164-
const k = key as TexelComponent;
165-
const act = actual[k]!;
166-
const exp = expected[k];
167-
if (exp === undefined) return false;
168-
return numbersApproximatelyEqual(act, exp, maxDiff);
169-
});
205+
const similar = (a: PerTexelComponent<number>, b: PerTexelComponent<number>, maxDiff: number) =>
206+
Object.keys(a).every(key => {
207+
const k = key as TexelComponent;
208+
const act = a[k]!;
209+
const exp = b[k];
210+
if (exp === undefined) return false;
211+
return numbersApproximatelyEqual(act, exp, maxDiff);
212+
});
213+
214+
if (similar(actual, expected, maxDiff)) {
215+
return true;
216+
}
217+
if (alternateExpected) {
218+
return similar(actual, alternateExpected, maxDiff);
219+
}
220+
return false;
170221
}
171222

172223
/** Create a new mappable GPUBuffer, and copy a subrectangle of GPUTexture data into it. */
@@ -196,13 +247,17 @@ export function findFailedPixels(
196247
format: EncodableTextureFormat,
197248
subrectOrigin: Required<GPUOrigin3DDict>,
198249
subrectSize: Required<GPUExtent3DDict>,
199-
{ actTexelView, expTexelView }: { actTexelView: TexelView; expTexelView: TexelView },
250+
{
251+
actTexelView,
252+
expTexelView,
253+
altExpTexelView,
254+
}: { actTexelView: TexelView; expTexelView: TexelView; altExpTexelView?: TexelView },
200255
texelCompareOptions: TexelCompareOptions,
201256
coords?: Generator<Required<GPUOrigin3DDict>>
202257
) {
203258
const comparer = makeTexelViewComparer(
204259
format,
205-
{ actTexelView, expTexelView },
260+
{ actTexelView, expTexelView, altExpTexelView },
206261
texelCompareOptions
207262
);
208263

@@ -260,6 +315,15 @@ export function findFailedPixels(
260315
yield `${repr.componentOrder.map(ch => numericToString(pixel[ch]!)).join(',')}`;
261316
}
262317
})();
318+
const printAlternateExpectedColors = (function* () {
319+
if (altExpTexelView) {
320+
yield* [' alternateExp. colors', '==', componentOrderStr];
321+
for (const coords of failedPixels) {
322+
const pixel = altExpTexelView.color(coords);
323+
yield `${repr.componentOrder.map(ch => numericToString(pixel[ch]!)).join(',')}`;
324+
}
325+
}
326+
})();
263327
const printActualULPs = (function* () {
264328
yield* [' act. normal-ULPs-from-zero', '==', componentOrderStr];
265329
for (const coords of failedPixels) {
@@ -274,6 +338,15 @@ export function findFailedPixels(
274338
yield `${repr.componentOrder.map(ch => pixel[ch]).join(',')}`;
275339
}
276340
})();
341+
const printAlternateExpectedULPs = (function* () {
342+
if (altExpTexelView) {
343+
yield* [` alternateExp. normal-ULPs-from-zero`, '==', componentOrderStr];
344+
for (const coords of failedPixels) {
345+
const pixel = altExpTexelView.ulpFromZero(coords);
346+
yield `${repr.componentOrder.map(ch => pixel[ch]).join(',')}`;
347+
}
348+
}
349+
})();
277350

278351
const opts = {
279352
fillToWidth: 120,
@@ -286,8 +359,10 @@ ${generatePrettyTable(opts, [
286359
printActualBytes,
287360
printActualColors,
288361
printExpectedColors,
362+
printAlternateExpectedColors,
289363
printActualULPs,
290364
printExpectedULPs,
365+
printAlternateExpectedULPs,
291366
...comparer.tableRows(failedPixels),
292367
])}`;
293368
}

0 commit comments

Comments
 (0)