Skip to content

Commit e583fa4

Browse files
authored
Add 'skipUndefined' Comparator (#2082)
This adds a Comparator that if the Expectation is undefined, just returns true, so that tests can include an 'Any' test condition. This is used in 'pack2x16float' to replace its custom Comparator and allow for serialization/usage of the cache. Fixes #2077
1 parent e0e7353 commit e583fa4

File tree

4 files changed

+93
-36
lines changed

4 files changed

+93
-36
lines changed

src/unittests/serialization.spec.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import {
77
deserializeExpectation,
88
serializeExpectation,
99
} from '../webgpu/shader/execution/expression/case_cache.js';
10-
import { anyOf, deserializeComparator, SerializedComparator } from '../webgpu/util/compare.js';
10+
import {
11+
anyOf,
12+
deserializeComparator,
13+
SerializedComparator,
14+
skipUndefined,
15+
} from '../webgpu/util/compare.js';
1116
import { kValue } from '../webgpu/util/constants.js';
1217
import {
1318
bool,
@@ -226,3 +231,29 @@ g.test('anyOf').fn(t => {
226231
}
227232
});
228233
});
234+
235+
g.test('skipUndefined').fn(t => {
236+
enableBuildingDataCache(() => {
237+
for (const c of [
238+
{
239+
comparator: skipUndefined(i32(123)),
240+
testCases: [f32(0), f32(10), f32(122), f32(123), f32(124), f32(200)],
241+
},
242+
{
243+
comparator: skipUndefined(undefined),
244+
testCases: [f32(0), f32(10), f32(122), f32(123), f32(124), f32(200)],
245+
},
246+
]) {
247+
const serialized = c.comparator as SerializedComparator;
248+
const deserialized = deserializeComparator(serialized);
249+
for (const val of c.testCases) {
250+
const got = deserialized(val);
251+
const expect = c.comparator(val);
252+
t.expect(
253+
got.matched === expect.matched,
254+
`comparator(${val}): got: ${expect.matched}, expect: ${got.matched}`
255+
);
256+
}
257+
}
258+
});
259+
});

src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,19 @@ which is then placed in bits 16 × i through 16 × i + 15 of the result.
55
`;
66

77
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
8-
import { assert } from '../../../../../../common/util/util.js';
98
import { GPUTest } from '../../../../../gpu_test.js';
10-
import { anyOf, Comparator } from '../../../../../util/compare.js';
9+
import { anyOf, skipUndefined } from '../../../../../util/compare.js';
1110
import {
1211
f32,
1312
pack2x16float,
14-
Scalar,
1513
TypeF32,
1614
TypeU32,
1715
TypeVec,
1816
u32,
1917
vec2,
2018
} from '../../../../../util/conversion.js';
2119
import { cartesianProduct, fullF32Range, quantizeToF32 } from '../../../../../util/math.js';
20+
import { makeCaseCache } from '../../case_cache.js';
2221
import { allInputSources, Case, run } from '../../expression.js';
2322

2423
import { builtin } from './builtin.js';
@@ -28,29 +27,6 @@ export const g = makeTestGroup(GPUTest);
2827
// pack2x16float has somewhat unusual behaviour, specifically around how it is
2928
// supposed to behave when values go OOB and when they are considered to have
3029
// gone OOB, so has its own bespoke implementation.
31-
//
32-
// The use of a custom Comparator prevents easy serialization in the case cache,
33-
// https://github.com/gpuweb/cts/issues/2077
34-
35-
/**
36-
* @returns a custom comparator for a possible result from pack2x16float
37-
* @param expectation element of the array generated by pack2x16float
38-
*/
39-
function cmp(expectation: number | undefined): Comparator {
40-
return got => {
41-
assert(got instanceof Scalar, `Received non-Scalar Value in pack2x16float comparator`);
42-
let matched = true;
43-
if (expectation !== undefined) {
44-
matched = (got.value as number) === expectation;
45-
}
46-
47-
return {
48-
matched,
49-
got: `${got}`,
50-
expected: `${expectation !== undefined ? u32(expectation) : 'Any'}`,
51-
};
52-
};
53-
}
5430

5531
/**
5632
* @returns a Case for `pack2x16float`
@@ -68,7 +44,12 @@ function makeCase(param0: number, param1: number, filter_undefined: boolean): Ca
6844
return undefined;
6945
}
7046

71-
return { input: [vec2(f32(param0), f32(param1))], expected: anyOf(...results.map(cmp)) };
47+
return {
48+
input: [vec2(f32(param0), f32(param1))],
49+
expected: anyOf(
50+
...results.map(r => (r === undefined ? skipUndefined(undefined) : skipUndefined(u32(r))))
51+
),
52+
};
7253
}
7354

7455
/**
@@ -84,6 +65,15 @@ function generateCases(param0s: number[], param1s: number[], filter_undefined: b
8465
.filter((c): c is Case => c !== undefined);
8566
}
8667

68+
export const d = makeCaseCache('pack2x16float', {
69+
f32_const: () => {
70+
return generateCases(fullF32Range(), fullF32Range(), true);
71+
},
72+
f32_non_const: () => {
73+
return generateCases(fullF32Range(), fullF32Range(), false);
74+
},
75+
});
76+
8777
g.test('pack')
8878
.specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions')
8979
.desc(
@@ -93,9 +83,6 @@ g.test('pack')
9383
)
9484
.params(u => u.combine('inputSource', allInputSources))
9585
.fn(async t => {
96-
const cases =
97-
t.params.inputSource === 'const'
98-
? generateCases(fullF32Range(), fullF32Range(), true)
99-
: generateCases(fullF32Range(), fullF32Range(), false);
86+
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
10087
await run(t, builtin('pack2x16float'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases);
10188
});

src/webgpu/shader/execution/expression/case_cache.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,15 @@ export function serializeExpectation(e: Expectation): SerializedExpectation {
7979
}
8080
if (e instanceof Function) {
8181
const comp = (e as unknown) as SerializedComparator;
82-
if (comp.kind !== undefined && comp.data !== undefined) {
83-
return { kind: 'comparator', value: { kind: comp.kind, data: comp.data } };
82+
if (comp !== undefined) {
83+
// if blocks used to refine the type of comp.kind, otherwise it is
84+
// actually the union of the string values
85+
if (comp.kind === 'anyOf') {
86+
return { kind: 'comparator', value: { kind: comp.kind, data: comp.data } };
87+
}
88+
if (comp.kind === 'skipUndefined') {
89+
return { kind: 'comparator', value: { kind: comp.kind, data: comp.data } };
90+
}
8491
}
8592
throw 'cannot serialize comparator';
8693
}

src/webgpu/util/compare.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,14 +226,43 @@ export function anyOf(
226226
return comparator;
227227
}
228228

229+
/** @returns a Comparator that skips the test if the expectation is undefined */
230+
export function skipUndefined(
231+
expectation: Expectation | undefined
232+
): Comparator | (Comparator & SerializedComparator) {
233+
const comparator = (got: Value) => {
234+
if (expectation !== undefined) {
235+
return toComparator(expectation)(got);
236+
}
237+
return { matched: true, got: got.toString(), expected: `Treating 'undefined' as Any` };
238+
};
239+
240+
if (getIsBuildingDataCache()) {
241+
// If there's an active DataCache, and it supports storing, then append the
242+
// comparator kind and serialized expectations to the comparator, so it can
243+
// be serialized.
244+
comparator.kind = 'skipUndefined';
245+
if (expectation !== undefined) {
246+
comparator.data = serializeExpectation(expectation);
247+
}
248+
}
249+
return comparator;
250+
}
251+
229252
/** SerializedComparatorAnyOf is the serialized type of an `anyOf` comparator. */
230253
type SerializedComparatorAnyOf = {
231254
kind: 'anyOf';
232255
data: SerializedExpectation[];
233256
};
234257

258+
/** SerializedComparatorSkipUndefined is the serialized type of an `skipUndefined` comparator. */
259+
type SerializedComparatorSkipUndefined = {
260+
kind: 'skipUndefined';
261+
data?: SerializedExpectation;
262+
};
263+
235264
/** SerializedComparator is a union of all the possible serialized comparator types. */
236-
export type SerializedComparator = SerializedComparatorAnyOf;
265+
export type SerializedComparator = SerializedComparatorAnyOf | SerializedComparatorSkipUndefined;
237266

238267
/**
239268
* Deserializes a comparator from a SerializedComparator.
@@ -245,6 +274,9 @@ export function deserializeComparator(data: SerializedComparator): Comparator {
245274
case 'anyOf': {
246275
return anyOf(...data.data.map(e => deserializeExpectation(e)));
247276
}
277+
case 'skipUndefined': {
278+
return skipUndefined(data.data !== undefined ? deserializeExpectation(data.data) : undefined);
279+
}
248280
}
249-
throw `unhandled comparator kind: ${data.kind}`;
281+
throw `unhandled comparator kind`;
250282
}

0 commit comments

Comments
 (0)