diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin')
167 files changed, 7494 insertions, 4544 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.cache.ts new file mode 100644 index 0000000000..9634ae8f34 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.cache.ts @@ -0,0 +1,26 @@ +import { abstractInt } from '../../../../../util/conversion.js'; +import { FP } from '../../../../../util/floating_point.js'; +import { absBigInt, fullI64Range } from '../../../../../util/math.js'; +import { CaseListBuilder, makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract_float|abstract_int] +const cases: Record<string, CaseListBuilder> = { + ...(['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait === 'abstract' ? 'abstract_float' : trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + FP[trait].scalarRange(), + 'unfiltered', + FP[trait].absInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}), + abstract_int: () => { + return fullI64Range().map(e => { + return { input: abstractInt(e), expected: abstractInt(absBigInt(e)) }; + }); + }, +}; + +export const d = makeCaseCache('abs', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts index 05d5242f73..75d41ab163 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts @@ -1,14 +1,14 @@ export const description = ` Execution tests for the 'abs' builtin function -S is AbstractInt, i32, or u32 +S is abstract-int, i32, or u32 T is S or vecN<S> @const fn abs(e: T ) -> T The absolute value of e. Component-wise when T is a vector. If e is a signed integral scalar type and evaluates to the largest negative value, then the result is e. If e is an unsigned integral type, then the result is e. -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn abs(e: T ) -> T Returns the absolute value of e (e.g. e with a positive sign bit). @@ -18,47 +18,26 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; import { kBit } from '../../../../../util/constants.js'; -import { - i32Bits, - TypeF32, - TypeF16, - TypeI32, - TypeU32, - u32Bits, - TypeAbstractFloat, -} from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range, fullF64Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type, i32Bits, u32Bits } from '../../../../../util/conversion.js'; import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { abstractBuiltin, builtin } from './builtin.js'; +import { d } from './abs.cache.js'; +import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('abs', { - f32: () => { - return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.absInterval); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.absInterval); - }, - abstract: () => { - return FP.abstract.generateScalarToIntervalCases( - fullF64Range(), - 'unfiltered', - FP.abstract.absInterval - ); - }, -}); - g.test('abstract_int') .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') .desc(`abstract int tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_int'); + await run(t, abstractIntBuiltin('abs'), [Type.abstractInt], Type.abstractInt, t.params, cases); + }); g.test('u32') .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') @@ -67,7 +46,7 @@ g.test('u32') u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - await run(t, builtin('abs'), [TypeU32], TypeU32, t.params, [ + await run(t, builtin('abs'), [Type.u32], Type.u32, t.params, [ // Min and Max u32 { input: u32Bits(kBit.u32.min), expected: u32Bits(kBit.u32.min) }, { input: u32Bits(kBit.u32.max), expected: u32Bits(kBit.u32.max) }, @@ -114,7 +93,7 @@ g.test('i32') u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - await run(t, builtin('abs'), [TypeI32], TypeI32, t.params, [ + await run(t, builtin('abs'), [Type.i32], Type.i32, t.params, [ // Min and max i32 // If e evaluates to the largest negative value, then the result is e. { input: i32Bits(kBit.i32.negative.min), expected: i32Bits(kBit.i32.negative.min) }, @@ -166,8 +145,15 @@ g.test('abstract_float') .combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - const cases = await d.get('abstract'); - await run(t, abstractBuiltin('abs'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases); + const cases = await d.get('abstract_float'); + await run( + t, + abstractFloatBuiltin('abs'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); }); g.test('f32') @@ -178,7 +164,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('abs'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('abs'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -192,5 +178,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('abs'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('abs'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.cache.ts new file mode 100644 index 0000000000..466618b6db --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.cache.ts @@ -0,0 +1,24 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { linearRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarToIntervalCases( + [...linearRange(-1, 1, 100), ...FP[trait].scalarRange()], // acos is defined on [-1, 1] + nonConst ? 'unfiltered' : 'finite', + // acos has an ulp or inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].acosInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('acos', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts index 5755c07905..46089d8576 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'acos' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn acos(e: T ) -> T Returns the arc cosine of e. Component-wise when T is a vector. @@ -9,48 +9,33 @@ Returns the arc cosine of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { linearRange, fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { d } from './acos.cache.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; export const g = makeTestGroup(GPUTest); -const f32_inputs = [ - ...linearRange(-1, 1, 100), // acos is defined on [-1, 1] - ...fullF32Range(), -]; - -const f16_inputs = [ - ...linearRange(-1, 1, 100), // acos is defined on [-1, 1] - ...fullF16Range(), -]; - -export const d = makeCaseCache('acos', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.acosInterval); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.acosInterval); - }, - f16_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.acosInterval); - }, - f16_non_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.acosInterval); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('acos'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -60,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('acos'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('acos'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -74,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('acos'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('acos'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.cache.ts new file mode 100644 index 0000000000..587131140d --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.cache.ts @@ -0,0 +1,24 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { biasedRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarToIntervalCases( + [...biasedRange(1, 2, 100), ...FP[trait].scalarRange()], // x near 1 can be problematic to implement + nonConst ? 'unfiltered' : 'finite', + // acosh has an inherited accuracy, so is only expected to be as accurate as f32 + ...FP[trait !== 'abstract' ? trait : 'f32'].acoshIntervals + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('acosh', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts index cc78ce3eee..531a2479c6 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'acosh' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn acosh(e: T ) -> T Returns the hyperbolic arc cosine of e. The result is 0 when e < 1. @@ -13,47 +13,33 @@ Note: The result is not mathematically meaningful when e < 1. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { biasedRange, fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { d } from './acosh.cache.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; export const g = makeTestGroup(GPUTest); -const f32_inputs = [ - ...biasedRange(1, 2, 100), // x near 1 can be problematic to implement - ...fullF32Range(), -]; -const f16_inputs = [ - ...biasedRange(1, 2, 100), // x near 1 can be problematic to implement - ...fullF16Range(), -]; - -export const d = makeCaseCache('acosh', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', ...FP.f32.acoshIntervals); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', ...FP.f32.acoshIntervals); - }, - f16_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', ...FP.f16.acoshIntervals); - }, - f16_non_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', ...FP.f16.acoshIntervals); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('acosh'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -63,7 +49,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('acosh'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('acosh'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -77,5 +63,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('acosh'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('acosh'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts index 9a2938c1d5..74e072703d 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts @@ -10,15 +10,7 @@ Returns true if each component of e is true if e is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { - False, - True, - TypeBool, - TypeVec, - vec2, - vec3, - vec4, -} from '../../../../../util/conversion.js'; +import { False, True, Type, vec2, vec3, vec4 } from '../../../../../util/conversion.js'; import { allInputSources, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -36,14 +28,14 @@ g.test('bool') .fn(async t => { const overloads = { scalar: { - type: TypeBool, + type: Type.bool, cases: [ { input: False, expected: False }, { input: True, expected: True }, ], }, vec2: { - type: TypeVec(2, TypeBool), + type: Type.vec(2, Type.bool), cases: [ { input: vec2(False, False), expected: False }, { input: vec2(True, False), expected: False }, @@ -52,7 +44,7 @@ g.test('bool') ], }, vec3: { - type: TypeVec(3, TypeBool), + type: Type.vec(3, Type.bool), cases: [ { input: vec3(False, False, False), expected: False }, { input: vec3(True, False, False), expected: False }, @@ -65,7 +57,7 @@ g.test('bool') ], }, vec4: { - type: TypeVec(4, TypeBool), + type: Type.vec(4, Type.bool), cases: [ { input: vec4(False, False, False, False), expected: False }, { input: vec4(False, True, False, False), expected: False }, @@ -88,5 +80,5 @@ g.test('bool') }; const overload = overloads[t.params.overload]; - await run(t, builtin('all'), [overload.type], TypeBool, t.params, overload.cases); + await run(t, builtin('all'), [overload.type], Type.bool, t.params, overload.cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts index 19ed7d186f..43c599e2aa 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts @@ -10,15 +10,7 @@ Returns true if any component of e is true if e is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { - False, - True, - TypeBool, - TypeVec, - vec2, - vec3, - vec4, -} from '../../../../../util/conversion.js'; +import { False, True, Type, vec2, vec3, vec4 } from '../../../../../util/conversion.js'; import { allInputSources, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -36,14 +28,14 @@ g.test('bool') .fn(async t => { const overloads = { scalar: { - type: TypeBool, + type: Type.bool, cases: [ { input: False, expected: False }, { input: True, expected: True }, ], }, vec2: { - type: TypeVec(2, TypeBool), + type: Type.vec(2, Type.bool), cases: [ { input: vec2(False, False), expected: False }, { input: vec2(True, False), expected: True }, @@ -52,7 +44,7 @@ g.test('bool') ], }, vec3: { - type: TypeVec(3, TypeBool), + type: Type.vec(3, Type.bool), cases: [ { input: vec3(False, False, False), expected: False }, { input: vec3(True, False, False), expected: True }, @@ -65,7 +57,7 @@ g.test('bool') ], }, vec4: { - type: TypeVec(4, TypeBool), + type: Type.vec(4, Type.bool), cases: [ { input: vec4(False, False, False, False), expected: False }, { input: vec4(False, True, False, False), expected: True }, @@ -88,5 +80,5 @@ g.test('bool') }; const overload = overloads[t.params.overload]; - await run(t, builtin('any'), [overload.type], TypeBool, t.params, overload.cases); + await run(t, builtin('any'), [overload.type], Type.bool, t.params, overload.cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.cache.ts new file mode 100644 index 0000000000..d9e6280e0d --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.cache.ts @@ -0,0 +1,24 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { linearRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarToIntervalCases( + [...linearRange(-1, 1, 100), ...FP[trait].scalarRange()], // asin is defined on [-1, 1] + nonConst ? 'unfiltered' : 'finite', + // asin has an ulp or inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].asinInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('asin', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts index 8d18ebb303..f368c3838c 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'asin' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn asin(e: T ) -> T Returns the arc sine of e. Component-wise when T is a vector. @@ -9,48 +9,33 @@ Returns the arc sine of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { linearRange, fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { d } from './asin.cache.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; export const g = makeTestGroup(GPUTest); -const f32_inputs = [ - ...linearRange(-1, 1, 100), // asin is defined on [-1, 1] - ...fullF32Range(), -]; - -const f16_inputs = [ - ...linearRange(-1, 1, 100), // asin is defined on [-1, 1] - ...fullF16Range(), -]; - -export const d = makeCaseCache('asin', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.asinInterval); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.asinInterval); - }, - f16_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.asinInterval); - }, - f16_non_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.asinInterval); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('asin'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -60,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('asin'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('asin'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -74,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('asin'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('asin'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.cache.ts new file mode 100644 index 0000000000..4ee66f1ea6 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.cache.ts @@ -0,0 +1,18 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + FP[trait].scalarRange(), + 'unfiltered', + // asinh has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].asinhInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('asinh', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts index 9a8384e090..60867a9bce 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'sinh' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn asinh(e: T ) -> T Returns the hyperbolic arc sine of e. @@ -12,32 +12,33 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { d } from './asinh.cache.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('asinh', { - f32: () => { - return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.asinhInterval); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.asinhInterval); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float test`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractFloatBuiltin('asinh'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -47,7 +48,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('asinh'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('asinh'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -61,5 +62,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('asinh'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('asinh'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.cache.ts new file mode 100644 index 0000000000..e39e40448e --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.cache.ts @@ -0,0 +1,25 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +const known_values = [-Math.sqrt(3), -1, -1 / Math.sqrt(3), 0, 1, 1 / Math.sqrt(3), Math.sqrt(3)]; + +// Cases: [f32|f16|abstract]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarToIntervalCases( + [...known_values, ...FP[trait].scalarRange()], + nonConst ? 'unfiltered' : 'finite', + // atan has an ulp accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].atanInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('atan', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts index 3d0d3e6725..824a7cd0b5 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'atan' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn atan(e: T ) -> T Returns the arc tangent of e. Component-wise when T is a vector. @@ -10,43 +10,33 @@ Returns the arc tangent of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { d } from './atan.cache.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; export const g = makeTestGroup(GPUTest); -const known_values = [-Math.sqrt(3), -1, -1 / Math.sqrt(3), 0, 1, 1 / Math.sqrt(3), Math.sqrt(3)]; - -const f32_inputs = [...known_values, ...fullF32Range()]; -const f16_inputs = [...known_values, ...fullF16Range()]; - -export const d = makeCaseCache('atan', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.atanInterval); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.atanInterval); - }, - f16_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.atanInterval); - }, - f16_non_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.atanInterval); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('atan'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -62,7 +52,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1] ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('atan'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('atan'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -76,5 +66,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('atan'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('atan'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.cache.ts new file mode 100644 index 0000000000..be25c2dffb --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.cache.ts @@ -0,0 +1,35 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { linearRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + // Using sparse range since there are N^2 cases being generated, and also including extra values + // around 0, where there is a discontinuity that implementations may behave badly at. + const numeric_range = [ + ...FP[trait].sparseScalarRange(), + ...linearRange( + FP[trait].constants().negative.max, + FP[trait].constants().positive.min, + 10 + ), + ]; + return FP[trait].generateScalarPairToIntervalCases( + numeric_range, + numeric_range, + nonConst ? 'unfiltered' : 'finite', + // atan2 has an ulp accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].atan2Interval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('atan2', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts index fbace73dd2..067d1fdc66 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'atan2' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn atan2(e1: T ,e2: T ) -> T Returns the arc tangent of e1 over e2. Component-wise when T is a vector. @@ -9,47 +9,33 @@ Returns the arc tangent of e1 over e2. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { linearRange, sparseF32Range, sparseF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { d } from './atan2.cache.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; export const g = makeTestGroup(GPUTest); -const cases = (['f32', 'f16'] as const) - .flatMap(kind => - ([true, false] as const).map(nonConst => ({ - [`${kind}_${nonConst ? 'non_const' : 'const'}`]: () => { - const fp = FP[kind]; - // Using sparse range since there are N^2 cases being generated, and also including extra values - // around 0, where there is a discontinuity that implementations may behave badly at. - const numeric_range = [ - ...(kind === 'f32' ? sparseF32Range() : sparseF16Range()), - ...linearRange(fp.constants().negative.max, fp.constants().positive.min, 10), - ]; - return fp.generateScalarPairToIntervalCases( - numeric_range, - numeric_range, - nonConst ? 'unfiltered' : 'finite', - fp.atan2Interval - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -export const d = makeCaseCache('atan2', cases); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get(`abstract_const`); + await run( + t, + abstractFloatBuiltin('atan2'), + [Type.abstractFloat, Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -65,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1] ) .fn(async t => { const cases = await d.get(`f32_${t.params.inputSource === 'const' ? 'const' : 'non_const'}`); - await run(t, builtin('atan2'), [TypeF32, TypeF32], TypeF32, t.params, cases); + await run(t, builtin('atan2'), [Type.f32, Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -79,5 +65,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(`f16_${t.params.inputSource === 'const' ? 'const' : 'non_const'}`); - await run(t, builtin('atan2'), [TypeF16, TypeF16], TypeF16, t.params, cases); + await run(t, builtin('atan2'), [Type.f16, Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.cache.ts new file mode 100644 index 0000000000..bcd000bf61 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.cache.ts @@ -0,0 +1,32 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { biasedRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarToIntervalCases( + [ + // discontinuity at x = -1 + ...biasedRange(FP[trait].constants().negative.less_than_one, -0.9, 20), + -1, + // discontinuity at x = 1 + ...biasedRange(FP[trait].constants().positive.less_than_one, 0.9, 20), + 1, + ...FP[trait].scalarRange(), + ], + nonConst ? 'unfiltered' : 'finite', + // atanh has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].atanhInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('atanh', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts index 90f322a7ea..644efafd2f 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'atanh' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn atanh(e: T ) -> T Returns the hyperbolic arc tangent of e. The result is 0 when abs(e) ≥ 1. @@ -12,54 +12,33 @@ Note: The result is not mathematically meaningful when abs(e) >= 1. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { kValue } from '../../../../../util/constants.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { biasedRange, fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { d } from './atanh.cache.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; export const g = makeTestGroup(GPUTest); -const f32_inputs = [ - ...biasedRange(kValue.f32.negative.less_than_one, -0.9, 20), // discontinuity at x = -1 - -1, - ...biasedRange(kValue.f32.positive.less_than_one, 0.9, 20), // discontinuity at x = 1 - 1, - ...fullF32Range(), -]; -const f16_inputs = [ - ...biasedRange(kValue.f16.negative.less_than_one, -0.9, 20), // discontinuity at x = -1 - -1, - ...biasedRange(kValue.f16.positive.less_than_one, 0.9, 20), // discontinuity at x = 1 - 1, - ...fullF16Range(), -]; - -export const d = makeCaseCache('atanh', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.atanhInterval); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.atanhInterval); - }, - f16_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.atanhInterval); - }, - f16_non_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.atanhInterval); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('atanh'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -69,7 +48,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('atanh'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('atanh'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -83,5 +62,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('atanh'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('atanh'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts index 37d3ce5292..187a555449 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts @@ -35,7 +35,7 @@ fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T u .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize * t.params.dispatchSize; @@ -72,7 +72,7 @@ fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T u .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { // Allocate one extra element to ensure it doesn't get modified diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts index ed5cfa84a3..ad05bd851d 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts @@ -38,7 +38,7 @@ fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize * t.params.dispatchSize; @@ -91,7 +91,7 @@ fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize; diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts index 2556bb744b..79e0597af6 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts @@ -48,7 +48,7 @@ struct __atomic_compare_exchange_result<T> { .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(async t => { const numInvocations = t.params.workgroupSize * t.params.dispatchSize; @@ -187,7 +187,7 @@ struct __atomic_compare_exchange_result<T> { .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(async t => { const numInvocations = t.params.workgroupSize; @@ -333,12 +333,17 @@ struct __atomic_compare_exchange_result<T> { .params(u => u .combine('workgroupSize', onlyWorkgroupSizes) // - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(async t => { const numInvocations = t.params.workgroupSize; const scalarType = t.params.scalarType; + t.skipIf( + numInvocations > t.device.limits.maxComputeWorkgroupSizeX, + `${numInvocations} > maxComputeWorkgroupSizeX(${t.device.limits.maxComputeWorkgroupSizeX})` + ); + // Number of times each workgroup attempts to exchange the same value to the same memory address const numWrites = 4; @@ -550,12 +555,17 @@ struct __atomic_compare_exchange_result<T> { .params(u => u .combine('workgroupSize', onlyWorkgroupSizes) // - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(async t => { const numInvocations = t.params.workgroupSize; const scalarType = t.params.scalarType; + t.skipIf( + numInvocations > t.device.limits.maxComputeWorkgroupSizeX, + `${numInvocations} > maxComputeWorkgroupSizeX(${t.device.limits.maxComputeWorkgroupSizeX})` + ); + // Number of times each workgroup attempts to exchange the same value to the same memory address const numWrites = 4; diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts index 540ac16b07..00b6ddb7e3 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts @@ -26,7 +26,7 @@ fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize * t.params.dispatchSize; @@ -125,7 +125,7 @@ fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize; @@ -236,7 +236,7 @@ fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(async t => { const numInvocations = t.params.workgroupSize * t.params.dispatchSize; @@ -350,7 +350,7 @@ fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(async t => { const numInvocations = t.params.workgroupSize; diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts index 2aac7bb9b9..23ca127cae 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts @@ -26,7 +26,7 @@ fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize * t.params.dispatchSize; @@ -117,7 +117,7 @@ fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize; diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts index 066d673018..86bb8b460d 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts @@ -35,7 +35,7 @@ fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T u .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize * t.params.dispatchSize; @@ -72,7 +72,7 @@ fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T u .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { // Allocate one extra element to ensure it doesn't get modified diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts index ad880c4182..d9a42d15ee 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts @@ -35,7 +35,7 @@ fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T u .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { // Allocate one extra element to ensure it doesn't get modified @@ -71,7 +71,7 @@ fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T u .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { // Allocate one extra element to ensure it doesn't get modified diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts index 3892d41b38..8ddba24385 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts @@ -38,7 +38,7 @@ fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize * t.params.dispatchSize; @@ -90,7 +90,7 @@ fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize; diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts index 18ff72975d..4d226e312b 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts @@ -32,7 +32,7 @@ fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize * t.params.dispatchSize; @@ -72,7 +72,7 @@ fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize; @@ -117,7 +117,7 @@ one of the values written. .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(async t => { const numInvocations = t.params.workgroupSize * t.params.dispatchSize; @@ -210,7 +210,7 @@ one of the values written. .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(async t => { const numInvocations = t.params.workgroupSize; diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts index 6cea190299..8fdced1df2 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts @@ -35,7 +35,7 @@ fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T u .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize * t.params.dispatchSize; @@ -72,7 +72,7 @@ fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T u .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { // Allocate one extra element to ensure it doesn't get modified diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts index 99192fd9fe..2240043590 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts @@ -38,7 +38,7 @@ fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize * t.params.dispatchSize; @@ -91,7 +91,7 @@ fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T .combine('workgroupSize', workgroupSizes) .combine('dispatchSize', dispatchSizes) .combine('mapId', keysOf(kMapId)) - .combine('scalarType', ['u32', 'i32']) + .combine('scalarType', ['u32', 'i32'] as const) ) .fn(t => { const numInvocations = t.params.workgroupSize; diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts index ed02467f80..e12cf537cd 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts @@ -25,15 +25,14 @@ export const kMapId = { }, }; -export function typedArrayCtor(scalarType: string): TypedArrayBufferViewConstructor { +export function typedArrayCtor( + scalarType: 'u32' | 'i32' +): TypedArrayBufferViewConstructor<Uint32Array | Int32Array> { switch (scalarType) { case 'u32': return Uint32Array; case 'i32': return Int32Array; - default: - assert(false, 'Atomic variables can only by u32 or i32'); - return Uint8Array; } } diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts new file mode 100644 index 0000000000..75dfea0086 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts @@ -0,0 +1,837 @@ +import { assert } from '../../../../../../common/util/util.js'; +import { Comparator, alwaysPass, anyOf } from '../../../../../util/compare.js'; +import { kBit, kValue } from '../../../../../util/constants.js'; +import { + ScalarValue, + VectorValue, + f16, + f32, + i32, + toVector, + u32, + abstractFloat, + abstractInt, +} from '../../../../../util/conversion.js'; +import { FP, FPInterval } from '../../../../../util/floating_point.js'; +import { + cartesianProduct, + fullI32Range, + fullU32Range, + isFiniteF16, + isFiniteF32, + isSubnormalNumberF16, + isSubnormalNumberF32, + linearRange, + scalarF16Range, + scalarF32Range, +} from '../../../../../util/math.js'; +import { + reinterpretF16AsU16, + reinterpretF32AsI32, + reinterpretF32AsU32, + reinterpretI32AsF32, + reinterpretI32AsU32, + reinterpretU16AsF16, + reinterpretU32AsF32, + reinterpretU32AsI32, +} from '../../../../../util/reinterpret.js'; +import { makeCaseCache } from '../../case_cache.js'; + +const numNaNs = 11; +const f32InfAndNaNInU32: number[] = [ + // Cover NaNs evenly in integer space. + // The positive NaN with the lowest integer representation is the integer + // for infinity, plus one. + // The positive NaN with the highest integer representation is i32.max (!) + ...linearRange(kBit.f32.positive.infinity + 1, kBit.i32.positive.max, numNaNs), + // The negative NaN with the lowest integer representation is the integer + // for negative infinity, plus one. + // The negative NaN with the highest integer representation is u32.max (!) + ...linearRange(kBit.f32.negative.infinity + 1, kBit.u32.max, numNaNs), + kBit.f32.positive.infinity, + kBit.f32.negative.infinity, +]; +const f32InfAndNaNInF32 = f32InfAndNaNInU32.map(u => reinterpretU32AsF32(u)); +const f32InfAndNaNInI32 = f32InfAndNaNInU32.map(u => reinterpretU32AsI32(u)); + +const f32ZerosInU32 = [0, kBit.f32.negative.zero]; +const f32ZerosInF32 = f32ZerosInU32.map(u => reinterpretU32AsF32(u)); +const f32ZerosInI32 = f32ZerosInU32.map(u => reinterpretU32AsI32(u)); +const f32ZerosInterval: FPInterval = new FPInterval('f32', -0.0, 0.0); + +// f32FiniteRange is a list of finite f32s. fullF32Range() already +// has +0, we only need to add -0. +const f32FiniteRange: number[] = [...scalarF32Range(), kValue.f32.negative.zero]; +const f32RangeWithInfAndNaN: number[] = [...f32FiniteRange, ...f32InfAndNaNInF32]; + +// Type.f16 values, finite, Inf/NaN, and zeros. Represented in float and u16. +const f16FiniteInF16: number[] = [...scalarF16Range(), kValue.f16.negative.zero]; +const f16FiniteInU16: number[] = f16FiniteInF16.map(u => reinterpretF16AsU16(u)); + +const f16InfAndNaNInU16: number[] = [ + // Cover NaNs evenly in integer space. + // The positive NaN with the lowest integer representation is the integer + // for infinity, plus one. + // The positive NaN with the highest integer representation is u16 0x7fff i.e. 32767. + ...linearRange(kBit.f16.positive.infinity + 1, 32767, numNaNs).map(v => Math.ceil(v)), + // The negative NaN with the lowest integer representation is the integer + // for negative infinity, plus one. + // The negative NaN with the highest integer representation is u16 0xffff i.e. 65535 + ...linearRange(kBit.f16.negative.infinity + 1, 65535, numNaNs).map(v => Math.floor(v)), + kBit.f16.positive.infinity, + kBit.f16.negative.infinity, +]; +const f16InfAndNaNInF16 = f16InfAndNaNInU16.map(u => reinterpretU16AsF16(u)); + +const f16ZerosInU16 = [kBit.f16.negative.zero, 0]; + +// f16 interval that match +/-0.0. +const f16ZerosInterval: FPInterval = new FPInterval('f16', -0.0, 0.0); + +/** + * @returns an u32 whose lower and higher 16bits are the two elements of the + * given array of two u16 respectively, in little-endian. + */ +function u16x2ToU32(u16x2: readonly number[]): number { + assert(u16x2.length === 2); + // Create a DataView with 4 bytes buffer. + const buffer = new ArrayBuffer(4); + const view = new DataView(buffer); + // Enforce little-endian. + view.setUint16(0, u16x2[0], true); + view.setUint16(2, u16x2[1], true); + return view.getUint32(0, true); +} + +/** + * @returns an array of two u16, respectively the lower and higher 16bits of + * given u32 in little-endian. + */ +function u32ToU16x2(u32: number): number[] { + // Create a DataView with 4 bytes buffer. + const buffer = new ArrayBuffer(4); + const view = new DataView(buffer); + // Enforce little-endian. + view.setUint32(0, u32, true); + return [view.getUint16(0, true), view.getUint16(2, true)]; +} + +/** + * @returns a vec2<f16> from an array of two u16, each reinterpreted as f16. + */ +function u16x2ToVec2F16(u16x2: number[]): VectorValue { + assert(u16x2.length === 2); + return toVector(u16x2.map(reinterpretU16AsF16), f16); +} + +/** + * @returns a vec4<f16> from an array of four u16, each reinterpreted as f16. + */ +function u16x4ToVec4F16(u16x4: number[]): VectorValue { + assert(u16x4.length === 4); + return toVector(u16x4.map(reinterpretU16AsF16), f16); +} + +/** + * @returns true if and only if a given u32 can bitcast to a vec2<f16> with all elements + * being finite f16 values. + */ +function canU32BitcastToFiniteVec2F16(u32: number): boolean { + return u32ToU16x2(u32) + .map(u16 => isFiniteF16(reinterpretU16AsF16(u16))) + .reduce((a, b) => a && b, true); +} + +/** + * @returns an array of N elements with the i-th element being an array of len elements + * [a_i, a_((i+1)%N), ..., a_((i+len-1)%N)], for the input array of N element [a_1, ... a_N] + * and the given len. For example, slidingSlice([1, 2, 3], 2) result in + * [[1, 2], [2, 3], [3, 1]]. + * This helper function is used for generating vector cases from scalar values array. + */ +function slidingSlice(input: number[], len: number) { + const result: number[][] = []; + for (let i = 0; i < input.length; i++) { + const sub: number[] = []; + for (let j = 0; j < len; j++) { + sub.push(input[(i + j) % input.length]); + } + result.push(sub); + } + return result; +} + +// vec2<f16> interesting (zeros, Inf, and NaN) values for testing cases. +// vec2<f16> values that has at least one Inf/NaN f16 element, reinterpreted as u32/i32. +const f16Vec2InfAndNaNInU32 = [ + ...cartesianProduct(f16InfAndNaNInU16, [...f16InfAndNaNInU16, ...f16FiniteInU16]), + ...cartesianProduct(f16FiniteInU16, f16InfAndNaNInU16), +].map(u16x2ToU32); +const f16Vec2InfAndNaNInI32 = f16Vec2InfAndNaNInU32.map(u => reinterpretU32AsI32(u)); +// vec2<f16> values with two f16 0.0 element, reinterpreted as u32/i32. +const f16Vec2ZerosInU32 = cartesianProduct(f16ZerosInU16, f16ZerosInU16).map(u16x2ToU32); +const f16Vec2ZerosInI32 = f16Vec2ZerosInU32.map(u => reinterpretU32AsI32(u)); + +// i32/u32/f32 range for bitcasting to vec2<f16> +// u32 values for bitcasting to vec2<f16> finite, Inf, and NaN. +const u32RangeForF16Vec2FiniteInfNaN: number[] = [ + ...fullU32Range(), + ...f16Vec2ZerosInU32, + ...f16Vec2InfAndNaNInU32, +]; +// u32 values for bitcasting to finite only vec2<f16>, used for constant evaluation. +const u32RangeForF16Vec2Finite: number[] = u32RangeForF16Vec2FiniteInfNaN.filter( + canU32BitcastToFiniteVec2F16 +); +// i32 values for bitcasting to vec2<f16> finite, zeros, Inf, and NaN. +const i32RangeForF16Vec2FiniteInfNaN: number[] = [ + ...fullI32Range(), + ...f16Vec2ZerosInI32, + ...f16Vec2InfAndNaNInI32, +]; +// i32 values for bitcasting to finite only vec2<f16>, used for constant evaluation. +const i32RangeForF16Vec2Finite: number[] = i32RangeForF16Vec2FiniteInfNaN.filter(u => + canU32BitcastToFiniteVec2F16(reinterpretI32AsU32(u)) +); +// f32 values with finite/Inf/NaN f32, for bitcasting to vec2<f16> finite, zeros, Inf, and NaN. +const f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN: number[] = [ + ...f32RangeWithInfAndNaN, + ...u32RangeForF16Vec2FiniteInfNaN.map(reinterpretU32AsF32), +]; +// Finite f32 values for bitcasting to finite only vec2<f16>, used for constant evaluation. +const f32FiniteRangeForF16Vec2Finite: number[] = f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN + .filter(isFiniteF32) + .filter(u => canU32BitcastToFiniteVec2F16(reinterpretF32AsU32(u))); + +// vec2<f16> cases for bitcasting to i32/u32/f32, by combining f16 values into pairs +const f16Vec2FiniteInU16x2 = slidingSlice(f16FiniteInU16, 2); +const f16Vec2FiniteInfNanInU16x2 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 2); +// vec4<f16> cases for bitcasting to vec2<i32/u32/f32>, by combining f16 values 4-by-4 +const f16Vec2FiniteInU16x4 = slidingSlice(f16FiniteInU16, 4); +const f16Vec2FiniteInfNanInU16x4 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 4); + +// alwaysPass comparator for i32/u32/f32 cases. For f32/f16 we also use unbound interval, which +// allow per-element unbounded expectation for vector. +const anyF32 = alwaysPass('any f32'); +const anyI32 = alwaysPass('any i32'); +const anyU32 = alwaysPass('any u32'); + +// Unbounded FPInterval +const f32UnboundedInterval = FP.f32.constants().unboundedInterval; +const f16UnboundedInterval = FP.f16.constants().unboundedInterval; + +// i32 and u32 cases for bitcasting to f32. +// i32 cases for bitcasting to f32 finite, zeros, Inf, and NaN. +const i32RangeForF32FiniteInfNaN: number[] = [ + ...fullI32Range(), + ...f32ZerosInI32, + ...f32InfAndNaNInI32, +]; +// i32 cases for bitcasting to f32 finite only. +const i32RangeForF32Finite: number[] = i32RangeForF32FiniteInfNaN.filter(i => + isFiniteF32(reinterpretI32AsF32(i)) +); +// u32 cases for bitcasting to f32 finite, zeros, Inf, and NaN. +const u32RangeForF32FiniteInfNaN: number[] = [ + ...fullU32Range(), + ...f32ZerosInU32, + ...f32InfAndNaNInU32, +]; +// u32 cases for bitcasting to f32 finite only. +const u32RangeForF32Finite: number[] = u32RangeForF32FiniteInfNaN.filter(u => + isFiniteF32(reinterpretU32AsF32(u)) +); + +/** + * @returns a Comparator for checking if a f32 value is a valid + * bitcast conversion from f32. + */ +function bitcastF32ToF32Comparator(f: number): Comparator { + if (!isFiniteF32(f)) return anyF32; + const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])]; + return anyOf(...acceptable.map(f32)); +} + +/** + * @returns a Comparator for checking if a u32 value is a valid + * bitcast conversion from f32. + */ +function bitcastF32ToU32Comparator(f: number): Comparator { + if (!isFiniteF32(f)) return anyU32; + const acceptable: number[] = [ + reinterpretF32AsU32(f), + ...(isSubnormalNumberF32(f) ? f32ZerosInU32 : []), + ]; + return anyOf(...acceptable.map(u32)); +} + +/** + * @returns a Comparator for checking if a i32 value is a valid + * bitcast conversion from f32. + */ +function bitcastF32ToI32Comparator(f: number): Comparator { + if (!isFiniteF32(f)) return anyI32; + const acceptable: number[] = [ + reinterpretF32AsI32(f), + ...(isSubnormalNumberF32(f) ? f32ZerosInI32 : []), + ]; + return anyOf(...acceptable.map(i32)); +} + +/** + * @returns a Comparator for checking if a f32 value is a valid + * bitcast conversion from i32. + */ +function bitcastI32ToF32Comparator(i: number): Comparator { + const f: number = reinterpretI32AsF32(i); + if (!isFiniteF32(f)) return anyI32; + // Positive or negative zero bit pattern map to any zero. + if (f32ZerosInI32.includes(i)) return anyOf(...f32ZerosInF32.map(f32)); + const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])]; + return anyOf(...acceptable.map(f32)); +} + +/** + * @returns a Comparator for checking if a f32 value is a valid + * bitcast conversion from u32. + */ +function bitcastU32ToF32Comparator(u: number): Comparator { + const f: number = reinterpretU32AsF32(u); + if (!isFiniteF32(f)) return anyU32; + // Positive or negative zero bit pattern map to any zero. + if (f32ZerosInU32.includes(u)) return anyOf(...f32ZerosInF32.map(f32)); + const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])]; + return anyOf(...acceptable.map(f32)); +} + +/** + * @returns an array of expected f16 FPInterval for the given bitcasted f16 value, which may be + * subnormal, Inf, or NaN. Test cases that bitcasted to vector of f16 use this function to get + * per-element expectation and build vector expectation using cartesianProduct. + */ +function generateF16ExpectationIntervals(bitcastedF16Value: number): FPInterval[] { + // If the bitcasted f16 value is inf or nan, the result is unbounded + if (!isFiniteF16(bitcastedF16Value)) { + return [f16UnboundedInterval]; + } + // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0. + if (bitcastedF16Value === 0.0) { + return [f16ZerosInterval]; + } + const exactInterval = FP.f16.toInterval(bitcastedF16Value); + // If the casted f16 value is subnormal, it also may be flushed to +/-0.0. + return [exactInterval, ...(isSubnormalNumberF16(bitcastedF16Value) ? [f16ZerosInterval] : [])]; +} + +/** + * @returns a Comparator for checking if a f16 value is a valid + * bitcast conversion from f16. + */ +function bitcastF16ToF16Comparator(f: number): Comparator { + if (!isFiniteF16(f)) return anyOf(f16UnboundedInterval); + return anyOf(...generateF16ExpectationIntervals(f)); +} + +/** + * @returns a Comparator for checking if a vec2<f16> is a valid bitcast + * conversion from u32. + */ +function bitcastU32ToVec2F16Comparator(u: number): Comparator { + const bitcastedVec2F16InU16x2 = u32ToU16x2(u).map(reinterpretU16AsF16); + // Generate expection for vec2 f16 result, by generating expected intervals for each elements and + // then do cartesian product. + const expectedIntervalsCombination = cartesianProduct( + ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals) + ); + return anyOf(...expectedIntervalsCombination); +} + +/** + * @returns a Comparator for checking if a vec2<f16> value is a valid + * bitcast conversion from i32. + */ +function bitcastI32ToVec2F16Comparator(i: number): Comparator { + const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretI32AsU32(i)).map(reinterpretU16AsF16); + // Generate expection for vec2 f16 result, by generating expected intervals for each elements and + // then do cartesian product. + const expectedIntervalsCombination = cartesianProduct( + ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals) + ); + return anyOf(...expectedIntervalsCombination); +} + +/** + * @returns a Comparator for checking if a vec2<f16> value is a valid + * bitcast conversion from f32. + */ +function bitcastF32ToVec2F16Comparator(f: number): Comparator { + // If input f32 is not finite, it can be evaluated to any value and thus any result f16 vec2 is + // possible. + if (!isFiniteF32(f)) { + return anyOf([f16UnboundedInterval, f16UnboundedInterval]); + } + const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretF32AsU32(f)).map(reinterpretU16AsF16); + // Generate expection for vec2 f16 result, by generating expected intervals for each elements and + // then do cartesian product. + const expectedIntervalsCombination = cartesianProduct( + ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals) + ); + return anyOf(...expectedIntervalsCombination); +} + +/** + * @returns a Comparator for checking if a vec4<f16> is a valid + * bitcast conversion from vec2<u32>. + */ +function bitcastVec2U32ToVec4F16Comparator(u32x2: number[]): Comparator { + assert(u32x2.length === 2); + const bitcastedVec4F16InU16x4 = u32x2.flatMap(u32ToU16x2).map(reinterpretU16AsF16); + // Generate expection for vec4 f16 result, by generating expected intervals for each elements and + // then do cartesian product. + const expectedIntervalsCombination = cartesianProduct( + ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals) + ); + return anyOf(...expectedIntervalsCombination); +} + +/** + * @returns a Comparator for checking if a vec4<f16> is a valid + * bitcast conversion from vec2<i32>. + */ +function bitcastVec2I32ToVec4F16Comparator(i32x2: number[]): Comparator { + assert(i32x2.length === 2); + const bitcastedVec4F16InU16x4 = i32x2 + .map(reinterpretI32AsU32) + .flatMap(u32ToU16x2) + .map(reinterpretU16AsF16); + // Generate expection for vec4 f16 result, by generating expected intervals for each elements and + // then do cartesian product. + const expectedIntervalsCombination = cartesianProduct( + ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals) + ); + return anyOf(...expectedIntervalsCombination); +} + +/** + * @returns a Comparator for checking if a vec4<f16> is a valid + * bitcast conversion from vec2<f32>. + */ +function bitcastVec2F32ToVec4F16Comparator(f32x2: number[]): Comparator { + assert(f32x2.length === 2); + const bitcastedVec4F16InU16x4 = f32x2 + .map(reinterpretF32AsU32) + .flatMap(u32ToU16x2) + .map(reinterpretU16AsF16); + // Generate expection for vec4 f16 result, by generating expected intervals for each elements and + // then do cartesian product. + const expectedIntervalsCombination = cartesianProduct( + ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals) + ); + return anyOf(...expectedIntervalsCombination); +} + +// Structure that store the expectations of a single 32bit scalar/element bitcasted from two f16. +interface ExpectionFor32BitsScalarFromF16x2 { + // possibleExpectations is Scalar array if the expectation is for i32/u32 and FPInterval array for + // f32. Note that if the expectation for i32/u32 is unbound, possibleExpectations is meaningless. + possibleExpectations: (ScalarValue | FPInterval)[]; + isUnbounded: boolean; +} + +/** + * @returns the array of possible 16bits, represented in u16, that bitcasted + * from a given finite f16 represented in u16, handling the possible subnormal + * flushing. Used to build up 32bits or larger results. + */ +function possibleBitsInU16FromFiniteF16InU16(f16InU16: number): number[] { + const h = reinterpretU16AsF16(f16InU16); + assert(isFiniteF16(h)); + return [f16InU16, ...(isSubnormalNumberF16(h) ? f16ZerosInU16 : [])]; +} + +/** + * @returns the expectation for a single 32bit scalar bitcasted from given pair of + * f16, result in ExpectionFor32BitsScalarFromF16x2. + */ +function possible32BitScalarIntervalsFromF16x2( + f16x2InU16x2: number[], + type: 'i32' | 'u32' | 'f32' +): ExpectionFor32BitsScalarFromF16x2 { + assert(f16x2InU16x2.length === 2); + let reinterpretFromU32: (x: number) => number; + let expectationsForValue: (x: number) => ScalarValue[] | FPInterval[]; + let unboundedExpectations: FPInterval[] | ScalarValue[]; + if (type === 'u32') { + reinterpretFromU32 = (x: number) => x; + expectationsForValue = x => [u32(x)]; + // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a + // placeholder, and the possibleExpectations should be ignored if the result is unbounded. + unboundedExpectations = [u32(0)]; + } else if (type === 'i32') { + reinterpretFromU32 = (x: number) => reinterpretU32AsI32(x); + expectationsForValue = x => [i32(x)]; + // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a + // placeholder, and the possibleExpectations should be ignored if the result is unbounded. + unboundedExpectations = [i32(0)]; + } else { + assert(type === 'f32'); + reinterpretFromU32 = (x: number) => reinterpretU32AsF32(x); + expectationsForValue = x => { + // Handle the possible Inf/NaN/zeros and subnormal cases for f32 result. + if (!isFiniteF32(x)) { + return [f32UnboundedInterval]; + } + // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0. + if (x === 0.0) { + return [f32ZerosInterval]; + } + const exactInterval = FP.f32.toInterval(x); + // If the casted f16 value is subnormal, it also may be flushed to +/-0.0. + return [exactInterval, ...(isSubnormalNumberF32(x) ? [f32ZerosInterval] : [])]; + }; + unboundedExpectations = [f32UnboundedInterval]; + } + // Return unbounded expection if f16 Inf/NaN occurs + if ( + !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[0])) || + !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[1])) + ) { + return { possibleExpectations: unboundedExpectations, isUnbounded: true }; + } + const possibleU16Bits = f16x2InU16x2.map(possibleBitsInU16FromFiniteF16InU16); + const possibleExpectations = cartesianProduct(...possibleU16Bits).flatMap< + ScalarValue | FPInterval + >((possibleBitsU16x2: readonly number[]) => { + assert(possibleBitsU16x2.length === 2); + return expectationsForValue(reinterpretFromU32(u16x2ToU32(possibleBitsU16x2))); + }); + return { possibleExpectations, isUnbounded: false }; +} + +/** + * @returns a Comparator for checking if a u32 value is a valid + * bitcast conversion from vec2 f16. + */ +function bitcastVec2F16ToU32Comparator(vec2F16InU16x2: number[]): Comparator { + assert(vec2F16InU16x2.length === 2); + const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'u32'); + // Return alwaysPass if result is expected unbounded. + if (expectations.isUnbounded) { + return anyU32; + } + return anyOf(...expectations.possibleExpectations); +} + +/** + * @returns a Comparator for checking if a i32 value is a valid + * bitcast conversion from vec2 f16. + */ +function bitcastVec2F16ToI32Comparator(vec2F16InU16x2: number[]): Comparator { + assert(vec2F16InU16x2.length === 2); + const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'i32'); + // Return alwaysPass if result is expected unbounded. + if (expectations.isUnbounded) { + return anyI32; + } + return anyOf(...expectations.possibleExpectations); +} + +/** + * @returns a Comparator for checking if a i32 value is a valid + * bitcast conversion from vec2 f16. + */ +function bitcastVec2F16ToF32Comparator(vec2F16InU16x2: number[]): Comparator { + assert(vec2F16InU16x2.length === 2); + const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'f32'); + // Return alwaysPass if result is expected unbounded. + if (expectations.isUnbounded) { + return anyF32; + } + return anyOf(...expectations.possibleExpectations); +} + +/** + * @returns a Comparator for checking if a vec2 u32 value is a valid + * bitcast conversion from vec4 f16. + */ +function bitcastVec4F16ToVec2U32Comparator(vec4F16InU16x4: number[]): Comparator { + assert(vec4F16InU16x4.length === 4); + const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e => + possible32BitScalarIntervalsFromF16x2(e, 'u32') + ); + // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded + // element in the result vector, currently we don't have a way to build a comparator that expect + // only one element of i32/u32 vector unbounded. + if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) { + return alwaysPass('any vec2<u32>'); + } + return anyOf( + ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map( + e => new VectorValue(e as ScalarValue[]) + ) + ); +} + +/** + * @returns a Comparator for checking if a vec2 i32 value is a valid + * bitcast conversion from vec4 f16. + */ +function bitcastVec4F16ToVec2I32Comparator(vec4F16InU16x4: number[]): Comparator { + assert(vec4F16InU16x4.length === 4); + const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e => + possible32BitScalarIntervalsFromF16x2(e, 'i32') + ); + // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded + // element in the result vector, currently we don't have a way to build a comparator that expect + // only one element of i32/u32 vector unbounded. + if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) { + return alwaysPass('any vec2<i32>'); + } + return anyOf( + ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map( + e => new VectorValue(e as ScalarValue[]) + ) + ); +} + +/** + * @returns a Comparator for checking if a vec2 f32 value is a valid + * bitcast conversion from vec4 f16. + */ +function bitcastVec4F16ToVec2F32Comparator(vec4F16InU16x4: number[]): Comparator { + assert(vec4F16InU16x4.length === 4); + const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e => + possible32BitScalarIntervalsFromF16x2(e, 'f32') + ); + return anyOf( + ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(e => [ + e[0] as FPInterval, + e[1] as FPInterval, + ]) + ); +} + +export const d = makeCaseCache('bitcast', { + // Identity Cases + i32_to_i32: () => fullI32Range().map(e => ({ input: i32(e), expected: i32(e) })), + u32_to_u32: () => fullU32Range().map(e => ({ input: u32(e), expected: u32(e) })), + f32_inf_nan_to_f32: () => + f32RangeWithInfAndNaN.map(e => ({ + input: f32(e), + expected: bitcastF32ToF32Comparator(e), + })), + f32_to_f32: () => + f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToF32Comparator(e) })), + f16_inf_nan_to_f16: () => + [...f16FiniteInF16, ...f16InfAndNaNInF16].map(e => ({ + input: f16(e), + expected: bitcastF16ToF16Comparator(e), + })), + f16_to_f16: () => + f16FiniteInF16.map(e => ({ input: f16(e), expected: bitcastF16ToF16Comparator(e) })), + + // i32,u32,f32,Abstract to different i32,u32,f32 + i32_to_u32: () => fullI32Range().map(e => ({ input: i32(e), expected: u32(e) })), + i32_to_f32: () => + i32RangeForF32Finite.map(e => ({ + input: i32(e), + expected: bitcastI32ToF32Comparator(e), + })), + ai_to_i32: () => fullI32Range().map(e => ({ input: abstractInt(BigInt(e)), expected: i32(e) })), + ai_to_u32: () => fullU32Range().map(e => ({ input: abstractInt(BigInt(e)), expected: u32(e) })), + ai_to_f32: () => + // AbstractInt is converted to i32, because there is no explicit overload + i32RangeForF32Finite.map(e => ({ + input: abstractInt(BigInt(e)), + expected: bitcastI32ToF32Comparator(e), + })), + i32_to_f32_inf_nan: () => + i32RangeForF32FiniteInfNaN.map(e => ({ + input: i32(e), + expected: bitcastI32ToF32Comparator(e), + })), + u32_to_i32: () => fullU32Range().map(e => ({ input: u32(e), expected: i32(e) })), + u32_to_f32: () => + u32RangeForF32Finite.map(e => ({ + input: u32(e), + expected: bitcastU32ToF32Comparator(e), + })), + u32_to_f32_inf_nan: () => + u32RangeForF32FiniteInfNaN.map(e => ({ + input: u32(e), + expected: bitcastU32ToF32Comparator(e), + })), + f32_inf_nan_to_i32: () => + f32RangeWithInfAndNaN.map(e => ({ + input: f32(e), + expected: bitcastF32ToI32Comparator(e), + })), + f32_to_i32: () => + f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToI32Comparator(e) })), + + f32_inf_nan_to_u32: () => + f32RangeWithInfAndNaN.map(e => ({ + input: f32(e), + expected: bitcastF32ToU32Comparator(e), + })), + f32_to_u32: () => + f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToU32Comparator(e) })), + + // i32,u32,f32,AbstractInt to vec2<f16> + u32_to_vec2_f16_inf_nan: () => + u32RangeForF16Vec2FiniteInfNaN.map(e => ({ + input: u32(e), + expected: bitcastU32ToVec2F16Comparator(e), + })), + u32_to_vec2_f16: () => + u32RangeForF16Vec2Finite.map(e => ({ + input: u32(e), + expected: bitcastU32ToVec2F16Comparator(e), + })), + i32_to_vec2_f16_inf_nan: () => + i32RangeForF16Vec2FiniteInfNaN.map(e => ({ + input: i32(e), + expected: bitcastI32ToVec2F16Comparator(e), + })), + i32_to_vec2_f16: () => + i32RangeForF16Vec2Finite.map(e => ({ + input: i32(e), + expected: bitcastI32ToVec2F16Comparator(e), + })), + ai_to_vec2_f16: () => + // AbstractInt is converted to i32, because there is no explicit overload + i32RangeForF16Vec2Finite.map(e => ({ + input: abstractInt(BigInt(e)), + expected: bitcastI32ToVec2F16Comparator(e), + })), + f32_inf_nan_to_vec2_f16_inf_nan: () => + f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN.map(e => ({ + input: f32(e), + expected: bitcastF32ToVec2F16Comparator(e), + })), + f32_to_vec2_f16: () => + f32FiniteRangeForF16Vec2Finite.map(e => ({ + input: f32(e), + expected: bitcastF32ToVec2F16Comparator(e), + })), + af_to_vec2_f16: () => + f32FiniteRangeForF16Vec2Finite.map(e => ({ + input: abstractFloat(e), + expected: bitcastF32ToVec2F16Comparator(e), + })), + + // vec2<i32>, vec2<u32>, vec2<f32>, vec2<AbstractInt> to vec4<f16> + vec2_i32_to_vec4_f16_inf_nan: () => + slidingSlice(i32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({ + input: toVector(e, i32), + expected: bitcastVec2I32ToVec4F16Comparator(e), + })), + vec2_i32_to_vec4_f16: () => + slidingSlice(i32RangeForF16Vec2Finite, 2).map(e => ({ + input: toVector(e, i32), + expected: bitcastVec2I32ToVec4F16Comparator(e), + })), + vec2_ai_to_vec4_f16: () => + // AbstractInt is converted to i32, because there is no explicit overload + slidingSlice(i32RangeForF16Vec2Finite, 2).map(e => ({ + input: toVector(e, (n: number) => abstractInt(BigInt(n))), + expected: bitcastVec2I32ToVec4F16Comparator(e), + })), + vec2_u32_to_vec4_f16_inf_nan: () => + slidingSlice(u32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({ + input: toVector(e, u32), + expected: bitcastVec2U32ToVec4F16Comparator(e), + })), + vec2_u32_to_vec4_f16: () => + slidingSlice(u32RangeForF16Vec2Finite, 2).map(e => ({ + input: toVector(e, u32), + expected: bitcastVec2U32ToVec4F16Comparator(e), + })), + vec2_f32_inf_nan_to_vec4_f16_inf_nan: () => + slidingSlice(f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN, 2).map(e => ({ + input: toVector(e, f32), + expected: bitcastVec2F32ToVec4F16Comparator(e), + })), + vec2_f32_to_vec4_f16: () => + slidingSlice(f32FiniteRangeForF16Vec2Finite, 2).map(e => ({ + input: toVector(e, f32), + expected: bitcastVec2F32ToVec4F16Comparator(e), + })), + vec2_af_to_vec4_f16: () => + slidingSlice(f32FiniteRangeForF16Vec2Finite, 2).map(e => ({ + input: toVector(e, abstractFloat), + expected: bitcastVec2F32ToVec4F16Comparator(e), + })), + + // vec2<f16> to i32, u32, f32 + vec2_f16_to_u32: () => + f16Vec2FiniteInU16x2.map(e => ({ + input: u16x2ToVec2F16(e), + expected: bitcastVec2F16ToU32Comparator(e), + })), + vec2_f16_inf_nan_to_u32: () => + f16Vec2FiniteInfNanInU16x2.map(e => ({ + input: u16x2ToVec2F16(e), + expected: bitcastVec2F16ToU32Comparator(e), + })), + vec2_f16_to_i32: () => + f16Vec2FiniteInU16x2.map(e => ({ + input: u16x2ToVec2F16(e), + expected: bitcastVec2F16ToI32Comparator(e), + })), + vec2_f16_inf_nan_to_i32: () => + f16Vec2FiniteInfNanInU16x2.map(e => ({ + input: u16x2ToVec2F16(e), + expected: bitcastVec2F16ToI32Comparator(e), + })), + vec2_f16_to_f32_finite: () => + f16Vec2FiniteInU16x2 + .filter(u16x2 => isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x2)))) + .map(e => ({ + input: u16x2ToVec2F16(e), + expected: bitcastVec2F16ToF32Comparator(e), + })), + vec2_f16_inf_nan_to_f32: () => + f16Vec2FiniteInfNanInU16x2.map(e => ({ + input: u16x2ToVec2F16(e), + expected: bitcastVec2F16ToF32Comparator(e), + })), + + // vec4<f16> to vec2 of i32, u32, f32 + vec4_f16_to_vec2_u32: () => + f16Vec2FiniteInU16x4.map(e => ({ + input: u16x4ToVec4F16(e), + expected: bitcastVec4F16ToVec2U32Comparator(e), + })), + vec4_f16_inf_nan_to_vec2_u32: () => + f16Vec2FiniteInfNanInU16x4.map(e => ({ + input: u16x4ToVec4F16(e), + expected: bitcastVec4F16ToVec2U32Comparator(e), + })), + vec4_f16_to_vec2_i32: () => + f16Vec2FiniteInU16x4.map(e => ({ + input: u16x4ToVec4F16(e), + expected: bitcastVec4F16ToVec2I32Comparator(e), + })), + vec4_f16_inf_nan_to_vec2_i32: () => + f16Vec2FiniteInfNanInU16x4.map(e => ({ + input: u16x4ToVec4F16(e), + expected: bitcastVec4F16ToVec2I32Comparator(e), + })), + vec4_f16_to_vec2_f32_finite: () => + f16Vec2FiniteInU16x4 + .filter( + u16x4 => + isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(0, 2)))) && + isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(2, 4)))) + ) + .map(e => ({ + input: u16x4ToVec4F16(e), + expected: bitcastVec4F16ToVec2F32Comparator(e), + })), + vec4_f16_inf_nan_to_vec2_f32: () => + f16Vec2FiniteInfNanInU16x4.map(e => ({ + input: u16x4ToVec4F16(e), + expected: bitcastVec4F16ToVec2F32Comparator(e), + })), +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts index 390129f2c7..02acf98ef4 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts @@ -11,6 +11,9 @@ S is i32, u32, f32 T is i32, u32, f32, and T is not S Reinterpretation of bits. Beware non-normal f32 values. +@const @must_use fn bitcast<u32>(e : Type.abstractInt) -> T +@const @must_use fn bitcast<vecN<u32>>(e : vecN<Type.abstractInt>) -> T + @const @must_use fn bitcast<T>(e: vec2<f16> ) -> T @const @must_use fn bitcast<vec2<T>>(e: vec4<f16> ) -> vec2<T> @const @must_use fn bitcast<vec2<f16>>(e: T ) -> vec2<f16> @@ -20,823 +23,26 @@ T is i32, u32, f32 import { TestParams } from '../../../../../../common/framework/fixture.js'; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; -import { assert } from '../../../../../../common/util/util.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { Comparator, alwaysPass, anyOf } from '../../../../../util/compare.js'; -import { kBit, kValue } from '../../../../../util/constants.js'; +import { anyOf } from '../../../../../util/compare.js'; import { f32, - i32, u32, - f16, - TypeF32, - TypeI32, - TypeU32, - TypeF16, - TypeVec, - Vector, - Scalar, - toVector, + i32, + abstractFloat, + uint32ToFloat32, + u32Bits, + Type, } from '../../../../../util/conversion.js'; -import { FPInterval, FP } from '../../../../../util/floating_point.js'; -import { - fullF32Range, - fullI32Range, - fullU32Range, - fullF16Range, - linearRange, - isSubnormalNumberF32, - isSubnormalNumberF16, - cartesianProduct, - isFiniteF32, - isFiniteF16, -} from '../../../../../util/math.js'; -import { - reinterpretI32AsF32, - reinterpretI32AsU32, - reinterpretF32AsI32, - reinterpretF32AsU32, - reinterpretU32AsF32, - reinterpretU32AsI32, - reinterpretU16AsF16, - reinterpretF16AsU16, -} from '../../../../../util/reinterpret.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run, ShaderBuilder } from '../../expression.js'; +import { FP } from '../../../../../util/floating_point.js'; +import { scalarF32Range } from '../../../../../util/math.js'; +import { ShaderBuilder, allInputSources, onlyConstInputSource, run } from '../../expression.js'; +import { d } from './bitcast.cache.js'; import { builtinWithPredeclaration } from './builtin.js'; export const g = makeTestGroup(GPUTest); -const numNaNs = 11; -const f32InfAndNaNInU32: number[] = [ - // Cover NaNs evenly in integer space. - // The positive NaN with the lowest integer representation is the integer - // for infinity, plus one. - // The positive NaN with the highest integer representation is i32.max (!) - ...linearRange(kBit.f32.positive.infinity + 1, kBit.i32.positive.max, numNaNs), - // The negative NaN with the lowest integer representation is the integer - // for negative infinity, plus one. - // The negative NaN with the highest integer representation is u32.max (!) - ...linearRange(kBit.f32.negative.infinity + 1, kBit.u32.max, numNaNs), - kBit.f32.positive.infinity, - kBit.f32.negative.infinity, -]; -const f32InfAndNaNInF32 = f32InfAndNaNInU32.map(u => reinterpretU32AsF32(u)); -const f32InfAndNaNInI32 = f32InfAndNaNInU32.map(u => reinterpretU32AsI32(u)); - -const f32ZerosInU32 = [0, kBit.f32.negative.zero]; -const f32ZerosInF32 = f32ZerosInU32.map(u => reinterpretU32AsF32(u)); -const f32ZerosInI32 = f32ZerosInU32.map(u => reinterpretU32AsI32(u)); -const f32ZerosInterval: FPInterval = new FPInterval('f32', -0.0, 0.0); - -// f32FiniteRange is a list of finite f32s. fullF32Range() already -// has +0, we only need to add -0. -const f32FiniteRange: number[] = [...fullF32Range(), kValue.f32.negative.zero]; -const f32RangeWithInfAndNaN: number[] = [...f32FiniteRange, ...f32InfAndNaNInF32]; - -// F16 values, finite, Inf/NaN, and zeros. Represented in float and u16. -const f16FiniteInF16: number[] = [...fullF16Range(), kValue.f16.negative.zero]; -const f16FiniteInU16: number[] = f16FiniteInF16.map(u => reinterpretF16AsU16(u)); - -const f16InfAndNaNInU16: number[] = [ - // Cover NaNs evenly in integer space. - // The positive NaN with the lowest integer representation is the integer - // for infinity, plus one. - // The positive NaN with the highest integer representation is u16 0x7fff i.e. 32767. - ...linearRange(kBit.f16.positive.infinity + 1, 32767, numNaNs).map(v => Math.ceil(v)), - // The negative NaN with the lowest integer representation is the integer - // for negative infinity, plus one. - // The negative NaN with the highest integer representation is u16 0xffff i.e. 65535 - ...linearRange(kBit.f16.negative.infinity + 1, 65535, numNaNs).map(v => Math.floor(v)), - kBit.f16.positive.infinity, - kBit.f16.negative.infinity, -]; -const f16InfAndNaNInF16 = f16InfAndNaNInU16.map(u => reinterpretU16AsF16(u)); - -const f16ZerosInU16 = [kBit.f16.negative.zero, 0]; - -// f16 interval that match +/-0.0. -const f16ZerosInterval: FPInterval = new FPInterval('f16', -0.0, 0.0); - -/** - * @returns an u32 whose lower and higher 16bits are the two elements of the - * given array of two u16 respectively, in little-endian. - */ -function u16x2ToU32(u16x2: readonly number[]): number { - assert(u16x2.length === 2); - // Create a DataView with 4 bytes buffer. - const buffer = new ArrayBuffer(4); - const view = new DataView(buffer); - // Enforce little-endian. - view.setUint16(0, u16x2[0], true); - view.setUint16(2, u16x2[1], true); - return view.getUint32(0, true); -} - -/** - * @returns an array of two u16, respectively the lower and higher 16bits of - * given u32 in little-endian. - */ -function u32ToU16x2(u32: number): number[] { - // Create a DataView with 4 bytes buffer. - const buffer = new ArrayBuffer(4); - const view = new DataView(buffer); - // Enforce little-endian. - view.setUint32(0, u32, true); - return [view.getUint16(0, true), view.getUint16(2, true)]; -} - -/** - * @returns a vec2<f16> from an array of two u16, each reinterpreted as f16. - */ -function u16x2ToVec2F16(u16x2: number[]): Vector { - assert(u16x2.length === 2); - return toVector(u16x2.map(reinterpretU16AsF16), f16); -} - -/** - * @returns a vec4<f16> from an array of four u16, each reinterpreted as f16. - */ -function u16x4ToVec4F16(u16x4: number[]): Vector { - assert(u16x4.length === 4); - return toVector(u16x4.map(reinterpretU16AsF16), f16); -} - -/** - * @returns true if and only if a given u32 can bitcast to a vec2<f16> with all elements - * being finite f16 values. - */ -function canU32BitcastToFiniteVec2F16(u32: number): boolean { - return u32ToU16x2(u32) - .map(u16 => isFiniteF16(reinterpretU16AsF16(u16))) - .reduce((a, b) => a && b, true); -} - -/** - * @returns an array of N elements with the i-th element being an array of len elements - * [a_i, a_((i+1)%N), ..., a_((i+len-1)%N)], for the input array of N element [a_1, ... a_N] - * and the given len. For example, slidingSlice([1, 2, 3], 2) result in - * [[1, 2], [2, 3], [3, 1]]. - * This helper function is used for generating vector cases from scalar values array. - */ -function slidingSlice(input: number[], len: number) { - const result: number[][] = []; - for (let i = 0; i < input.length; i++) { - const sub: number[] = []; - for (let j = 0; j < len; j++) { - sub.push(input[(i + j) % input.length]); - } - result.push(sub); - } - return result; -} - -// vec2<f16> interesting (zeros, Inf, and NaN) values for testing cases. -// vec2<f16> values that has at least one Inf/NaN f16 element, reinterpreted as u32/i32. -const f16Vec2InfAndNaNInU32 = [ - ...cartesianProduct(f16InfAndNaNInU16, [...f16InfAndNaNInU16, ...f16FiniteInU16]), - ...cartesianProduct(f16FiniteInU16, f16InfAndNaNInU16), -].map(u16x2ToU32); -const f16Vec2InfAndNaNInI32 = f16Vec2InfAndNaNInU32.map(u => reinterpretU32AsI32(u)); -// vec2<f16> values with two f16 0.0 element, reinterpreted as u32/i32. -const f16Vec2ZerosInU32 = cartesianProduct(f16ZerosInU16, f16ZerosInU16).map(u16x2ToU32); -const f16Vec2ZerosInI32 = f16Vec2ZerosInU32.map(u => reinterpretU32AsI32(u)); - -// i32/u32/f32 range for bitcasting to vec2<f16> -// u32 values for bitcasting to vec2<f16> finite, Inf, and NaN. -const u32RangeForF16Vec2FiniteInfNaN: number[] = [ - ...fullU32Range(), - ...f16Vec2ZerosInU32, - ...f16Vec2InfAndNaNInU32, -]; -// u32 values for bitcasting to finite only vec2<f16>, used for constant evaluation. -const u32RangeForF16Vec2Finite: number[] = u32RangeForF16Vec2FiniteInfNaN.filter( - canU32BitcastToFiniteVec2F16 -); -// i32 values for bitcasting to vec2<f16> finite, zeros, Inf, and NaN. -const i32RangeForF16Vec2FiniteInfNaN: number[] = [ - ...fullI32Range(), - ...f16Vec2ZerosInI32, - ...f16Vec2InfAndNaNInI32, -]; -// i32 values for bitcasting to finite only vec2<f16>, used for constant evaluation. -const i32RangeForF16Vec2Finite: number[] = i32RangeForF16Vec2FiniteInfNaN.filter(u => - canU32BitcastToFiniteVec2F16(reinterpretI32AsU32(u)) -); -// f32 values with finite/Inf/NaN f32, for bitcasting to vec2<f16> finite, zeros, Inf, and NaN. -const f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN: number[] = [ - ...f32RangeWithInfAndNaN, - ...u32RangeForF16Vec2FiniteInfNaN.map(reinterpretU32AsF32), -]; -// Finite f32 values for bitcasting to finite only vec2<f16>, used for constant evaluation. -const f32FiniteRangeForF16Vec2Finite: number[] = f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN - .filter(isFiniteF32) - .filter(u => canU32BitcastToFiniteVec2F16(reinterpretF32AsU32(u))); - -// vec2<f16> cases for bitcasting to i32/u32/f32, by combining f16 values into pairs -const f16Vec2FiniteInU16x2 = slidingSlice(f16FiniteInU16, 2); -const f16Vec2FiniteInfNanInU16x2 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 2); -// vec4<f16> cases for bitcasting to vec2<i32/u32/f32>, by combining f16 values 4-by-4 -const f16Vec2FiniteInU16x4 = slidingSlice(f16FiniteInU16, 4); -const f16Vec2FiniteInfNanInU16x4 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 4); - -// alwaysPass comparator for i32/u32/f32 cases. For f32/f16 we also use unbound interval, which -// allow per-element unbounded expectation for vector. -const anyF32 = alwaysPass('any f32'); -const anyI32 = alwaysPass('any i32'); -const anyU32 = alwaysPass('any u32'); - -// Unbounded FPInterval -const f32UnboundedInterval = FP.f32.constants().unboundedInterval; -const f16UnboundedInterval = FP.f16.constants().unboundedInterval; - -// i32 and u32 cases for bitcasting to f32. -// i32 cases for bitcasting to f32 finite, zeros, Inf, and NaN. -const i32RangeForF32FiniteInfNaN: number[] = [ - ...fullI32Range(), - ...f32ZerosInI32, - ...f32InfAndNaNInI32, -]; -// i32 cases for bitcasting to f32 finite only. -const i32RangeForF32Finite: number[] = i32RangeForF32FiniteInfNaN.filter(i => - isFiniteF32(reinterpretI32AsF32(i)) -); -// u32 cases for bitcasting to f32 finite, zeros, Inf, and NaN. -const u32RangeForF32FiniteInfNaN: number[] = [ - ...fullU32Range(), - ...f32ZerosInU32, - ...f32InfAndNaNInU32, -]; -// u32 cases for bitcasting to f32 finite only. -const u32RangeForF32Finite: number[] = u32RangeForF32FiniteInfNaN.filter(u => - isFiniteF32(reinterpretU32AsF32(u)) -); - -/** - * @returns a Comparator for checking if a f32 value is a valid - * bitcast conversion from f32. - */ -function bitcastF32ToF32Comparator(f: number): Comparator { - if (!isFiniteF32(f)) return anyF32; - const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])]; - return anyOf(...acceptable.map(f32)); -} - -/** - * @returns a Comparator for checking if a u32 value is a valid - * bitcast conversion from f32. - */ -function bitcastF32ToU32Comparator(f: number): Comparator { - if (!isFiniteF32(f)) return anyU32; - const acceptable: number[] = [ - reinterpretF32AsU32(f), - ...(isSubnormalNumberF32(f) ? f32ZerosInU32 : []), - ]; - return anyOf(...acceptable.map(u32)); -} - -/** - * @returns a Comparator for checking if a i32 value is a valid - * bitcast conversion from f32. - */ -function bitcastF32ToI32Comparator(f: number): Comparator { - if (!isFiniteF32(f)) return anyI32; - const acceptable: number[] = [ - reinterpretF32AsI32(f), - ...(isSubnormalNumberF32(f) ? f32ZerosInI32 : []), - ]; - return anyOf(...acceptable.map(i32)); -} - -/** - * @returns a Comparator for checking if a f32 value is a valid - * bitcast conversion from i32. - */ -function bitcastI32ToF32Comparator(i: number): Comparator { - const f: number = reinterpretI32AsF32(i); - if (!isFiniteF32(f)) return anyI32; - // Positive or negative zero bit pattern map to any zero. - if (f32ZerosInI32.includes(i)) return anyOf(...f32ZerosInF32.map(f32)); - const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])]; - return anyOf(...acceptable.map(f32)); -} - -/** - * @returns a Comparator for checking if a f32 value is a valid - * bitcast conversion from u32. - */ -function bitcastU32ToF32Comparator(u: number): Comparator { - const f: number = reinterpretU32AsF32(u); - if (!isFiniteF32(f)) return anyU32; - // Positive or negative zero bit pattern map to any zero. - if (f32ZerosInU32.includes(u)) return anyOf(...f32ZerosInF32.map(f32)); - const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])]; - return anyOf(...acceptable.map(f32)); -} - -/** - * @returns an array of expected f16 FPInterval for the given bitcasted f16 value, which may be - * subnormal, Inf, or NaN. Test cases that bitcasted to vector of f16 use this function to get - * per-element expectation and build vector expectation using cartesianProduct. - */ -function generateF16ExpectationIntervals(bitcastedF16Value: number): FPInterval[] { - // If the bitcasted f16 value is inf or nan, the result is unbounded - if (!isFiniteF16(bitcastedF16Value)) { - return [f16UnboundedInterval]; - } - // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0. - if (bitcastedF16Value === 0.0) { - return [f16ZerosInterval]; - } - const exactInterval = FP.f16.toInterval(bitcastedF16Value); - // If the casted f16 value is subnormal, it also may be flushed to +/-0.0. - return [exactInterval, ...(isSubnormalNumberF16(bitcastedF16Value) ? [f16ZerosInterval] : [])]; -} - -/** - * @returns a Comparator for checking if a f16 value is a valid - * bitcast conversion from f16. - */ -function bitcastF16ToF16Comparator(f: number): Comparator { - if (!isFiniteF16(f)) return anyOf(f16UnboundedInterval); - return anyOf(...generateF16ExpectationIntervals(f)); -} - -/** - * @returns a Comparator for checking if a vec2<f16> is a valid bitcast - * conversion from u32. - */ -function bitcastU32ToVec2F16Comparator(u: number): Comparator { - const bitcastedVec2F16InU16x2 = u32ToU16x2(u).map(reinterpretU16AsF16); - // Generate expection for vec2 f16 result, by generating expected intervals for each elements and - // then do cartesian product. - const expectedIntervalsCombination = cartesianProduct( - ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals) - ); - return anyOf(...expectedIntervalsCombination); -} - -/** - * @returns a Comparator for checking if a vec2<f16> value is a valid - * bitcast conversion from i32. - */ -function bitcastI32ToVec2F16Comparator(i: number): Comparator { - const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretI32AsU32(i)).map(reinterpretU16AsF16); - // Generate expection for vec2 f16 result, by generating expected intervals for each elements and - // then do cartesian product. - const expectedIntervalsCombination = cartesianProduct( - ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals) - ); - return anyOf(...expectedIntervalsCombination); -} - -/** - * @returns a Comparator for checking if a vec2<f16> value is a valid - * bitcast conversion from f32. - */ -function bitcastF32ToVec2F16Comparator(f: number): Comparator { - // If input f32 is not finite, it can be evaluated to any value and thus any result f16 vec2 is - // possible. - if (!isFiniteF32(f)) { - return anyOf([f16UnboundedInterval, f16UnboundedInterval]); - } - const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretF32AsU32(f)).map(reinterpretU16AsF16); - // Generate expection for vec2 f16 result, by generating expected intervals for each elements and - // then do cartesian product. - const expectedIntervalsCombination = cartesianProduct( - ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals) - ); - return anyOf(...expectedIntervalsCombination); -} - -/** - * @returns a Comparator for checking if a vec4<f16> is a valid - * bitcast conversion from vec2<u32>. - */ -function bitcastVec2U32ToVec4F16Comparator(u32x2: number[]): Comparator { - assert(u32x2.length === 2); - const bitcastedVec4F16InU16x4 = u32x2.flatMap(u32ToU16x2).map(reinterpretU16AsF16); - // Generate expection for vec4 f16 result, by generating expected intervals for each elements and - // then do cartesian product. - const expectedIntervalsCombination = cartesianProduct( - ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals) - ); - return anyOf(...expectedIntervalsCombination); -} - -/** - * @returns a Comparator for checking if a vec4<f16> is a valid - * bitcast conversion from vec2<i32>. - */ -function bitcastVec2I32ToVec4F16Comparator(i32x2: number[]): Comparator { - assert(i32x2.length === 2); - const bitcastedVec4F16InU16x4 = i32x2 - .map(reinterpretI32AsU32) - .flatMap(u32ToU16x2) - .map(reinterpretU16AsF16); - // Generate expection for vec4 f16 result, by generating expected intervals for each elements and - // then do cartesian product. - const expectedIntervalsCombination = cartesianProduct( - ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals) - ); - return anyOf(...expectedIntervalsCombination); -} - -/** - * @returns a Comparator for checking if a vec4<f16> is a valid - * bitcast conversion from vec2<f32>. - */ -function bitcastVec2F32ToVec4F16Comparator(f32x2: number[]): Comparator { - assert(f32x2.length === 2); - const bitcastedVec4F16InU16x4 = f32x2 - .map(reinterpretF32AsU32) - .flatMap(u32ToU16x2) - .map(reinterpretU16AsF16); - // Generate expection for vec4 f16 result, by generating expected intervals for each elements and - // then do cartesian product. - const expectedIntervalsCombination = cartesianProduct( - ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals) - ); - return anyOf(...expectedIntervalsCombination); -} - -// Structure that store the expectations of a single 32bit scalar/element bitcasted from two f16. -interface ExpectionFor32BitsScalarFromF16x2 { - // possibleExpectations is Scalar array if the expectation is for i32/u32 and FPInterval array for - // f32. Note that if the expectation for i32/u32 is unbound, possibleExpectations is meaningless. - possibleExpectations: (Scalar | FPInterval)[]; - isUnbounded: boolean; -} - -/** - * @returns the array of possible 16bits, represented in u16, that bitcasted - * from a given finite f16 represented in u16, handling the possible subnormal - * flushing. Used to build up 32bits or larger results. - */ -function possibleBitsInU16FromFiniteF16InU16(f16InU16: number): number[] { - const h = reinterpretU16AsF16(f16InU16); - assert(isFiniteF16(h)); - return [f16InU16, ...(isSubnormalNumberF16(h) ? f16ZerosInU16 : [])]; -} - -/** - * @returns the expectation for a single 32bit scalar bitcasted from given pair of - * f16, result in ExpectionFor32BitsScalarFromF16x2. - */ -function possible32BitScalarIntervalsFromF16x2( - f16x2InU16x2: number[], - type: 'i32' | 'u32' | 'f32' -): ExpectionFor32BitsScalarFromF16x2 { - assert(f16x2InU16x2.length === 2); - let reinterpretFromU32: (x: number) => number; - let expectationsForValue: (x: number) => Scalar[] | FPInterval[]; - let unboundedExpectations: FPInterval[] | Scalar[]; - if (type === 'u32') { - reinterpretFromU32 = (x: number) => x; - expectationsForValue = x => [u32(x)]; - // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a - // placeholder, and the possibleExpectations should be ignored if the result is unbounded. - unboundedExpectations = [u32(0)]; - } else if (type === 'i32') { - reinterpretFromU32 = (x: number) => reinterpretU32AsI32(x); - expectationsForValue = x => [i32(x)]; - // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a - // placeholder, and the possibleExpectations should be ignored if the result is unbounded. - unboundedExpectations = [i32(0)]; - } else { - assert(type === 'f32'); - reinterpretFromU32 = (x: number) => reinterpretU32AsF32(x); - expectationsForValue = x => { - // Handle the possible Inf/NaN/zeros and subnormal cases for f32 result. - if (!isFiniteF32(x)) { - return [f32UnboundedInterval]; - } - // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0. - if (x === 0.0) { - return [f32ZerosInterval]; - } - const exactInterval = FP.f32.toInterval(x); - // If the casted f16 value is subnormal, it also may be flushed to +/-0.0. - return [exactInterval, ...(isSubnormalNumberF32(x) ? [f32ZerosInterval] : [])]; - }; - unboundedExpectations = [f32UnboundedInterval]; - } - // Return unbounded expection if f16 Inf/NaN occurs - if ( - !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[0])) || - !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[1])) - ) { - return { possibleExpectations: unboundedExpectations, isUnbounded: true }; - } - const possibleU16Bits = f16x2InU16x2.map(possibleBitsInU16FromFiniteF16InU16); - const possibleExpectations = cartesianProduct(...possibleU16Bits).flatMap<Scalar | FPInterval>( - (possibleBitsU16x2: readonly number[]) => { - assert(possibleBitsU16x2.length === 2); - return expectationsForValue(reinterpretFromU32(u16x2ToU32(possibleBitsU16x2))); - } - ); - return { possibleExpectations, isUnbounded: false }; -} - -/** - * @returns a Comparator for checking if a u32 value is a valid - * bitcast conversion from vec2 f16. - */ -function bitcastVec2F16ToU32Comparator(vec2F16InU16x2: number[]): Comparator { - assert(vec2F16InU16x2.length === 2); - const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'u32'); - // Return alwaysPass if result is expected unbounded. - if (expectations.isUnbounded) { - return anyU32; - } - return anyOf(...expectations.possibleExpectations); -} - -/** - * @returns a Comparator for checking if a i32 value is a valid - * bitcast conversion from vec2 f16. - */ -function bitcastVec2F16ToI32Comparator(vec2F16InU16x2: number[]): Comparator { - assert(vec2F16InU16x2.length === 2); - const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'i32'); - // Return alwaysPass if result is expected unbounded. - if (expectations.isUnbounded) { - return anyI32; - } - return anyOf(...expectations.possibleExpectations); -} - -/** - * @returns a Comparator for checking if a i32 value is a valid - * bitcast conversion from vec2 f16. - */ -function bitcastVec2F16ToF32Comparator(vec2F16InU16x2: number[]): Comparator { - assert(vec2F16InU16x2.length === 2); - const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'f32'); - // Return alwaysPass if result is expected unbounded. - if (expectations.isUnbounded) { - return anyF32; - } - return anyOf(...expectations.possibleExpectations); -} - -/** - * @returns a Comparator for checking if a vec2 u32 value is a valid - * bitcast conversion from vec4 f16. - */ -function bitcastVec4F16ToVec2U32Comparator(vec4F16InU16x4: number[]): Comparator { - assert(vec4F16InU16x4.length === 4); - const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e => - possible32BitScalarIntervalsFromF16x2(e, 'u32') - ); - // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded - // element in the result vector, currently we don't have a way to build a comparator that expect - // only one element of i32/u32 vector unbounded. - if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) { - return alwaysPass('any vec2<u32>'); - } - return anyOf( - ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map( - e => new Vector(e as Scalar[]) - ) - ); -} - -/** - * @returns a Comparator for checking if a vec2 i32 value is a valid - * bitcast conversion from vec4 f16. - */ -function bitcastVec4F16ToVec2I32Comparator(vec4F16InU16x4: number[]): Comparator { - assert(vec4F16InU16x4.length === 4); - const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e => - possible32BitScalarIntervalsFromF16x2(e, 'i32') - ); - // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded - // element in the result vector, currently we don't have a way to build a comparator that expect - // only one element of i32/u32 vector unbounded. - if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) { - return alwaysPass('any vec2<i32>'); - } - return anyOf( - ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map( - e => new Vector(e as Scalar[]) - ) - ); -} - -/** - * @returns a Comparator for checking if a vec2 f32 value is a valid - * bitcast conversion from vec4 f16. - */ -function bitcastVec4F16ToVec2F32Comparator(vec4F16InU16x4: number[]): Comparator { - assert(vec4F16InU16x4.length === 4); - const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e => - possible32BitScalarIntervalsFromF16x2(e, 'f32') - ); - return anyOf( - ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(e => [ - e[0] as FPInterval, - e[1] as FPInterval, - ]) - ); -} - -export const d = makeCaseCache('bitcast', { - // Identity Cases - i32_to_i32: () => fullI32Range().map(e => ({ input: i32(e), expected: i32(e) })), - u32_to_u32: () => fullU32Range().map(e => ({ input: u32(e), expected: u32(e) })), - f32_inf_nan_to_f32: () => - f32RangeWithInfAndNaN.map(e => ({ - input: f32(e), - expected: bitcastF32ToF32Comparator(e), - })), - f32_to_f32: () => - f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToF32Comparator(e) })), - f16_inf_nan_to_f16: () => - [...f16FiniteInF16, ...f16InfAndNaNInF16].map(e => ({ - input: f16(e), - expected: bitcastF16ToF16Comparator(e), - })), - f16_to_f16: () => - f16FiniteInF16.map(e => ({ input: f16(e), expected: bitcastF16ToF16Comparator(e) })), - - // i32,u32,f32 to different i32,u32,f32 - i32_to_u32: () => fullI32Range().map(e => ({ input: i32(e), expected: u32(e) })), - i32_to_f32: () => - i32RangeForF32Finite.map(e => ({ - input: i32(e), - expected: bitcastI32ToF32Comparator(e), - })), - i32_to_f32_inf_nan: () => - i32RangeForF32FiniteInfNaN.map(e => ({ - input: i32(e), - expected: bitcastI32ToF32Comparator(e), - })), - u32_to_i32: () => fullU32Range().map(e => ({ input: u32(e), expected: i32(e) })), - u32_to_f32: () => - u32RangeForF32Finite.map(e => ({ - input: u32(e), - expected: bitcastU32ToF32Comparator(e), - })), - u32_to_f32_inf_nan: () => - u32RangeForF32FiniteInfNaN.map(e => ({ - input: u32(e), - expected: bitcastU32ToF32Comparator(e), - })), - f32_inf_nan_to_i32: () => - f32RangeWithInfAndNaN.map(e => ({ - input: f32(e), - expected: bitcastF32ToI32Comparator(e), - })), - f32_to_i32: () => - f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToI32Comparator(e) })), - - f32_inf_nan_to_u32: () => - f32RangeWithInfAndNaN.map(e => ({ - input: f32(e), - expected: bitcastF32ToU32Comparator(e), - })), - f32_to_u32: () => - f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToU32Comparator(e) })), - - // i32,u32,f32 to vec2<f16> - u32_to_vec2_f16_inf_nan: () => - u32RangeForF16Vec2FiniteInfNaN.map(e => ({ - input: u32(e), - expected: bitcastU32ToVec2F16Comparator(e), - })), - u32_to_vec2_f16: () => - u32RangeForF16Vec2Finite.map(e => ({ - input: u32(e), - expected: bitcastU32ToVec2F16Comparator(e), - })), - i32_to_vec2_f16_inf_nan: () => - i32RangeForF16Vec2FiniteInfNaN.map(e => ({ - input: i32(e), - expected: bitcastI32ToVec2F16Comparator(e), - })), - i32_to_vec2_f16: () => - i32RangeForF16Vec2Finite.map(e => ({ - input: i32(e), - expected: bitcastI32ToVec2F16Comparator(e), - })), - f32_inf_nan_to_vec2_f16_inf_nan: () => - f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN.map(e => ({ - input: f32(e), - expected: bitcastF32ToVec2F16Comparator(e), - })), - f32_to_vec2_f16: () => - f32FiniteRangeForF16Vec2Finite.map(e => ({ - input: f32(e), - expected: bitcastF32ToVec2F16Comparator(e), - })), - - // vec2<i32>, vec2<u32>, vec2<f32> to vec4<f16> - vec2_i32_to_vec4_f16_inf_nan: () => - slidingSlice(i32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({ - input: toVector(e, i32), - expected: bitcastVec2I32ToVec4F16Comparator(e), - })), - vec2_i32_to_vec4_f16: () => - slidingSlice(i32RangeForF16Vec2Finite, 2).map(e => ({ - input: toVector(e, i32), - expected: bitcastVec2I32ToVec4F16Comparator(e), - })), - vec2_u32_to_vec4_f16_inf_nan: () => - slidingSlice(u32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({ - input: toVector(e, u32), - expected: bitcastVec2U32ToVec4F16Comparator(e), - })), - vec2_u32_to_vec4_f16: () => - slidingSlice(u32RangeForF16Vec2Finite, 2).map(e => ({ - input: toVector(e, u32), - expected: bitcastVec2U32ToVec4F16Comparator(e), - })), - vec2_f32_inf_nan_to_vec4_f16_inf_nan: () => - slidingSlice(f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN, 2).map(e => ({ - input: toVector(e, f32), - expected: bitcastVec2F32ToVec4F16Comparator(e), - })), - vec2_f32_to_vec4_f16: () => - slidingSlice(f32FiniteRangeForF16Vec2Finite, 2).map(e => ({ - input: toVector(e, f32), - expected: bitcastVec2F32ToVec4F16Comparator(e), - })), - - // vec2<f16> to i32, u32, f32 - vec2_f16_to_u32: () => - f16Vec2FiniteInU16x2.map(e => ({ - input: u16x2ToVec2F16(e), - expected: bitcastVec2F16ToU32Comparator(e), - })), - vec2_f16_inf_nan_to_u32: () => - f16Vec2FiniteInfNanInU16x2.map(e => ({ - input: u16x2ToVec2F16(e), - expected: bitcastVec2F16ToU32Comparator(e), - })), - vec2_f16_to_i32: () => - f16Vec2FiniteInU16x2.map(e => ({ - input: u16x2ToVec2F16(e), - expected: bitcastVec2F16ToI32Comparator(e), - })), - vec2_f16_inf_nan_to_i32: () => - f16Vec2FiniteInfNanInU16x2.map(e => ({ - input: u16x2ToVec2F16(e), - expected: bitcastVec2F16ToI32Comparator(e), - })), - vec2_f16_to_f32_finite: () => - f16Vec2FiniteInU16x2 - .filter(u16x2 => isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x2)))) - .map(e => ({ - input: u16x2ToVec2F16(e), - expected: bitcastVec2F16ToF32Comparator(e), - })), - vec2_f16_inf_nan_to_f32: () => - f16Vec2FiniteInfNanInU16x2.map(e => ({ - input: u16x2ToVec2F16(e), - expected: bitcastVec2F16ToF32Comparator(e), - })), - - // vec4<f16> to vec2 of i32, u32, f32 - vec4_f16_to_vec2_u32: () => - f16Vec2FiniteInU16x4.map(e => ({ - input: u16x4ToVec4F16(e), - expected: bitcastVec4F16ToVec2U32Comparator(e), - })), - vec4_f16_inf_nan_to_vec2_u32: () => - f16Vec2FiniteInfNanInU16x4.map(e => ({ - input: u16x4ToVec4F16(e), - expected: bitcastVec4F16ToVec2U32Comparator(e), - })), - vec4_f16_to_vec2_i32: () => - f16Vec2FiniteInU16x4.map(e => ({ - input: u16x4ToVec4F16(e), - expected: bitcastVec4F16ToVec2I32Comparator(e), - })), - vec4_f16_inf_nan_to_vec2_i32: () => - f16Vec2FiniteInfNanInU16x4.map(e => ({ - input: u16x4ToVec4F16(e), - expected: bitcastVec4F16ToVec2I32Comparator(e), - })), - vec4_f16_to_vec2_f32_finite: () => - f16Vec2FiniteInU16x4 - .filter( - u16x4 => - isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(0, 2)))) && - isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(2, 4)))) - ) - .map(e => ({ - input: u16x4ToVec4F16(e), - expected: bitcastVec4F16ToVec2F32Comparator(e), - })), - vec4_f16_inf_nan_to_vec2_f32: () => - f16Vec2FiniteInfNanInU16x4.map(e => ({ - input: u16x4ToVec4F16(e), - expected: bitcastVec4F16ToVec2F32Comparator(e), - })), -}); - /** * @returns a ShaderBuilder that generates a call to bitcast, * using appropriate destination type, which optionally can be @@ -865,7 +71,7 @@ g.test('i32_to_i32') ) .fn(async t => { const cases = await d.get('i32_to_i32'); - await run(t, bitcastBuilder('i32', t.params), [TypeI32], TypeI32, t.params, cases); + await run(t, bitcastBuilder('i32', t.params), [Type.i32], Type.i32, t.params, cases); }); g.test('u32_to_u32') @@ -879,7 +85,7 @@ g.test('u32_to_u32') ) .fn(async t => { const cases = await d.get('u32_to_u32'); - await run(t, bitcastBuilder('u32', t.params), [TypeU32], TypeU32, t.params, cases); + await run(t, bitcastBuilder('u32', t.params), [Type.u32], Type.u32, t.params, cases); }); g.test('f32_to_f32') @@ -896,7 +102,7 @@ g.test('f32_to_f32') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'f32_to_f32' : 'f32_inf_nan_to_f32' ); - await run(t, bitcastBuilder('f32', t.params), [TypeF32], TypeF32, t.params, cases); + await run(t, bitcastBuilder('f32', t.params), [Type.f32], Type.f32, t.params, cases); }); // To i32 from u32, f32 @@ -911,7 +117,7 @@ g.test('u32_to_i32') ) .fn(async t => { const cases = await d.get('u32_to_i32'); - await run(t, bitcastBuilder('i32', t.params), [TypeU32], TypeI32, t.params, cases); + await run(t, bitcastBuilder('i32', t.params), [Type.u32], Type.i32, t.params, cases); }); g.test('f32_to_i32') @@ -928,7 +134,7 @@ g.test('f32_to_i32') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'f32_to_i32' : 'f32_inf_nan_to_i32' ); - await run(t, bitcastBuilder('i32', t.params), [TypeF32], TypeI32, t.params, cases); + await run(t, bitcastBuilder('i32', t.params), [Type.f32], Type.i32, t.params, cases); }); // To u32 from i32, f32 @@ -943,7 +149,7 @@ g.test('i32_to_u32') ) .fn(async t => { const cases = await d.get('i32_to_u32'); - await run(t, bitcastBuilder('u32', t.params), [TypeI32], TypeU32, t.params, cases); + await run(t, bitcastBuilder('u32', t.params), [Type.i32], Type.u32, t.params, cases); }); g.test('f32_to_u32') @@ -960,7 +166,7 @@ g.test('f32_to_u32') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'f32_to_u32' : 'f32_inf_nan_to_u32' ); - await run(t, bitcastBuilder('u32', t.params), [TypeF32], TypeU32, t.params, cases); + await run(t, bitcastBuilder('u32', t.params), [Type.f32], Type.u32, t.params, cases); }); // To f32 from i32, u32 @@ -978,7 +184,7 @@ g.test('i32_to_f32') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'i32_to_f32' : 'i32_to_f32_inf_nan' ); - await run(t, bitcastBuilder('f32', t.params), [TypeI32], TypeF32, t.params, cases); + await run(t, bitcastBuilder('f32', t.params), [Type.i32], Type.f32, t.params, cases); }); g.test('u32_to_f32') @@ -995,7 +201,7 @@ g.test('u32_to_f32') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'u32_to_f32' : 'u32_to_f32_inf_nan' ); - await run(t, bitcastBuilder('f32', t.params), [TypeU32], TypeF32, t.params, cases); + await run(t, bitcastBuilder('f32', t.params), [Type.u32], Type.f32, t.params, cases); }); // 16 bit types @@ -1020,7 +226,7 @@ g.test('f16_to_f16') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'f16_to_f16' : 'f16_inf_nan_to_f16' ); - await run(t, bitcastBuilder('f16', t.params), [TypeF16], TypeF16, t.params, cases); + await run(t, bitcastBuilder('f16', t.params), [Type.f16], Type.f16, t.params, cases); }); // f16: 32-bit scalar numeric to vec2<f16> @@ -1036,14 +242,7 @@ g.test('i32_to_vec2h') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'i32_to_vec2_f16' : 'i32_to_vec2_f16_inf_nan' ); - await run( - t, - bitcastBuilder('vec2<f16>', t.params), - [TypeI32], - TypeVec(2, TypeF16), - t.params, - cases - ); + await run(t, bitcastBuilder('vec2<f16>', t.params), [Type.i32], Type.vec2h, t.params, cases); }); g.test('u32_to_vec2h') @@ -1058,14 +257,7 @@ g.test('u32_to_vec2h') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'u32_to_vec2_f16' : 'u32_to_vec2_f16_inf_nan' ); - await run( - t, - bitcastBuilder('vec2<f16>', t.params), - [TypeU32], - TypeVec(2, TypeF16), - t.params, - cases - ); + await run(t, bitcastBuilder('vec2<f16>', t.params), [Type.u32], Type.vec2h, t.params, cases); }); g.test('f32_to_vec2h') @@ -1080,14 +272,7 @@ g.test('f32_to_vec2h') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'f32_to_vec2_f16' : 'f32_inf_nan_to_vec2_f16_inf_nan' ); - await run( - t, - bitcastBuilder('vec2<f16>', t.params), - [TypeF32], - TypeVec(2, TypeF16), - t.params, - cases - ); + await run(t, bitcastBuilder('vec2<f16>', t.params), [Type.f32], Type.vec2h, t.params, cases); }); // f16: vec2<32-bit scalar numeric> to vec4<f16> @@ -1103,14 +288,7 @@ g.test('vec2i_to_vec4h') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'vec2_i32_to_vec4_f16' : 'vec2_i32_to_vec4_f16_inf_nan' ); - await run( - t, - bitcastBuilder('vec4<f16>', t.params), - [TypeVec(2, TypeI32)], - TypeVec(4, TypeF16), - t.params, - cases - ); + await run(t, bitcastBuilder('vec4<f16>', t.params), [Type.vec2i], Type.vec4h, t.params, cases); }); g.test('vec2u_to_vec4h') @@ -1125,14 +303,7 @@ g.test('vec2u_to_vec4h') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'vec2_u32_to_vec4_f16' : 'vec2_u32_to_vec4_f16_inf_nan' ); - await run( - t, - bitcastBuilder('vec4<f16>', t.params), - [TypeVec(2, TypeU32)], - TypeVec(4, TypeF16), - t.params, - cases - ); + await run(t, bitcastBuilder('vec4<f16>', t.params), [Type.vec2u], Type.vec4h, t.params, cases); }); g.test('vec2f_to_vec4h') @@ -1149,14 +320,7 @@ g.test('vec2f_to_vec4h') ? 'vec2_f32_to_vec4_f16' : 'vec2_f32_inf_nan_to_vec4_f16_inf_nan' ); - await run( - t, - bitcastBuilder('vec4<f16>', t.params), - [TypeVec(2, TypeF32)], - TypeVec(4, TypeF16), - t.params, - cases - ); + await run(t, bitcastBuilder('vec4<f16>', t.params), [Type.vec2f], Type.vec4h, t.params, cases); }); // f16: vec2<f16> to 32-bit scalar numeric @@ -1172,7 +336,7 @@ g.test('vec2h_to_i32') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'vec2_f16_to_i32' : 'vec2_f16_inf_nan_to_i32' ); - await run(t, bitcastBuilder('i32', t.params), [TypeVec(2, TypeF16)], TypeI32, t.params, cases); + await run(t, bitcastBuilder('i32', t.params), [Type.vec2h], Type.i32, t.params, cases); }); g.test('vec2h_to_u32') @@ -1187,7 +351,7 @@ g.test('vec2h_to_u32') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'vec2_f16_to_u32' : 'vec2_f16_inf_nan_to_u32' ); - await run(t, bitcastBuilder('u32', t.params), [TypeVec(2, TypeF16)], TypeU32, t.params, cases); + await run(t, bitcastBuilder('u32', t.params), [Type.vec2h], Type.u32, t.params, cases); }); g.test('vec2h_to_f32') @@ -1202,7 +366,7 @@ g.test('vec2h_to_f32') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'vec2_f16_to_f32_finite' : 'vec2_f16_inf_nan_to_f32' ); - await run(t, bitcastBuilder('f32', t.params), [TypeVec(2, TypeF16)], TypeF32, t.params, cases); + await run(t, bitcastBuilder('f32', t.params), [Type.vec2h], Type.f32, t.params, cases); }); // f16: vec4<f16> to vec2<32-bit scalar numeric> @@ -1218,14 +382,7 @@ g.test('vec4h_to_vec2i') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'vec4_f16_to_vec2_i32' : 'vec4_f16_inf_nan_to_vec2_i32' ); - await run( - t, - bitcastBuilder('vec2<i32>', t.params), - [TypeVec(4, TypeF16)], - TypeVec(2, TypeI32), - t.params, - cases - ); + await run(t, bitcastBuilder('vec2<i32>', t.params), [Type.vec4h], Type.vec2i, t.params, cases); }); g.test('vec4h_to_vec2u') @@ -1240,14 +397,7 @@ g.test('vec4h_to_vec2u') // Infinities and NaNs are errors in const-eval. t.params.inputSource === 'const' ? 'vec4_f16_to_vec2_u32' : 'vec4_f16_inf_nan_to_vec2_u32' ); - await run( - t, - bitcastBuilder('vec2<u32>', t.params), - [TypeVec(4, TypeF16)], - TypeVec(2, TypeU32), - t.params, - cases - ); + await run(t, bitcastBuilder('vec2<u32>', t.params), [Type.vec4h], Type.vec2u, t.params, cases); }); g.test('vec4h_to_vec2f') @@ -1264,12 +414,230 @@ g.test('vec4h_to_vec2f') ? 'vec4_f16_to_vec2_f32_finite' : 'vec4_f16_inf_nan_to_vec2_f32' ); + await run(t, bitcastBuilder('vec2<f32>', t.params), [Type.vec4h], Type.vec2f, t.params, cases); + }); + +// Abstract Float +g.test('af_to_f32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast abstract float to f32 tests`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = scalarF32Range().map(u => { + const res = FP['f32'].addFlushedIfNeeded([u]).map(f => { + return f32(f); + }); + return { + input: abstractFloat(u), + expected: anyOf(...res), + }; + }); + + await run(t, bitcastBuilder('f32', t.params), [Type.abstractFloat], Type.f32, t.params, cases); + }); + +g.test('af_to_i32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast abstract float to i32 tests`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const values = [ + 0, + 1, + 10, + 256, + u32Bits(0b11111111011111111111111111111111).value, + u32Bits(0b11111111010000000000000000000000).value, + u32Bits(0b11111110110000000000000000000000).value, + u32Bits(0b11111101110000000000000000000000).value, + u32Bits(0b11111011110000000000000000000000).value, + u32Bits(0b11110111110000000000000000000000).value, + u32Bits(0b11101111110000000000000000000000).value, + u32Bits(0b11011111110000000000000000000000).value, + u32Bits(0b10111111110000000000000000000000).value, + u32Bits(0b01111111011111111111111111111111).value, + u32Bits(0b01111111010000000000000000000000).value, + u32Bits(0b01111110110000000000000000000000).value, + u32Bits(0b01111101110000000000000000000000).value, + u32Bits(0b01111011110000000000000000000000).value, + u32Bits(0b01110111110000000000000000000000).value, + u32Bits(0b01101111110000000000000000000000).value, + u32Bits(0b01011111110000000000000000000000).value, + u32Bits(0b00111111110000000000000000000000).value, + ]; + + const cases = values.map(u => { + return { + input: abstractFloat(uint32ToFloat32(u)), + expected: i32(u), + }; + }); + + await run(t, bitcastBuilder('i32', t.params), [Type.abstractFloat], Type.i32, t.params, cases); + }); + +g.test('af_to_u32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast abstract float to u32 tests`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const values = [ + 0, + 1, + 10, + 256, + u32Bits(0b11111111011111111111111111111111).value, + u32Bits(0b11111111010000000000000000000000).value, + u32Bits(0b11111110110000000000000000000000).value, + u32Bits(0b11111101110000000000000000000000).value, + u32Bits(0b11111011110000000000000000000000).value, + u32Bits(0b11110111110000000000000000000000).value, + u32Bits(0b11101111110000000000000000000000).value, + u32Bits(0b11011111110000000000000000000000).value, + u32Bits(0b10111111110000000000000000000000).value, + u32Bits(0b01111111011111111111111111111111).value, + u32Bits(0b01111111010000000000000000000000).value, + u32Bits(0b01111110110000000000000000000000).value, + u32Bits(0b01111101110000000000000000000000).value, + u32Bits(0b01111011110000000000000000000000).value, + u32Bits(0b01110111110000000000000000000000).value, + u32Bits(0b01101111110000000000000000000000).value, + u32Bits(0b01011111110000000000000000000000).value, + u32Bits(0b00111111110000000000000000000000).value, + ]; + + const cases = values.map(u => { + return { + input: abstractFloat(uint32ToFloat32(u)), + expected: u32(u), + }; + }); + + await run(t, bitcastBuilder('u32', t.params), [Type.abstractFloat], Type.u32, t.params, cases); + }); + +g.test('af_to_vec2f16') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast abstract float to f16 tests`) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('af_to_vec2_f16'); + + await run( + t, + bitcastBuilder('vec2<f16>', t.params), + [Type.abstractFloat], + Type.vec2h, + t.params, + cases + ); + }); + +g.test('vec2af_to_vec4f16') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast abstract float to f16 tests`) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('vec2_af_to_vec4_f16'); + + await run( + t, + bitcastBuilder('vec4<f16>', t.params), + [Type.vec(2, Type.abstractFloat)], + Type.vec4h, + t.params, + cases + ); + }); + +// Abstract Int +g.test('ai_to_i32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast abstract int to i32 tests`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('alias', [false, true]) + ) + .fn(async t => { + const cases = await d.get('ai_to_i32'); + await run(t, bitcastBuilder('i32', t.params), [Type.abstractInt], Type.i32, t.params, cases); + }); + +g.test('ai_to_u32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast abstract int to u32 tests`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('alias', [false, true]) + ) + .fn(async t => { + const cases = await d.get('ai_to_u32'); + await run(t, bitcastBuilder('u32', t.params), [Type.abstractInt], Type.u32, t.params, cases); + }); + +g.test('ai_to_f32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast abstract int to f32 tests`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('alias', [false, true]) + ) + .fn(async t => { + const cases = await d.get('ai_to_f32'); + await run(t, bitcastBuilder('f32', t.params), [Type.abstractInt], Type.f32, t.params, cases); + }); + +g.test('ai_to_vec2h') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast ai to vec2h tests`) + .params(u => u.combine('inputSource', onlyConstInputSource).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('ai_to_vec2_f16'); await run( t, - bitcastBuilder('vec2<f32>', t.params), - [TypeVec(4, TypeF16)], - TypeVec(2, TypeF32), + bitcastBuilder('vec2<f16>', t.params), + [Type.abstractInt], + Type.vec2h, t.params, cases ); }); + +g.test('vec2ai_to_vec4h') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast vec2ai to vec4h tests`) + .params(u => u.combine('inputSource', onlyConstInputSource).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('vec2_ai_to_vec4_f16'); + await run(t, bitcastBuilder('vec4<f16>', t.params), [Type.vec2ai], Type.vec4h, t.params, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts index 282feea703..0afd8c3980 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts @@ -1,5 +1,6 @@ import { abstractFloatShaderBuilder, + abstractIntShaderBuilder, basicExpressionBuilder, basicExpressionWithPredeclarationBuilder, ShaderBuilder, @@ -11,10 +12,15 @@ export function builtin(name: string): ShaderBuilder { } /* @returns a ShaderBuilder that calls the builtin with the given name that returns AbstractFloats */ -export function abstractBuiltin(name: string): ShaderBuilder { +export function abstractFloatBuiltin(name: string): ShaderBuilder { return abstractFloatShaderBuilder(values => `${name}(${values.join(', ')})`); } +/* @returns a ShaderBuilder that calls the builtin with the given name that returns AbstractInts */ +export function abstractIntBuiltin(name: string): ShaderBuilder { + return abstractIntShaderBuilder(values => `${name}(${values.join(', ')})`); +} + /* @returns a ShaderBuilder that calls the builtin with the given name and has given predeclaration */ export function builtinWithPredeclaration(name: string, predeclaration: string): ShaderBuilder { return basicExpressionWithPredeclarationBuilder( diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.cache.ts new file mode 100644 index 0000000000..c0178d9b83 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.cache.ts @@ -0,0 +1,26 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +const kSmallMagnitudeTestValues = [0.1, 0.9, 1.0, 1.1, 1.9, -0.1, -0.9, -1.0, -1.1, -1.9]; + +// See https://github.com/gpuweb/cts/issues/2766 for details +const kIssue2766Value = { + f32: 0x8000_0000, + f16: 0x8000, + abstract: 0x8000_0000_0000_0000, +}; + +// Cases: [f32|f16] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + [...kSmallMagnitudeTestValues, kIssue2766Value[trait], ...FP[trait].scalarRange()], + 'unfiltered', + FP[trait].ceilInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('ceil', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts index 6cdf90986b..842875f094 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'ceil' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn ceil(e: T ) -> T Returns the ceiling of e. Component-wise when T is a vector. @@ -10,70 +10,33 @@ Returns the ceiling of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './ceil.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('ceil', { - f32: () => { - return FP.f32.generateScalarToIntervalCases( - [ - // Small positive numbers - 0.1, - 0.9, - 1.0, - 1.1, - 1.9, - // Small negative numbers - -0.1, - -0.9, - -1.0, - -1.1, - -1.9, - 0x80000000, // https://github.com/gpuweb/cts/issues/2766 - ...fullF32Range(), - ], - 'unfiltered', - FP.f32.ceilInterval - ); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases( - [ - // Small positive numbers - 0.1, - 0.9, - 1.0, - 1.1, - 1.9, - // Small negative numbers - -0.1, - -0.9, - -1.0, - -1.1, - -1.9, - 0x8000, // https://github.com/gpuweb/cts/issues/2766 - ...fullF16Range(), - ], - 'unfiltered', - FP.f16.ceilInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractFloatBuiltin('ceil'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -83,7 +46,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('ceil'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('ceil'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -97,5 +60,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('ceil'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('ceil'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.cache.ts new file mode 100644 index 0000000000..909d15e7e7 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.cache.ts @@ -0,0 +1,131 @@ +import { kValue } from '../../../../../util/constants.js'; +import { ScalarType, Type } from '../../../../../util/conversion.js'; +import { FP } from '../../../../../util/floating_point.js'; +import { maxBigInt, minBigInt } from '../../../../../util/math.js'; +import { Case } from '../../case.js'; +import { makeCaseCache } from '../../case_cache.js'; + +const u32Values = [0, 1, 2, 3, 0x70000000, 0x80000000, kValue.u32.max]; + +const i32Values = [ + kValue.i32.negative.min, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 0x70000000, + kValue.i32.positive.max, +]; + +const abstractFloatValues = [ + kValue.i64.negative.min, + -3n, + -2n, + -1n, + 0n, + 1n, + 2n, + 3n, + 0x70000000n, + kValue.i64.positive.max, +]; + +/** @returns a set of clamp test cases from an ascending list of concrete integer values */ +function generateConcreteIntegerTestCases( + test_values: Array<number>, + type: ScalarType, + stage: 'const' | 'non_const' +): Array<Case> { + return test_values.flatMap(low => + test_values.flatMap(high => + stage === 'const' && low > high + ? [] + : test_values.map(e => ({ + input: [type.create(e), type.create(low), type.create(high)], + expected: type.create(Math.min(Math.max(e, low), high)), + })) + ) + ); +} + +/** @returns a set of clamp test cases from an ascending list of abstract integer values */ +function generateAbstractIntegerTestCases(test_values: Array<bigint>): Array<Case> { + return test_values.flatMap(low => + test_values.flatMap(high => + low > high + ? [] + : test_values.map(e => ({ + input: [ + Type.abstractInt.create(e), + Type.abstractInt.create(low), + Type.abstractInt.create(high), + ], + expected: Type.abstractInt.create(minBigInt(maxBigInt(e, low), high)), + })) + ) + ); +} + +function generateFloatTestCases( + test_values: readonly number[], + trait: 'f32' | 'f16' | 'abstract', + stage: 'const' | 'non_const' +): Array<Case> { + return test_values.flatMap(low => + test_values.flatMap(high => + stage === 'const' && low > high + ? [] + : test_values.flatMap(e => { + const c = FP[trait].makeScalarTripleToIntervalCase( + e, + low, + high, + stage === 'const' ? 'finite' : 'unfiltered', + ...FP[trait].clampIntervals + ); + return c === undefined ? [] : [c]; + }) + ) + ); +} + +// Cases: [f32|f16|abstract]_[non_]const +// abstract_non_const is empty and unused +const fp_cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return generateFloatTestCases( + FP[trait].sparseScalarRange(), + trait, + nonConst ? 'non_const' : 'const' + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('clamp', { + u32_non_const: () => { + return generateConcreteIntegerTestCases(u32Values, Type.u32, 'non_const'); + }, + u32_const: () => { + return generateConcreteIntegerTestCases(u32Values, Type.u32, 'const'); + }, + i32_non_const: () => { + return generateConcreteIntegerTestCases(i32Values, Type.i32, 'non_const'); + }, + i32_const: () => { + return generateConcreteIntegerTestCases(i32Values, Type.i32, 'const'); + }, + abstract_int: () => { + return generateAbstractIntegerTestCases(abstractFloatValues); + }, + ...fp_cases, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts index 0113fd656f..0b524bccf0 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts @@ -1,12 +1,12 @@ export const description = ` Execution tests for the 'clamp' builtin function -S is AbstractInt, i32, or u32 +S is abstract-int, i32, or u32 T is S or vecN<S> @const fn clamp(e: T , low: T, high: T) -> T Returns min(max(e,low),high). Component-wise when T is a vector. -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const clamp(e: T , low: T , high: T) -> T Returns either min(max(e,low),high), or the median of the three values e, low, high. @@ -15,117 +15,33 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { kValue } from '../../../../../util/constants.js'; -import { - ScalarType, - TypeF32, - TypeF16, - TypeI32, - TypeU32, - TypeAbstractFloat, -} from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { sparseF32Range, sparseF16Range, sparseF64Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, Case, onlyConstInputSource, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { abstractBuiltin, builtin } from './builtin.js'; +import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js'; +import { d } from './clamp.cache.js'; export const g = makeTestGroup(GPUTest); -const u32Values = [0, 1, 2, 3, 0x70000000, 0x80000000, kValue.u32.max]; - -const i32Values = [ - kValue.i32.negative.min, - -3, - -2, - -1, - 0, - 1, - 2, - 3, - 0x70000000, - kValue.i32.positive.max, -]; - -export const d = makeCaseCache('clamp', { - u32_non_const: () => { - return generateIntegerTestCases(u32Values, TypeU32, 'non-const'); - }, - u32_const: () => { - return generateIntegerTestCases(u32Values, TypeU32, 'const'); - }, - i32_non_const: () => { - return generateIntegerTestCases(i32Values, TypeI32, 'non-const'); - }, - i32_const: () => { - return generateIntegerTestCases(i32Values, TypeI32, 'const'); - }, - f32_const: () => { - return generateFloatTestCases(sparseF32Range(), 'f32', 'const'); - }, - f32_non_const: () => { - return generateFloatTestCases(sparseF32Range(), 'f32', 'non-const'); - }, - f16_const: () => { - return generateFloatTestCases(sparseF16Range(), 'f16', 'const'); - }, - f16_non_const: () => { - return generateFloatTestCases(sparseF16Range(), 'f16', 'non-const'); - }, - abstract: () => { - return generateFloatTestCases(sparseF64Range(), 'abstract', 'const'); - }, -}); - -/** @returns a set of clamp test cases from an ascending list of integer values */ -function generateIntegerTestCases( - test_values: Array<number>, - type: ScalarType, - stage: 'const' | 'non-const' -): Array<Case> { - return test_values.flatMap(low => - test_values.flatMap(high => - stage === 'const' && low > high - ? [] - : test_values.map(e => ({ - input: [type.create(e), type.create(low), type.create(high)], - expected: type.create(Math.min(Math.max(e, low), high)), - })) - ) - ); -} - -function generateFloatTestCases( - test_values: readonly number[], - trait: 'f32' | 'f16' | 'abstract', - stage: 'const' | 'non-const' -): Array<Case> { - return test_values.flatMap(low => - test_values.flatMap(high => - stage === 'const' && low > high - ? [] - : test_values.flatMap(e => { - const c = FP[trait].makeScalarTripleToIntervalCase( - e, - low, - high, - stage === 'const' ? 'finite' : 'unfiltered', - ...FP[trait].clampIntervals - ); - return c === undefined ? [] : [c]; - }) - ) - ); -} - g.test('abstract_int') .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') .desc(`abstract int tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_int'); + await run( + t, + abstractIntBuiltin('clamp'), + [Type.abstractInt, Type.abstractInt, Type.abstractInt], + Type.abstractInt, + t.params, + cases + ); + }); g.test('u32') .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') @@ -135,7 +51,7 @@ g.test('u32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const'); - await run(t, builtin('clamp'), [TypeU32, TypeU32, TypeU32], TypeU32, t.params, cases); + await run(t, builtin('clamp'), [Type.u32, Type.u32, Type.u32], Type.u32, t.params, cases); }); g.test('i32') @@ -146,7 +62,7 @@ g.test('i32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'i32_const' : 'i32_non_const'); - await run(t, builtin('clamp'), [TypeI32, TypeI32, TypeI32], TypeI32, t.params, cases); + await run(t, builtin('clamp'), [Type.i32, Type.i32, Type.i32], Type.i32, t.params, cases); }); g.test('abstract_float') @@ -158,12 +74,12 @@ g.test('abstract_float') .combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - const cases = await d.get('abstract'); + const cases = await d.get('abstract_const'); await run( t, - abstractBuiltin('clamp'), - [TypeAbstractFloat, TypeAbstractFloat, TypeAbstractFloat], - TypeAbstractFloat, + abstractFloatBuiltin('clamp'), + [Type.abstractFloat, Type.abstractFloat, Type.abstractFloat], + Type.abstractFloat, t.params, cases ); @@ -177,7 +93,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('clamp'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases); + await run(t, builtin('clamp'), [Type.f32, Type.f32, Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -191,5 +107,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('clamp'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases); + await run(t, builtin('clamp'), [Type.f16, Type.f16, Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.cache.ts new file mode 100644 index 0000000000..11c9f50a1f --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.cache.ts @@ -0,0 +1,23 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { linearRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + [ + // Well-defined accuracy range + ...linearRange(-Math.PI, Math.PI, 100), + ...FP[trait].scalarRange(), + ], + trait === 'abstract' ? 'finite' : 'unfiltered', + // cos has an absolute accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].cosInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('cos', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts index 723bca2efd..5675f220bf 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'cos' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn cos(e: T ) -> T Returns the cosine of e. Component-wise when T is a vector. @@ -9,48 +9,33 @@ Returns the cosine of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './cos.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('cos', { - f32: () => { - return FP.f32.generateScalarToIntervalCases( - [ - // Well-defined accuracy range - ...linearRange(-Math.PI, Math.PI, 1000), - ...fullF32Range(), - ], - 'unfiltered', - FP.f32.cosInterval - ); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases( - [ - // Well-defined accuracy range - ...linearRange(-Math.PI, Math.PI, 1000), - ...fullF16Range(), - ], - 'unfiltered', - FP.f16.cosInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractFloatBuiltin('cos'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -66,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1] ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('cos'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('cos'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -80,5 +65,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('cos'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('cos'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.cache.ts new file mode 100644 index 0000000000..0c90f178df --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.cache.ts @@ -0,0 +1,23 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarToIntervalCases( + FP[trait].scalarRange(), + nonConst ? 'unfiltered' : 'finite', + // cosh has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].coshInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('cosh', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts index 37fb961c98..d043df9527 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'cosh' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn cosh(e: T ) -> T Returns the hyperbolic cosine of e. Component-wise when T is a vector @@ -9,38 +9,33 @@ Returns the hyperbolic cosine of e. Component-wise when T is a vector import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './cosh.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('cosh', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.coshInterval); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.coshInterval); - }, - f16_const: () => { - return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.coshInterval); - }, - f16_non_const: () => { - return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.coshInterval); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('cosh'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -50,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('cosh'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('cosh'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -64,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('cosh'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('cosh'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts index cfae4bb6e0..ea0c38ae58 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts @@ -12,7 +12,7 @@ Also known as "clz" in some languages. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeU32, u32Bits, u32, TypeI32, i32Bits, i32 } from '../../../../../util/conversion.js'; +import { Type, u32Bits, u32, i32Bits, i32 } from '../../../../../util/conversion.js'; import { allInputSources, Config, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -27,7 +27,7 @@ g.test('u32') ) .fn(async t => { const cfg: Config = t.params; - await run(t, builtin('countLeadingZeros'), [TypeU32], TypeU32, cfg, [ + await run(t, builtin('countLeadingZeros'), [Type.u32], Type.u32, cfg, [ // Zero { input: u32Bits(0b00000000000000000000000000000000), expected: u32(32) }, @@ -142,7 +142,7 @@ g.test('i32') ) .fn(async t => { const cfg: Config = t.params; - await run(t, builtin('countLeadingZeros'), [TypeI32], TypeI32, cfg, [ + await run(t, builtin('countLeadingZeros'), [Type.i32], Type.i32, cfg, [ // Zero { input: i32Bits(0b00000000000000000000000000000000), expected: i32(32) }, diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts index f0be916285..1937e04283 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts @@ -11,7 +11,7 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeU32, u32Bits, u32, TypeI32, i32Bits, i32 } from '../../../../../util/conversion.js'; +import { Type, u32Bits, u32, i32Bits, i32 } from '../../../../../util/conversion.js'; import { allInputSources, Config, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -26,7 +26,7 @@ g.test('u32') ) .fn(async t => { const cfg: Config = t.params; - await run(t, builtin('countOneBits'), [TypeU32], TypeU32, cfg, [ + await run(t, builtin('countOneBits'), [Type.u32], Type.u32, cfg, [ // Zero { input: u32Bits(0b00000000000000000000000000000000), expected: u32(0) }, @@ -141,7 +141,7 @@ g.test('i32') ) .fn(async t => { const cfg: Config = t.params; - await run(t, builtin('countOneBits'), [TypeI32], TypeI32, cfg, [ + await run(t, builtin('countOneBits'), [Type.i32], Type.i32, cfg, [ // Zero { input: i32Bits(0b00000000000000000000000000000000), expected: i32(0) }, diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts index d0b3198f49..3392a47810 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts @@ -12,7 +12,7 @@ Also known as "ctz" in some languages. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js'; +import { i32, i32Bits, Type, u32, u32Bits } from '../../../../../util/conversion.js'; import { allInputSources, Config, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -27,7 +27,7 @@ g.test('u32') ) .fn(async t => { const cfg: Config = t.params; - await run(t, builtin('countTrailingZeros'), [TypeU32], TypeU32, cfg, [ + await run(t, builtin('countTrailingZeros'), [Type.u32], Type.u32, cfg, [ // Zero { input: u32Bits(0b00000000000000000000000000000000), expected: u32(32) }, @@ -142,7 +142,7 @@ g.test('i32') ) .fn(async t => { const cfg: Config = t.params; - await run(t, builtin('countTrailingZeros'), [TypeI32], TypeI32, cfg, [ + await run(t, builtin('countTrailingZeros'), [Type.i32], Type.i32, cfg, [ // Zero { input: i32Bits(0b00000000000000000000000000000000), expected: i32(32) }, diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.cache.ts new file mode 100644 index 0000000000..396789b4ed --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.cache.ts @@ -0,0 +1,25 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +// abstract_non_const is empty and not used +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateVectorPairToVectorCases( + FP[trait].vectorRange(3), + FP[trait].vectorRange(3), + nonConst ? 'unfiltered' : 'finite', + // cross has an inherited accuracy, so abstract is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].crossInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('cross', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts index 2b0b3e58ce..cc32537076 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts @@ -1,77 +1,32 @@ export const description = ` Execution tests for the 'cross' builtin function -T is AbstractFloat, f32, or f16 +T is abstract-float, f32, or f16 @const fn cross(e1: vec3<T> ,e2: vec3<T>) -> vec3<T> Returns the cross product of e1 and e2. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeAbstractFloat, TypeF16, TypeF32, TypeVec } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { sparseVectorF64Range, vectorF16Range, vectorF32Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { abstractBuiltin, builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './cross.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('cross', { - f32_const: () => { - return FP.f32.generateVectorPairToVectorCases( - vectorF32Range(3), - vectorF32Range(3), - 'finite', - FP.f32.crossInterval - ); - }, - f32_non_const: () => { - return FP.f32.generateVectorPairToVectorCases( - vectorF32Range(3), - vectorF32Range(3), - 'unfiltered', - FP.f32.crossInterval - ); - }, - f16_const: () => { - return FP.f16.generateVectorPairToVectorCases( - vectorF16Range(3), - vectorF16Range(3), - 'finite', - FP.f16.crossInterval - ); - }, - f16_non_const: () => { - return FP.f16.generateVectorPairToVectorCases( - vectorF16Range(3), - vectorF16Range(3), - 'unfiltered', - FP.f16.crossInterval - ); - }, - abstract: () => { - return FP.abstract.generateVectorPairToVectorCases( - sparseVectorF64Range(3), - sparseVectorF64Range(3), - 'finite', - FP.abstract.crossInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => u.combine('inputSource', onlyConstInputSource)) .fn(async t => { - const cases = await d.get('abstract'); + const cases = await d.get('abstract_const'); await run( t, - abstractBuiltin('cross'), - [TypeVec(3, TypeAbstractFloat), TypeVec(3, TypeAbstractFloat)], - TypeVec(3, TypeAbstractFloat), + abstractFloatBuiltin('cross'), + [Type.vec(3, Type.abstractFloat), Type.vec(3, Type.abstractFloat)], + Type.vec(3, Type.abstractFloat), t.params, cases ); @@ -83,14 +38,7 @@ g.test('f32') .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run( - t, - builtin('cross'), - [TypeVec(3, TypeF32), TypeVec(3, TypeF32)], - TypeVec(3, TypeF32), - t.params, - cases - ); + await run(t, builtin('cross'), [Type.vec3f, Type.vec3f], Type.vec3f, t.params, cases); }); g.test('f16') @@ -102,12 +50,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run( - t, - builtin('cross'), - [TypeVec(3, TypeF16), TypeVec(3, TypeF16)], - TypeVec(3, TypeF16), - t.params, - cases - ); + await run(t, builtin('cross'), [Type.vec3h, Type.vec3h], Type.vec3h, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.cache.ts new file mode 100644 index 0000000000..16abe41f34 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.cache.ts @@ -0,0 +1,24 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +// abstract_non_const is empty and not used +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarToIntervalCases( + FP[trait].scalarRange(), + nonConst ? 'unfiltered' : 'finite', + // degrees has an inherited accuracy, so abstract is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].degreesInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('degrees', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts index f82153ffca..e8589c9933 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'degrees' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<T> @const fn degrees(e1: T ) -> T Converts radians to degrees, approximating e1 × 180 ÷ π. Component-wise when T is a vector @@ -9,46 +9,14 @@ Converts radians to degrees, approximating e1 × 180 ÷ π. Component-wise when import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF16Range, fullF32Range, fullF64Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { abstractBuiltin, builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './degrees.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('degrees', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.degreesInterval); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases( - fullF32Range(), - 'unfiltered', - FP.f32.degreesInterval - ); - }, - f16_const: () => { - return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.degreesInterval); - }, - f16_non_const: () => { - return FP.f16.generateScalarToIntervalCases( - fullF16Range(), - 'unfiltered', - FP.f16.degreesInterval - ); - }, - abstract: () => { - return FP.abstract.generateScalarToIntervalCases( - fullF64Range(), - 'finite', - FP.abstract.degreesInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) @@ -58,12 +26,12 @@ g.test('abstract_float') .combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - const cases = await d.get('abstract'); + const cases = await d.get('abstract_const'); await run( t, - abstractBuiltin('degrees'), - [TypeAbstractFloat], - TypeAbstractFloat, + abstractFloatBuiltin('degrees'), + [Type.abstractFloat], + Type.abstractFloat, t.params, cases ); @@ -77,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('degrees'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('degrees'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -91,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('degrees'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('degrees'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.cache.ts new file mode 100644 index 0000000000..ebd414f395 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.cache.ts @@ -0,0 +1,14 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { sparseScalarF32Range } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +export const d = makeCaseCache('derivatives', { + scalar: () => { + return FP.f32.generateScalarPairToIntervalCases( + sparseScalarF32Range(), + sparseScalarF32Range(), + 'unfiltered', + FP.f32.subtractionInterval + ); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.ts new file mode 100644 index 0000000000..2af4a48096 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.ts @@ -0,0 +1,215 @@ +import { GPUTest } from '../../../../../gpu_test.js'; +import { Type, Value } from '../../../../../util/conversion.js'; +import { Case } from '../../case.js'; +import { toComparator } from '../../expectation.js'; +import { packScalarsToVector } from '../../expression.js'; + +/** + * Run a test for a derivative builtin function. + * @param t the GPUTest + * @param cases list of test cases to run + * @param builtin the builtin function to test + * @param non_uniform_discard if true, one of each pair of invocations will discard + * @param vectorize if defined, the vector width to use (2, 3, or 4) + */ +export function runDerivativeTest( + t: GPUTest, + cases: Case[], + builtin: string, + non_uniform_discard: boolean, + vectorize?: number +) { + // If the 'vectorize' config option was provided, pack the cases into vectors. + let type: Type = Type.f32; + if (vectorize !== undefined) { + const packed = packScalarsToVector([type, type], type, cases, vectorize); + cases = packed.cases; + type = packed.resultType; + } + + //////////////////////////////////////////////////////////////// + // The two input values for a given case are distributed to two different invocations in a quad. + // We will populate a storage buffer with these input values laid out sequentially: + // [ case_0_input_1, case_0_input_0, case_1_input_1, case_1_input_0, ...] + // + // The render pipeline will be launched several times over a viewport size of (2, 2). Each draw + // call will execute a single quad (four fragment invocation), which will exercise two test cases. + // Each of these draw calls will use a different instance index, which is forwarded to the + // fragment shader. Each invocation will determine its index into the storage buffer using its + // fragment position and the instance index for that draw call. + // + // Consider two draw calls that test 4 cases (c_0, c_1, c_2, c_3). + // + // For derivatives along the 'x' direction, the mapping from fragment position to case input is: + // Quad 0: | c_0_i_1 | c_0_i_0 | Quad 1: | c_2_i_1 | c_2_i_0 | + // | c_1_i_1 | c_1_i_0 | | c_3_i_1 | c_3_i_0 | + // + // For derivatives along the 'y' direction, the mapping from fragment position to case input is: + // Quad 0: | c_0_i_1 | c_1_i_1 | Quad 1: | c_2_i_1 | c_3_i_1 | + // | c_0_i_0 | c_1_i_0 | | c_2_i_0 | c_3_i_0 | + // + //////////////////////////////////////////////////////////////// + + // Determine the direction of the derivative ('x' or 'y') from the builtin name. + const dir = builtin[3]; + + // Determine the WGSL type to use in the shader, and the stride in bytes between values. + let valueStride = 4; + let wgslType = 'f32'; + if (vectorize) { + wgslType = `vec${vectorize}f`; + valueStride = vectorize * 4; + if (vectorize === 3) { + valueStride = 16; + } + } + + // Define a vertex shader that draws a triangle over the full viewport, and a fragment shader that + // calls the derivative builtin with a value loaded from that fragment's index into the storage + // buffer (determined using the quad index and fragment position, as described above). + const code = ` +struct CaseInfo { + @builtin(position) position: vec4f, + @location(0) @interpolate(flat) quad_idx: u32, +} + +@vertex +fn vert(@builtin(vertex_index) vertex_idx: u32, + @builtin(instance_index) instance_idx: u32) -> CaseInfo { + const kVertices = array( + vec2f(-2, -2), + vec2f( 2, -2), + vec2f( 0, 2), + ); + return CaseInfo(vec4(kVertices[vertex_idx], 0, 1), instance_idx); +} + +@group(0) @binding(0) var<storage, read> inputs : array<${wgslType}>; +@group(0) @binding(1) var<storage, read_write> outputs : array<${wgslType}>; + +@fragment +fn frag(info : CaseInfo) { + let case_idx = u32(info.position.${dir === 'x' ? 'y' : 'x'}); + let inv_idx = u32(info.position.${dir}); + let index = info.quad_idx*4 + case_idx*2 + inv_idx; + let input = inputs[index]; + ${non_uniform_discard ? 'if inv_idx == 0 { discard; }' : ''} + outputs[index] = ${builtin}(input); +} +`; + + // Create the render pipeline. + const module = t.device.createShaderModule({ code }); + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + vertex: { module }, + fragment: { module, targets: [{ format: 'rgba8unorm', writeMask: 0 }] }, + }); + + // Create storage buffers to hold the inputs and outputs. + const bufferSize = cases.length * 2 * valueStride; + const inputBuffer = t.device.createBuffer({ + size: bufferSize, + usage: GPUBufferUsage.STORAGE, + mappedAtCreation: true, + }); + const outputBuffer = t.device.createBuffer({ + size: bufferSize, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + + // Populate the input storage buffer with case input values. + const valuesData = new Uint8Array(inputBuffer.getMappedRange()); + for (let i = 0; i < cases.length; i++) { + const inputs = cases[i].input as ReadonlyArray<Value>; + inputs[0].copyTo(valuesData, (i * 2 + 1) * valueStride); + inputs[1].copyTo(valuesData, i * 2 * valueStride); + } + inputBuffer.unmap(); + + // Create a bind group for the storage buffers. + const group = t.device.createBindGroup({ + entries: [ + { binding: 0, resource: { buffer: inputBuffer } }, + { binding: 1, resource: { buffer: outputBuffer } }, + ], + layout: pipeline.getBindGroupLayout(0), + }); + + // Create a texture to use as a color attachment. + // We only need this for launching the desired number of fragment invocations. + const colorAttachment = t.device.createTexture({ + size: { width: 2, height: 2 }, + format: 'rgba8unorm', + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); + + // Submit the render pass to the device. + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: colorAttachment.createView(), + loadOp: 'clear', + storeOp: 'discard', + }, + ], + }); + pass.setPipeline(pipeline); + pass.setBindGroup(0, group); + for (let quad = 0; quad < cases.length / 2; quad++) { + pass.draw(3, 1, undefined, quad); + } + pass.end(); + t.queue.submit([encoder.finish()]); + + // Check the outputs match the expected results. + t.expectGPUBufferValuesPassCheck( + outputBuffer, + (outputData: Uint8Array) => { + for (let i = 0; i < cases.length; i++) { + const c = cases[i]; + + // Both invocations involved in the derivative should get the same result. + for (let d = 0; d < 2; d++) { + if (non_uniform_discard && d === 0) { + continue; + } + + const index = (i * 2 + d) * valueStride; + const result = type.read(outputData, index); + const cmp = toComparator(c.expected).compare(result); + if (!cmp.matched) { + // If this is a coarse derivative, the implementation is also allowed to calculate only + // one of the two derivatives and return that result to all of the invocations. + if (!builtin.endsWith('Fine')) { + const c0 = cases[i % 2 === 0 ? i + 1 : i - 1]; + const cmp0 = toComparator(c0.expected).compare(result); + if (!cmp0.matched) { + return new Error(` + 1st pair: (${(c.input as Value[]).join(', ')}) + expected: ${cmp.expected} + + 2nd pair: (${(c0.input as Value[]).join(', ')}) + expected: ${cmp0.expected} + + returned: ${result}`); + } + } else { + return new Error(` + inputs: (${(c.input as Value[]).join(', ')}) + expected: ${cmp.expected} + + returned: ${result}`); + } + } + } + } + return undefined; + }, + { + type: Uint8Array, + typedLength: bufferSize, + } + ); +} diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.cache.ts new file mode 100644 index 0000000000..ccd073bce5 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.cache.ts @@ -0,0 +1,99 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Accuracy for determinant is only defined for e, where e is an integer and +// |e| < quadroot(2**21) [~38], +// due to computational complexity of calculating the general solution for 4x4, +// so custom matrices are used. +// +// Note: For 2x2 and 3x3 the limits are squareroot and cuberoot instead of +// quadroot, but using the tighter 4x4 limits for all cases for simplicity. +const kDeterminantValues = [-38, -10, -5, -1, 0, 1, 5, 10, 38]; + +const kDeterminantMatrixValues = { + 2: kDeterminantValues.map((f, idx) => [ + [idx % 4 === 0 ? f : idx, idx % 4 === 1 ? f : -idx], + [idx % 4 === 2 ? f : -idx, idx % 4 === 3 ? f : idx], + ]), + 3: kDeterminantValues.map((f, idx) => [ + [idx % 9 === 0 ? f : idx, idx % 9 === 1 ? f : -idx, idx % 9 === 2 ? f : idx], + [idx % 9 === 3 ? f : -idx, idx % 9 === 4 ? f : idx, idx % 9 === 5 ? f : -idx], + [idx % 9 === 6 ? f : idx, idx % 9 === 7 ? f : -idx, idx % 9 === 8 ? f : idx], + ]), + 4: kDeterminantValues.map((f, idx) => [ + [ + idx % 16 === 0 ? f : idx, + idx % 16 === 1 ? f : -idx, + idx % 16 === 2 ? f : idx, + idx % 16 === 3 ? f : -idx, + ], + [ + idx % 16 === 4 ? f : -idx, + idx % 16 === 5 ? f : idx, + idx % 16 === 6 ? f : -idx, + idx % 16 === 7 ? f : idx, + ], + [ + idx % 16 === 8 ? f : idx, + idx % 16 === 9 ? f : -idx, + idx % 16 === 10 ? f : idx, + idx % 16 === 11 ? f : -idx, + ], + [ + idx % 16 === 12 ? f : -idx, + idx % 16 === 13 ? f : idx, + idx % 16 === 14 ? f : -idx, + idx % 16 === 15 ? f : idx, + ], + ]), +}; + +// Cases: f32_matDxD_[non_]const +const f32_cases = ([2, 3, 4] as const) + .flatMap(dim => + ([true, false] as const).map(nonConst => ({ + [`f32_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => { + return FP.f32.generateMatrixToScalarCases( + kDeterminantMatrixValues[dim], + nonConst ? 'unfiltered' : 'finite', + FP.f32.determinantInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +// Cases: f16_matDxD_[non_]const +const f16_cases = ([2, 3, 4] as const) + .flatMap(dim => + ([true, false] as const).map(nonConst => ({ + [`f16_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => { + return FP.f16.generateMatrixToScalarCases( + kDeterminantMatrixValues[dim], + nonConst ? 'unfiltered' : 'finite', + FP.f16.determinantInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +// Cases: abstract_matDxD +const abstract_cases = ([2, 3, 4] as const) + .map(dim => ({ + [`abstract_mat${dim}x${dim}`]: () => { + return FP.abstract.generateMatrixToScalarCases( + kDeterminantMatrixValues[dim], + 'finite', + // determinant has an inherited accuracy, so abstract is only expected to be as accurate as f32 + FP.f32.determinantInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('determinant', { + ...f32_cases, + ...f16_cases, + ...abstract_cases, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts index f08f4f0b6b..638af80aca 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts @@ -1,109 +1,37 @@ export const description = ` Execution tests for the 'determinant' builtin function -T is AbstractFloat, f32, or f16 +T is abstract-float, f32, or f16 @const determinant(e: matCxC<T> ) -> T Returns the determinant of e. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16, TypeMat } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './determinant.cache.js'; export const g = makeTestGroup(GPUTest); -// Accuracy for determinant is only defined for e, where e is an integer and -// |e| < quadroot(2**21) [~38], -// due to computational complexity of calculating the general solution for 4x4, -// so custom matrices are used. -// -// Note: For 2x2 and 3x3 the limits are squareroot and cuberoot instead of -// quadroot, but using the tighter 4x4 limits for all cases for simplicity. -const kDeterminantValues = [-38, -10, -5, -1, 0, 1, 5, 10, 38]; - -const kDeterminantMatrixValues = { - 2: kDeterminantValues.map((f, idx) => [ - [idx % 4 === 0 ? f : idx, idx % 4 === 1 ? f : -idx], - [idx % 4 === 2 ? f : -idx, idx % 4 === 3 ? f : idx], - ]), - 3: kDeterminantValues.map((f, idx) => [ - [idx % 9 === 0 ? f : idx, idx % 9 === 1 ? f : -idx, idx % 9 === 2 ? f : idx], - [idx % 9 === 3 ? f : -idx, idx % 9 === 4 ? f : idx, idx % 9 === 5 ? f : -idx], - [idx % 9 === 6 ? f : idx, idx % 9 === 7 ? f : -idx, idx % 9 === 8 ? f : idx], - ]), - 4: kDeterminantValues.map((f, idx) => [ - [ - idx % 16 === 0 ? f : idx, - idx % 16 === 1 ? f : -idx, - idx % 16 === 2 ? f : idx, - idx % 16 === 3 ? f : -idx, - ], - [ - idx % 16 === 4 ? f : -idx, - idx % 16 === 5 ? f : idx, - idx % 16 === 6 ? f : -idx, - idx % 16 === 7 ? f : idx, - ], - [ - idx % 16 === 8 ? f : idx, - idx % 16 === 9 ? f : -idx, - idx % 16 === 10 ? f : idx, - idx % 16 === 11 ? f : -idx, - ], - [ - idx % 16 === 12 ? f : -idx, - idx % 16 === 13 ? f : idx, - idx % 16 === 14 ? f : -idx, - idx % 16 === 15 ? f : idx, - ], - ]), -}; - -// Cases: f32_matDxD_[non_]const -const f32_cases = ([2, 3, 4] as const) - .flatMap(dim => - ([true, false] as const).map(nonConst => ({ - [`f32_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f32.generateMatrixToScalarCases( - kDeterminantMatrixValues[dim], - nonConst ? 'unfiltered' : 'finite', - FP.f32.determinantInterval - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -// Cases: f16_matDxD_[non_]const -const f16_cases = ([2, 3, 4] as const) - .flatMap(dim => - ([true, false] as const).map(nonConst => ({ - [`f16_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f16.generateMatrixToScalarCases( - kDeterminantMatrixValues[dim], - nonConst ? 'unfiltered' : 'finite', - FP.f16.determinantInterval - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -export const d = makeCaseCache('determinant', { - ...f32_cases, - ...f16_cases, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions') .desc(`abstract float tests`) - .params(u => u.combine('inputSource', allInputSources).combine('dimension', [2, 3, 4] as const)) - .unimplemented(); + .params(u => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4] as const)) + .fn(async t => { + const dim = t.params.dim; + const cases = await d.get(`abstract_mat${dim}x${dim}`); + await run( + t, + abstractFloatBuiltin('determinant'), + [Type.mat(dim, dim, Type.abstractFloat)], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions') @@ -116,7 +44,7 @@ g.test('f32') ? `f32_mat${dim}x${dim}_const` : `f32_mat${dim}x${dim}_non_const` ); - await run(t, builtin('determinant'), [TypeMat(dim, dim, TypeF32)], TypeF32, t.params, cases); + await run(t, builtin('determinant'), [Type.mat(dim, dim, Type.f32)], Type.f32, t.params, cases); }); g.test('f16') @@ -133,5 +61,5 @@ g.test('f16') ? `f16_mat${dim}x${dim}_const` : `f16_mat${dim}x${dim}_non_const` ); - await run(t, builtin('determinant'), [TypeMat(dim, dim, TypeF16)], TypeF16, t.params, cases); + await run(t, builtin('determinant'), [Type.mat(dim, dim, Type.f16)], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.cache.ts new file mode 100644 index 0000000000..0b37190cf7 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.cache.ts @@ -0,0 +1,49 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +const scalar_cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarPairToIntervalCases( + FP[trait].scalarRange(), + FP[trait].scalarRange(), + nonConst ? 'unfiltered' : 'finite', + // distance has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].distanceInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +// Cases: [f32|f16|abstract]_vecN_[non_]const +const vec_cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([2, 3, 4] as const).flatMap(dim => + ([true, false] as const).map(nonConst => ({ + [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateVectorPairToIntervalCases( + FP[trait].sparseVectorRange(dim), + FP[trait].sparseVectorRange(dim), + nonConst ? 'unfiltered' : 'finite', + // distance has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].distanceInterval + ); + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('distance', { + ...scalar_cases, + ...vec_cases, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts index 13cddf6403..5f03c49319 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'distance' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn distance(e1: T ,e2: T ) -> f32 Returns the distance between e1 and e2 (e.g. length(e1-e2)). @@ -10,97 +10,77 @@ Returns the distance between e1 and e2 (e.g. length(e1-e2)). import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { - fullF32Range, - fullF16Range, - sparseVectorF32Range, - sparseVectorF16Range, -} from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; - -import { builtin } from './builtin.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; + +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './distance.cache.js'; export const g = makeTestGroup(GPUTest); -// Cases: f32_vecN_[non_]const -const f32_vec_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f32.generateVectorPairToIntervalCases( - sparseVectorF32Range(n), - sparseVectorF32Range(n), - nonConst ? 'unfiltered' : 'finite', - FP.f32.distanceInterval - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -// Cases: f16_vecN_[non_]const -const f16_vec_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f16.generateVectorPairToIntervalCases( - sparseVectorF16Range(n), - sparseVectorF16Range(n), - nonConst ? 'unfiltered' : 'finite', - FP.f16.distanceInterval - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -export const d = makeCaseCache('distance', { - f32_const: () => { - return FP.f32.generateScalarPairToIntervalCases( - fullF32Range(), - fullF32Range(), - 'finite', - FP.f32.distanceInterval - ); - }, - f32_non_const: () => { - return FP.f32.generateScalarPairToIntervalCases( - fullF32Range(), - fullF32Range(), - 'unfiltered', - FP.f32.distanceInterval +g.test('abstract_float') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`abstract float tests`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('distance'), + [Type.abstractFloat, Type.abstractFloat], + Type.abstractFloat, + t.params, + cases ); - }, - ...f32_vec_cases, - f16_const: () => { - return FP.f16.generateScalarPairToIntervalCases( - fullF16Range(), - fullF16Range(), - 'finite', - FP.f16.distanceInterval + }); + +g.test('abstract_float_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec2s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec2_const'); + await run( + t, + abstractFloatBuiltin('distance'), + [Type.vec2af, Type.vec2af], + Type.abstractFloat, + t.params, + cases ); - }, - f16_non_const: () => { - return FP.f16.generateScalarPairToIntervalCases( - fullF16Range(), - fullF16Range(), - 'unfiltered', - FP.f16.distanceInterval + }); + +g.test('abstract_float_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec3s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec3_const'); + await run( + t, + abstractFloatBuiltin('distance'), + [Type.vec3af, Type.vec3af], + Type.abstractFloat, + t.params, + cases ); - }, - ...f16_vec_cases, -}); + }); -g.test('abstract_float') - .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') - .desc(`abstract float tests`) - .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) - ) - .unimplemented(); +g.test('abstract_float_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec4s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec4_const'); + await run( + t, + abstractFloatBuiltin('distance'), + [Type.vec4af, Type.vec4af], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') @@ -108,7 +88,7 @@ g.test('f32') .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('distance'), [TypeF32, TypeF32], TypeF32, t.params, cases); + await run(t, builtin('distance'), [Type.f32, Type.f32], Type.f32, t.params, cases); }); g.test('f32_vec2') @@ -119,14 +99,7 @@ g.test('f32_vec2') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const' ); - await run( - t, - builtin('distance'), - [TypeVec(2, TypeF32), TypeVec(2, TypeF32)], - TypeF32, - t.params, - cases - ); + await run(t, builtin('distance'), [Type.vec2f, Type.vec2f], Type.f32, t.params, cases); }); g.test('f32_vec3') @@ -137,14 +110,7 @@ g.test('f32_vec3') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const' ); - await run( - t, - builtin('distance'), - [TypeVec(3, TypeF32), TypeVec(3, TypeF32)], - TypeF32, - t.params, - cases - ); + await run(t, builtin('distance'), [Type.vec3f, Type.vec3f], Type.f32, t.params, cases); }); g.test('f32_vec4') @@ -155,14 +121,7 @@ g.test('f32_vec4') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const' ); - await run( - t, - builtin('distance'), - [TypeVec(4, TypeF32), TypeVec(4, TypeF32)], - TypeF32, - t.params, - cases - ); + await run(t, builtin('distance'), [Type.vec4f, Type.vec4f], Type.f32, t.params, cases); }); g.test('f16') @@ -174,7 +133,7 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('distance'), [TypeF16, TypeF16], TypeF16, t.params, cases); + await run(t, builtin('distance'), [Type.f16, Type.f16], Type.f16, t.params, cases); }); g.test('f16_vec2') @@ -188,14 +147,7 @@ g.test('f16_vec2') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const' ); - await run( - t, - builtin('distance'), - [TypeVec(2, TypeF16), TypeVec(2, TypeF16)], - TypeF16, - t.params, - cases - ); + await run(t, builtin('distance'), [Type.vec2h, Type.vec2h], Type.f16, t.params, cases); }); g.test('f16_vec3') @@ -209,14 +161,7 @@ g.test('f16_vec3') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const' ); - await run( - t, - builtin('distance'), - [TypeVec(3, TypeF16), TypeVec(3, TypeF16)], - TypeF16, - t.params, - cases - ); + await run(t, builtin('distance'), [Type.vec3h, Type.vec3h], Type.f16, t.params, cases); }); g.test('f16_vec4') @@ -230,12 +175,5 @@ g.test('f16_vec4') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const' ); - await run( - t, - builtin('distance'), - [TypeVec(4, TypeF16), TypeVec(4, TypeF16)], - TypeF16, - t.params, - cases - ); + await run(t, builtin('distance'), [Type.vec4h, Type.vec4h], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.cache.ts new file mode 100644 index 0000000000..6d1f22def1 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.cache.ts @@ -0,0 +1,118 @@ +import { ROArrayArray } from '../../../../../../common/util/types.js'; +import { assert } from '../../../../../../common/util/util.js'; +import { kValue } from '../../../../../util/constants.js'; +import { FP } from '../../../../../util/floating_point.js'; +import { + calculatePermutations, + sparseVectorI32Range, + sparseVectorI64Range, + sparseVectorU32Range, + vectorI32Range, + vectorI64Range, + vectorU32Range, +} from '../../../../../util/math.js'; +import { + generateVectorVectorToI32Cases, + generateVectorVectorToI64Cases, + generateVectorVectorToU32Cases, +} from '../../case.js'; +import { makeCaseCache } from '../../case_cache.js'; + +function ai_dot(x: bigint[], y: bigint[]): bigint | undefined { + assert(x.length === y.length, 'Cannot calculate dot for vectors of different lengths'); + const multiplications = x.map((_, idx) => x[idx] * y[idx]); + if (multiplications.some(kValue.i64.isOOB)) return undefined; + + const result = multiplications.reduce((prev, curr) => prev + curr); + if (kValue.i64.isOOB(result)) return undefined; + + // The spec does not state the ordering of summation, so all the + // permutations are calculated and the intermediate results checked for + // going OOB. vec2 does not need permutations, since a + b === b + a. + // All the end results should be the same regardless of the order if the + // intermediate additions stay inbounds. + if (x.length !== 2) { + let wentOOB: boolean = false; + const permutations: ROArrayArray<bigint> = calculatePermutations(multiplications); + permutations.forEach(p => { + if (!wentOOB) { + p.reduce((prev, curr) => { + const next = prev + curr; + if (kValue.i64.isOOB(next)) { + wentOOB = true; + } + return next; + }); + } + }); + + if (wentOOB) return undefined; + } + + return !kValue.i64.isOOB(result) ? result : undefined; +} + +function ci_dot(x: number[], y: number[]): number | undefined { + assert(x.length === y.length, 'Cannot calculate dot for vectors of different lengths'); + return x.reduce((prev, _, idx) => prev + Math.imul(x[idx], y[idx]), 0); +} + +// Cases: [f32|f16|abstract]_vecN_[non_]const +const float_cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([2, 3, 4] as const).flatMap(N => + ([true, false] as const).map(nonConst => ({ + [`${trait === 'abstract' ? 'abstract_float' : trait}_vec${N}_${ + nonConst ? 'non_const' : 'const' + }`]: () => { + // Emit an empty array for not const abstract float, since they will never be run + if (trait === 'abstract' && nonConst) { + return []; + } + // vec3 and vec4 require calculating all possible permutations, so their runtime is much + // longer per test, so only using sparse vectors for them. + return FP[trait].generateVectorPairToIntervalCases( + N === 2 ? FP[trait].vectorRange(2) : FP[trait].sparseVectorRange(N), + N === 2 ? FP[trait].vectorRange(2) : FP[trait].sparseVectorRange(N), + nonConst ? 'unfiltered' : 'finite', + // dot has an inherited accuracy, so abstract is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].dotInterval + ); + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +const cases = { + ...float_cases, + abstract_int_vec2: () => { + return generateVectorVectorToI64Cases(vectorI64Range(2), vectorI64Range(2), ai_dot); + }, + abstract_int_vec3: () => { + return generateVectorVectorToI64Cases(sparseVectorI64Range(3), sparseVectorI64Range(3), ai_dot); + }, + abstract_int_vec4: () => { + return generateVectorVectorToI64Cases(sparseVectorI64Range(4), sparseVectorI64Range(4), ai_dot); + }, + i32_vec2: () => { + return generateVectorVectorToI32Cases(vectorI32Range(2), vectorI32Range(2), ci_dot); + }, + i32_vec3: () => { + return generateVectorVectorToI32Cases(sparseVectorI32Range(3), sparseVectorI32Range(3), ci_dot); + }, + i32_vec4: () => { + return generateVectorVectorToI32Cases(sparseVectorI32Range(4), sparseVectorI32Range(4), ci_dot); + }, + u32_vec2: () => { + return generateVectorVectorToU32Cases(vectorU32Range(2), vectorU32Range(2), ci_dot); + }, + u32_vec3: () => { + return generateVectorVectorToU32Cases(sparseVectorU32Range(3), sparseVectorU32Range(3), ci_dot); + }, + u32_vec4: () => { + return generateVectorVectorToU32Cases(sparseVectorU32Range(4), sparseVectorU32Range(4), ci_dot); + }, +}; + +export const d = makeCaseCache('dot', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts index 2726546183..188409bf21 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts @@ -1,87 +1,182 @@ export const description = ` Execution tests for the 'dot' builtin function -T is AbstractInt, AbstractFloat, i32, u32, f32, or f16 +T is Type.abstractInt, Type.abstractFloat, i32, u32, f32, or f16 @const fn dot(e1: vecN<T>,e2: vecN<T>) -> T Returns the dot product of e1 and e2. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { sparseVectorF32Range, vectorF32Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js'; +import { d } from './dot.cache.js'; export const g = makeTestGroup(GPUTest); -// Cases: [f32|f16]_vecN_[non_]const -const cases = (['f32', 'f16'] as const) - .flatMap(trait => - ([2, 3, 4] as const).flatMap(N => - ([true, false] as const).map(nonConst => ({ - [`${trait}_vec${N}_${nonConst ? 'non_const' : 'const'}`]: () => { - // vec3 and vec4 require calculating all possible permutations, so their runtime is much - // longer per test, so only using sparse vectors for them. - return FP[trait].generateVectorPairToIntervalCases( - N === 2 ? vectorF32Range(2) : sparseVectorF32Range(N), - N === 2 ? vectorF32Range(2) : sparseVectorF32Range(N), - nonConst ? 'unfiltered' : 'finite', - FP[trait].dotInterval - ); - }, - })) - ) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -export const d = makeCaseCache('dot', cases); - -g.test('abstract_int') - .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') - .desc(`abstract int tests`) +g.test('abstract_int_vec2') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`abstract integer tests using vec2s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_int_vec2'); + await run( + t, + abstractIntBuiltin('dot'), + [Type.vec(2, Type.abstractInt), Type.vec(2, Type.abstractInt)], + Type.abstractInt, + t.params, + cases + ); + }); + +g.test('abstract_int_vec3') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`abstract integer tests using vec3s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_int_vec3'); + await run( + t, + abstractIntBuiltin('dot'), + [Type.vec(3, Type.abstractInt), Type.vec(3, Type.abstractInt)], + Type.abstractInt, + t.params, + cases + ); + }); + +g.test('abstract_int_vec4') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`abstract integer tests using vec4s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_int_vec4'); + await run( + t, + abstractIntBuiltin('dot'), + [Type.vec(4, Type.abstractInt), Type.vec(4, Type.abstractInt)], + Type.abstractInt, + t.params, + cases + ); + }); + +g.test('i32_vec2') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`i32 tests using vec2s`) .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .fn(async t => { + const cases = await d.get('i32_vec2'); + await run(t, builtin('dot'), [Type.vec2i, Type.vec2i], Type.i32, t.params, cases); + }); -g.test('i32') +g.test('i32_vec3') .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') - .desc(`i32 tests`) + .desc(`i32 tests using vec3s`) .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .fn(async t => { + const cases = await d.get('i32_vec3'); + await run(t, builtin('dot'), [Type.vec3i, Type.vec3i], Type.i32, t.params, cases); + }); -g.test('u32') +g.test('i32_vec4') .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') - .desc(`u32 tests`) + .desc(`i32 tests using vec4s`) .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .fn(async t => { + const cases = await d.get('i32_vec4'); + await run(t, builtin('dot'), [Type.vec4i, Type.vec4i], Type.i32, t.params, cases); + }); -g.test('abstract_float') +g.test('u32_vec2') .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') - .desc(`abstract float test`) + .desc(`u32 tests using vec2s`) .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .fn(async t => { + const cases = await d.get('u32_vec2'); + await run(t, builtin('dot'), [Type.vec2u, Type.vec2u], Type.u32, t.params, cases); + }); -g.test('f32_vec2') +g.test('u32_vec3') .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') - .desc(`f32 tests using vec2s`) + .desc(`u32 tests using vec3s`) .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { - const cases = await d.get( - t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const' + const cases = await d.get('u32_vec3'); + await run(t, builtin('dot'), [Type.vec3u, Type.vec3u], Type.u32, t.params, cases); + }); + +g.test('u32_vec4') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`u32 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cases = await d.get('u32_vec4'); + await run(t, builtin('dot'), [Type.vec4u, Type.vec4u], Type.u32, t.params, cases); + }); + +g.test('abstract_float_vec2') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`abstract float tests using vec2s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_float_vec2_const'); + await run( + t, + abstractFloatBuiltin('dot'), + [Type.vec(2, Type.abstractFloat), Type.vec(2, Type.abstractFloat)], + Type.abstractFloat, + t.params, + cases ); + }); + +g.test('abstract_float_vec3') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`abstract float tests using vec3s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_float_vec3_const'); await run( t, - builtin('dot'), - [TypeVec(2, TypeF32), TypeVec(2, TypeF32)], - TypeF32, + abstractFloatBuiltin('dot'), + [Type.vec(3, Type.abstractFloat), Type.vec(3, Type.abstractFloat)], + Type.abstractFloat, t.params, cases ); }); +g.test('abstract_float_vec4') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`abstract float tests using vec4s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_float_vec4_const'); + await run( + t, + abstractFloatBuiltin('dot'), + [Type.vec(4, Type.abstractFloat), Type.vec(4, Type.abstractFloat)], + Type.abstractFloat, + t.params, + cases + ); + }); + +g.test('f32_vec2') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`f32 tests using vec2s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const' + ); + await run(t, builtin('dot'), [Type.vec2f, Type.vec2f], Type.f32, t.params, cases); + }); + g.test('f32_vec3') .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') .desc(`f32 tests using vec3s`) @@ -90,14 +185,7 @@ g.test('f32_vec3') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const' ); - await run( - t, - builtin('dot'), - [TypeVec(3, TypeF32), TypeVec(3, TypeF32)], - TypeF32, - t.params, - cases - ); + await run(t, builtin('dot'), [Type.vec3f, Type.vec3f], Type.f32, t.params, cases); }); g.test('f32_vec4') @@ -108,14 +196,7 @@ g.test('f32_vec4') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const' ); - await run( - t, - builtin('dot'), - [TypeVec(4, TypeF32), TypeVec(4, TypeF32)], - TypeF32, - t.params, - cases - ); + await run(t, builtin('dot'), [Type.vec4f, Type.vec4f], Type.f32, t.params, cases); }); g.test('f16_vec2') @@ -129,14 +210,7 @@ g.test('f16_vec2') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const' ); - await run( - t, - builtin('dot'), - [TypeVec(2, TypeF16), TypeVec(2, TypeF16)], - TypeF16, - t.params, - cases - ); + await run(t, builtin('dot'), [Type.vec2h, Type.vec2h], Type.f16, t.params, cases); }); g.test('f16_vec3') @@ -150,14 +224,7 @@ g.test('f16_vec3') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const' ); - await run( - t, - builtin('dot'), - [TypeVec(3, TypeF16), TypeVec(3, TypeF16)], - TypeF16, - t.params, - cases - ); + await run(t, builtin('dot'), [Type.vec3h, Type.vec3h], Type.f16, t.params, cases); }); g.test('f16_vec4') @@ -171,12 +238,5 @@ g.test('f16_vec4') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const' ); - await run( - t, - builtin('dot'), - [TypeVec(4, TypeF16), TypeVec(4, TypeF16)], - TypeF16, - t.params, - cases - ); + await run(t, builtin('dot'), [Type.vec4h, Type.vec4h], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4I8Packed.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4I8Packed.spec.ts new file mode 100644 index 0000000000..de537c473e --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4I8Packed.spec.ts @@ -0,0 +1,74 @@ +export const description = ` +Execution tests for the 'dot4I8Packed' builtin function + +@const fn dot4I8Packed(e1: u32 ,e2: u32) -> i32 +e1 and e2 are interpreted as vectors with four 8-bit signed integer components. Return the signed +integer dot product of these two vectors. Each component is sign-extended to i32 before performing +the multiply, and then the add operations are done in WGSL i32 with wrapping behaviour. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { Type, i32, u32 } from '../../../../../util/conversion.js'; +import { Case } from '../../case.js'; +import { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('basic') + .specURL('https://www.w3.org/TR/WGSL/#dot4I8Packed-builtin') + .desc( + ` +@const fn dot4I8Packed(e1: u32, e2: u32) -> i32 + ` + ) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cfg: Config = t.params; + + const dot4I8Packed = (e1: number, e2: number) => { + let result = 0; + for (let i = 0; i < 4; ++i) { + let e1_i = (e1 >> (i * 8)) & 0xff; + if (e1_i >= 128) { + e1_i -= 256; + } + let e2_i = (e2 >> (i * 8)) & 0xff; + if (e2_i >= 128) { + e2_i -= 256; + } + result += e1_i * e2_i; + } + return result; + }; + + const testInputs = [ + // dot({0, 0, 0, 0}, {0, 0, 0, 0}) + [0, 0], + // dot({127, 127, 127, 127}, {127, 127, 127, 127}) + [0x7f7f7f7f, 0x7f7f7f7f], + // dot({-128, -128, -128, -128}, {-128, -128, -128, -128}) + [0x80808080, 0x80808080], + // dot({127, 127, 127, 127}, {-128, -128, -128, -128}) + [0x7f7f7f7f, 0x80808080], + // dot({1, 2, 3, 4}, {5, 6, 7, 8}) + [0x01020304, 0x05060708], + // dot({1, 2, 3, 4}, {-1, -2, -3, -4}) + [0x01020304, 0xfffefdfc], + // dot({-5, -6, -7, -8}, {5, 6, 7, 8}) + [0xfbfaf9f8, 0x05060708], + // dot({-9, -10, -11, -12}, {-13, -14, -15, -16}) + [0xf7f6f5f4, 0xf3f2f1f0], + ] as const; + + const makeCase = (x: number, y: number): Case => { + return { input: [u32(x), u32(y)], expected: i32(dot4I8Packed(x, y)) }; + }; + const cases: Array<Case> = testInputs.flatMap(v => { + return [makeCase(...(v as [number, number]))]; + }); + + await run(t, builtin('dot4I8Packed'), [Type.u32, Type.u32], Type.i32, cfg, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4U8Packed.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4U8Packed.spec.ts new file mode 100644 index 0000000000..a12a3d0123 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4U8Packed.spec.ts @@ -0,0 +1,59 @@ +export const description = ` +Execution tests for the 'dot4U8Packed' builtin function + +@const fn dot4U8Packed(e1: u32 ,e2: u32) -> u32 +e1 and e2 are interpreted as vectors with four 8-bit unsigned integer components. Return the +unsigned integer dot product of these two vectors. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { Type, u32 } from '../../../../../util/conversion.js'; +import { Case } from '../../case.js'; +import { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('basic') + .specURL('https://www.w3.org/TR/WGSL/#dot4U8Packed-builtin') + .desc( + ` +@const fn dot4U8Packed(e1: u32, e2: u32) -> u32 + ` + ) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cfg: Config = t.params; + + const dot4U8Packed = (e1: number, e2: number) => { + let result = 0; + for (let i = 0; i < 4; ++i) { + const e1_i = (e1 >> (i * 8)) & 0xff; + const e2_i = (e2 >> (i * 8)) & 0xff; + result += e1_i * e2_i; + } + return result; + }; + + const testInputs = [ + // dot({0, 0, 0, 0}, {0, 0, 0, 0}) + [0, 0], + // dot({255u, 255u, 255u, 255u}, {255u, 255u, 255u, 255u}) + [0xffffffff, 0xffffffff], + // dot({1u, 2u, 3u, 4u}, {5u, 6u, 7u, 8u}) + [0x01020304, 0x05060708], + // dot({120u, 90u, 60u, 30u}, {50u, 100u, 150u, 200u}) + [0x785a3c1e, 0x326496c8], + ] as const; + + const makeCase = (x: number, y: number): Case => { + return { input: [u32(x), u32(y)], expected: u32(dot4U8Packed(x, y)) }; + }; + const cases: Array<Case> = testInputs.flatMap(v => { + return [makeCase(...(v as [number, number]))]; + }); + + await run(t, builtin('dot4U8Packed'), [Type.u32, Type.u32], Type.u32, cfg, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts index 287a51c699..d922603a9f 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts @@ -10,14 +10,22 @@ The result is the same as either dpdxFine(e) or dpdxCoarse(e). import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { allInputSources } from '../../expression.js'; + +import { d } from './derivatives.cache.js'; +import { runDerivativeTest } from './derivatives.js'; export const g = makeTestGroup(GPUTest); +const builtin = 'dpdx'; + g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions') - .desc(`f32 tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('non_uniform_discard', [false, true]) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('scalar'); + runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts index 67a75bb010..488f7e5440 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts @@ -9,14 +9,22 @@ This may result in fewer unique positions that dpdxFine(e). import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { allInputSources } from '../../expression.js'; + +import { d } from './derivatives.cache.js'; +import { runDerivativeTest } from './derivatives.js'; export const g = makeTestGroup(GPUTest); +const builtin = 'dpdxCoarse'; + g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions') - .desc(`f32 tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('non_uniform_discard', [false, true]) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('scalar'); + runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts index 91d65b990b..180aec2ec4 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts @@ -8,14 +8,22 @@ Returns the partial derivative of e with respect to window x coordinates. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { allInputSources } from '../../expression.js'; + +import { d } from './derivatives.cache.js'; +import { runDerivativeTest } from './derivatives.js'; export const g = makeTestGroup(GPUTest); +const builtin = 'dpdxFine'; + g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions') - .desc(`f32 tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('non_uniform_discard', [false, true]) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('scalar'); + runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts index 0cd9cafdb9..94df913d5c 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts @@ -9,14 +9,22 @@ The result is the same as either dpdyFine(e) or dpdyCoarse(e). import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { allInputSources } from '../../expression.js'; + +import { d } from './derivatives.cache.js'; +import { runDerivativeTest } from './derivatives.js'; export const g = makeTestGroup(GPUTest); +const builtin = 'dpdy'; + g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions') - .desc(`f32 tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('non_uniform_discard', [false, true]) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('scalar'); + runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts index f06869fdc2..2974475a6c 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts @@ -9,14 +9,22 @@ This may result in fewer unique positions that dpdyFine(e). import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { allInputSources } from '../../expression.js'; + +import { d } from './derivatives.cache.js'; +import { runDerivativeTest } from './derivatives.js'; export const g = makeTestGroup(GPUTest); +const builtin = 'dpdyCoarse'; + g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions') - .desc(`f32 test`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('non_uniform_discard', [false, true]) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('scalar'); + runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts index e09761de95..5772024cc6 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts @@ -8,14 +8,22 @@ Returns the partial derivative of e with respect to window y coordinates. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { allInputSources } from '../../expression.js'; + +import { d } from './derivatives.cache.js'; +import { runDerivativeTest } from './derivatives.js'; export const g = makeTestGroup(GPUTest); +const builtin = 'dpdyFine'; + g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions') - .desc(`f32 tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('non_uniform_discard', [false, true]) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('scalar'); + runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.cache.ts new file mode 100644 index 0000000000..5ecc8b2d97 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.cache.ts @@ -0,0 +1,44 @@ +import { kValue } from '../../../../../util/constants.js'; +import { FP } from '../../../../../util/floating_point.js'; +import { biasedRange, linearRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// floor(ln(max f32 value)) = 88, so exp(88) will be within range of a f32, but exp(89) will not +// floor(ln(max f64 value)) = 709, so exp(709) can be handled by the testing framework, but exp(710) will misbehave +const f32_inputs = [ + 0, // Returns 1 by definition + -89, // Returns subnormal value + kValue.f32.negative.min, // Closest to returning 0 as possible + ...biasedRange(kValue.f32.negative.max, -88, 100), + ...biasedRange(kValue.f32.positive.min, 88, 100), + ...linearRange(89, 709, 10), // Overflows f32, but not f64 +]; + +// floor(ln(max f16 value)) = 11, so exp(11) will be within range of a f16, but exp(12) will not +const f16_inputs = [ + 0, // Returns 1 by definition + -12, // Returns subnormal value + kValue.f16.negative.min, // Closest to returning 0 as possible + ...biasedRange(kValue.f16.negative.max, -11, 100), + ...biasedRange(kValue.f16.positive.min, 11, 100), + ...linearRange(12, 709, 10), // Overflows f16, but not f64 +]; + +export const d = makeCaseCache('exp', { + f32_const: () => { + return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.expInterval); + }, + f32_non_const: () => { + return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.expInterval); + }, + f16_const: () => { + return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.expInterval); + }, + f16_non_const: () => { + return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.expInterval); + }, + abstract: () => { + // exp has an ulp accuracy, so is only expected to be as accurate as f32 + return FP.abstract.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.expInterval); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts index 8b1ced3cab..e6bf65fe4f 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'exp' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn exp(e1: T ) -> T Returns the natural exponentiation of e1 (e.g. e^e1). Component-wise when T is a vector. @@ -9,60 +9,33 @@ Returns the natural exponentiation of e1 (e.g. e^e1). Component-wise when T is a import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { kValue } from '../../../../../util/constants.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { biasedRange, linearRange } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './exp.cache.js'; export const g = makeTestGroup(GPUTest); -// floor(ln(max f32 value)) = 88, so exp(88) will be within range of a f32, but exp(89) will not -// floor(ln(max f64 value)) = 709, so exp(709) can be handled by the testing framework, but exp(710) will misbehave -const f32_inputs = [ - 0, // Returns 1 by definition - -89, // Returns subnormal value - kValue.f32.negative.min, // Closest to returning 0 as possible - ...biasedRange(kValue.f32.negative.max, -88, 100), - ...biasedRange(kValue.f32.positive.min, 88, 100), - ...linearRange(89, 709, 10), // Overflows f32, but not f64 -]; - -// floor(ln(max f16 value)) = 11, so exp(11) will be within range of a f16, but exp(12) will not -const f16_inputs = [ - 0, // Returns 1 by definition - -12, // Returns subnormal value - kValue.f16.negative.min, // Closest to returning 0 as possible - ...biasedRange(kValue.f16.negative.max, -11, 100), - ...biasedRange(kValue.f16.positive.min, 11, 100), - ...linearRange(12, 709, 10), // Overflows f16, but not f64 -]; - -export const d = makeCaseCache('exp', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.expInterval); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.expInterval); - }, - f16_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.expInterval); - }, - f16_non_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.expInterval); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractFloatBuiltin('exp'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -72,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('exp'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('exp'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -86,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('exp'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('exp'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.cache.ts new file mode 100644 index 0000000000..e2d0f1c16c --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.cache.ts @@ -0,0 +1,44 @@ +import { kValue } from '../../../../../util/constants.js'; +import { FP } from '../../../../../util/floating_point.js'; +import { biasedRange, linearRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// floor(log2(max f32 value)) = 127, so exp2(127) will be within range of a f32, but exp2(128) will not +// floor(ln(max f64 value)) = 1023, so exp2(1023) can be handled by the testing framework, but exp2(1024) will misbehave +const f32_inputs = [ + 0, // Returns 1 by definition + -128, // Returns subnormal value + kValue.f32.negative.min, // Closest to returning 0 as possible + ...biasedRange(kValue.f32.negative.max, -127, 100), + ...biasedRange(kValue.f32.positive.min, 127, 100), + ...linearRange(128, 1023, 10), // Overflows f32, but not f64 +]; + +// floor(log2(max f16 value)) = 15, so exp2(15) will be within range of a f16, but exp2(15) will not +const f16_inputs = [ + 0, // Returns 1 by definition + -16, // Returns subnormal value + kValue.f16.negative.min, // Closest to returning 0 as possible + ...biasedRange(kValue.f16.negative.max, -15, 100), + ...biasedRange(kValue.f16.positive.min, 15, 100), + ...linearRange(16, 1023, 10), // Overflows f16, but not f64 +]; + +export const d = makeCaseCache('exp2', { + f32_const: () => { + return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.exp2Interval); + }, + f32_non_const: () => { + return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.exp2Interval); + }, + f16_const: () => { + return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.exp2Interval); + }, + f16_non_const: () => { + return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.exp2Interval); + }, + abstract: () => { + // exp2 has an ulp accuracy, so is only expected to be as accurate as f32 + return FP.abstract.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.exp2Interval); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts index 67e123cb30..f47d2e5066 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'exp2' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn exp2(e: T ) -> T Returns 2 raised to the power e (e.g. 2^e). Component-wise when T is a vector. @@ -9,60 +9,33 @@ Returns 2 raised to the power e (e.g. 2^e). Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { kValue } from '../../../../../util/constants.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { biasedRange, linearRange } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './exp2.cache.js'; export const g = makeTestGroup(GPUTest); -// floor(log2(max f32 value)) = 127, so exp2(127) will be within range of a f32, but exp2(128) will not -// floor(ln(max f64 value)) = 1023, so exp2(1023) can be handled by the testing framework, but exp2(1024) will misbehave -const f32_inputs = [ - 0, // Returns 1 by definition - -128, // Returns subnormal value - kValue.f32.negative.min, // Closest to returning 0 as possible - ...biasedRange(kValue.f32.negative.max, -127, 100), - ...biasedRange(kValue.f32.positive.min, 127, 100), - ...linearRange(128, 1023, 10), // Overflows f32, but not f64 -]; - -// floor(log2(max f16 value)) = 15, so exp2(15) will be within range of a f16, but exp2(15) will not -const f16_inputs = [ - 0, // Returns 1 by definition - -16, // Returns subnormal value - kValue.f16.negative.min, // Closest to returning 0 as possible - ...biasedRange(kValue.f16.negative.max, -15, 100), - ...biasedRange(kValue.f16.positive.min, 15, 100), - ...linearRange(16, 1023, 10), // Overflows f16, but not f64 -]; - -export const d = makeCaseCache('exp2', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.exp2Interval); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.exp2Interval); - }, - f16_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.exp2Interval); - }, - f16_non_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.exp2Interval); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractFloatBuiltin('exp2'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -72,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('exp2'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('exp2'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -86,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('exp2'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('exp2'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts index d535bf5d74..ef04b661bd 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts @@ -33,17 +33,7 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { - i32Bits, - TypeI32, - u32, - TypeU32, - u32Bits, - vec2, - vec3, - vec4, - TypeVec, -} from '../../../../../util/conversion.js'; +import { i32Bits, Type, u32, u32Bits, vec2, vec3, vec4 } from '../../../../../util/conversion.js'; import { allInputSources, Config, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -57,7 +47,7 @@ g.test('u32') .fn(async t => { const cfg: Config = t.params; - const T = t.params.width === 1 ? TypeU32 : TypeVec(t.params.width, TypeU32); + const T = t.params.width === 1 ? Type.u32 : Type.vec(t.params.width, Type.u32); const V = (x: number, y?: number, z?: number, w?: number) => { y = y === undefined ? x : y; @@ -193,7 +183,7 @@ g.test('u32') ); } - await run(t, builtin('extractBits'), [T, TypeU32, TypeU32], T, cfg, cases); + await run(t, builtin('extractBits'), [T, Type.u32, Type.u32], T, cfg, cases); }); g.test('i32') @@ -203,7 +193,7 @@ g.test('i32') .fn(async t => { const cfg: Config = t.params; - const T = t.params.width === 1 ? TypeI32 : TypeVec(t.params.width, TypeI32); + const T = t.params.width === 1 ? Type.i32 : Type.vec(t.params.width, Type.i32); const V = (x: number, y?: number, z?: number, w?: number) => { y = y === undefined ? x : y; @@ -333,5 +323,5 @@ g.test('i32') ); } - await run(t, builtin('extractBits'), [T, TypeU32, TypeU32], T, cfg, cases); + await run(t, builtin('extractBits'), [T, Type.u32, Type.u32], T, cfg, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.cache.ts new file mode 100644 index 0000000000..17a5d0fd8f --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.cache.ts @@ -0,0 +1,125 @@ +import { ROArrayArray } from '../../../../../../common/util/types.js'; +import { anyOf } from '../../../../../util/compare.js'; +import { toVector } from '../../../../../util/conversion.js'; +import { FP, FPKind, FPVector } from '../../../../../util/floating_point.js'; +import { cartesianProduct } from '../../../../../util/math.js'; +import { Case, selectNCases } from '../../case.js'; +import { makeCaseCache } from '../../case_cache.js'; +import { IntervalFilter } from '../../interval_filter.js'; + +// Using a bespoke implementation of make*Case and generate*Cases here +// since faceForwardIntervals is the only builtin with the API signature +// (vec, vec, vec) -> vec +// +// Additionally faceForward has significant complexities around it due to the +// fact that `dot` is calculated in its operation, but the result of dot isn't +// used to calculate the builtin's result. + +/** + * @returns a Case for `faceForward` + * @param argumentKind what kind of floating point numbers being operated on + * @param parameterKind what kind of floating point operation should be performed, + * should be the same as argumentKind, except for abstract + * @param x the `x` param for the case + * @param y the `y` param for the case + * @param z the `z` param for the case + * @param check what interval checking to apply + * */ +function makeCase( + argumentKind: FPKind, + parameterKind: FPKind, + x: readonly number[], + y: readonly number[], + z: readonly number[], + check: IntervalFilter +): Case | undefined { + const fp = FP[argumentKind]; + x = x.map(fp.quantize); + y = y.map(fp.quantize); + z = z.map(fp.quantize); + + const results = FP[parameterKind].faceForwardIntervals(x, y, z); + if (check === 'finite' && results.some(r => r === undefined)) { + return undefined; + } + + // Stripping the undefined results, since undefined is used to signal that an OOB + // could occur within the calculation that isn't reflected in the result + // intervals. + const define_results = results.filter((r): r is FPVector => r !== undefined); + + return { + input: [ + toVector(x, fp.scalarBuilder), + toVector(y, fp.scalarBuilder), + toVector(z, fp.scalarBuilder), + ], + expected: anyOf(...define_results), + }; +} + +/** + * @returns an array of Cases for `faceForward` + * @param argumentKind what kind of floating point numbers being operated on + * @param parameterKind what kind of floating point operation should be performed, + * should be the same as argumentKind, except for abstract + * @param xs array of inputs to try for the `x` param + * @param ys array of inputs to try for the `y` param + * @param zs array of inputs to try for the `z` param + * @param check what interval checking to apply + */ +function generateCases( + argumentKind: FPKind, + parameterKind: FPKind, + xs: ROArrayArray<number>, + ys: ROArrayArray<number>, + zs: ROArrayArray<number>, + check: IntervalFilter +): Case[] { + // Cannot use `cartesianProduct` here due to heterogeneous param types + return cartesianProduct(xs, ys, zs) + .map(e => makeCase(argumentKind, parameterKind, e[0], e[1], e[2], check)) + .filter((c): c is Case => c !== undefined); +} + +// Cases: [f32|f16|abstract]_vecN_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([2, 3, 4] as const).flatMap(dim => + ([true, false] as const).map(nonConst => ({ + [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + if (trait !== 'abstract') { + return generateCases( + trait, + trait, + FP[trait].sparseVectorRange(dim), + FP[trait].sparseVectorRange(dim), + FP[trait].sparseVectorRange(dim), + nonConst ? 'unfiltered' : 'finite' + ); + } else { + // Restricting the number of cases, because a vector of abstract floats needs to be returned, which is costly. + return selectNCases( + 'faceForward', + 20, + generateCases( + trait, + // faceForward has an inherited accuracy, so is only expected to be as accurate as f32 + 'f32', + FP[trait].sparseVectorRange(dim), + FP[trait].sparseVectorRange(dim), + FP[trait].sparseVectorRange(dim), + nonConst ? 'unfiltered' : 'finite' + ) + ); + } + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('faceForward', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts index 6b6794fb9f..096abe034f 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts @@ -1,142 +1,68 @@ export const description = ` Execution tests for the 'faceForward' builtin function -T is vecN<AbstractFloat>, vecN<f32>, or vecN<f16> +T is vecN<Type.abstractFloat>, vecN<f32>, or vecN<f16> @const fn faceForward(e1: T ,e2: T ,e3: T ) -> T Returns e1 if dot(e2,e3) is negative, and -e1 otherwise. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; -import { ROArrayArray } from '../../../../../../common/util/types.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { anyOf } from '../../../../../util/compare.js'; -import { toVector, TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js'; -import { FP, FPKind, FPVector } from '../../../../../util/floating_point.js'; -import { - cartesianProduct, - sparseVectorF32Range, - sparseVectorF16Range, -} from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, Case, IntervalFilter, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './faceForward.cache.js'; export const g = makeTestGroup(GPUTest); -// Using a bespoke implementation of make*Case and generate*Cases here -// since faceForwardIntervals is the only builtin with the API signature -// (vec, vec, vec) -> vec -// -// Additionally faceForward has significant complexities around it due to the -// fact that `dot` is calculated in it s operation, but the result of dot isn't -// used to calculate the builtin's result. - -/** - * @returns a Case for `faceForward` - * @param kind what kind of floating point numbers being operated on - * @param x the `x` param for the case - * @param y the `y` param for the case - * @param z the `z` param for the case - * @param check what interval checking to apply - * */ -function makeCase( - kind: FPKind, - x: readonly number[], - y: readonly number[], - z: readonly number[], - check: IntervalFilter -): Case | undefined { - const fp = FP[kind]; - x = x.map(fp.quantize); - y = y.map(fp.quantize); - z = z.map(fp.quantize); - - const results = FP[kind].faceForwardIntervals(x, y, z); - if (check === 'finite' && results.some(r => r === undefined)) { - return undefined; - } - - // Stripping the undefined results, since undefined is used to signal that an OOB - // could occur within the calculation that isn't reflected in the result - // intervals. - const define_results = results.filter((r): r is FPVector => r !== undefined); - - return { - input: [ - toVector(x, fp.scalarBuilder), - toVector(y, fp.scalarBuilder), - toVector(z, fp.scalarBuilder), - ], - expected: anyOf(...define_results), - }; -} - -/** - * @returns an array of Cases for `faceForward` - * @param kind what kind of floating point numbers being operated on - * @param xs array of inputs to try for the `x` param - * @param ys array of inputs to try for the `y` param - * @param zs array of inputs to try for the `z` param - * @param check what interval checking to apply - */ -function generateCases( - kind: FPKind, - xs: ROArrayArray<number>, - ys: ROArrayArray<number>, - zs: ROArrayArray<number>, - check: IntervalFilter -): Case[] { - // Cannot use `cartesianProduct` here due to heterogeneous param types - return cartesianProduct(xs, ys, zs) - .map(e => makeCase(kind, e[0], e[1], e[2], check)) - .filter((c): c is Case => c !== undefined); -} - -// Cases: f32_vecN_[non_]const -const f32_vec_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => { - return generateCases( - 'f32', - sparseVectorF32Range(n), - sparseVectorF32Range(n), - sparseVectorF32Range(n), - nonConst ? 'unfiltered' : 'finite' - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -// Cases: f16_vecN_[non_]const -const f16_vec_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => { - return generateCases( - 'f16', - sparseVectorF16Range(n), - sparseVectorF16Range(n), - sparseVectorF16Range(n), - nonConst ? 'unfiltered' : 'finite' - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); +g.test('abstract_float_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec2s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec2_const'); + await run( + t, + abstractFloatBuiltin('faceForward'), + [Type.vec2af, Type.vec2af, Type.vec2af], + Type.vec2af, + t.params, + cases + ); + }); -export const d = makeCaseCache('faceForward', { - ...f32_vec_cases, - ...f16_vec_cases, -}); +g.test('abstract_float_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec3s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec3_const'); + await run( + t, + abstractFloatBuiltin('faceForward'), + [Type.vec3af, Type.vec3af, Type.vec3af], + Type.vec3af, + t.params, + cases + ); + }); -g.test('abstract_float') - .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') - .desc(`abstract float tests`) - .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const)) - .unimplemented(); +g.test('abstract_float_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec4s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec4_const'); + await run( + t, + abstractFloatBuiltin('faceForward'), + [Type.vec4af, Type.vec4af, Type.vec4af], + Type.vec4af, + t.params, + cases + ); + }); g.test('f32_vec2') .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') @@ -149,8 +75,8 @@ g.test('f32_vec2') await run( t, builtin('faceForward'), - [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeVec(2, TypeF32)], - TypeVec(2, TypeF32), + [Type.vec2f, Type.vec2f, Type.vec2f], + Type.vec2f, t.params, cases ); @@ -167,8 +93,8 @@ g.test('f32_vec3') await run( t, builtin('faceForward'), - [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeVec(3, TypeF32)], - TypeVec(3, TypeF32), + [Type.vec3f, Type.vec3f, Type.vec3f], + Type.vec3f, t.params, cases ); @@ -185,8 +111,8 @@ g.test('f32_vec4') await run( t, builtin('faceForward'), - [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeVec(4, TypeF32)], - TypeVec(4, TypeF32), + [Type.vec4f, Type.vec4f, Type.vec4f], + Type.vec4f, t.params, cases ); @@ -206,8 +132,8 @@ g.test('f16_vec2') await run( t, builtin('faceForward'), - [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeVec(2, TypeF16)], - TypeVec(2, TypeF16), + [Type.vec2h, Type.vec2h, Type.vec2h], + Type.vec2h, t.params, cases ); @@ -227,8 +153,8 @@ g.test('f16_vec3') await run( t, builtin('faceForward'), - [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeVec(3, TypeF16)], - TypeVec(3, TypeF16), + [Type.vec3h, Type.vec3h, Type.vec3h], + Type.vec3h, t.params, cases ); @@ -248,8 +174,8 @@ g.test('f16_vec4') await run( t, builtin('faceForward'), - [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeVec(4, TypeF16)], - TypeVec(4, TypeF16), + [Type.vec4h, Type.vec4h, Type.vec4h], + Type.vec4h, t.params, cases ); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts index 26216563cd..9248b1e2bf 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts @@ -16,7 +16,7 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js'; +import { i32, i32Bits, Type, u32, u32Bits } from '../../../../../util/conversion.js'; import { allInputSources, Config, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -31,7 +31,7 @@ g.test('u32') ) .fn(async t => { const cfg: Config = t.params; - await run(t, builtin('firstLeadingBit'), [TypeU32], TypeU32, cfg, [ + await run(t, builtin('firstLeadingBit'), [Type.u32], Type.u32, cfg, [ // Zero { input: u32Bits(0b00000000000000000000000000000000), expected: u32(-1) }, @@ -146,7 +146,7 @@ g.test('i32') ) .fn(async t => { const cfg: Config = t.params; - await run(t, builtin('firstLeadingBit'), [TypeI32], TypeI32, cfg, [ + await run(t, builtin('firstLeadingBit'), [Type.i32], Type.i32, cfg, [ // Zero { input: i32Bits(0b00000000000000000000000000000000), expected: i32(-1) }, diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts index 5c65f59d28..a8dd27ee87 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts @@ -12,7 +12,7 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js'; +import { i32, i32Bits, Type, u32, u32Bits } from '../../../../../util/conversion.js'; import { allInputSources, Config, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -27,7 +27,7 @@ g.test('u32') ) .fn(async t => { const cfg: Config = t.params; - await run(t, builtin('firstTrailingBit'), [TypeU32], TypeU32, cfg, [ + await run(t, builtin('firstTrailingBit'), [Type.u32], Type.u32, cfg, [ // Zero { input: u32Bits(0b00000000000000000000000000000000), expected: u32(-1) }, @@ -142,7 +142,7 @@ g.test('i32') ) .fn(async t => { const cfg: Config = t.params; - await run(t, builtin('firstTrailingBit'), [TypeI32], TypeI32, cfg, [ + await run(t, builtin('firstTrailingBit'), [Type.i32], Type.i32, cfg, [ // Zero { input: i32Bits(0b00000000000000000000000000000000), expected: i32(-1) }, diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.cache.ts new file mode 100644 index 0000000000..0af966cfd2 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.cache.ts @@ -0,0 +1,26 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +const kSmallMagnitudeTestValues = [0.1, 0.9, 1.0, 1.1, 1.9, -0.1, -0.9, -1.0, -1.1, -1.9]; + +// See https://github.com/gpuweb/cts/issues/2766 for details +const kIssue2766Value = { + abstract: 0x8000_0000_0000_0000, + f32: 0x8000_0000, + f16: 0x8000, +}; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + [...kSmallMagnitudeTestValues, kIssue2766Value[trait], ...FP[trait].scalarRange()], + 'unfiltered', + FP[trait].floorInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('floor', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts index 873a6772c3..26cffe5d10 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'floor' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn floor(e: T ) -> T Returns the floor of e. Component-wise when T is a vector. @@ -9,54 +9,14 @@ Returns the floor of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16, TypeAbstractFloat } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range, fullF64Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { abstractBuiltin, builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './floor.cache.js'; export const g = makeTestGroup(GPUTest); -const kSmallMagnitudeTestValues = [0.1, 0.9, 1.0, 1.1, 1.9, -0.1, -0.9, -1.0, -1.1, -1.9]; - -export const d = makeCaseCache('floor', { - f32: () => { - return FP.f32.generateScalarToIntervalCases( - [ - ...kSmallMagnitudeTestValues, - ...fullF32Range(), - 0x8000_0000, // https://github.com/gpuweb/cts/issues/2766 - ], - 'unfiltered', - FP.f32.floorInterval - ); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases( - [ - ...kSmallMagnitudeTestValues, - ...fullF16Range(), - 0x8000, // https://github.com/gpuweb/cts/issues/2766 - ], - 'unfiltered', - FP.f16.floorInterval - ); - }, - abstract: () => { - return FP.abstract.generateScalarToIntervalCases( - [ - ...kSmallMagnitudeTestValues, - ...fullF64Range(), - 0x8000_0000_0000_0000, // https://github.com/gpuweb/cts/issues/2766 - ], - 'unfiltered', - FP.abstract.floorInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) @@ -67,7 +27,14 @@ g.test('abstract_float') ) .fn(async t => { const cases = await d.get('abstract'); - await run(t, abstractBuiltin('floor'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases); + await run( + t, + abstractFloatBuiltin('floor'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); }); g.test('f32') @@ -78,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('floor'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('floor'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -92,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('floor'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('floor'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.cache.ts new file mode 100644 index 0000000000..20a61ce837 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.cache.ts @@ -0,0 +1,26 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +// abstract_non_const is empty and not used +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarTripleToIntervalCases( + FP[trait].sparseScalarRange(), + FP[trait].sparseScalarRange(), + FP[trait].sparseScalarRange(), + nonConst ? 'unfiltered' : 'finite', + // fma has an inherited accuracy, so abstract is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].fmaInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('fma', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts index 701f9d7ca9..620792d420 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'fma' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn fma(e1: T ,e2: T ,e3: T ) -> T Returns e1 * e2 + e3. Component-wise when T is a vector. @@ -9,64 +9,14 @@ Returns e1 * e2 + e3. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16, TypeAbstractFloat } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { sparseF32Range, sparseF16Range, sparseF64Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { abstractBuiltin, builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './fma.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('fma', { - f32_const: () => { - return FP.f32.generateScalarTripleToIntervalCases( - sparseF32Range(), - sparseF32Range(), - sparseF32Range(), - 'finite', - FP.f32.fmaInterval - ); - }, - f32_non_const: () => { - return FP.f32.generateScalarTripleToIntervalCases( - sparseF32Range(), - sparseF32Range(), - sparseF32Range(), - 'unfiltered', - FP.f32.fmaInterval - ); - }, - f16_const: () => { - return FP.f16.generateScalarTripleToIntervalCases( - sparseF16Range(), - sparseF16Range(), - sparseF16Range(), - 'finite', - FP.f16.fmaInterval - ); - }, - f16_non_const: () => { - return FP.f16.generateScalarTripleToIntervalCases( - sparseF16Range(), - sparseF16Range(), - sparseF16Range(), - 'unfiltered', - FP.f16.fmaInterval - ); - }, - abstract: () => { - return FP.abstract.generateScalarTripleToIntervalCases( - sparseF64Range(), - sparseF64Range(), - sparseF64Range(), - 'finite', - FP.abstract.fmaInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) @@ -76,12 +26,12 @@ g.test('abstract_float') .combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - const cases = await d.get('abstract'); + const cases = await d.get('abstract_const'); await run( t, - abstractBuiltin('fma'), - [TypeAbstractFloat, TypeAbstractFloat, TypeAbstractFloat], - TypeAbstractFloat, + abstractFloatBuiltin('fma'), + [Type.abstractFloat, Type.abstractFloat, Type.abstractFloat], + Type.abstractFloat, t.params, cases ); @@ -95,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('fma'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases); + await run(t, builtin('fma'), [Type.f32, Type.f32, Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -109,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('fma'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases); + await run(t, builtin('fma'), [Type.f16, Type.f16, Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.cache.ts new file mode 100644 index 0000000000..5d0933554b --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.cache.ts @@ -0,0 +1,50 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +const kCommonValues = [ + 0.5, // 0.5 -> 0.5 + 0.9, // ~0.9 -> ~0.9 + 1, // 1 -> 0 + 2, // 2 -> 0 + 1.11, // ~1.11 -> ~0.11 + -0.1, // ~-0.1 -> ~0.9 + -0.5, // -0.5 -> 0.5 + -0.9, // ~-0.9 -> ~0.1 + -1, // -1 -> 0 + -2, // -2 -> 0 + -1.11, // ~-1.11 -> ~0.89 +]; + +const kTraitSpecificValues = { + f32: [ + 10.0001, // ~10.0001 -> ~0.0001 + -10.0001, // -10.0001 -> ~0.9999 + 0x8000_0000, // https://github.com/gpuweb/cts/issues/2766 + ], + f16: [ + 10.0078125, // 10.0078125 -> 0.0078125 + -10.0078125, // -10.0078125 -> 0.9921875 + 658.5, // 658.5 -> 0.5 + 0x8000, // https://github.com/gpuweb/cts/issues/2766 + ], + abstract: [ + 10.0001, // ~10.0001 -> ~0.0001 + -10.0001, // -10.0001 -> ~0.9999 + 0x8000_0000, // https://github.com/gpuweb/cts/issues/2766 + ], +}; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + [...kCommonValues, ...kTraitSpecificValues[trait], ...FP[trait].scalarRange()], + trait === 'abstract' ? 'finite' : 'unfiltered', + FP[trait].fractInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('fract', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts index 44ea31fde2..f1f7279c0b 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'fract' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn fract(e: T ) -> T Returns the fractional part of e, computed as e - floor(e). @@ -10,72 +10,33 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './fract.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('fract', { - f32: () => { - return FP.f32.generateScalarToIntervalCases( - [ - 0.5, // 0.5 -> 0.5 - 0.9, // ~0.9 -> ~0.9 - 1, // 1 -> 0 - 2, // 2 -> 0 - 1.11, // ~1.11 -> ~0.11 - 10.0001, // ~10.0001 -> ~0.0001 - -0.1, // ~-0.1 -> ~0.9 - -0.5, // -0.5 -> 0.5 - -0.9, // ~-0.9 -> ~0.1 - -1, // -1 -> 0 - -2, // -2 -> 0 - -1.11, // ~-1.11 -> ~0.89 - -10.0001, // -10.0001 -> ~0.9999 - 0x80000000, // https://github.com/gpuweb/cts/issues/2766 - ...fullF32Range(), - ], - 'unfiltered', - FP.f32.fractInterval - ); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases( - [ - 0.5, // 0.5 -> 0.5 - 0.9, // ~0.9 -> ~0.9 - 1, // 1 -> 0 - 2, // 2 -> 0 - 1.11, // ~1.11 -> ~0.11 - 10.0078125, // 10.0078125 -> 0.0078125 - -0.1, // ~-0.1 -> ~0.9 - -0.5, // -0.5 -> 0.5 - -0.9, // ~-0.9 -> ~0.1 - -1, // -1 -> 0 - -2, // -2 -> 0 - -1.11, // ~-1.11 -> ~0.89 - -10.0078125, // -10.0078125 -> 0.9921875 - 658.5, // 658.5 -> 0.5 - ...fullF16Range(), - ], - 'unfiltered', - FP.f16.fractInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractFloatBuiltin('fract'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -85,7 +46,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('fract'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('fract'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -99,5 +60,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('fract'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('fract'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.cache.ts new file mode 100644 index 0000000000..2211e2adc9 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.cache.ts @@ -0,0 +1,103 @@ +import { skipUndefined } from '../../../../../util/compare.js'; +import { + ScalarValue, + VectorValue, + i32, + toVector, + abstractInt, +} from '../../../../../util/conversion.js'; +import { FP } from '../../../../../util/floating_point.js'; +import { frexp } from '../../../../../util/math.js'; +import { Case } from '../../case.js'; +import { makeCaseCache } from '../../case_cache.js'; + +/* @returns a fract Case for a given scalar or vector input */ +function makeCaseFract(v: number | readonly number[], trait: 'f32' | 'f16' | 'abstract'): Case { + const fp = FP[trait]; + let toInput: (n: readonly number[]) => ScalarValue | VectorValue; + let toOutput: (n: readonly number[]) => ScalarValue | VectorValue; + if (v instanceof Array) { + // Input is vector + toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder); + toOutput = (n: readonly number[]) => toVector(n, fp.scalarBuilder); + } else { + // Input is scalar, also wrap it in an array. + v = [v]; + toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]); + toOutput = (n: readonly number[]) => fp.scalarBuilder(n[0]); + } + + v = v.map(fp.quantize); + if (v.some(e => e !== 0 && fp.isSubnormal(e))) { + return { input: toInput(v), expected: skipUndefined(undefined) }; + } + + const fs = v.map(e => { + return frexp(e, trait !== 'abstract' ? trait : 'f64').fract; + }); + + return { input: toInput(v), expected: toOutput(fs) }; +} + +/* @returns an exp Case for a given scalar or vector input */ +function makeCaseExp(v: number | readonly number[], trait: 'f32' | 'f16' | 'abstract'): Case { + const fp = FP[trait]; + let toInput: (n: readonly number[]) => ScalarValue | VectorValue; + let toOutput: (n: readonly number[]) => ScalarValue | VectorValue; + if (v instanceof Array) { + // Input is vector + toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder); + toOutput = (n: readonly number[]) => + toVector(n, trait !== 'abstract' ? i32 : (n: number) => abstractInt(BigInt(n))); + } else { + // Input is scalar, also wrap it in an array. + v = [v]; + toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]); + toOutput = (n: readonly number[]) => + trait !== 'abstract' ? i32(n[0]) : abstractInt(BigInt(n[0])); + } + + v = v.map(fp.quantize); + if (v.some(e => e !== 0 && fp.isSubnormal(e))) { + return { input: toInput(v), expected: skipUndefined(undefined) }; + } + + const fs = v.map(e => { + return frexp(e, trait !== 'abstract' ? trait : 'f64').exp; + }); + + return { input: toInput(v), expected: toOutput(fs) }; +} + +// Cases: [f32|f16]_vecN_[exp|whole] +const vec_cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([2, 3, 4] as const).flatMap(dim => + (['exp', 'fract'] as const).map(portion => ({ + [`${trait}_vec${dim}_${portion}`]: () => { + return FP[trait] + .vectorRange(dim) + .map(v => (portion === 'exp' ? makeCaseExp(v, trait) : makeCaseFract(v, trait))); + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +// Cases: [f32|f16]_[exp|whole] +const scalar_cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + (['exp', 'fract'] as const).map(portion => ({ + [`${trait}_${portion}`]: () => { + return FP[trait] + .scalarRange() + .map(v => (portion === 'exp' ? makeCaseExp(v, trait) : makeCaseFract(v, trait))); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('frexp', { + ...scalar_cases, + ...vec_cases, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts index ffe672b08c..8f07f3990d 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts @@ -15,34 +15,19 @@ The magnitude of the significand is in the range of [0.5, 1.0) or 0. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { skipUndefined } from '../../../../../util/compare.js'; -import { - i32, - Scalar, - toVector, - TypeF32, - TypeF16, - TypeI32, - TypeVec, - Vector, -} from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { - frexp, - fullF16Range, - fullF32Range, - vectorF16Range, - vectorF32Range, -} from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { + ShaderBuilder, allInputSources, basicExpressionBuilder, - Case, run, - ShaderBuilder, + abstractFloatShaderBuilder, + abstractIntShaderBuilder, + onlyConstInputSource, } from '../../expression.js'; +import { d } from './frexp.cache.js'; + export const g = makeTestGroup(GPUTest); /* @returns an ShaderBuilder that evaluates frexp and returns .fract from the result structure */ @@ -55,112 +40,159 @@ function expBuilder(): ShaderBuilder { return basicExpressionBuilder(value => `frexp(${value}).exp`); } -/* @returns a fract Case for a given scalar or vector input */ -function makeVectorCaseFract(v: number | readonly number[], trait: 'f32' | 'f16'): Case { - const fp = FP[trait]; - let toInput: (n: readonly number[]) => Scalar | Vector; - let toOutput: (n: readonly number[]) => Scalar | Vector; - if (v instanceof Array) { - // Input is vector - toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder); - toOutput = (n: readonly number[]) => toVector(n, fp.scalarBuilder); - } else { - // Input is scalar, also wrap it in an array. - v = [v]; - toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]); - toOutput = (n: readonly number[]) => fp.scalarBuilder(n[0]); - } - - v = v.map(fp.quantize); - if (v.some(e => e !== 0 && fp.isSubnormal(e))) { - return { input: toInput(v), expected: skipUndefined(undefined) }; - } - - const fs = v.map(e => { - return frexp(e, trait).fract; +/* @returns an ShaderBuilder that evaluates frexp and returns .fract from the result structure, for abstract inputs */ +function abstractFractBuilder(): ShaderBuilder { + return abstractFloatShaderBuilder(value => `frexp(${value}).fract`); +} + +/* @returns an ShaderBuilder that evaluates frexp and returns .exp from the result structure, for abstract inputs */ +function abstractExpBuilder(): ShaderBuilder { + return abstractIntShaderBuilder(value => `frexp(${value}).exp`); +} + +g.test('abstract_float_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is AbstractFloat + +struct __frexp_result_abstract { + fract : AbstractFloat, // fract part + exp : AbstractInt // exponent part +} +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_fract'); + await run(t, abstractFractBuilder(), [Type.abstractFloat], Type.abstractFloat, t.params, cases); + }); + +g.test('abstract_float_exp') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is AbstractFloat + +struct __frexp_result_abstract { + fract : AbstractFloat, // fract part + exp : AbstractInt // exponent part +} +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_exp'); + await run(t, abstractExpBuilder(), [Type.abstractFloat], Type.abstractInt, t.params, cases); + }); + +g.test('abstract_float_vec2_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec2<AbstractFloat> + +struct __frexp_result_vec2_abstract { + fract : vec2<AbstractFloat>, // fract part + exp : vec2<AbstractInt> // exponent part +} +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec2_fract'); + await run(t, abstractFractBuilder(), [Type.vec2af], Type.vec2af, t.params, cases); + }); + +g.test('abstract_float_vec2_exp') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec2<AbstractFloat> + +struct __frexp_result_vec2_abstract { + fract : vec2<AbstractFloat>, // fractional part + exp : vec2<AbstractInt> // exponent part +} +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec2_exp'); + await run(t, abstractExpBuilder(), [Type.vec2af], Type.vec2ai, t.params, cases); }); - return { input: toInput(v), expected: toOutput(fs) }; +g.test('abstract_float_vec3_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec3<AbstractFloat> + +struct __frexp_result_vec3_abstract { + fract : vec3<AbstractFloat>, // fractional part + exp : vec3<AbstractInt> // exponent part } +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec3_fract'); + await run(t, abstractFractBuilder(), [Type.vec3af], Type.vec3af, t.params, cases); + }); -/* @returns an exp Case for a given scalar or vector input */ -function makeVectorCaseExp(v: number | readonly number[], trait: 'f32' | 'f16'): Case { - const fp = FP[trait]; - let toInput: (n: readonly number[]) => Scalar | Vector; - let toOutput: (n: readonly number[]) => Scalar | Vector; - if (v instanceof Array) { - // Input is vector - toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder); - toOutput = (n: readonly number[]) => toVector(n, i32); - } else { - // Input is scalar, also wrap it in an array. - v = [v]; - toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]); - toOutput = (n: readonly number[]) => i32(n[0]); - } - - v = v.map(fp.quantize); - if (v.some(e => e !== 0 && fp.isSubnormal(e))) { - return { input: toInput(v), expected: skipUndefined(undefined) }; - } - - const fs = v.map(e => { - return frexp(e, trait).exp; +g.test('abstract_float_vec3_exp') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec3<AbstractFloat> + +struct __frexp_result_vec3_abstract { + fract : vec3<AbstractFloat>, // fractional part + exp : vec3<AbstractInt> // exponent part +} +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec3_exp'); + await run(t, abstractExpBuilder(), [Type.vec3af], Type.vec3ai, t.params, cases); }); - return { input: toInput(v), expected: toOutput(fs) }; +g.test('abstract_float_vec4_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec4<AbstractFloat> + +struct __frexp_result_vec4_abstract { + fract : vec4<AbstractFloat>, // fractional part + exp : vec4<AbstractInt> // exponent part } +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec4_fract'); + await run(t, abstractFractBuilder(), [Type.vec4af], Type.vec4af, t.params, cases); + }); + +g.test('abstract_float_vec4_exp') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec4<AbstractFloat> -export const d = makeCaseCache('frexp', { - f32_fract: () => { - return fullF32Range().map(v => makeVectorCaseFract(v, 'f32')); - }, - f32_exp: () => { - return fullF32Range().map(v => makeVectorCaseExp(v, 'f32')); - }, - f32_vec2_fract: () => { - return vectorF32Range(2).map(v => makeVectorCaseFract(v, 'f32')); - }, - f32_vec2_exp: () => { - return vectorF32Range(2).map(v => makeVectorCaseExp(v, 'f32')); - }, - f32_vec3_fract: () => { - return vectorF32Range(3).map(v => makeVectorCaseFract(v, 'f32')); - }, - f32_vec3_exp: () => { - return vectorF32Range(3).map(v => makeVectorCaseExp(v, 'f32')); - }, - f32_vec4_fract: () => { - return vectorF32Range(4).map(v => makeVectorCaseFract(v, 'f32')); - }, - f32_vec4_exp: () => { - return vectorF32Range(4).map(v => makeVectorCaseExp(v, 'f32')); - }, - f16_fract: () => { - return fullF16Range().map(v => makeVectorCaseFract(v, 'f16')); - }, - f16_exp: () => { - return fullF16Range().map(v => makeVectorCaseExp(v, 'f16')); - }, - f16_vec2_fract: () => { - return vectorF16Range(2).map(v => makeVectorCaseFract(v, 'f16')); - }, - f16_vec2_exp: () => { - return vectorF16Range(2).map(v => makeVectorCaseExp(v, 'f16')); - }, - f16_vec3_fract: () => { - return vectorF16Range(3).map(v => makeVectorCaseFract(v, 'f16')); - }, - f16_vec3_exp: () => { - return vectorF16Range(3).map(v => makeVectorCaseExp(v, 'f16')); - }, - f16_vec4_fract: () => { - return vectorF16Range(4).map(v => makeVectorCaseFract(v, 'f16')); - }, - f16_vec4_exp: () => { - return vectorF16Range(4).map(v => makeVectorCaseExp(v, 'f16')); - }, -}); +struct __frexp_result_vec4_abstract { + fract : vec4<AbstractFloat>, // fractional part + exp : vec4<AbstractInt> // exponent part +} +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec4_exp'); + await run(t, abstractExpBuilder(), [Type.vec4af], Type.vec4ai, t.params, cases); + }); g.test('f32_fract') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -177,7 +209,7 @@ struct __frexp_result_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_fract'); - await run(t, fractBuilder(), [TypeF32], TypeF32, t.params, cases); + await run(t, fractBuilder(), [Type.f32], Type.f32, t.params, cases); }); g.test('f32_exp') @@ -195,7 +227,7 @@ struct __frexp_result_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_exp'); - await run(t, expBuilder(), [TypeF32], TypeI32, t.params, cases); + await run(t, expBuilder(), [Type.f32], Type.i32, t.params, cases); }); g.test('f32_vec2_fract') @@ -213,7 +245,7 @@ struct __frexp_result_vec2_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_vec2_fract'); - await run(t, fractBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases); + await run(t, fractBuilder(), [Type.vec2f], Type.vec2f, t.params, cases); }); g.test('f32_vec2_exp') @@ -231,7 +263,7 @@ struct __frexp_result_vec2_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_vec2_exp'); - await run(t, expBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeI32), t.params, cases); + await run(t, expBuilder(), [Type.vec2f], Type.vec2i, t.params, cases); }); g.test('f32_vec3_fract') @@ -249,7 +281,7 @@ struct __frexp_result_vec3_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_vec3_fract'); - await run(t, fractBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases); + await run(t, fractBuilder(), [Type.vec3f], Type.vec3f, t.params, cases); }); g.test('f32_vec3_exp') @@ -267,7 +299,7 @@ struct __frexp_result_vec3_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_vec3_exp'); - await run(t, expBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeI32), t.params, cases); + await run(t, expBuilder(), [Type.vec3f], Type.vec3i, t.params, cases); }); g.test('f32_vec4_fract') @@ -285,7 +317,7 @@ struct __frexp_result_vec4_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_vec4_fract'); - await run(t, fractBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases); + await run(t, fractBuilder(), [Type.vec4f], Type.vec4f, t.params, cases); }); g.test('f32_vec4_exp') @@ -303,7 +335,7 @@ struct __frexp_result_vec4_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_vec4_exp'); - await run(t, expBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeI32), t.params, cases); + await run(t, expBuilder(), [Type.vec4f], Type.vec4i, t.params, cases); }); g.test('f16_fract') @@ -324,7 +356,7 @@ struct __frexp_result_f16 { }) .fn(async t => { const cases = await d.get('f16_fract'); - await run(t, fractBuilder(), [TypeF16], TypeF16, t.params, cases); + await run(t, fractBuilder(), [Type.f16], Type.f16, t.params, cases); }); g.test('f16_exp') @@ -345,7 +377,7 @@ struct __frexp_result_f16 { }) .fn(async t => { const cases = await d.get('f16_exp'); - await run(t, expBuilder(), [TypeF16], TypeI32, t.params, cases); + await run(t, expBuilder(), [Type.f16], Type.i32, t.params, cases); }); g.test('f16_vec2_fract') @@ -366,7 +398,7 @@ struct __frexp_result_vec2_f16 { }) .fn(async t => { const cases = await d.get('f16_vec2_fract'); - await run(t, fractBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases); + await run(t, fractBuilder(), [Type.vec2h], Type.vec2h, t.params, cases); }); g.test('f16_vec2_exp') @@ -387,7 +419,7 @@ struct __frexp_result_vec2_f16 { }) .fn(async t => { const cases = await d.get('f16_vec2_exp'); - await run(t, expBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeI32), t.params, cases); + await run(t, expBuilder(), [Type.vec2h], Type.vec2i, t.params, cases); }); g.test('f16_vec3_fract') @@ -408,7 +440,7 @@ struct __frexp_result_vec3_f16 { }) .fn(async t => { const cases = await d.get('f16_vec3_fract'); - await run(t, fractBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases); + await run(t, fractBuilder(), [Type.vec3h], Type.vec3h, t.params, cases); }); g.test('f16_vec3_exp') @@ -429,7 +461,7 @@ struct __frexp_result_vec3_f16 { }) .fn(async t => { const cases = await d.get('f16_vec3_exp'); - await run(t, expBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeI32), t.params, cases); + await run(t, expBuilder(), [Type.vec3h], Type.vec3i, t.params, cases); }); g.test('f16_vec4_fract') @@ -450,7 +482,7 @@ struct __frexp_result_vec4_f16 { }) .fn(async t => { const cases = await d.get('f16_vec4_fract'); - await run(t, fractBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases); + await run(t, fractBuilder(), [Type.vec4h], Type.vec4h, t.params, cases); }); g.test('f16_vec4_exp') @@ -471,5 +503,5 @@ struct __frexp_result_vec4_f16 { }) .fn(async t => { const cases = await d.get('f16_vec4_exp'); - await run(t, expBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeI32), t.params, cases); + await run(t, expBuilder(), [Type.vec4h], Type.vec4i, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts index 1068e76252..b3eb65781d 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts @@ -18,17 +18,7 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { - i32Bits, - TypeI32, - u32, - TypeU32, - u32Bits, - vec2, - vec3, - vec4, - TypeVec, -} from '../../../../../util/conversion.js'; +import { i32Bits, Type, u32, u32Bits, vec2, vec3, vec4 } from '../../../../../util/conversion.js'; import { allInputSources, Config, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -46,8 +36,8 @@ g.test('integer') ) .fn(async t => { const cfg: Config = t.params; - const scalarType = t.params.signed ? TypeI32 : TypeU32; - const T = t.params.width === 1 ? scalarType : TypeVec(t.params.width, scalarType); + const scalarType = t.params.signed ? Type.i32 : Type.u32; + const T = t.params.width === 1 ? scalarType : Type.vec(t.params.width, scalarType); const V = (x: number, y?: number, z?: number, w?: number) => { y = y === undefined ? x : y; @@ -382,5 +372,5 @@ g.test('integer') ); } - await run(t, builtin('insertBits'), [T, T, TypeU32, TypeU32], T, cfg, cases); + await run(t, builtin('insertBits'), [T, T, Type.u32, Type.u32], T, cfg, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.cache.ts new file mode 100644 index 0000000000..e50d6d4866 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.cache.ts @@ -0,0 +1,44 @@ +import { kValue } from '../../../../../util/constants.js'; +import { FP } from '../../../../../util/floating_point.js'; +import { biasedRange, linearRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +export const d = makeCaseCache('inverseSqrt', { + f32: () => { + return FP.f32.generateScalarToIntervalCases( + [ + // 0 < x <= 1 linearly spread + ...linearRange(kValue.f32.positive.min, 1, 100), + // 1 <= x < 2^32, biased towards 1 + ...biasedRange(1, 2 ** 32, 1000), + ], + 'unfiltered', + FP.f32.inverseSqrtInterval + ); + }, + f16: () => { + return FP.f16.generateScalarToIntervalCases( + [ + // 0 < x <= 1 linearly spread + ...linearRange(kValue.f16.positive.min, 1, 100), + // 1 <= x < 2^15, biased towards 1 + ...biasedRange(1, 2 ** 15, 1000), + ], + 'unfiltered', + FP.f16.inverseSqrtInterval + ); + }, + abstract: () => { + return FP.abstract.generateScalarToIntervalCases( + [ + // 0 < x <= 1 linearly spread + ...linearRange(kValue.f64.positive.min, 1, 100), + // 1 <= x < 2^64, biased towards 1, only using 100 steps, because af tests are more expensive per case + ...biasedRange(1, 2 ** 64, 100), + ], + 'finite', + // inverseSqrt has an ulp accuracy, so is only expected to be as accurate as f32 + FP.f32.inverseSqrtInterval + ); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts index 3e83816387..954718de6c 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'inverseSqrt' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn inverseSqrt(e: T ) -> T Returns the reciprocal of sqrt(e). Component-wise when T is a vector. @@ -9,51 +9,33 @@ Returns the reciprocal of sqrt(e). Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { kValue } from '../../../../../util/constants.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { biasedRange, linearRange } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './inversesqrt.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('inverseSqrt', { - f32: () => { - return FP.f32.generateScalarToIntervalCases( - [ - // 0 < x <= 1 linearly spread - ...linearRange(kValue.f32.positive.min, 1, 100), - // 1 <= x < 2^32, biased towards 1 - ...biasedRange(1, 2 ** 32, 1000), - ], - 'unfiltered', - FP.f32.inverseSqrtInterval - ); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases( - [ - // 0 < x <= 1 linearly spread - ...linearRange(kValue.f16.positive.min, 1, 100), - // 1 <= x < 2^15, biased towards 1 - ...biasedRange(1, 2 ** 15, 1000), - ], - 'unfiltered', - FP.f16.inverseSqrtInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractFloatBuiltin('inverseSqrt'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -63,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('inverseSqrt'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('inverseSqrt'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -77,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('inverseSqrt'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('inverseSqrt'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.cache.ts new file mode 100644 index 0000000000..cf9194319b --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.cache.ts @@ -0,0 +1,61 @@ +import { assert } from '../../../../../../common/util/util.js'; +import { anyOf } from '../../../../../util/compare.js'; +import { abstractInt, i32 } from '../../../../../util/conversion.js'; +import { FP } from '../../../../../util/floating_point.js'; +import { biasedRange, quantizeToI32, sparseI32Range } from '../../../../../util/math.js'; +import { Case } from '../../case.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// ldexpInterval's return interval doesn't cover the flush-to-zero cases when e2 + bias <= 0, thus +// special examination is required. +// See the comment block on ldexpInterval for more details +// e2 is an integer (i32) while e1 is float. +const makeCase = (trait: 'f32' | 'f16' | 'abstract', e1: number, e2: number): Case => { + const FPTrait = FP[trait]; + e1 = FPTrait.quantize(e1); + // e2 should be in i32 range for the convenience. + assert(-2147483648 <= e2 && e2 <= 2147483647, 'e2 should be in i32 range'); + e2 = quantizeToI32(e2); + + const expected = FPTrait.ldexpInterval(e1, e2); + + const e2_scalar = trait === 'abstract' ? abstractInt(BigInt(e2)) : i32(e2); + // Result may be zero if e2 + bias <= 0 + if (e2 + FPTrait.constants().bias <= 0) { + return { + input: [FPTrait.scalarBuilder(e1), e2_scalar], + expected: anyOf(expected, FPTrait.constants().zeroInterval), + }; + } + + return { input: [FPTrait.scalarBuilder(e1), e2_scalar], expected }; +}; + +// Cases: [f32|f16|abstract]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (nonConst) { + if (trait === 'abstract') { + return []; + } + return FP[trait] + .sparseScalarRange() + .flatMap(e1 => sparseI32Range().map(e2 => makeCase(trait, e1, e2))); + } + const bias = FP[trait].constants().bias; + // const + return FP[trait] + .sparseScalarRange() + .flatMap(e1 => + biasedRange(-bias - 10, bias + 1, 10).flatMap(e2 => + FP[trait].isFinite(e1 * 2 ** quantizeToI32(e2)) ? makeCase(trait, e1, e2) : [] + ) + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('ldexp', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts index 3829867752..d6cebfef6d 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts @@ -1,10 +1,10 @@ export const description = ` Execution tests for the 'ldexp' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> -K is AbstractInt, i32 +K is Type.abstractInt, i32 I is K or vecN<K>, where I is a scalar if T is a scalar, or a vector when T is a vector @@ -13,77 +13,15 @@ Returns e1 * 2^e2. Component-wise when T is a vector. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; -import { assert } from '../../../../../../common/util/util.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { anyOf } from '../../../../../util/compare.js'; -import { i32, TypeF32, TypeF16, TypeI32 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { - biasedRange, - quantizeToI32, - sparseF32Range, - sparseI32Range, - sparseF16Range, -} from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, Case, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './ldexp.cache.js'; export const g = makeTestGroup(GPUTest); -const bias = { - f32: 127, - f16: 15, -} as const; - -// ldexpInterval's return interval doesn't cover the flush-to-zero cases when e2 + bias <= 0, thus -// special examination is required. -// See the comment block on ldexpInterval for more details -// e2 is an integer (i32) while e1 is float. -const makeCase = (trait: 'f32' | 'f16', e1: number, e2: number): Case => { - const FPTrait = FP[trait]; - e1 = FPTrait.quantize(e1); - // e2 should be in i32 range for the convinience. - assert(-2147483648 <= e2 && e2 <= 2147483647, 'e2 should be in i32 range'); - e2 = quantizeToI32(e2); - - const expected = FPTrait.ldexpInterval(e1, e2); - - // Result may be zero if e2 + bias <= 0 - if (e2 + bias[trait] <= 0) { - return { - input: [FPTrait.scalarBuilder(e1), i32(e2)], - expected: anyOf(expected, FPTrait.constants().zeroInterval), - }; - } - - return { input: [FPTrait.scalarBuilder(e1), i32(e2)], expected }; -}; - -export const d = makeCaseCache('ldexp', { - f32_non_const: () => { - return sparseF32Range().flatMap(e1 => sparseI32Range().map(e2 => makeCase('f32', e1, e2))); - }, - f32_const: () => { - return sparseF32Range().flatMap(e1 => - biasedRange(-bias.f32 - 10, bias.f32 + 1, 10).flatMap(e2 => - FP.f32.isFinite(e1 * 2 ** quantizeToI32(e2)) ? makeCase('f32', e1, e2) : [] - ) - ); - }, - f16_non_const: () => { - return sparseF16Range().flatMap(e1 => sparseI32Range().map(e2 => makeCase('f16', e1, e2))); - }, - f16_const: () => { - return sparseF16Range().flatMap(e1 => - biasedRange(-bias.f16 - 10, bias.f16 + 1, 10).flatMap(e2 => - FP.f16.isFinite(e1 * 2 ** quantizeToI32(e2)) ? makeCase('f16', e1, e2) : [] - ) - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc( @@ -91,9 +29,21 @@ g.test('abstract_float') ` ) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('ldexp'), + [Type.abstractFloat, Type.abstractInt], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -103,7 +53,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('ldexp'), [TypeF32, TypeI32], TypeF32, t.params, cases); + await run(t, builtin('ldexp'), [Type.f32, Type.i32], Type.f32, t.params, cases); }); g.test('f16') @@ -117,5 +67,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('ldexp'), [TypeF16, TypeI32], TypeF16, t.params, cases); + await run(t, builtin('ldexp'), [Type.f16, Type.i32], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.cache.ts new file mode 100644 index 0000000000..e7a2ee22ba --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.cache.ts @@ -0,0 +1,42 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +const scalar_cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + FP[trait].scalarRange(), + trait !== 'abstract' ? 'unfiltered' : 'finite', + // length has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].lengthInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +// Cases: [f32|f16|abstract]_vecN_[non_]const +const vec_cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([2, 3, 4] as const).flatMap(dim => + ([true, false] as const).map(nonConst => ({ + [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateVectorToIntervalCases( + FP[trait].vectorRange(dim), + nonConst ? 'unfiltered' : 'finite', + // length has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].lengthInterval + ); + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('length', { + ...scalar_cases, + ...vec_cases, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts index 85c1f85169..735c8468b7 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'length' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn length(e: T ) -> f32 Returns the length of e (e.g. abs(e) if T is a scalar, or sqrt(e[0]^2 + e[1]^2 + ...) if T is a vector). @@ -9,77 +9,77 @@ Returns the length of e (e.g. abs(e) if T is a scalar, or sqrt(e[0]^2 + e[1]^2 + import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { - fullF32Range, - fullF16Range, - vectorF32Range, - vectorF16Range, -} from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; - -import { builtin } from './builtin.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; + +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './length.cache.js'; export const g = makeTestGroup(GPUTest); -// Cases: f32_vecN_[non_]const -const f32_vec_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f32.generateVectorToIntervalCases( - vectorF32Range(n), - nonConst ? 'unfiltered' : 'finite', - FP.f32.lengthInterval - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -// Cases: f16_vecN_[non_]const -const f16_vec_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f16.generateVectorToIntervalCases( - vectorF16Range(n), - nonConst ? 'unfiltered' : 'finite', - FP.f16.lengthInterval - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -export const d = makeCaseCache('length', { - f32: () => { - return FP.f32.generateScalarToIntervalCases( - fullF32Range(), - 'unfiltered', - FP.f32.lengthInterval +g.test('abstract_float') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract_float tests`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractFloatBuiltin('length'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); + +g.test('abstract_float_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec2s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec2_const'); + await run( + t, + abstractFloatBuiltin('length'), + [Type.vec2af], + Type.abstractFloat, + t.params, + cases ); - }, - ...f32_vec_cases, - f16: () => { - return FP.f16.generateScalarToIntervalCases( - fullF16Range(), - 'unfiltered', - FP.f16.lengthInterval + }); + +g.test('abstract_float_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract_float tests using vec3s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec3_const'); + await run( + t, + abstractFloatBuiltin('length'), + [Type.vec3af], + Type.abstractFloat, + t.params, + cases ); - }, - ...f16_vec_cases, -}); + }); -g.test('abstract_float') +g.test('abstract_float_vec4') .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') - .desc(`abstract float tests`) - .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) - ) - .unimplemented(); + .desc(`abstract_float tests using vec4s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec4_const'); + await run( + t, + abstractFloatBuiltin('length'), + [Type.vec4af], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') @@ -87,7 +87,7 @@ g.test('f32') .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('length'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('length'), [Type.f32], Type.f32, t.params, cases); }); g.test('f32_vec2') @@ -98,7 +98,7 @@ g.test('f32_vec2') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const' ); - await run(t, builtin('length'), [TypeVec(2, TypeF32)], TypeF32, t.params, cases); + await run(t, builtin('length'), [Type.vec2f], Type.f32, t.params, cases); }); g.test('f32_vec3') @@ -109,7 +109,7 @@ g.test('f32_vec3') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const' ); - await run(t, builtin('length'), [TypeVec(3, TypeF32)], TypeF32, t.params, cases); + await run(t, builtin('length'), [Type.vec3f], Type.f32, t.params, cases); }); g.test('f32_vec4') @@ -120,7 +120,7 @@ g.test('f32_vec4') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const' ); - await run(t, builtin('length'), [TypeVec(4, TypeF32)], TypeF32, t.params, cases); + await run(t, builtin('length'), [Type.vec4f], Type.f32, t.params, cases); }); g.test('f16') @@ -132,7 +132,7 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('length'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('length'), [Type.f16], Type.f16, t.params, cases); }); g.test('f16_vec2') @@ -146,7 +146,7 @@ g.test('f16_vec2') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const' ); - await run(t, builtin('length'), [TypeVec(2, TypeF16)], TypeF16, t.params, cases); + await run(t, builtin('length'), [Type.vec2h], Type.f16, t.params, cases); }); g.test('f16_vec3') @@ -160,7 +160,7 @@ g.test('f16_vec3') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const' ); - await run(t, builtin('length'), [TypeVec(3, TypeF16)], TypeF16, t.params, cases); + await run(t, builtin('length'), [Type.vec3h], Type.f16, t.params, cases); }); g.test('f16_vec4') @@ -174,5 +174,5 @@ g.test('f16_vec4') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const' ); - await run(t, builtin('length'), [TypeVec(4, TypeF16)], TypeF16, t.params, cases); + await run(t, builtin('length'), [Type.vec4h], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.cache.ts new file mode 100644 index 0000000000..76b8bfa1af --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.cache.ts @@ -0,0 +1,30 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { biasedRange, linearRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// log's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] } +// Cases: [f32|f16|abstract]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarToIntervalCases( + [ + ...linearRange(FP[trait].constants().positive.min, 0.5, 20), + ...linearRange(0.5, 2.0, 20), + ...biasedRange(2.0, 2 ** 32, 1000), + ...FP[trait].scalarRange(), + ], + nonConst ? 'unfiltered' : 'finite', + // log has an absolute or ulp accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].logInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('log', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts index ac60e2b1bc..99b4a6983e 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'log' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn log(e: T ) -> T Returns the natural logarithm of e. Component-wise when T is a vector. @@ -9,53 +9,33 @@ Returns the natural logarithm of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { kValue } from '../../../../../util/constants.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { biasedRange, fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './log.cache.js'; export const g = makeTestGroup(GPUTest); -// log's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] } -const f32_inputs = [ - ...linearRange(kValue.f32.positive.min, 0.5, 20), - ...linearRange(0.5, 2.0, 20), - ...biasedRange(2.0, 2 ** 32, 1000), - ...fullF32Range(), -]; -const f16_inputs = [ - ...linearRange(kValue.f16.positive.min, 0.5, 20), - ...linearRange(0.5, 2.0, 20), - ...biasedRange(2.0, 2 ** 32, 1000), - ...fullF16Range(), -]; - -export const d = makeCaseCache('log', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.logInterval); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.logInterval); - }, - f16_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.logInterval); - }, - f16_non_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.logInterval); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('log'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -71,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1] ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('log'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('log'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -85,5 +65,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('log'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('log'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.cache.ts new file mode 100644 index 0000000000..d7781f328d --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.cache.ts @@ -0,0 +1,30 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { biasedRange, linearRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// log2's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] } +// Cases: [f32|f16|abstract]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarToIntervalCases( + [ + ...linearRange(FP[trait].constants().positive.min, 0.5, 20), + ...linearRange(0.5, 2.0, 20), + ...biasedRange(2.0, 2 ** 32, 1000), + ...FP[trait].scalarRange(), + ], + nonConst ? 'unfiltered' : 'finite', + // log2 has an absolute or ulp accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].log2Interval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('log2', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts index 37931579b9..dc623a6784 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'log2' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn log2(e: T ) -> T Returns the base-2 logarithm of e. Component-wise when T is a vector. @@ -9,53 +9,33 @@ Returns the base-2 logarithm of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { kValue } from '../../../../../util/constants.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { biasedRange, fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './log2.cache.js'; export const g = makeTestGroup(GPUTest); -// log2's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] } -const f32_inputs = [ - ...linearRange(kValue.f32.positive.min, 0.5, 20), - ...linearRange(0.5, 2.0, 20), - ...biasedRange(2.0, 2 ** 32, 1000), - ...fullF32Range(), -]; -const f16_inputs = [ - ...linearRange(kValue.f16.positive.min, 0.5, 20), - ...linearRange(0.5, 2.0, 20), - ...biasedRange(2.0, 2 ** 32, 1000), - ...fullF16Range(), -]; - -export const d = makeCaseCache('log2', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.log2Interval); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.log2Interval); - }, - f16_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.log2Interval); - }, - f16_non_const: () => { - return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.log2Interval); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('log2'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -71,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1] ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('log2'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('log2'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -85,5 +65,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('log2'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('log2'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.cache.ts new file mode 100644 index 0000000000..72def03dab --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.cache.ts @@ -0,0 +1,18 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarPairToIntervalCases( + FP[trait].sparseScalarRange(), + FP[trait].sparseScalarRange(), + 'unfiltered', + FP[trait].maxInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('max', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts index 6654b4951c..ee7cb0d674 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts @@ -1,12 +1,12 @@ export const description = ` Execution tests for the 'max' builtin function -S is AbstractInt, i32, or u32 +S is abstract-int, i32, or u32 T is S or vecN<S> @const fn max(e1: T ,e2: T) -> T Returns e2 if e1 is less than e2, and e1 otherwise. Component-wise when T is a vector. -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is vecN<S> @const fn max(e1: T ,e2: T) -> T Returns e2 if e1 is less than e2, and e1 otherwise. @@ -18,72 +18,50 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { - i32, - TypeF32, - TypeF16, - TypeI32, - TypeU32, - u32, - TypeAbstractFloat, -} from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range, sparseF64Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, Case, onlyConstInputSource, run } from '../../expression.js'; - -import { abstractBuiltin, builtin } from './builtin.js'; +import { Type, i32, u32, abstractInt } from '../../../../../util/conversion.js'; +import { maxBigInt } from '../../../../../util/math.js'; +import { Case } from '../../case.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; + +import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js'; +import { d } from './max.cache.js'; /** Generate set of max test cases from list of interesting values */ -function generateTestCases( - values: Array<number>, - makeCase: (x: number, y: number) => Case -): Array<Case> { - const cases = new Array<Case>(); - values.forEach(e => { - values.forEach(f => { - cases.push(makeCase(e, f)); +function generateTestCases<Type>(values: Type[], makeCase: (x: Type, y: Type) => Case): Case[] { + return values.flatMap(e => { + return values.map(f => { + return makeCase(e, f); }); }); - return cases; } export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('max', { - f32: () => { - return FP.f32.generateScalarPairToIntervalCases( - fullF32Range(), - fullF32Range(), - 'unfiltered', - FP.f32.maxInterval - ); - }, - f16: () => { - return FP.f16.generateScalarPairToIntervalCases( - fullF16Range(), - fullF16Range(), - 'unfiltered', - FP.f16.maxInterval - ); - }, - abstract: () => { - return FP.abstract.generateScalarPairToIntervalCases( - sparseF64Range(), - sparseF64Range(), - 'unfiltered', - FP.abstract.maxInterval - ); - }, -}); - g.test('abstract_int') .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') .desc(`abstract int tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const makeCase = (x: bigint, y: bigint): Case => { + return { input: [abstractInt(x), abstractInt(y)], expected: abstractInt(maxBigInt(x, y)) }; + }; + + const test_values = [-0x70000000n, -2n, -1n, 0n, 1n, 2n, 0x70000000n]; + const cases = generateTestCases(test_values, makeCase); + + await run( + t, + abstractIntBuiltin('max'), + [Type.abstractInt, Type.abstractInt], + Type.abstractInt, + t.params, + cases + ); + }); g.test('u32') .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') @@ -96,10 +74,10 @@ g.test('u32') return { input: [u32(x), u32(y)], expected: u32(Math.max(x, y)) }; }; - const test_values: Array<number> = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff]; + const test_values: number[] = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff]; const cases = generateTestCases(test_values, makeCase); - await run(t, builtin('max'), [TypeU32, TypeU32], TypeU32, t.params, cases); + await run(t, builtin('max'), [Type.u32, Type.u32], Type.u32, t.params, cases); }); g.test('i32') @@ -113,10 +91,10 @@ g.test('i32') return { input: [i32(x), i32(y)], expected: i32(Math.max(x, y)) }; }; - const test_values: Array<number> = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000]; + const test_values: number[] = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000]; const cases = generateTestCases(test_values, makeCase); - await run(t, builtin('max'), [TypeI32, TypeI32], TypeI32, t.params, cases); + await run(t, builtin('max'), [Type.i32, Type.i32], Type.i32, t.params, cases); }); g.test('abstract_float') @@ -131,9 +109,9 @@ g.test('abstract_float') const cases = await d.get('abstract'); await run( t, - abstractBuiltin('max'), - [TypeAbstractFloat, TypeAbstractFloat], - TypeAbstractFloat, + abstractFloatBuiltin('max'), + [Type.abstractFloat, Type.abstractFloat], + Type.abstractFloat, t.params, cases ); @@ -147,7 +125,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('max'), [TypeF32, TypeF32], TypeF32, t.params, cases); + await run(t, builtin('max'), [Type.f32, Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -161,5 +139,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('max'), [TypeF16, TypeF16], TypeF16, t.params, cases); + await run(t, builtin('max'), [Type.f16, Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.cache.ts new file mode 100644 index 0000000000..cddca325f0 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.cache.ts @@ -0,0 +1,18 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarPairToIntervalCases( + FP[trait].sparseScalarRange(), + FP[trait].sparseScalarRange(), + 'unfiltered', + FP[trait].minInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('min', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts index 6c05319546..ac63641399 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts @@ -1,12 +1,12 @@ export const description = ` Execution tests for the 'min' builtin function -S is AbstractInt, i32, or u32 +S is abstract-int, i32, or u32 T is S or vecN<S> @const fn min(e1: T ,e2: T) -> T Returns e1 if e1 is less than e2, and e2 otherwise. Component-wise when T is a vector. -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn min(e1: T ,e2: T) -> T Returns e2 if e2 is less than e1, and e1 otherwise. @@ -17,72 +17,50 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { - i32, - TypeF32, - TypeF16, - TypeI32, - TypeU32, - u32, - TypeAbstractFloat, -} from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range, sparseF64Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, Case, onlyConstInputSource, run } from '../../expression.js'; - -import { abstractBuiltin, builtin } from './builtin.js'; +import { Type, i32, u32, abstractInt } from '../../../../../util/conversion.js'; +import { minBigInt } from '../../../../../util/math.js'; +import { Case } from '../../case.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -export const g = makeTestGroup(GPUTest); +import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js'; +import { d } from './min.cache.js'; -export const d = makeCaseCache('min', { - f32: () => { - return FP.f32.generateScalarPairToIntervalCases( - fullF32Range(), - fullF32Range(), - 'unfiltered', - FP.f32.minInterval - ); - }, - f16: () => { - return FP.f16.generateScalarPairToIntervalCases( - fullF16Range(), - fullF16Range(), - 'unfiltered', - FP.f16.minInterval - ); - }, - abstract: () => { - return FP.abstract.generateScalarPairToIntervalCases( - sparseF64Range(), - sparseF64Range(), - 'unfiltered', - FP.abstract.minInterval - ); - }, -}); +export const g = makeTestGroup(GPUTest); /** Generate set of min test cases from list of interesting values */ -function generateTestCases( - values: Array<number>, - makeCase: (x: number, y: number) => Case -): Array<Case> { - const cases = new Array<Case>(); - values.forEach(e => { - values.forEach(f => { - cases.push(makeCase(e, f)); +function generateTestCases<Type>(values: Type[], makeCase: (x: Type, y: Type) => Case): Case[] { + return values.flatMap(e => { + return values.map(f => { + return makeCase(e, f); }); }); - return cases; } g.test('abstract_int') .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') .desc(`abstract int tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const makeCase = (x: bigint, y: bigint): Case => { + return { input: [abstractInt(x), abstractInt(y)], expected: abstractInt(minBigInt(x, y)) }; + }; + + const test_values = [-0x70000000n, -2n, -1n, 0n, 1n, 2n, 0x70000000n]; + const cases = generateTestCases(test_values, makeCase); + + await run( + t, + abstractIntBuiltin('min'), + [Type.abstractInt, Type.abstractInt], + Type.abstractInt, + t.params, + cases + ); + }); g.test('u32') .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') @@ -95,10 +73,10 @@ g.test('u32') return { input: [u32(x), u32(y)], expected: u32(Math.min(x, y)) }; }; - const test_values: Array<number> = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff]; + const test_values: number[] = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff]; const cases = generateTestCases(test_values, makeCase); - await run(t, builtin('min'), [TypeU32, TypeU32], TypeU32, t.params, cases); + await run(t, builtin('min'), [Type.u32, Type.u32], Type.u32, t.params, cases); }); g.test('i32') @@ -112,10 +90,10 @@ g.test('i32') return { input: [i32(x), i32(y)], expected: i32(Math.min(x, y)) }; }; - const test_values: Array<number> = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000]; + const test_values: number[] = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000]; const cases = generateTestCases(test_values, makeCase); - await run(t, builtin('min'), [TypeI32, TypeI32], TypeI32, t.params, cases); + await run(t, builtin('min'), [Type.i32, Type.i32], Type.i32, t.params, cases); }); g.test('abstract_float') @@ -130,9 +108,9 @@ g.test('abstract_float') const cases = await d.get('abstract'); await run( t, - abstractBuiltin('min'), - [TypeAbstractFloat, TypeAbstractFloat], - TypeAbstractFloat, + abstractFloatBuiltin('min'), + [Type.abstractFloat, Type.abstractFloat], + Type.abstractFloat, t.params, cases ); @@ -146,7 +124,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('min'), [TypeF32, TypeF32], TypeF32, t.params, cases); + await run(t, builtin('min'), [Type.f32, Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -160,5 +138,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('min'), [TypeF16, TypeF16], TypeF16, t.params, cases); + await run(t, builtin('min'), [Type.f16, Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.cache.ts new file mode 100644 index 0000000000..be221297b0 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.cache.ts @@ -0,0 +1,56 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { selectNCases } from '../../case.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +// abstract_non_const is empty and unused +const scalar_cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + const cases = FP[trait].generateScalarTripleToIntervalCases( + FP[trait].sparseScalarRange(), + FP[trait].sparseScalarRange(), + FP[trait].sparseScalarRange(), + nonConst ? 'unfiltered' : 'finite', + // mix has an inherited accuracy, so abstract is only expected to be as accurate as f32 + ...FP[trait !== 'abstract' ? trait : 'f32'].mixIntervals + ); + return selectNCases('mix_scalar', trait === 'abstract' ? 50 : cases.length, cases); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +// Cases: [f32|f16]_vecN_scalar_[non_]const +// abstract_vecN_non_const is empty and unused +const vec_scalar_cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([2, 3, 4] as const).flatMap(dim => + ([true, false] as const).map(nonConst => ({ + [`${trait}_vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + const cases = FP[trait].generateVectorPairScalarToVectorComponentWiseCase( + FP[trait].sparseVectorRange(dim), + FP[trait].sparseVectorRange(dim), + FP[trait].sparseScalarRange(), + nonConst ? 'unfiltered' : 'finite', + // mix has an inherited accuracy, so abstract is only expected to be as accurate as f32 + ...FP[trait !== 'abstract' ? trait : 'f32'].mixIntervals + ); + return selectNCases('mix_vector', trait === 'abstract' ? 50 : cases.length, cases); + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('mix', { + ...scalar_cases, + ...vec_scalar_cases, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts index 95e9f6b310..0005ab5c0e 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts @@ -1,12 +1,12 @@ export const description = ` Execution tests for the 'mix' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn mix(e1: T, e2: T, e3: T) -> T Returns the linear blend of e1 and e2 (e.g. e1*(1-e3)+e2*e3). Component-wise when T is a vector. -T is AbstractFloat, f32, or f16 +T is abstract-float, f32, or f16 T2 is vecN<T> @const fn mix(e1: T2, e2: T2, e3: T) -> T2 Returns the component-wise linear blend of e1 and e2, using scalar blending factor e3 for each component. @@ -16,121 +16,81 @@ Same as mix(e1,e2,T2(e3)). import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeVec, TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { - sparseF32Range, - sparseF16Range, - sparseVectorF32Range, - sparseVectorF16Range, -} from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './mix.cache.js'; export const g = makeTestGroup(GPUTest); -// Cases: f32_vecN_scalar_[non_]const -const f32_vec_scalar_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f32_vec${n}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f32.generateVectorPairScalarToVectorComponentWiseCase( - sparseVectorF32Range(n), - sparseVectorF32Range(n), - sparseF32Range(), - nonConst ? 'unfiltered' : 'finite', - ...FP.f32.mixIntervals - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -// Cases: f16_vecN_scalar_[non_]const -const f16_vec_scalar_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f16_vec${n}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f16.generateVectorPairScalarToVectorComponentWiseCase( - sparseVectorF16Range(n), - sparseVectorF16Range(n), - sparseF16Range(), - nonConst ? 'unfiltered' : 'finite', - ...FP.f16.mixIntervals - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -export const d = makeCaseCache('mix', { - f32_const: () => { - return FP.f32.generateScalarTripleToIntervalCases( - sparseF32Range(), - sparseF32Range(), - sparseF32Range(), - 'finite', - ...FP.f32.mixIntervals - ); - }, - f32_non_const: () => { - return FP.f32.generateScalarTripleToIntervalCases( - sparseF32Range(), - sparseF32Range(), - sparseF32Range(), - 'unfiltered', - ...FP.f32.mixIntervals - ); - }, - ...f32_vec_scalar_cases, - f16_const: () => { - return FP.f16.generateScalarTripleToIntervalCases( - sparseF16Range(), - sparseF16Range(), - sparseF16Range(), - 'finite', - ...FP.f16.mixIntervals - ); - }, - f16_non_const: () => { - return FP.f16.generateScalarTripleToIntervalCases( - sparseF16Range(), - sparseF16Range(), - sparseF16Range(), - 'unfiltered', - ...FP.f16.mixIntervals - ); - }, - ...f16_vec_scalar_cases, -}); - g.test('abstract_float_matching') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract_float test with matching third param`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('mix'), + [Type.abstractFloat, Type.abstractFloat, Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('abstract_float_nonmatching_vec2') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract_float tests with two vec2<abstract_float> params and scalar third param`) - .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec2_scalar_const'); + await run( + t, + abstractFloatBuiltin('mix'), + [Type.vec(2, Type.abstractFloat), Type.vec(2, Type.abstractFloat), Type.abstractFloat], + Type.vec(2, Type.abstractFloat), + t.params, + cases + ); + }); g.test('abstract_float_nonmatching_vec3') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract_float tests with two vec3<abstract_float> params and scalar third param`) - .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec3_scalar_const'); + await run( + t, + abstractFloatBuiltin('mix'), + [Type.vec(3, Type.abstractFloat), Type.vec(3, Type.abstractFloat), Type.abstractFloat], + Type.vec(3, Type.abstractFloat), + t.params, + cases + ); + }); g.test('abstract_float_nonmatching_vec4') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract_float tests with two vec4<abstract_float> params and scalar third param`) - .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec4_scalar_const'); + await run( + t, + abstractFloatBuiltin('mix'), + [Type.vec(4, Type.abstractFloat), Type.vec(4, Type.abstractFloat), Type.abstractFloat], + Type.vec(4, Type.abstractFloat), + t.params, + cases + ); + }); g.test('f32_matching') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -140,7 +100,7 @@ g.test('f32_matching') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('mix'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases); + await run(t, builtin('mix'), [Type.f32, Type.f32, Type.f32], Type.f32, t.params, cases); }); g.test('f32_nonmatching_vec2') @@ -151,14 +111,7 @@ g.test('f32_nonmatching_vec2') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec2_scalar_const' : 'f32_vec2_scalar_non_const' ); - await run( - t, - builtin('mix'), - [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeF32], - TypeVec(2, TypeF32), - t.params, - cases - ); + await run(t, builtin('mix'), [Type.vec2f, Type.vec2f, Type.f32], Type.vec2f, t.params, cases); }); g.test('f32_nonmatching_vec3') @@ -169,14 +122,7 @@ g.test('f32_nonmatching_vec3') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec3_scalar_const' : 'f32_vec3_scalar_non_const' ); - await run( - t, - builtin('mix'), - [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeF32], - TypeVec(3, TypeF32), - t.params, - cases - ); + await run(t, builtin('mix'), [Type.vec3f, Type.vec3f, Type.f32], Type.vec3f, t.params, cases); }); g.test('f32_nonmatching_vec4') @@ -187,14 +133,7 @@ g.test('f32_nonmatching_vec4') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec4_scalar_const' : 'f32_vec4_scalar_non_const' ); - await run( - t, - builtin('mix'), - [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeF32], - TypeVec(4, TypeF32), - t.params, - cases - ); + await run(t, builtin('mix'), [Type.vec4f, Type.vec4f, Type.f32], Type.vec4f, t.params, cases); }); g.test('f16_matching') @@ -208,7 +147,7 @@ g.test('f16_matching') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('mix'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases); + await run(t, builtin('mix'), [Type.f16, Type.f16, Type.f16], Type.f16, t.params, cases); }); g.test('f16_nonmatching_vec2') @@ -222,14 +161,7 @@ g.test('f16_nonmatching_vec2') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec2_scalar_const' : 'f16_vec2_scalar_non_const' ); - await run( - t, - builtin('mix'), - [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeF16], - TypeVec(2, TypeF16), - t.params, - cases - ); + await run(t, builtin('mix'), [Type.vec2h, Type.vec2h, Type.f16], Type.vec2h, t.params, cases); }); g.test('f16_nonmatching_vec3') @@ -243,14 +175,7 @@ g.test('f16_nonmatching_vec3') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec3_scalar_const' : 'f16_vec3_scalar_non_const' ); - await run( - t, - builtin('mix'), - [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeF16], - TypeVec(3, TypeF16), - t.params, - cases - ); + await run(t, builtin('mix'), [Type.vec3h, Type.vec3h, Type.f16], Type.vec3h, t.params, cases); }); g.test('f16_nonmatching_vec4') @@ -264,12 +189,5 @@ g.test('f16_nonmatching_vec4') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec4_scalar_const' : 'f16_vec4_scalar_non_const' ); - await run( - t, - builtin('mix'), - [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeF16], - TypeVec(4, TypeF16), - t.params, - cases - ); + await run(t, builtin('mix'), [Type.vec4h, Type.vec4h, Type.f16], Type.vec4h, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.cache.ts new file mode 100644 index 0000000000..1a76de56bb --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.cache.ts @@ -0,0 +1,75 @@ +import { toVector } from '../../../../../util/conversion.js'; +import { FP, FPKind } from '../../../../../util/floating_point.js'; +import { Case } from '../../case.js'; +import { makeCaseCache } from '../../case_cache.js'; + +/** @returns a fract Case for a scalar vector input */ +function makeScalarCaseFract(kind: FPKind, n: number): Case { + const fp = FP[kind]; + n = fp.quantize(n); + const result = fp.modfInterval(n).fract; + + return { input: fp.scalarBuilder(n), expected: result }; +} + +/** @returns a whole Case for a scalar vector input */ +function makeScalarCaseWhole(kind: FPKind, n: number): Case { + const fp = FP[kind]; + n = fp.quantize(n); + const result = fp.modfInterval(n).whole; + + return { input: fp.scalarBuilder(n), expected: result }; +} + +/** @returns a fract Case for a given vector input */ +function makeVectorCaseFract(kind: FPKind, v: readonly number[]): Case { + const fp = FP[kind]; + v = v.map(fp.quantize); + const fs = v.map(e => { + return fp.modfInterval(e).fract; + }); + + return { input: toVector(v, fp.scalarBuilder), expected: fs }; +} + +/** @returns a whole Case for a given vector input */ +function makeVectorCaseWhole(kind: FPKind, v: readonly number[]): Case { + const fp = FP[kind]; + v = v.map(fp.quantize); + const ws = v.map(e => { + return fp.modfInterval(e).whole; + }); + + return { input: toVector(v, fp.scalarBuilder), expected: ws }; +} + +// Cases: [f32|f16|abstract]_[fract|whole] +const scalar_cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(kind => + (['whole', 'fract'] as const).map(portion => ({ + [`${kind}_${portion}`]: () => { + const makeCase = portion === 'whole' ? makeScalarCaseWhole : makeScalarCaseFract; + return FP[kind].scalarRange().map(makeCase.bind(null, kind)); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +// Cases: [f32|f16|abstract]_vecN_[fract|whole] +const vec_cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(kind => + ([2, 3, 4] as const).flatMap(n => + (['whole', 'fract'] as const).map(portion => ({ + [`${kind}_vec${n}_${portion}`]: () => { + const makeCase = portion === 'whole' ? makeVectorCaseWhole : makeVectorCaseFract; + return FP[kind].vectorRange(n).map(makeCase.bind(null, kind)); + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('modf', { + ...scalar_cases, + ...vec_cases, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts index 1a3d8a2850..6c988008f8 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts @@ -1,13 +1,13 @@ export const description = ` Execution tests for the 'modf' builtin function -T is f32 or f16 or AbstractFloat +T is f32 or f16 or Type.abstractFloat @const fn modf(e:T) -> result_struct Splits |e| into fractional and whole number parts. The whole part is (|e| % 1.0), and the fractional part is |e| minus the whole part. Returns the result_struct for the given type. -S is f32 or f16 or AbstractFloat +S is f32 or f16 or Type.abstractFloat T is vecN<S> @const fn modf(e:T) -> result_struct Splits the components of |e| into fractional and whole number parts. @@ -18,33 +18,18 @@ Returns the result_struct for the given type. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { - toVector, - TypeAbstractFloat, - TypeF16, - TypeF32, - TypeVec, -} from '../../../../../util/conversion.js'; -import { FP, FPKind } from '../../../../../util/floating_point.js'; -import { - fullF16Range, - fullF32Range, - fullF64Range, - vectorF16Range, - vectorF32Range, - vectorF64Range, -} from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { abstractFloatShaderBuilder, allInputSources, basicExpressionBuilder, - Case, onlyConstInputSource, run, ShaderBuilder, } from '../../expression.js'; +import { d } from './modf.cache.js'; + export const g = makeTestGroup(GPUTest); /** @returns an ShaderBuilder that evaluates modf and returns .whole from the result structure */ @@ -67,101 +52,6 @@ function abstractFractBuilder(): ShaderBuilder { return abstractFloatShaderBuilder(value => `modf(${value}).fract`); } -/** @returns a fract Case for a scalar vector input */ -function makeScalarCaseFract(kind: FPKind, n: number): Case { - const fp = FP[kind]; - n = fp.quantize(n); - const result = fp.modfInterval(n).fract; - - return { input: fp.scalarBuilder(n), expected: result }; -} - -/** @returns a whole Case for a scalar vector input */ -function makeScalarCaseWhole(kind: FPKind, n: number): Case { - const fp = FP[kind]; - n = fp.quantize(n); - const result = fp.modfInterval(n).whole; - - return { input: fp.scalarBuilder(n), expected: result }; -} - -/** @returns a fract Case for a given vector input */ -function makeVectorCaseFract(kind: FPKind, v: readonly number[]): Case { - const fp = FP[kind]; - v = v.map(fp.quantize); - const fs = v.map(e => { - return fp.modfInterval(e).fract; - }); - - return { input: toVector(v, fp.scalarBuilder), expected: fs }; -} - -/** @returns a whole Case for a given vector input */ -function makeVectorCaseWhole(kind: FPKind, v: readonly number[]): Case { - const fp = FP[kind]; - v = v.map(fp.quantize); - const ws = v.map(e => { - return fp.modfInterval(e).whole; - }); - - return { input: toVector(v, fp.scalarBuilder), expected: ws }; -} - -const scalar_range = { - f32: fullF32Range(), - f16: fullF16Range(), - abstract: fullF64Range(), -}; - -const vector_range = { - f32: { - 2: vectorF32Range(2), - 3: vectorF32Range(3), - 4: vectorF32Range(4), - }, - f16: { - 2: vectorF16Range(2), - 3: vectorF16Range(3), - 4: vectorF16Range(4), - }, - abstract: { - 2: vectorF64Range(2), - 3: vectorF64Range(3), - 4: vectorF64Range(4), - }, -}; - -// Cases: [f32|f16|abstract]_[fract|whole] -const scalar_cases = (['f32', 'f16', 'abstract'] as const) - .flatMap(kind => - (['whole', 'fract'] as const).map(portion => ({ - [`${kind}_${portion}`]: () => { - const makeCase = portion === 'whole' ? makeScalarCaseWhole : makeScalarCaseFract; - return scalar_range[kind].map(makeCase.bind(null, kind)); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -// Cases: [f32|f16|abstract]_vecN_[fract|whole] -const vec_cases = (['f32', 'f16', 'abstract'] as const) - .flatMap(kind => - ([2, 3, 4] as const).flatMap(n => - (['whole', 'fract'] as const).map(portion => ({ - [`${kind}_vec${n}_${portion}`]: () => { - const makeCase = portion === 'whole' ? makeVectorCaseWhole : makeVectorCaseFract; - return vector_range[kind][n].map(makeCase.bind(null, kind)); - }, - })) - ) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -export const d = makeCaseCache('modf', { - ...scalar_cases, - ...vec_cases, -}); - g.test('f32_fract') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc( @@ -177,7 +67,7 @@ struct __modf_result_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_fract'); - await run(t, fractBuilder(), [TypeF32], TypeF32, t.params, cases); + await run(t, fractBuilder(), [Type.f32], Type.f32, t.params, cases); }); g.test('f32_whole') @@ -195,7 +85,7 @@ struct __modf_result_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_whole'); - await run(t, wholeBuilder(), [TypeF32], TypeF32, t.params, cases); + await run(t, wholeBuilder(), [Type.f32], Type.f32, t.params, cases); }); g.test('f32_vec2_fract') @@ -213,7 +103,7 @@ struct __modf_result_vec2_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_vec2_fract'); - await run(t, fractBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases); + await run(t, fractBuilder(), [Type.vec2f], Type.vec2f, t.params, cases); }); g.test('f32_vec2_whole') @@ -231,7 +121,7 @@ struct __modf_result_vec2_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_vec2_whole'); - await run(t, wholeBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases); + await run(t, wholeBuilder(), [Type.vec2f], Type.vec2f, t.params, cases); }); g.test('f32_vec3_fract') @@ -249,7 +139,7 @@ struct __modf_result_vec3_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_vec3_fract'); - await run(t, fractBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases); + await run(t, fractBuilder(), [Type.vec3f], Type.vec3f, t.params, cases); }); g.test('f32_vec3_whole') @@ -267,7 +157,7 @@ struct __modf_result_vec3_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_vec3_whole'); - await run(t, wholeBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases); + await run(t, wholeBuilder(), [Type.vec3f], Type.vec3f, t.params, cases); }); g.test('f32_vec4_fract') @@ -285,7 +175,7 @@ struct __modf_result_vec4_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_vec4_fract'); - await run(t, fractBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases); + await run(t, fractBuilder(), [Type.vec4f], Type.vec4f, t.params, cases); }); g.test('f32_vec4_whole') @@ -303,7 +193,7 @@ struct __modf_result_vec4_f32 { .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get('f32_vec4_whole'); - await run(t, wholeBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases); + await run(t, wholeBuilder(), [Type.vec4f], Type.vec4f, t.params, cases); }); g.test('f16_fract') @@ -324,7 +214,7 @@ struct __modf_result_f16 { }) .fn(async t => { const cases = await d.get('f16_fract'); - await run(t, fractBuilder(), [TypeF16], TypeF16, t.params, cases); + await run(t, fractBuilder(), [Type.f16], Type.f16, t.params, cases); }); g.test('f16_whole') @@ -345,7 +235,7 @@ struct __modf_result_f16 { }) .fn(async t => { const cases = await d.get('f16_whole'); - await run(t, wholeBuilder(), [TypeF16], TypeF16, t.params, cases); + await run(t, wholeBuilder(), [Type.f16], Type.f16, t.params, cases); }); g.test('f16_vec2_fract') @@ -366,7 +256,7 @@ struct __modf_result_vec2_f16 { }) .fn(async t => { const cases = await d.get('f16_vec2_fract'); - await run(t, fractBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases); + await run(t, fractBuilder(), [Type.vec2h], Type.vec2h, t.params, cases); }); g.test('f16_vec2_whole') @@ -387,7 +277,7 @@ struct __modf_result_vec2_f16 { }) .fn(async t => { const cases = await d.get('f16_vec2_whole'); - await run(t, wholeBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases); + await run(t, wholeBuilder(), [Type.vec2h], Type.vec2h, t.params, cases); }); g.test('f16_vec3_fract') @@ -408,7 +298,7 @@ struct __modf_result_vec3_f16 { }) .fn(async t => { const cases = await d.get('f16_vec3_fract'); - await run(t, fractBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases); + await run(t, fractBuilder(), [Type.vec3h], Type.vec3h, t.params, cases); }); g.test('f16_vec3_whole') @@ -429,7 +319,7 @@ struct __modf_result_vec3_f16 { }) .fn(async t => { const cases = await d.get('f16_vec3_whole'); - await run(t, wholeBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases); + await run(t, wholeBuilder(), [Type.vec3h], Type.vec3h, t.params, cases); }); g.test('f16_vec4_fract') @@ -450,7 +340,7 @@ struct __modf_result_vec4_f16 { }) .fn(async t => { const cases = await d.get('f16_vec4_fract'); - await run(t, fractBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases); + await run(t, fractBuilder(), [Type.vec4h], Type.vec4h, t.params, cases); }); g.test('f16_vec4_whole') @@ -471,43 +361,43 @@ struct __modf_result_vec4_f16 { }) .fn(async t => { const cases = await d.get('f16_vec4_whole'); - await run(t, wholeBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases); + await run(t, wholeBuilder(), [Type.vec4h], Type.vec4h, t.params, cases); }); g.test('abstract_fract') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc( ` -T is AbstractFloat +T is abstract-float struct __modf_result_abstract { - fract : AbstractFloat, // fractional part - whole : AbstractFloat // whole part + fract : Type.abstractFloat, // fractional part + whole : Type.abstractFloat // whole part } ` ) .params(u => u.combine('inputSource', onlyConstInputSource)) .fn(async t => { const cases = await d.get('abstract_fract'); - await run(t, abstractFractBuilder(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases); + await run(t, abstractFractBuilder(), [Type.abstractFloat], Type.abstractFloat, t.params, cases); }); g.test('abstract_whole') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc( ` -T is AbstractFloat +T is abstract-float struct __modf_result_abstract { - fract : AbstractFloat, // fractional part - whole : AbstractFloat // whole part + fract : Type.abstractFloat, // fractional part + whole : Type.abstractFloat // whole part } ` ) .params(u => u.combine('inputSource', onlyConstInputSource)) .fn(async t => { const cases = await d.get('abstract_whole'); - await run(t, abstractWholeBuilder(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases); + await run(t, abstractWholeBuilder(), [Type.abstractFloat], Type.abstractFloat, t.params, cases); }); g.test('abstract_vec2_fract') @@ -528,8 +418,8 @@ struct __modf_result_vec2_abstract { await run( t, abstractFractBuilder(), - [TypeVec(2, TypeAbstractFloat)], - TypeVec(2, TypeAbstractFloat), + [Type.vec(2, Type.abstractFloat)], + Type.vec(2, Type.abstractFloat), t.params, cases ); @@ -553,8 +443,8 @@ struct __modf_result_vec2_abstract { await run( t, abstractWholeBuilder(), - [TypeVec(2, TypeAbstractFloat)], - TypeVec(2, TypeAbstractFloat), + [Type.vec(2, Type.abstractFloat)], + Type.vec(2, Type.abstractFloat), t.params, cases ); @@ -578,8 +468,8 @@ struct __modf_result_vec3_abstract { await run( t, abstractFractBuilder(), - [TypeVec(3, TypeAbstractFloat)], - TypeVec(3, TypeAbstractFloat), + [Type.vec(3, Type.abstractFloat)], + Type.vec(3, Type.abstractFloat), t.params, cases ); @@ -603,8 +493,8 @@ struct __modf_result_vec3_abstract { await run( t, abstractWholeBuilder(), - [TypeVec(3, TypeAbstractFloat)], - TypeVec(3, TypeAbstractFloat), + [Type.vec(3, Type.abstractFloat)], + Type.vec(3, Type.abstractFloat), t.params, cases ); @@ -628,8 +518,8 @@ struct __modf_result_vec4_abstract { await run( t, abstractFractBuilder(), - [TypeVec(4, TypeAbstractFloat)], - TypeVec(4, TypeAbstractFloat), + [Type.vec(4, Type.abstractFloat)], + Type.vec(4, Type.abstractFloat), t.params, cases ); @@ -653,8 +543,8 @@ struct __modf_result_vec4_abstract { await run( t, abstractWholeBuilder(), - [TypeVec(4, TypeAbstractFloat)], - TypeVec(4, TypeAbstractFloat), + [Type.vec(4, Type.abstractFloat)], + Type.vec(4, Type.abstractFloat), t.params, cases ); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.cache.ts new file mode 100644 index 0000000000..7da5a43c22 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.cache.ts @@ -0,0 +1,25 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_vecN_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([2, 3, 4] as const).flatMap(dim => + ([true, false] as const).map(nonConst => ({ + [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateVectorToVectorCases( + FP[trait].vectorRange(dim), + nonConst ? 'unfiltered' : 'finite', + // normalize has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].normalizeInterval + ); + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('normalize', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts index 615617b448..06be8a125e 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts @@ -1,65 +1,47 @@ export const description = ` Execution tests for the 'normalize' builtin function -T is AbstractFloat, f32, or f16 +T is abstract-float, f32, or f16 @const fn normalize(e: vecN<T> ) -> vecN<T> Returns a unit vector in the same direction as e. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { vectorF32Range, vectorF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './normalize.cache.js'; export const g = makeTestGroup(GPUTest); -// Cases: f32_vecN_[non_]const -const f32_vec_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f32.generateVectorToVectorCases( - vectorF32Range(n), - nonConst ? 'unfiltered' : 'finite', - FP.f32.normalizeInterval - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -// Cases: f16_vecN_[non_]const -const f16_vec_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f16.generateVectorToVectorCases( - vectorF16Range(n), - nonConst ? 'unfiltered' : 'finite', - FP.f16.normalizeInterval - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); +g.test('abstract_float_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec2s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec2_const'); + await run(t, abstractFloatBuiltin('normalize'), [Type.vec2af], Type.vec2af, t.params, cases); + }); -export const d = makeCaseCache('normalize', { - ...f32_vec_cases, - ...f16_vec_cases, -}); +g.test('abstract_float_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec3s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec3_const'); + await run(t, abstractFloatBuiltin('normalize'), [Type.vec3af], Type.vec3af, t.params, cases); + }); -g.test('abstract_float') - .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') - .desc(`abstract float tests`) - .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) - ) - .unimplemented(); +g.test('abstract_float_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec4s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec4_const'); + await run(t, abstractFloatBuiltin('normalize'), [Type.vec4af], Type.vec4af, t.params, cases); + }); g.test('f32_vec2') .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') @@ -69,7 +51,7 @@ g.test('f32_vec2') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const' ); - await run(t, builtin('normalize'), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases); + await run(t, builtin('normalize'), [Type.vec2f], Type.vec2f, t.params, cases); }); g.test('f32_vec3') @@ -80,7 +62,7 @@ g.test('f32_vec3') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const' ); - await run(t, builtin('normalize'), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases); + await run(t, builtin('normalize'), [Type.vec3f], Type.vec3f, t.params, cases); }); g.test('f32_vec4') @@ -91,7 +73,7 @@ g.test('f32_vec4') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const' ); - await run(t, builtin('normalize'), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases); + await run(t, builtin('normalize'), [Type.vec4f], Type.vec4f, t.params, cases); }); g.test('f16_vec2') @@ -105,7 +87,7 @@ g.test('f16_vec2') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const' ); - await run(t, builtin('normalize'), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases); + await run(t, builtin('normalize'), [Type.vec2h], Type.vec2h, t.params, cases); }); g.test('f16_vec3') @@ -119,7 +101,7 @@ g.test('f16_vec3') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const' ); - await run(t, builtin('normalize'), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases); + await run(t, builtin('normalize'), [Type.vec3h], Type.vec3h, t.params, cases); }); g.test('f16_vec4') @@ -133,5 +115,5 @@ g.test('f16_vec4') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const' ); - await run(t, builtin('normalize'), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases); + await run(t, builtin('normalize'), [Type.vec4h], Type.vec4h, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.cache.ts new file mode 100644 index 0000000000..9cd7824cd9 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.cache.ts @@ -0,0 +1,55 @@ +import { anyOf, skipUndefined } from '../../../../../util/compare.js'; +import { f32, pack2x16float, u32, vec2 } from '../../../../../util/conversion.js'; +import { cartesianProduct, quantizeToF32, scalarF32Range } from '../../../../../util/math.js'; +import { Case } from '../../case.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// pack2x16float has somewhat unusual behaviour, specifically around how it is +// supposed to behave when values go OOB and when they are considered to have +// gone OOB, so has its own bespoke implementation. + +/** + * @returns a Case for `pack2x16float` + * @param param0 first param for the case + * @param param1 second param for the case + * @param filter_undefined should inputs that cause an undefined expectation be + * filtered out, needed for const-eval + */ +function makeCase(param0: number, param1: number, filter_undefined: boolean): Case | undefined { + param0 = quantizeToF32(param0); + param1 = quantizeToF32(param1); + + const results = pack2x16float(param0, param1); + if (filter_undefined && results.some(r => r === undefined)) { + return undefined; + } + + return { + input: [vec2(f32(param0), f32(param1))], + expected: anyOf( + ...results.map(r => (r === undefined ? skipUndefined(undefined) : skipUndefined(u32(r)))) + ), + }; +} + +/** + * @returns an array of Cases for `pack2x16float` + * @param param0s array of inputs to try for the first param + * @param param1s array of inputs to try for the second param + * @param filter_undefined should inputs that cause an undefined expectation be + * filtered out, needed for const-eval + */ +function generateCases(param0s: number[], param1s: number[], filter_undefined: boolean): Case[] { + return cartesianProduct(param0s, param1s) + .map(e => makeCase(e[0], e[1], filter_undefined)) + .filter((c): c is Case => c !== undefined); +} + +export const d = makeCaseCache('pack2x16float', { + f32_const: () => { + return generateCases(scalarF32Range(), scalarF32Range(), true); + }, + f32_non_const: () => { + return generateCases(scalarF32Range(), scalarF32Range(), false); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts index 790e54720c..5ba6993427 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts @@ -6,74 +6,14 @@ which is then placed in bits 16 × i through 16 × i + 15 of the result. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { anyOf, skipUndefined } from '../../../../../util/compare.js'; -import { - f32, - pack2x16float, - TypeF32, - TypeU32, - TypeVec, - u32, - vec2, -} from '../../../../../util/conversion.js'; -import { cartesianProduct, fullF32Range, quantizeToF32 } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, Case, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, run } from '../../expression.js'; import { builtin } from './builtin.js'; +import { d } from './pack2x16float.cache.js'; export const g = makeTestGroup(GPUTest); -// pack2x16float has somewhat unusual behaviour, specifically around how it is -// supposed to behave when values go OOB and when they are considered to have -// gone OOB, so has its own bespoke implementation. - -/** - * @returns a Case for `pack2x16float` - * @param param0 first param for the case - * @param param1 second param for the case - * @param filter_undefined should inputs that cause an undefined expectation be - * filtered out, needed for const-eval - */ -function makeCase(param0: number, param1: number, filter_undefined: boolean): Case | undefined { - param0 = quantizeToF32(param0); - param1 = quantizeToF32(param1); - - const results = pack2x16float(param0, param1); - if (filter_undefined && results.some(r => r === undefined)) { - return undefined; - } - - return { - input: [vec2(f32(param0), f32(param1))], - expected: anyOf( - ...results.map(r => (r === undefined ? skipUndefined(undefined) : skipUndefined(u32(r)))) - ), - }; -} - -/** - * @returns an array of Cases for `pack2x16float` - * @param param0s array of inputs to try for the first param - * @param param1s array of inputs to try for the second param - * @param filter_undefined should inputs that cause an undefined expectation be - * filtered out, needed for const-eval - */ -function generateCases(param0s: number[], param1s: number[], filter_undefined: boolean): Case[] { - return cartesianProduct(param0s, param1s) - .map(e => makeCase(e[0], e[1], filter_undefined)) - .filter((c): c is Case => c !== undefined); -} - -export const d = makeCaseCache('pack2x16float', { - f32_const: () => { - return generateCases(fullF32Range(), fullF32Range(), true); - }, - f32_non_const: () => { - return generateCases(fullF32Range(), fullF32Range(), false); - }, -}); - g.test('pack') .specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions') .desc( @@ -84,5 +24,5 @@ g.test('pack') .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('pack2x16float'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases); + await run(t, builtin('pack2x16float'), [Type.vec2f], Type.u32, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts index 54bb21f6c6..1bcca2f73f 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts @@ -8,17 +8,10 @@ bits 16 × i through 16 × i + 15 of the result. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; import { kValue } from '../../../../../util/constants.js'; -import { - f32, - pack2x16snorm, - TypeF32, - TypeU32, - TypeVec, - u32, - vec2, -} from '../../../../../util/conversion.js'; +import { f32, pack2x16snorm, u32, vec2, Type } from '../../../../../util/conversion.js'; import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js'; -import { allInputSources, Case, run } from '../../expression.js'; +import { Case } from '../../case.js'; +import { allInputSources, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -51,5 +44,5 @@ g.test('pack') ]; }); - await run(t, builtin('pack2x16snorm'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases); + await run(t, builtin('pack2x16snorm'), [Type.vec2f], Type.u32, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts index a875a9c7e1..334d106482 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts @@ -8,17 +8,10 @@ bits 16 × i through 16 × i + 15 of the result. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; import { kValue } from '../../../../../util/constants.js'; -import { - f32, - pack2x16unorm, - TypeF32, - TypeU32, - TypeVec, - u32, - vec2, -} from '../../../../../util/conversion.js'; +import { f32, pack2x16unorm, u32, vec2, Type } from '../../../../../util/conversion.js'; import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js'; -import { allInputSources, Case, run } from '../../expression.js'; +import { Case } from '../../case.js'; +import { allInputSources, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -51,5 +44,5 @@ g.test('pack') ]; }); - await run(t, builtin('pack2x16unorm'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases); + await run(t, builtin('pack2x16unorm'), [Type.vec2f], Type.u32, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts index de0463e9fc..fbe362ea45 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts @@ -8,18 +8,10 @@ bits 8 × i through 8 × i + 7 of the result. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; import { kValue } from '../../../../../util/constants.js'; -import { - f32, - pack4x8snorm, - Scalar, - TypeF32, - TypeU32, - TypeVec, - u32, - vec4, -} from '../../../../../util/conversion.js'; +import { f32, pack4x8snorm, ScalarValue, u32, vec4, Type } from '../../../../../util/conversion.js'; import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js'; -import { allInputSources, Case, run } from '../../expression.js'; +import { Case } from '../../case.js'; +import { allInputSources, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -35,7 +27,12 @@ g.test('pack') .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const makeCase = (vals: [number, number, number, number]): Case => { - const vals_f32 = new Array<Scalar>(4) as [Scalar, Scalar, Scalar, Scalar]; + const vals_f32 = new Array<ScalarValue>(4) as [ + ScalarValue, + ScalarValue, + ScalarValue, + ScalarValue, + ]; for (const idx in vals) { vals[idx] = quantizeToF32(vals[idx]); vals_f32[idx] = f32(vals[idx]); @@ -56,5 +53,5 @@ g.test('pack') ]; }); - await run(t, builtin('pack4x8snorm'), [TypeVec(4, TypeF32)], TypeU32, t.params, cases); + await run(t, builtin('pack4x8snorm'), [Type.vec4f], Type.u32, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts index b670e92fbb..c7d62e722b 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts @@ -8,18 +8,10 @@ bits 8 × i through 8 × i + 7 of the result. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; import { kValue } from '../../../../../util/constants.js'; -import { - f32, - pack4x8unorm, - Scalar, - TypeF32, - TypeU32, - TypeVec, - u32, - vec4, -} from '../../../../../util/conversion.js'; +import { f32, pack4x8unorm, ScalarValue, u32, vec4, Type } from '../../../../../util/conversion.js'; import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js'; -import { allInputSources, Case, run } from '../../expression.js'; +import { Case } from '../../case.js'; +import { allInputSources, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -35,7 +27,12 @@ g.test('pack') .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const makeCase = (vals: [number, number, number, number]): Case => { - const vals_f32 = new Array<Scalar>(4) as [Scalar, Scalar, Scalar, Scalar]; + const vals_f32 = new Array<ScalarValue>(4) as [ + ScalarValue, + ScalarValue, + ScalarValue, + ScalarValue, + ]; for (const idx in vals) { vals[idx] = quantizeToF32(vals[idx]); vals_f32[idx] = f32(vals[idx]); @@ -56,5 +53,5 @@ g.test('pack') ]; }); - await run(t, builtin('pack4x8unorm'), [TypeVec(4, TypeF32)], TypeU32, t.params, cases); + await run(t, builtin('pack4x8unorm'), [Type.vec4f], Type.u32, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8.spec.ts new file mode 100644 index 0000000000..94aa67f0ae --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8.spec.ts @@ -0,0 +1,69 @@ +export const description = ` +Execution tests for the 'pack4xI8' builtin function + +@const fn pack4xI8(e: vec4<i32>) -> u32 +Pack the lower 8 bits of each component of e into a u32 value and drop all the unused bits. +Component e[i] of the input is mapped to bits (8 * i) through (8 * (i + 7)) of the result. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { u32, toVector, i32, Type } from '../../../../../util/conversion.js'; +import { Case } from '../../case.js'; +import { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('basic') + .specURL('https://www.w3.org/TR/WGSL/#pack4xI8-builtin') + .desc( + ` +@const fn pack4xI8(e: vec4<i32>) -> u32 + ` + ) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cfg: Config = t.params; + + const pack4xI8 = (vals: readonly [number, number, number, number]) => { + const result = new Uint32Array(1); + for (let i = 0; i < 4; ++i) { + result[0] |= (vals[i] & 0xff) << (i * 8); + } + return result[0]; + }; + + const testInputs = [ + [0, 0, 0, 0], + [1, 2, 3, 4], + [-1, 2, 3, 4], + [1, -2, 3, 4], + [1, 2, -3, 4], + [1, 2, 3, -4], + [-1, -2, 3, 4], + [-1, 2, -3, 4], + [-1, 2, 3, -4], + [1, -2, -3, 4], + [1, -2, 3, -4], + [1, 2, -3, -4], + [-1, -2, -3, 4], + [-1, -2, 3, -4], + [-1, 2, -3, -4], + [1, -2, -3, -4], + [-1, -2, -3, -4], + [127, 128, -128, -129], + [128, 128, -128, -128], + [32767, 32768, -32768, -32769], + ] as const; + + const makeCase = (vals: readonly [number, number, number, number]): Case => { + return { input: [toVector(vals, i32)], expected: u32(pack4xI8(vals)) }; + }; + const cases: Array<Case> = testInputs.flatMap(v => { + return [makeCase(v)]; + }); + + await run(t, builtin('pack4xI8'), [Type.vec4i], Type.u32, cfg, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8Clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8Clamp.spec.ts new file mode 100644 index 0000000000..4968ed1e04 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8Clamp.spec.ts @@ -0,0 +1,73 @@ +export const description = ` +Execution tests for the 'pack4xI8Clamp' builtin function + +@const fn pack4xI8Clamp(e: vec4<i32>) -> u32 +Clamp each component of e in the range [-128, 127] and then pack the lower 8 bits of each component +into a u32 value. Component e[i] of the input is mapped to bits (8 * i) through (8 * (i + 7)) of the +result. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { u32, toVector, i32, Type } from '../../../../../util/conversion.js'; +import { clamp } from '../../../../../util/math.js'; +import { Case } from '../../case.js'; +import { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('basic') + .specURL('https://www.w3.org/TR/WGSL/#pack4xI8Clamp-builtin') + .desc( + ` +@const fn pack4xI8Clamp(e: vec4<i32>) -> u32 + ` + ) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cfg: Config = t.params; + + const pack4xI8Clamp = (vals: readonly [number, number, number, number]) => { + const result = new Uint32Array(1); + for (let i = 0; i < 4; ++i) { + const clampedValue = clamp(vals[i], { min: -128, max: 127 }); + result[0] |= (clampedValue & 0xff) << (i * 8); + } + return result[0]; + }; + + const testInputs = [ + [0, 0, 0, 0], + [1, 2, 3, 4], + [-1, 2, 3, 4], + [1, -2, 3, 4], + [1, 2, -3, 4], + [1, 2, 3, -4], + [-1, -2, 3, 4], + [-1, 2, -3, 4], + [-1, 2, 3, -4], + [1, -2, -3, 4], + [1, -2, 3, -4], + [1, 2, -3, -4], + [-1, -2, -3, 4], + [-1, -2, 3, -4], + [-1, 2, -3, -4], + [1, -2, -3, -4], + [-1, -2, -3, -4], + [126, 127, 128, 129], + [-130, -129, -128, -127], + [127, 128, -128, -129], + [32767, 32768, -32768, -32769], + ] as const; + + const makeCase = (vals: readonly [number, number, number, number]): Case => { + return { input: [toVector(vals, i32)], expected: u32(pack4xI8Clamp(vals)) }; + }; + const cases: Array<Case> = testInputs.flatMap(v => { + return [makeCase(v)]; + }); + + await run(t, builtin('pack4xI8Clamp'), [Type.vec4i], Type.u32, cfg, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8.spec.ts new file mode 100644 index 0000000000..9d08e88d44 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8.spec.ts @@ -0,0 +1,54 @@ +export const description = ` +Execution tests for the 'pack4xU8' builtin function + +@const fn pack4xU8(e: vec4<u32>) -> u32 +Pack the lower 8 bits of each component of e into a u32 value and drop all the unused bits. +Component e[i] of the input is mapped to bits (8 * i) through (8 * (i + 7)) of the result. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { u32, toVector, Type } from '../../../../../util/conversion.js'; +import { Case } from '../../case.js'; +import { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('basic') + .specURL('https://www.w3.org/TR/WGSL/#pack4xU8-builtin') + .desc( + ` +@const fn pack4xU8(e: vec4<u32>) -> u32 + ` + ) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cfg: Config = t.params; + + const pack4xU8 = (vals: readonly [number, number, number, number]) => { + const result = new Uint32Array(1); + for (let i = 0; i < 4; ++i) { + result[0] |= (vals[i] & 0xff) << (i * 8); + } + return result[0]; + }; + + const testInputs = [ + [0, 0, 0, 0], + [1, 2, 3, 4], + [255, 255, 255, 255], + [254, 255, 256, 257], + [65535, 65536, 255, 254], + ] as const; + + const makeCase = (vals: readonly [number, number, number, number]): Case => { + return { input: [toVector(vals, u32)], expected: u32(pack4xU8(vals)) }; + }; + const cases: Array<Case> = testInputs.flatMap(v => { + return [makeCase(v)]; + }); + + await run(t, builtin('pack4xU8'), [Type.vec4u], Type.u32, cfg, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8Clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8Clamp.spec.ts new file mode 100644 index 0000000000..ffecf9b877 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8Clamp.spec.ts @@ -0,0 +1,57 @@ +export const description = ` +Execution tests for the 'pack4xU8Clamp' builtin function + +@const fn pack4xU8Clamp(e: vec4<u32>) -> u32 +Clamp each component of e in the range of [0, 255] and then pack the lower 8 bits of each component +into a u32 value. Component e[i] of the input is mapped to bits (8 * i) through (8 * (i + 7)) of the +result. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { u32, toVector, Type } from '../../../../../util/conversion.js'; +import { clamp } from '../../../../../util/math.js'; +import { Case } from '../../case.js'; +import { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('basic') + .specURL('https://www.w3.org/TR/WGSL/#pack4xU8Clamp-builtin') + .desc( + ` +@const fn pack4xU8Clamp(e: vec4<u32>) -> u32 + ` + ) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cfg: Config = t.params; + + const pack4xU8Clamp = (vals: readonly [number, number, number, number]) => { + const result = new Uint32Array(1); + for (let i = 0; i < 4; ++i) { + const clampedValue = clamp(vals[i], { min: 0, max: 255 }); + result[0] |= clampedValue << (i * 8); + } + return result[0]; + }; + + const testInputs = [ + [0, 0, 0, 0], + [1, 2, 3, 4], + [255, 255, 255, 255], + [254, 255, 256, 257], + [65535, 65536, 255, 254], + ] as const; + + const makeCase = (vals: readonly [number, number, number, number]): Case => { + return { input: [toVector(vals, u32)], expected: u32(pack4xU8Clamp(vals)) }; + }; + const cases: Array<Case> = testInputs.flatMap(v => { + return [makeCase(v)]; + }); + + await run(t, builtin('pack4xU8Clamp'), [Type.vec4u], Type.u32, cfg, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.cache.ts new file mode 100644 index 0000000000..54777e702f --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.cache.ts @@ -0,0 +1,24 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarPairToIntervalCases( + FP[trait].scalarRange(), + FP[trait].scalarRange(), + nonConst ? 'unfiltered' : 'finite', + // pow has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].powInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('pow', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts index f9b4fe1cfa..84e9649c96 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'pow' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn pow(e1: T ,e2: T ) -> T Returns e1 raised to the power e2. Component-wise when T is a vector. @@ -9,58 +9,33 @@ Returns e1 raised to the power e2. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './pow.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('pow', { - f32_const: () => { - return FP.f32.generateScalarPairToIntervalCases( - fullF32Range(), - fullF32Range(), - 'finite', - FP.f32.powInterval - ); - }, - f32_non_const: () => { - return FP.f32.generateScalarPairToIntervalCases( - fullF32Range(), - fullF32Range(), - 'unfiltered', - FP.f32.powInterval - ); - }, - f16_const: () => { - return FP.f16.generateScalarPairToIntervalCases( - fullF16Range(), - fullF16Range(), - 'finite', - FP.f16.powInterval - ); - }, - f16_non_const: () => { - return FP.f16.generateScalarPairToIntervalCases( - fullF16Range(), - fullF16Range(), - 'unfiltered', - FP.f16.powInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('pow'), + [Type.abstractFloat, Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -70,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('pow'), [TypeF32, TypeF32], TypeF32, t.params, cases); + await run(t, builtin('pow'), [Type.f32, Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -84,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('pow'), [TypeF16, TypeF16], TypeF16, t.params, cases); + await run(t, builtin('pow'), [Type.f16, Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.cache.ts new file mode 100644 index 0000000000..91aa845d29 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.cache.ts @@ -0,0 +1,41 @@ +import { kValue } from '../../../../../util/constants.js'; +import { FP } from '../../../../../util/floating_point.js'; +import { scalarF16Range, scalarF32Range } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +export const d = makeCaseCache('quantizeToF16', { + f32_const: () => { + return FP.f32.generateScalarToIntervalCases( + [ + kValue.f16.negative.min, + kValue.f16.negative.max, + kValue.f16.negative.subnormal.min, + kValue.f16.negative.subnormal.max, + kValue.f16.positive.subnormal.min, + kValue.f16.positive.subnormal.max, + kValue.f16.positive.min, + kValue.f16.positive.max, + ...scalarF16Range(), + ], + 'finite', + FP.f32.quantizeToF16Interval + ); + }, + f32_non_const: () => { + return FP.f32.generateScalarToIntervalCases( + [ + kValue.f16.negative.min, + kValue.f16.negative.max, + kValue.f16.negative.subnormal.min, + kValue.f16.negative.subnormal.max, + kValue.f16.positive.subnormal.min, + kValue.f16.positive.subnormal.max, + kValue.f16.positive.min, + kValue.f16.positive.max, + ...scalarF32Range(), + ], + 'unfiltered', + FP.f32.quantizeToF16Interval + ); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts index b37d4c5afb..0aa9669e93 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts @@ -10,54 +10,14 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { kValue } from '../../../../../util/constants.js'; -import { TypeF32 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF16Range, fullF32Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, run } from '../../expression.js'; import { builtin } from './builtin.js'; +import { d } from './quantizeToF16.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('quantizeToF16', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases( - [ - kValue.f16.negative.min, - kValue.f16.negative.max, - kValue.f16.negative.subnormal.min, - kValue.f16.negative.subnormal.max, - kValue.f16.positive.subnormal.min, - kValue.f16.positive.subnormal.max, - kValue.f16.positive.min, - kValue.f16.positive.max, - ...fullF16Range(), - ], - 'finite', - FP.f32.quantizeToF16Interval - ); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases( - [ - kValue.f16.negative.min, - kValue.f16.negative.max, - kValue.f16.negative.subnormal.min, - kValue.f16.negative.subnormal.max, - kValue.f16.positive.subnormal.min, - kValue.f16.positive.subnormal.max, - kValue.f16.positive.min, - kValue.f16.positive.max, - ...fullF32Range(), - ], - 'unfiltered', - FP.f32.quantizeToF16Interval - ); - }, -}); - g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`f32 tests`) @@ -66,5 +26,5 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('quantizeToF16'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('quantizeToF16'), [Type.f32], Type.f32, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.cache.ts new file mode 100644 index 0000000000..8ed0fbbd2b --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.cache.ts @@ -0,0 +1,18 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + FP[trait].scalarRange(), + trait !== 'abstract' ? 'unfiltered' : 'finite', + // radians has an inherited accuracy, so abstract is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].radiansInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('radians', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts index 63ae45b656..a405807ec0 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'radians' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn radians(e1: T ) -> T Converts degrees to radians, approximating e1 * π / 180. @@ -10,40 +10,14 @@ Component-wise when T is a vector import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF16Range, fullF32Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { abstractBuiltin, builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './radians.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('radians', { - f32: () => { - return FP.f32.generateScalarToIntervalCases( - fullF32Range(), - 'unfiltered', - FP.f32.radiansInterval - ); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases( - fullF16Range(), - 'unfiltered', - FP.f16.radiansInterval - ); - }, - abstract: () => { - return FP.abstract.generateScalarToIntervalCases( - fullF16Range(), - 'unfiltered', - FP.abstract.radiansInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) @@ -56,9 +30,9 @@ g.test('abstract_float') const cases = await d.get('abstract'); await run( t, - abstractBuiltin('radians'), - [TypeAbstractFloat], - TypeAbstractFloat, + abstractFloatBuiltin('radians'), + [Type.abstractFloat], + Type.abstractFloat, t.params, cases ); @@ -72,7 +46,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('radians'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('radians'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -86,5 +60,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('radians'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('radians'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.cache.ts new file mode 100644 index 0000000000..ca57226f3a --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.cache.ts @@ -0,0 +1,26 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_vecN_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([2, 3, 4] as const).flatMap(dim => + ([true, false] as const).map(nonConst => ({ + [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateVectorPairToVectorCases( + FP[trait].sparseVectorRange(dim), + FP[trait].sparseVectorRange(dim), + nonConst ? 'unfiltered' : 'finite', + // reflect has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].reflectInterval + ); + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('reflect', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts index 2614c4e686..e36558d30c 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'reflect' builtin function -T is vecN<AbstractFloat>, vecN<f32>, or vecN<f16> +T is vecN<Type.abstractFloat>, vecN<f32>, or vecN<f16> @const fn reflect(e1: T, e2: T ) -> T For the incident vector e1 and surface orientation e2, returns the reflection direction e1-2*dot(e2,e1)*e2. @@ -9,58 +9,61 @@ direction e1-2*dot(e2,e1)*e2. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { sparseVectorF32Range, sparseVectorF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './reflect.cache.js'; export const g = makeTestGroup(GPUTest); -// Cases: f32_vecN_[non_]const -const f32_vec_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f32.generateVectorPairToVectorCases( - sparseVectorF32Range(n), - sparseVectorF32Range(n), - nonConst ? 'unfiltered' : 'finite', - FP.f32.reflectInterval - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -// Cases: f16_vecN_[non_]const -const f16_vec_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f16.generateVectorPairToVectorCases( - sparseVectorF16Range(n), - sparseVectorF16Range(n), - nonConst ? 'unfiltered' : 'finite', - FP.f16.reflectInterval - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); +g.test('abstract_float_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec2s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec2_const'); + await run( + t, + abstractFloatBuiltin('reflect'), + [Type.vec2af, Type.vec2af], + Type.vec2af, + t.params, + cases + ); + }); -export const d = makeCaseCache('reflect', { - ...f32_vec_cases, - ...f16_vec_cases, -}); +g.test('abstract_float_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec3s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec3_const'); + await run( + t, + abstractFloatBuiltin('reflect'), + [Type.vec3af, Type.vec3af], + Type.vec3af, + t.params, + cases + ); + }); -g.test('abstract_float') - .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') - .desc(`abstract float tests`) - .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const)) - .unimplemented(); +g.test('abstract_float_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec4s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec4_const'); + await run( + t, + abstractFloatBuiltin('reflect'), + [Type.vec4af, Type.vec4af], + Type.vec4af, + t.params, + cases + ); + }); g.test('f32_vec2') .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') @@ -70,14 +73,7 @@ g.test('f32_vec2') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const' ); - await run( - t, - builtin('reflect'), - [TypeVec(2, TypeF32), TypeVec(2, TypeF32)], - TypeVec(2, TypeF32), - t.params, - cases - ); + await run(t, builtin('reflect'), [Type.vec2f, Type.vec2f], Type.vec2f, t.params, cases); }); g.test('f32_vec3') @@ -88,14 +84,7 @@ g.test('f32_vec3') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const' ); - await run( - t, - builtin('reflect'), - [TypeVec(3, TypeF32), TypeVec(3, TypeF32)], - TypeVec(3, TypeF32), - t.params, - cases - ); + await run(t, builtin('reflect'), [Type.vec3f, Type.vec3f], Type.vec3f, t.params, cases); }); g.test('f32_vec4') @@ -106,14 +95,7 @@ g.test('f32_vec4') const cases = await d.get( t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const' ); - await run( - t, - builtin('reflect'), - [TypeVec(4, TypeF32), TypeVec(4, TypeF32)], - TypeVec(4, TypeF32), - t.params, - cases - ); + await run(t, builtin('reflect'), [Type.vec4f, Type.vec4f], Type.vec4f, t.params, cases); }); g.test('f16_vec2') @@ -127,14 +109,7 @@ g.test('f16_vec2') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const' ); - await run( - t, - builtin('reflect'), - [TypeVec(2, TypeF16), TypeVec(2, TypeF16)], - TypeVec(2, TypeF16), - t.params, - cases - ); + await run(t, builtin('reflect'), [Type.vec2h, Type.vec2h], Type.vec2h, t.params, cases); }); g.test('f16_vec3') @@ -148,14 +123,7 @@ g.test('f16_vec3') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const' ); - await run( - t, - builtin('reflect'), - [TypeVec(3, TypeF16), TypeVec(3, TypeF16)], - TypeVec(3, TypeF16), - t.params, - cases - ); + await run(t, builtin('reflect'), [Type.vec3h, Type.vec3h], Type.vec3h, t.params, cases); }); g.test('f16_vec4') @@ -169,12 +137,5 @@ g.test('f16_vec4') const cases = await d.get( t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const' ); - await run( - t, - builtin('reflect'), - [TypeVec(4, TypeF16), TypeVec(4, TypeF16)], - TypeVec(4, TypeF16), - t.params, - cases - ); + await run(t, builtin('reflect'), [Type.vec4h, Type.vec4h], Type.vec4h, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.cache.ts new file mode 100644 index 0000000000..a759f5b669 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.cache.ts @@ -0,0 +1,116 @@ +import { ROArrayArray } from '../../../../../../common/util/types.js'; +import { toVector } from '../../../../../util/conversion.js'; +import { FP, FPKind } from '../../../../../util/floating_point.js'; +import { Case, selectNCases } from '../../case.js'; +import { makeCaseCache } from '../../case_cache.js'; +import { IntervalFilter } from '../../interval_filter.js'; + +// Using a bespoke implementation of make*Case and generate*Cases here +// since refract is the only builtin with the API signature +// (vec, vec, scalar) -> vec + +/** + * @returns a Case for `refract` + * @param argumentKind what kind of floating point numbers being operated on + * @param parameterKind what kind of floating point operation should be performed, + * should be the same as argumentKind, except for abstract + * @param i the `i` param for the case + * @param s the `s` param for the case + * @param r the `r` param for the case + * @param check what interval checking to apply + * */ +function makeCase( + argumentKind: FPKind, + parameterKind: FPKind, + i: readonly number[], + s: readonly number[], + r: number, + check: IntervalFilter +): Case | undefined { + const fp = FP[argumentKind]; + i = i.map(fp.quantize); + s = s.map(fp.quantize); + r = fp.quantize(r); + + const vectors = FP[parameterKind].refractInterval(i, s, r); + if (check === 'finite' && vectors.some(e => !e.isFinite())) { + return undefined; + } + + return { + input: [toVector(i, fp.scalarBuilder), toVector(s, fp.scalarBuilder), fp.scalarBuilder(r)], + expected: vectors, + }; +} + +/** + * @returns an array of Cases for `refract` + * @param argumentKind what kind of floating point numbers being operated on + * @param parameterKind what kind of floating point operation should be performed, + * should be the same as argumentKind, except for abstract + * @param param_is array of inputs to try for the `i` param + * @param param_ss array of inputs to try for the `s` param + * @param param_rs array of inputs to try for the `r` param + * @param check what interval checking to apply + */ +function generateCases( + argumentKind: FPKind, + parameterKind: FPKind, + param_is: ROArrayArray<number>, + param_ss: ROArrayArray<number>, + param_rs: readonly number[], + check: IntervalFilter +): Case[] { + // Cannot use `cartesianProduct` here due to heterogeneous param types + return param_is + .flatMap(i => { + return param_ss.flatMap(s => { + return param_rs.map(r => { + return makeCase(argumentKind, parameterKind, i, s, r, check); + }); + }); + }) + .filter((c): c is Case => c !== undefined); +} + +// Cases: [f32|f16|abstract]_vecN_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([2, 3, 4] as const).flatMap(dim => + ([true, false] as const).map(nonConst => ({ + [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + if (trait !== 'abstract') { + return generateCases( + trait, + trait, + FP[trait].sparseVectorRange(dim), + FP[trait].sparseVectorRange(dim), + FP[trait].sparseScalarRange(), + nonConst ? 'unfiltered' : 'finite' + ); + } else { + // Restricting the number of cases, because a vector of abstract floats needs to be returned, which is costly. + return selectNCases( + 'faceForward', + 20, + generateCases( + trait, + // refract has an inherited accuracy, so is only expected to be as accurate as f32 + 'f32', + FP[trait].sparseVectorRange(dim), + FP[trait].sparseVectorRange(dim), + FP[trait].sparseScalarRange(), + nonConst ? 'unfiltered' : 'finite' + ) + ); + } + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('refract', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts index be1a76b437..5b51f30eee 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts @@ -2,7 +2,7 @@ export const description = ` Execution tests for the 'refract' builtin function T is vecN<I> -I is AbstractFloat, f32, or f16 +I is abstract-float, f32, or f16 @const fn refract(e1: T ,e2: T ,e3: I ) -> T For the incident vector e1 and surface normal e2, and the ratio of indices of refraction e3, let k = 1.0 -e3*e3* (1.0 - dot(e2,e1) * dot(e2,e1)). @@ -11,129 +11,62 @@ vector e3*e1- (e3* dot(e2,e1) + sqrt(k)) *e2. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; -import { ROArrayArray } from '../../../../../../common/util/types.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { toVector, TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js'; -import { FP, FPKind } from '../../../../../util/floating_point.js'; -import { - sparseVectorF32Range, - sparseVectorF16Range, - sparseF32Range, - sparseF16Range, -} from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, Case, IntervalFilter, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './refract.cache.js'; export const g = makeTestGroup(GPUTest); -// Using a bespoke implementation of make*Case and generate*Cases here -// since refract is the only builtin with the API signature -// (vec, vec, scalar) -> vec - -/** - * @returns a Case for `refract` - * @param kind what type of floating point numbers to operate on - * @param i the `i` param for the case - * @param s the `s` param for the case - * @param r the `r` param for the case - * @param check what interval checking to apply - * */ -function makeCase( - kind: FPKind, - i: readonly number[], - s: readonly number[], - r: number, - check: IntervalFilter -): Case | undefined { - const fp = FP[kind]; - i = i.map(fp.quantize); - s = s.map(fp.quantize); - r = fp.quantize(r); - - const vectors = fp.refractInterval(i, s, r); - if (check === 'finite' && vectors.some(e => !e.isFinite())) { - return undefined; - } - - return { - input: [toVector(i, fp.scalarBuilder), toVector(s, fp.scalarBuilder), fp.scalarBuilder(r)], - expected: fp.refractInterval(i, s, r), - }; -} - -/** - * @returns an array of Cases for `refract` - * @param kind what type of floating point numbers to operate on - * @param param_is array of inputs to try for the `i` param - * @param param_ss array of inputs to try for the `s` param - * @param param_rs array of inputs to try for the `r` param - * @param check what interval checking to apply - */ -function generateCases( - kind: FPKind, - param_is: ROArrayArray<number>, - param_ss: ROArrayArray<number>, - param_rs: readonly number[], - check: IntervalFilter -): Case[] { - // Cannot use `cartesianProduct` here due to heterogeneous param types - return param_is - .flatMap(i => { - return param_ss.flatMap(s => { - return param_rs.map(r => { - return makeCase(kind, i, s, r, check); - }); - }); - }) - .filter((c): c is Case => c !== undefined); -} - -// Cases: f32_vecN_[non_]const -const f32_vec_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => { - return generateCases( - 'f32', - sparseVectorF32Range(n), - sparseVectorF32Range(n), - sparseF32Range(), - nonConst ? 'unfiltered' : 'finite' - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -// Cases: f16_vecN_[non_]const -const f16_vec_cases = ([2, 3, 4] as const) - .flatMap(n => - ([true, false] as const).map(nonConst => ({ - [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => { - return generateCases( - 'f16', - sparseVectorF16Range(n), - sparseVectorF16Range(n), - sparseF16Range(), - nonConst ? 'unfiltered' : 'finite' - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); +g.test('abstract_float_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec2s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec2_const'); + await run( + t, + abstractFloatBuiltin('refract'), + [Type.vec2af, Type.vec2af, Type.abstractFloat], + Type.vec2af, + t.params, + cases + ); + }); -export const d = makeCaseCache('refract', { - ...f32_vec_cases, - ...f16_vec_cases, -}); +g.test('abstract_float_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec3s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec3_const'); + await run( + t, + abstractFloatBuiltin('refract'), + [Type.vec3af, Type.vec3af, Type.abstractFloat], + Type.vec3af, + t.params, + cases + ); + }); -g.test('abstract_float') - .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') - .desc(`abstract float tests`) - .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const)) - .unimplemented(); +g.test('abstract_float_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`abstract float tests using vec4s`) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec4_const'); + await run( + t, + abstractFloatBuiltin('refract'), + [Type.vec4af, Type.vec4af, Type.abstractFloat], + Type.vec4af, + t.params, + cases + ); + }); g.test('f32_vec2') .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') @@ -146,8 +79,8 @@ g.test('f32_vec2') await run( t, builtin('refract'), - [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeF32], - TypeVec(2, TypeF32), + [Type.vec2f, Type.vec2f, Type.f32], + Type.vec2f, t.params, cases ); @@ -164,8 +97,8 @@ g.test('f32_vec3') await run( t, builtin('refract'), - [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeF32], - TypeVec(3, TypeF32), + [Type.vec3f, Type.vec3f, Type.f32], + Type.vec3f, t.params, cases ); @@ -182,8 +115,8 @@ g.test('f32_vec4') await run( t, builtin('refract'), - [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeF32], - TypeVec(4, TypeF32), + [Type.vec4f, Type.vec4f, Type.f32], + Type.vec4f, t.params, cases ); @@ -203,8 +136,8 @@ g.test('f16_vec2') await run( t, builtin('refract'), - [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeF16], - TypeVec(2, TypeF16), + [Type.vec2h, Type.vec2h, Type.f16], + Type.vec2h, t.params, cases ); @@ -224,8 +157,8 @@ g.test('f16_vec3') await run( t, builtin('refract'), - [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeF16], - TypeVec(3, TypeF16), + [Type.vec3h, Type.vec3h, Type.f16], + Type.vec3h, t.params, cases ); @@ -245,8 +178,8 @@ g.test('f16_vec4') await run( t, builtin('refract'), - [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeF16], - TypeVec(4, TypeF16), + [Type.vec4h, Type.vec4h, Type.f16], + Type.vec4h, t.params, cases ); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts index 6acb359822..e235e62a52 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts @@ -10,7 +10,7 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeU32, u32Bits, TypeI32, i32Bits } from '../../../../../util/conversion.js'; +import { u32Bits, i32Bits, Type } from '../../../../../util/conversion.js'; import { allInputSources, Config, run } from '../../expression.js'; import { builtin } from './builtin.js'; @@ -26,7 +26,7 @@ g.test('u32') .fn(async t => { const cfg: Config = t.params; // prettier-ignore - await run(t, builtin('reverseBits'), [TypeU32], TypeU32, cfg, [ + await run(t, builtin('reverseBits'), [Type.u32], Type.u32, cfg, [ // Zero { input: u32Bits(0b00000000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000000) }, @@ -142,7 +142,7 @@ g.test('i32') .fn(async t => { const cfg: Config = t.params; // prettier-ignore - await run(t, builtin('reverseBits'), [TypeI32], TypeI32, cfg, [ + await run(t, builtin('reverseBits'), [Type.i32], Type.i32, cfg, [ // Zero { input: i32Bits(0b00000000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000000) }, diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.cache.ts new file mode 100644 index 0000000000..e5383b2075 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.cache.ts @@ -0,0 +1,24 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// See https://github.com/gpuweb/cts/issues/2766 for details +const kIssue2766Value = { + abstract: 0x8000_0000_0000_0000, + f32: 0x8000_0000, + f16: 0x8000, +}; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + [kIssue2766Value[trait], ...FP[trait].scalarRange()], + 'unfiltered', + FP[trait].roundInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('round', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts index bd40ed4b2a..eeaf41b381 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'round' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn round(e: T) -> T Result is the integer k nearest to e, as a floating point value. @@ -12,46 +12,33 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './round.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('round', { - f32: () => { - return FP.f32.generateScalarToIntervalCases( - [ - 0x80000000, // https://github.com/gpuweb/cts/issues/2766, - ...fullF32Range(), - ], - 'unfiltered', - FP.f32.roundInterval - ); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases( - [ - 0x8000, // https://github.com/gpuweb/cts/issues/2766 - ...fullF16Range(), - ], - 'unfiltered', - FP.f16.roundInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractFloatBuiltin('round'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -61,7 +48,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('round'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('round'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -75,5 +62,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('round'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('round'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.cache.ts new file mode 100644 index 0000000000..4a4ffeee30 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.cache.ts @@ -0,0 +1,18 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { linearRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + [...linearRange(0.0, 1.0, 20), ...FP[trait].scalarRange()], + 'unfiltered', + FP[trait].saturateInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('saturate', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts index 2f16502921..79c61e4eec 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'saturate' builtin function -S is AbstractFloat, f32, or f16 +S is abstract-float, f32, or f16 T is S or vecN<S> @const fn saturate(e: T) -> T Returns clamp(e, 0.0, 1.0). Component-wise when T is a vector. @@ -9,52 +9,14 @@ Returns clamp(e, 0.0, 1.0). Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF16Range, fullF32Range, fullF64Range, linearRange } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { abstractBuiltin, builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './saturate.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('saturate', { - f32: () => { - return FP.f32.generateScalarToIntervalCases( - [ - // Non-clamped values - ...linearRange(0.0, 1.0, 20), - ...fullF32Range(), - ], - 'unfiltered', - FP.f32.saturateInterval - ); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases( - [ - // Non-clamped values - ...linearRange(0.0, 1.0, 20), - ...fullF16Range(), - ], - 'unfiltered', - FP.f16.saturateInterval - ); - }, - abstract: () => { - return FP.abstract.generateScalarToIntervalCases( - [ - // Non-clamped values - ...linearRange(0.0, 1.0, 20), - ...fullF64Range(), - ], - 'unfiltered', - FP.abstract.saturateInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) @@ -67,9 +29,9 @@ g.test('abstract_float') const cases = await d.get('abstract'); await run( t, - abstractBuiltin('saturate'), - [TypeAbstractFloat], - TypeAbstractFloat, + abstractFloatBuiltin('saturate'), + [Type.abstractFloat], + Type.abstractFloat, t.params, cases ); @@ -82,7 +44,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('saturate'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('saturate'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -96,5 +58,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('saturate'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('saturate'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts index c64f989f42..63accbc2d4 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts @@ -14,12 +14,6 @@ import { makeTestGroup } from '../../../../../../common/framework/test_group.js' import { GPUTest } from '../../../../../gpu_test.js'; import { VectorType, - TypeVec, - TypeBool, - TypeF32, - TypeF16, - TypeI32, - TypeU32, f32, f16, i32, @@ -31,11 +25,14 @@ import { vec3, vec4, abstractFloat, - TypeAbstractFloat, + abstractInt, + ScalarValue, + Type, } from '../../../../../util/conversion.js'; -import { run, CaseList, allInputSources } from '../../expression.js'; +import { Case } from '../../case.js'; +import { run, allInputSources } from '../../expression.js'; -import { abstractBuiltin, builtin } from './builtin.js'; +import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js'; export const g = makeTestGroup(GPUTest); @@ -43,32 +40,47 @@ function makeBool(n: number) { return bool((n & 1) === 1); } -type scalarKind = 'b' | 'af' | 'f' | 'h' | 'i' | 'u'; +type scalarKind = 'b' | 'af' | 'f' | 'h' | 'ai' | 'i' | 'u'; const dataType = { b: { - type: TypeBool, - constructor: makeBool, + type: Type.bool, + scalar_builder: makeBool, + shader_builder: builtin('select'), }, af: { - type: TypeAbstractFloat, - constructor: abstractFloat, + type: Type.abstractFloat, + scalar_builder: abstractFloat, + shader_builder: abstractFloatBuiltin('select'), }, f: { - type: TypeF32, - constructor: f32, + type: Type.f32, + scalar_builder: f32, + shader_builder: builtin('select'), }, h: { - type: TypeF16, - constructor: f16, + type: Type.f16, + scalar_builder: f16, + shader_builder: builtin('select'), + }, + ai: { + type: Type.abstractInt, + // Only ints are used in the tests below, so the conversion to bigint will + // be safe. If a non-int is passed in this will Error. + scalar_builder: (v: number): ScalarValue => { + return abstractInt(BigInt(v)); + }, + shader_builder: abstractIntBuiltin('select'), }, i: { - type: TypeI32, - constructor: i32, + type: Type.i32, + scalar_builder: i32, + shader_builder: builtin('select'), }, u: { - type: TypeU32, - constructor: u32, + type: Type.u32, + scalar_builder: u32, + shader_builder: builtin('select'), }, }; @@ -78,7 +90,7 @@ g.test('scalar') .params(u => u .combine('inputSource', allInputSources) - .combine('component', ['b', 'af', 'f', 'h', 'i', 'u'] as const) + .combine('component', ['b', 'af', 'f', 'h', 'ai', 'i', 'u'] as const) .combine('overload', ['scalar', 'vec2', 'vec3', 'vec4'] as const) ) .beforeAllSubcases(t => { @@ -86,10 +98,11 @@ g.test('scalar') t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); } t.skipIf(t.params.component === 'af' && t.params.inputSource !== 'const'); + t.skipIf(t.params.component === 'ai' && t.params.inputSource !== 'const'); }) .fn(async t => { - const componentType = dataType[t.params.component as scalarKind].type; - const cons = dataType[t.params.component as scalarKind].constructor; + const componentType = dataType[t.params.component].type; + const scalar_builder = dataType[t.params.component].scalar_builder; // Create the scalar values that will be selected from, either as scalars // or vectors. @@ -97,39 +110,40 @@ g.test('scalar') // Each boolean will select between c[k] and c[k+4]. Those values must // always compare as different. The tricky case is boolean, where the parity // has to be different, i.e. c[k]-c[k+4] must be odd. - const c = [0, 1, 2, 3, 5, 6, 7, 8].map(i => cons(i)); + const scalars = [0, 1, 2, 3, 5, 6, 7, 8].map(i => scalar_builder(i)); + // Now form vectors that will have different components from each other. - const v2a = vec2(c[0], c[1]); - const v2b = vec2(c[4], c[5]); - const v3a = vec3(c[0], c[1], c[2]); - const v3b = vec3(c[4], c[5], c[6]); - const v4a = vec4(c[0], c[1], c[2], c[3]); - const v4b = vec4(c[4], c[5], c[6], c[7]); + const v2a = vec2(scalars[0], scalars[1]); + const v2b = vec2(scalars[4], scalars[5]); + const v3a = vec3(scalars[0], scalars[1], scalars[2]); + const v3b = vec3(scalars[4], scalars[5], scalars[6]); + const v4a = vec4(scalars[0], scalars[1], scalars[2], scalars[3]); + const v4b = vec4(scalars[4], scalars[5], scalars[6], scalars[7]); const overloads = { scalar: { type: componentType, cases: [ - { input: [c[0], c[1], False], expected: c[0] }, - { input: [c[0], c[1], True], expected: c[1] }, + { input: [scalars[0], scalars[1], False], expected: scalars[0] }, + { input: [scalars[0], scalars[1], True], expected: scalars[1] }, ], }, vec2: { - type: TypeVec(2, componentType), + type: Type.vec(2, componentType), cases: [ { input: [v2a, v2b, False], expected: v2a }, { input: [v2a, v2b, True], expected: v2b }, ], }, vec3: { - type: TypeVec(3, componentType), + type: Type.vec(3, componentType), cases: [ { input: [v3a, v3b, False], expected: v3a }, { input: [v3a, v3b, True], expected: v3b }, ], }, vec4: { - type: TypeVec(4, componentType), + type: Type.vec(4, componentType), cases: [ { input: [v4a, v4b, False], expected: v4a }, { input: [v4a, v4b, True], expected: v4b }, @@ -140,8 +154,8 @@ g.test('scalar') await run( t, - t.params.component === 'af' ? abstractBuiltin('select') : builtin('select'), - [overload.type, overload.type, TypeBool], + dataType[t.params.component as scalarKind].shader_builder, + [overload.type, overload.type, Type.bool], overload.type, t.params, overload.cases @@ -154,7 +168,7 @@ g.test('vector') .params(u => u .combine('inputSource', allInputSources) - .combine('component', ['b', 'af', 'f', 'h', 'i', 'u'] as const) + .combine('component', ['b', 'af', 'f', 'h', 'ai', 'i', 'u'] as const) .combine('overload', ['vec2', 'vec3', 'vec4'] as const) ) .beforeAllSubcases(t => { @@ -162,29 +176,30 @@ g.test('vector') t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); } t.skipIf(t.params.component === 'af' && t.params.inputSource !== 'const'); + t.skipIf(t.params.component === 'ai' && t.params.inputSource !== 'const'); }) .fn(async t => { - const componentType = dataType[t.params.component as scalarKind].type; - const cons = dataType[t.params.component as scalarKind].constructor; + const componentType = dataType[t.params.component].type; + const scalar_builder = dataType[t.params.component].scalar_builder; // Create the scalar values that will be selected from. // // Each boolean will select between c[k] and c[k+4]. Those values must // always compare as different. The tricky case is boolean, where the parity // has to be different, i.e. c[k]-c[k+4] must be odd. - const c = [0, 1, 2, 3, 5, 6, 7, 8].map(i => cons(i)); + const scalars = [0, 1, 2, 3, 5, 6, 7, 8].map(i => scalar_builder(i)); const T = True; const F = False; - let tests: { dataType: VectorType; boolType: VectorType; cases: CaseList }; + let tests: { dataType: VectorType; boolType: VectorType; cases: Case[] }; switch (t.params.overload) { case 'vec2': { - const a = vec2(c[0], c[1]); - const b = vec2(c[4], c[5]); + const a = vec2(scalars[0], scalars[1]); + const b = vec2(scalars[4], scalars[5]); tests = { - dataType: TypeVec(2, componentType), - boolType: TypeVec(2, TypeBool), + dataType: Type.vec(2, componentType), + boolType: Type.vec(2, Type.bool), cases: [ { input: [a, b, vec2(F, F)], expected: vec2(a.x, a.y) }, { input: [a, b, vec2(F, T)], expected: vec2(a.x, b.y) }, @@ -195,11 +210,11 @@ g.test('vector') break; } case 'vec3': { - const a = vec3(c[0], c[1], c[2]); - const b = vec3(c[4], c[5], c[6]); + const a = vec3(scalars[0], scalars[1], scalars[2]); + const b = vec3(scalars[4], scalars[5], scalars[6]); tests = { - dataType: TypeVec(3, componentType), - boolType: TypeVec(3, TypeBool), + dataType: Type.vec(3, componentType), + boolType: Type.vec(3, Type.bool), cases: [ { input: [a, b, vec3(F, F, F)], expected: vec3(a.x, a.y, a.z) }, { input: [a, b, vec3(F, F, T)], expected: vec3(a.x, a.y, b.z) }, @@ -214,11 +229,11 @@ g.test('vector') break; } case 'vec4': { - const a = vec4(c[0], c[1], c[2], c[3]); - const b = vec4(c[4], c[5], c[6], c[7]); + const a = vec4(scalars[0], scalars[1], scalars[2], scalars[3]); + const b = vec4(scalars[4], scalars[5], scalars[6], scalars[7]); tests = { - dataType: TypeVec(4, componentType), - boolType: TypeVec(4, TypeBool), + dataType: Type.vec(4, componentType), + boolType: Type.vec(4, Type.bool), cases: [ { input: [a, b, vec4(F, F, F, F)], expected: vec4(a.x, a.y, a.z, a.w) }, { input: [a, b, vec4(F, F, F, T)], expected: vec4(a.x, a.y, a.z, b.w) }, @@ -244,7 +259,7 @@ g.test('vector') await run( t, - t.params.component === 'af' ? abstractBuiltin('select') : builtin('select'), + dataType[t.params.component].shader_builder, [tests.dataType, tests.dataType, tests.boolType], tests.dataType, t.params, diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.cache.ts new file mode 100644 index 0000000000..09f4de19ac --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.cache.ts @@ -0,0 +1,31 @@ +import { abstractInt, i32 } from '../../../../../util/conversion.js'; +import { FP } from '../../../../../util/floating_point.js'; +import { fullI32Range, fullI64Range } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract] +const fp_cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait === 'abstract' ? 'abstract_float' : trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + FP[trait].scalarRange(), + 'unfiltered', + FP[trait].signInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('sign', { + ...fp_cases, + i32: () => + fullI32Range().map(i => { + const signFunc = (i: number): number => (i < 0 ? -1 : i > 0 ? 1 : 0); + return { input: [i32(i)], expected: i32(signFunc(i)) }; + }), + abstract_int: () => + fullI64Range().map(i => { + const signFunc = (i: bigint): bigint => (i < 0n ? -1n : i > 0n ? 1n : 0n); + return { input: [abstractInt(i)], expected: abstractInt(signFunc(i)) }; + }), +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts index a147acf6fb..f7d2e3ccfd 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'sign' builtin function -S is AbstractFloat, AbstractInt, i32, f32, f16 +S is abstract-float, Type.abstractInt, i32, f32, f16 T is S or vecN<S> @const fn sign(e: T ) -> T Returns the sign of e. Component-wise when T is a vector. @@ -9,48 +9,14 @@ Returns the sign of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { - i32, - TypeF32, - TypeF16, - TypeI32, - TypeAbstractFloat, -} from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { - fullF32Range, - fullF16Range, - fullI32Range, - fullF64Range, -} from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { abstractBuiltin, builtin } from './builtin.js'; +import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js'; +import { d } from './sign.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('sign', { - f32: () => { - return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.signInterval); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.signInterval); - }, - abstract_float: () => { - return FP.abstract.generateScalarToIntervalCases( - fullF64Range(), - 'unfiltered', - FP.abstract.signInterval - ); - }, - i32: () => - fullI32Range().map(i => { - const signFunc = (i: number): number => (i < 0 ? -1 : i > 0 ? 1 : 0); - return { input: [i32(i)], expected: i32(signFunc(i)) }; - }), -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#sign-builtin') .desc(`abstract float tests`) @@ -61,16 +27,28 @@ g.test('abstract_float') ) .fn(async t => { const cases = await d.get('abstract_float'); - await run(t, abstractBuiltin('sign'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases); + await run( + t, + abstractFloatBuiltin('sign'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); }); g.test('abstract_int') .specURL('https://www.w3.org/TR/WGSL/#sign-builtin') .desc(`abstract int tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_int'); + await run(t, abstractIntBuiltin('sign'), [Type.abstractInt], Type.abstractInt, t.params, cases); + }); g.test('i32') .specURL('https://www.w3.org/TR/WGSL/#sign-builtin') @@ -80,7 +58,7 @@ g.test('i32') ) .fn(async t => { const cases = await d.get('i32'); - await run(t, builtin('sign'), [TypeI32], TypeI32, t.params, cases); + await run(t, builtin('sign'), [Type.i32], Type.i32, t.params, cases); }); g.test('f32') @@ -91,7 +69,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('sign'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('sign'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -105,5 +83,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('sign'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('sign'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.cache.ts new file mode 100644 index 0000000000..74acf6a62c --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.cache.ts @@ -0,0 +1,23 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { linearRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + [ + // Well-defined accuracy range + ...linearRange(-Math.PI, Math.PI, 100), + ...FP[trait].scalarRange(), + ], + trait === 'abstract' ? 'finite' : 'unfiltered', + // sin has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].sinInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('sin', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts index 4ab3ae7a3d..fc706c5d63 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'sin' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn sin(e: T ) -> T Returns the sine of e. Component-wise when T is a vector. @@ -9,48 +9,33 @@ Returns the sine of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './sin.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('sin', { - f32: () => { - return FP.f32.generateScalarToIntervalCases( - [ - // Well-defined accuracy range - ...linearRange(-Math.PI, Math.PI, 1000), - ...fullF32Range(), - ], - 'unfiltered', - FP.f32.sinInterval - ); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases( - [ - // Well-defined accuracy range - ...linearRange(-Math.PI, Math.PI, 1000), - ...fullF16Range(), - ], - 'unfiltered', - FP.f16.sinInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractFloatBuiltin('sin'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -66,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1] ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('sin'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('sin'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -80,5 +65,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('sin'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('sin'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.cache.ts new file mode 100644 index 0000000000..aba275581d --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.cache.ts @@ -0,0 +1,23 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarToIntervalCases( + FP[trait].scalarRange(), + nonConst ? 'unfiltered' : 'finite', + // sinh has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].sinhInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('sinh', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts index d9b93a3dc8..5ffe628de3 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'sinh' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn sinh(e: T ) -> T Returns the hyperbolic sine of e. Component-wise when T is a vector. @@ -9,38 +9,33 @@ Returns the hyperbolic sine of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './sinh.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('sinh', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.sinhInterval); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.sinhInterval); - }, - f16_const: () => { - return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.sinhInterval); - }, - f16_non_const: () => { - return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.sinhInterval); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('sinh'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -50,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('sinh'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('sinh'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -64,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('sinh'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('sinh'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.cache.ts new file mode 100644 index 0000000000..4ba7615b43 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.cache.ts @@ -0,0 +1,25 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarTripleToIntervalCases( + FP[trait].sparseScalarRange(), + FP[trait].sparseScalarRange(), + FP[trait].sparseScalarRange(), + nonConst ? 'unfiltered' : 'finite', + // smoothstep has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].smoothStepInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('smoothstep', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts index 20d2a4edbc..42d8d09ff5 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'smoothstep' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn smoothstep(low: T , high: T , x: T ) -> T Returns the smooth Hermite interpolation between 0 and 1. @@ -11,62 +11,33 @@ For scalar T, the result is t * t * (3.0 - 2.0 * t), where t = clamp((x - low) / import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { sparseF32Range, sparseF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './smoothstep.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('smoothstep', { - f32_const: () => { - return FP.f32.generateScalarTripleToIntervalCases( - sparseF32Range(), - sparseF32Range(), - sparseF32Range(), - 'finite', - FP.f32.smoothStepInterval - ); - }, - f32_non_const: () => { - return FP.f32.generateScalarTripleToIntervalCases( - sparseF32Range(), - sparseF32Range(), - sparseF32Range(), - 'unfiltered', - FP.f32.smoothStepInterval - ); - }, - f16_const: () => { - return FP.f16.generateScalarTripleToIntervalCases( - sparseF16Range(), - sparseF16Range(), - sparseF16Range(), - 'finite', - FP.f16.smoothStepInterval - ); - }, - f16_non_const: () => { - return FP.f16.generateScalarTripleToIntervalCases( - sparseF16Range(), - sparseF16Range(), - sparseF16Range(), - 'unfiltered', - FP.f16.smoothStepInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('smoothstep'), + [Type.abstractFloat, Type.abstractFloat, Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -76,7 +47,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('smoothstep'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases); + await run(t, builtin('smoothstep'), [Type.f32, Type.f32, Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -90,5 +61,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('smoothstep'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases); + await run(t, builtin('smoothstep'), [Type.f16, Type.f16, Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.cache.ts new file mode 100644 index 0000000000..2151b2730a --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.cache.ts @@ -0,0 +1,23 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16]_[non_]const +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([true, false] as const).map(nonConst => ({ + [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateScalarToIntervalCases( + FP[trait].scalarRange(), + nonConst ? 'unfiltered' : 'finite', + // sqrt has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].sqrtInterval + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('sqrt', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts index a092438043..3d6c4390e2 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'sqrt' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn sqrt(e: T ) -> T Returns the square root of e. Component-wise when T is a vector. @@ -9,38 +9,33 @@ Returns the square root of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './sqrt.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('sqrt', { - f32_const: () => { - return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.sqrtInterval); - }, - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.sqrtInterval); - }, - f16_const: () => { - return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.sqrtInterval); - }, - f16_non_const: () => { - return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.sqrtInterval); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract_const'); + await run( + t, + abstractFloatBuiltin('sqrt'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -50,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, builtin('sqrt'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('sqrt'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -64,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const'); - await run(t, builtin('sqrt'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('sqrt'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.cache.ts new file mode 100644 index 0000000000..28d1e1952b --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.cache.ts @@ -0,0 +1,41 @@ +import { anyOf } from '../../../../../util/compare.js'; +import { FP } from '../../../../../util/floating_point.js'; +import { Case } from '../../case.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// stepInterval's return value can't always be interpreted as a single acceptance +// interval, valid result may be 0.0 or 1.0 or both of them, but will never be a +// value in interval (0.0, 1.0). +// See the comment block on stepInterval for more details +const makeCase = (trait: 'f32' | 'f16' | 'abstract', edge: number, x: number): Case => { + const FPTrait = FP[trait]; + edge = FPTrait.quantize(edge); + x = FPTrait.quantize(x); + const expected = FPTrait.stepInterval(edge, x); + + // [0, 0], [1, 1], or [-∞, +∞] cases + if (expected.isPoint() || !expected.isFinite()) { + return { input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)], expected }; + } + + // [0, 1] case, valid result is either 0.0 or 1.0. + const zeroInterval = FPTrait.toInterval(0); + const oneInterval = FPTrait.toInterval(1); + return { + input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)], + expected: anyOf(zeroInterval, oneInterval), + }; +}; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait] + .sparseScalarRange() + .flatMap(edge => FP[trait].sparseScalarRange().map(x => makeCase(trait, edge, x))); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('step', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts index 752e2676e6..fd76ee16c1 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'step' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn step(edge: T ,x: T ) -> T Returns 1.0 if edge ≤ x, and 0.0 otherwise. Component-wise when T is a vector. @@ -9,57 +9,33 @@ Returns 1.0 if edge ≤ x, and 0.0 otherwise. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { anyOf } from '../../../../../util/compare.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, Case, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './step.cache.js'; export const g = makeTestGroup(GPUTest); -// stepInterval's return value can't always be interpreted as a single acceptance -// interval, valid result may be 0.0 or 1.0 or both of them, but will never be a -// value in interval (0.0, 1.0). -// See the comment block on stepInterval for more details -const makeCase = (trait: 'f32' | 'f16', edge: number, x: number): Case => { - const FPTrait = FP[trait]; - edge = FPTrait.quantize(edge); - x = FPTrait.quantize(x); - const expected = FPTrait.stepInterval(edge, x); - - // [0, 0], [1, 1], or [-∞, +∞] cases - if (expected.isPoint() || !expected.isFinite()) { - return { input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)], expected }; - } - - // [0, 1] case, valid result is either 0.0 or 1.0. - const zeroInterval = FPTrait.toInterval(0); - const oneInterval = FPTrait.toInterval(1); - return { - input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)], - expected: anyOf(zeroInterval, oneInterval), - }; -}; - -export const d = makeCaseCache('step', { - f32: () => { - return fullF32Range().flatMap(edge => fullF32Range().map(x => makeCase('f32', edge, x))); - }, - f16: () => { - return fullF16Range().flatMap(edge => fullF16Range().map(x => makeCase('f16', edge, x))); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractFloatBuiltin('step'), + [Type.abstractFloat, Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -69,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('step'), [TypeF32, TypeF32], TypeF32, t.params, cases); + await run(t, builtin('step'), [Type.f32, Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -83,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('step'), [TypeF16, TypeF16], TypeF16, t.params, cases); + await run(t, builtin('step'), [Type.f16, Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.cache.ts new file mode 100644 index 0000000000..8d8e0d980b --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.cache.ts @@ -0,0 +1,23 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { linearRange } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + [ + // Well-defined accuracy range + ...linearRange(-Math.PI, Math.PI, 100), + ...FP[trait].scalarRange(), + ], + 'unfiltered', + // tan has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].tanInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('tan', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts index be3bdee046..7b682d0968 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'tan' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn tan(e: T ) -> T Returns the tangent of e. Component-wise when T is a vector. @@ -9,48 +9,33 @@ Returns the tangent of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './tan.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('tan', { - f32: () => { - return FP.f32.generateScalarToIntervalCases( - [ - // Defined accuracy range - ...linearRange(-Math.PI, Math.PI, 100), - ...fullF32Range(), - ], - 'unfiltered', - FP.f32.tanInterval - ); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases( - [ - // Defined accuracy range - ...linearRange(-Math.PI, Math.PI, 100), - ...fullF16Range(), - ], - 'unfiltered', - FP.f16.tanInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractFloatBuiltin('tan'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -60,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('tan'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('tan'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -74,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('tan'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('tan'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.cache.ts new file mode 100644 index 0000000000..5bada7fef5 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.cache.ts @@ -0,0 +1,18 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + FP[trait].scalarRange(), + 'unfiltered', + // tanh has an inherited accuracy, so is only expected to be as accurate as f32 + FP[trait !== 'abstract' ? trait : 'f32'].tanhInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('tanh', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts index 3aca5b924b..17978926a3 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'tanh' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn tanh(e: T ) -> T Returns the hyperbolic tangent of e. Component-wise when T is a vector. @@ -9,32 +9,33 @@ Returns the hyperbolic tangent of e. Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { Type } from '../../../../../util/conversion.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './tanh.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('tanh', { - f32: () => { - return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.tanhInterval); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.tanhInterval); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractFloatBuiltin('tanh'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') @@ -44,7 +45,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('tanh'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('tanh'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -58,5 +59,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('tanh'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('tanh'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts deleted file mode 100644 index 0ecb9964cf..0000000000 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts +++ /dev/null @@ -1,160 +0,0 @@ -export const description = ` -Execution tests for the 'textureDimension' builtin function - -The dimensions of the texture in texels. -For textures based on cubes, the results are the dimensions of each face of the cube. -Cube faces are square, so the x and y components of the result are equal. -If level is outside the range [0, textureNumLevels(t)) then any valid value for the return type may be returned. -`; - -import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; -import { GPUTest } from '../../../../../gpu_test.js'; - -export const g = makeTestGroup(GPUTest); - -g.test('sampled') - .specURL('https://www.w3.org/TR/WGSL/#texturedimensions') - .desc( - ` -T: f32, i32, u32 - -fn textureDimensions(t: texture_1d<T>) -> u32 -fn textureDimensions(t: texture_1d<T>, level: u32) -> u32 -fn textureDimensions(t: texture_2d<T>) -> vec2<u32> -fn textureDimensions(t: texture_2d<T>, level: u32) -> vec2<u32> -fn textureDimensions(t: texture_2d_array<T>) -> vec2<u32> -fn textureDimensions(t: texture_2d_array<T>, level: u32) -> vec2<u32> -fn textureDimensions(t: texture_3d<T>) -> vec3<u32> -fn textureDimensions(t: texture_3d<T>, level: u32) -> vec3<u32> -fn textureDimensions(t: texture_cube<T>) -> vec2<u32> -fn textureDimensions(t: texture_cube<T>, level: u32) -> vec2<u32> -fn textureDimensions(t: texture_cube_array<T>) -> vec2<u32> -fn textureDimensions(t: texture_cube_array<T>, level: u32) -> vec2<u32> -fn textureDimensions(t: texture_multisampled_2d<T>)-> vec2<u32> - -Parameters: - * t: the sampled texture - * level: - - The mip level, with level 0 containing a full size version of the texture. - - If omitted, the dimensions of level 0 are returned. -` - ) - .params(u => - u - .combine('texture_type', [ - 'texture_1d', - 'texture_2d', - 'texture_2d_array', - 'texture_3d', - 'texture_cube', - 'texture_cube_array', - 'texture_multisampled_2d', - ] as const) - .beginSubcases() - .combine('sampled_type', ['f32-only', 'i32', 'u32'] as const) - .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const) - ) - .unimplemented(); - -g.test('depth') - .specURL('https://www.w3.org/TR/WGSL/#texturedimensions') - .desc( - ` -fn textureDimensions(t: texture_depth_2d) -> vec2<u32> -fn textureDimensions(t: texture_depth_2d, level: u32) -> vec2<u32> -fn textureDimensions(t: texture_depth_2d_array) -> vec2<u32> -fn textureDimensions(t: texture_depth_2d_array, level: u32) -> vec2<u32> -fn textureDimensions(t: texture_depth_cube) -> vec2<u32> -fn textureDimensions(t: texture_depth_cube, level: u32) -> vec2<u32> -fn textureDimensions(t: texture_depth_cube_array) -> vec2<u32> -fn textureDimensions(t: texture_depth_cube_array, level: u32) -> vec2<u32> -fn textureDimensions(t: texture_depth_multisampled_2d)-> vec2<u32> - -Parameters: - * t: the depth or multisampled texture - * level: - - The mip level, with level 0 containing a full size version of the texture. - - If omitted, the dimensions of level 0 are returned. -` - ) - .params(u => - u - .combine('texture_type', [ - 'texture_depth_2d', - 'texture_depth_2d_array', - 'texture_depth_cube', - 'texture_depth_cube_array', - 'texture_depth_multisampled_2d', - ]) - .beginSubcases() - .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const) - ) - .unimplemented(); - -g.test('storage') - .specURL('https://www.w3.org/TR/WGSL/#texturedimensions') - .desc( - ` -F: rgba8unorm - rgba8snorm - rgba8uint - rgba8sint - rgba16uint - rgba16sint - rgba16float - r32uint - r32sint - r32float - rg32uint - rg32sint - rg32float - rgba32uint - rgba32sint - rgba32float -A: read, write, read_write - -fn textureDimensions(t: texture_storage_1d<F,A>) -> u32 -fn textureDimensions(t: texture_storage_2d<F,A>) -> vec2<u32> -fn textureDimensions(t: texture_storage_2d_array<F,A>) -> vec2<u32> -fn textureDimensions(t: texture_storage_3d<F,A>) -> vec3<u32> - -Parameters: - * t: the storage texture -` - ) - .params(u => - u - .combine('texel_format', [ - 'rgba8unorm', - 'rgba8snorm', - 'rgba8uint', - 'rgba8sint', - 'rgba16uint', - 'rgba16sint', - 'rgba16float', - 'r32uint', - 'r32sint', - 'r32float', - 'rg32uint', - 'rg32sint', - 'rg32float', - 'rgba32uint', - 'rgba32sint', - 'rgba32float', - ] as const) - .beginSubcases() - .combine('access_mode', ['read', 'write', 'read_write'] as const) - ) - .unimplemented(); - -g.test('external') - .specURL('https://www.w3.org/TR/WGSL/#texturedimensions') - .desc( - ` -fn textureDimensions(t: texture_external) -> vec2<u32> - -Parameters: - * t: the external texture -` - ) - .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts new file mode 100644 index 0000000000..1e025ca618 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts @@ -0,0 +1,518 @@ +export const description = ` +Execution tests for the 'textureDimensions' builtin function + +The dimensions of the texture in texels. +For textures based on cubes, the results are the dimensions of each face of the cube. +Cube faces are square, so the x and y components of the result are equal. +If level is outside the range [0, textureNumLevels(t)) then any valid value for the return type may be returned. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { + kAllTextureFormats, + kColorTextureFormats, + kTextureFormatInfo, + sampleTypeForFormatAndAspect, + textureDimensionAndFormatCompatible, +} from '../../../../../format_info.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { align } from '../../../../../util/math.js'; + +export const g = makeTestGroup(GPUTest); + +/// The maximum number of texture mipmap levels to test. +/// Keep this small to reduce memory and test permutations. +const kMaxMipsForTest = 3; + +/// The maximum number of texture samples to test. +const kMaxSamplesForTest = 4; + +/// All the possible GPUTextureViewDimensions. +const kAllViewDimensions: readonly GPUTextureViewDimension[] = [ + '1d', + '2d', + '2d-array', + '3d', + 'cube', + 'cube-array', +] as const; + +/** @returns the aspects to test for the given format */ +function aspectsForFormat(format: GPUTextureFormat): readonly GPUTextureAspect[] { + const formatInfo = kTextureFormatInfo[format]; + if (formatInfo.depth !== undefined && formatInfo.stencil !== undefined) { + return ['depth-only', 'stencil-only']; + } + return ['all']; +} + +/** @returns the sample counts to test for the given format */ +function samplesForFormat(format: GPUTextureFormat): readonly number[] { + const info = kTextureFormatInfo[format]; + return info.multisample ? [1, kMaxSamplesForTest] : [1]; +} + +/** + * @returns a list of number of texture mipmap levels to test, given the format, view dimensions and + * number of samples. + */ +function textureMipCount(params: { + format: GPUTextureFormat; + dimensions: GPUTextureViewDimension; + samples?: number; +}): readonly number[] { + if (params.samples !== undefined && params.samples !== 1) { + // https://www.w3.org/TR/webgpu/#texture-creation + // If descriptor.sampleCount > 1: descriptor.mipLevelCount must be 1. + return [1]; + } + if (textureDimensionsForViewDimensions(params.dimensions) === '1d') { + // https://www.w3.org/TR/webgpu/#dom-gputexturedimension-2d + // Only "2d" textures may have mipmaps, be multisampled, use a compressed or depth/stencil + // format, and be used as a render attachment. + return [1]; + } + return [1, kMaxMipsForTest]; +} + +/** + * @returns a list of GPUTextureViewDescriptor.baseMipLevel to test, give the texture mipmap count. + */ +function baseMipLevel(params: { textureMipCount: number }): readonly number[] { + const out: number[] = []; + for (let i = 0; i < params.textureMipCount; i++) { + out.push(i); + } + return out; +} + +/** + * @returns the argument values for the textureDimensions() `level` parameter to test. + * An `undefined` represents a call to textureDimensions() without the level argument. + */ +function textureDimensionsLevel(params: { + samples?: number; + textureMipCount: number; + baseMipLevel: number; +}): readonly (number | undefined)[] { + if (params.samples !== undefined && params.samples > 1) { + return [undefined]; // textureDimensions() overload with `level` not available. + } + const out: (number | undefined)[] = [undefined]; + for (let i = 0; i < params.textureMipCount - params.baseMipLevel; i++) { + out.push(i); + } + return out; +} + +/** @returns the GPUTextureViewDimensions to test for the format and number of samples */ +function viewDimensions(params: { + format: GPUTextureFormat; + samples?: number; +}): readonly GPUTextureViewDimension[] { + if (params.samples !== undefined && params.samples > 1) { + // https://www.w3.org/TR/webgpu/#dom-gputexturedimension-2d + // Only 2d textures can be multisampled + return ['2d']; + } + + return kAllViewDimensions.filter(dim => + textureDimensionAndFormatCompatible(textureDimensionsForViewDimensions(dim), params.format) + ); +} + +/** @returns the GPUTextureDimension for the GPUTextureViewDimension */ +function textureDimensionsForViewDimensions(dim: GPUTextureViewDimension): GPUTextureDimension { + switch (dim) { + case '1d': + return '1d'; + case '2d': + case '2d-array': + case 'cube': + case 'cube-array': + return '2d'; + case '3d': + return '3d'; + } +} + +/** TestValues holds the texture size and expected return value of textureDimensions() */ +type TestValues = { + /** The value to pass to GPUTextureDescriptor.size, when creating the texture */ + size: number[]; + /** The expected result of calling textureDimensions() */ + expected: number[]; +}; + +/** @returns The TestValues to use for the given texture dimensions and format */ +function testValues(params: { + dimensions: GPUTextureViewDimension; + format: GPUTextureFormat; + baseMipLevel: number; + textureDimensionsLevel?: number; +}): TestValues { + // The minimum dimension length, given the number of mipmap levels that are being tested. + const kMinLen = 1 << kMaxMipsForTest; + const kNumCubeFaces = 6; + + const formatInfo = kTextureFormatInfo[params.format]; + const bw = formatInfo.blockWidth; + const bh = formatInfo.blockHeight; + let mip = params.baseMipLevel; + if (params.textureDimensionsLevel !== undefined) { + mip += params.textureDimensionsLevel; + } + + // Magic constants to multiply the minimum texture dimensions with, to provide + // different dimension values in the test. These could be parameterized, but + // these are currently fixed to reduce the number of test parameterizations. + const kMultipleA = 2; + const kMultipleB = 3; + const kMultipleC = 4; + + switch (params.dimensions) { + case '1d': { + const w = align(kMinLen, bw) * kMultipleA; + return { size: [w], expected: [w >>> mip] }; + } + case '2d': { + const w = align(kMinLen, bw) * kMultipleA; + const h = align(kMinLen, bh) * kMultipleB; + return { size: [w, h], expected: [w >>> mip, h >>> mip] }; + } + case '2d-array': { + const w = align(kMinLen, bw) * kMultipleC; + const h = align(kMinLen, bh) * kMultipleB; + return { size: [w, h, 4], expected: [w >>> mip, h >>> mip] }; + } + case '3d': { + const w = align(kMinLen, bw) * kMultipleA; + const h = align(kMinLen, bh) * kMultipleB; + const d = kMinLen * kMultipleC; + return { + size: [w, h, d], + expected: [w >>> mip, h >>> mip, d >>> mip], + }; + } + case 'cube': { + const l = align(kMinLen, bw) * align(kMinLen, bh) * kMultipleB; + return { + size: [l, l, kNumCubeFaces], + expected: [l >>> mip, l >>> mip], + }; + } + case 'cube-array': { + const l = align(kMinLen, bw) * align(kMinLen, bh) * kMultipleC; + return { + size: [l, l, kNumCubeFaces * 3], + expected: [l >>> mip, l >>> mip], + }; + } + } +} + +/** + * Builds a shader module with the texture view bound to the WGSL texture with the given WGSL type, + * which calls textureDimensions(), assigning the result to an output buffer. + * This shader is executed with a compute shader, and the output buffer is compared to + * `values.expected`. + */ +function run( + t: GPUTest, + view: GPUTextureView, + textureType: string, + levelArg: number | undefined, + values: TestValues +) { + const outputType = values.expected.length > 1 ? `vec${values.expected.length}u` : 'u32'; + const wgsl = ` +@group(0) @binding(0) var texture : ${textureType}; +@group(0) @binding(1) var<storage, read_write> output : ${outputType}; + +@compute @workgroup_size(1) +fn main() { +output = ${ + levelArg !== undefined + ? `textureDimensions(texture, ${levelArg})` + : 'textureDimensions(texture)' + }; +} +`; + const module = t.device.createShaderModule({ + code: wgsl, + }); + const pipeline = t.device.createComputePipeline({ + compute: { module }, + layout: 'auto', + }); + const outputBuffer = t.device.createBuffer({ + size: 32, + usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE, + }); + const bindgroup = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: view }, + { binding: 1, resource: { buffer: outputBuffer } }, + ], + }); + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginComputePass(); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindgroup); + pass.dispatchWorkgroups(1); + pass.end(); + t.device.queue.submit([encoder.finish()]); + + t.expectGPUBufferValuesEqual(outputBuffer, new Uint32Array(values.expected)); +} + +/** @returns true if the GPUTextureViewDimension is valid for a storage texture */ +function dimensionsValidForStorage(dimensions: GPUTextureViewDimension) { + switch (dimensions) { + case '1d': + case '2d': + case '2d-array': + case '3d': + return true; + default: + return false; + } +} + +g.test('sampled_and_multisampled') + .specURL('https://www.w3.org/TR/WGSL/#texturedimensions') + .desc( + ` +T: f32, i32, u32 + +fn textureDimensions(t: texture_1d<T>) -> u32 +fn textureDimensions(t: texture_1d<T>, level: u32) -> u32 +fn textureDimensions(t: texture_2d<T>) -> vec2<u32> +fn textureDimensions(t: texture_2d<T>, level: u32) -> vec2<u32> +fn textureDimensions(t: texture_2d_array<T>) -> vec2<u32> +fn textureDimensions(t: texture_2d_array<T>, level: u32) -> vec2<u32> +fn textureDimensions(t: texture_3d<T>) -> vec3<u32> +fn textureDimensions(t: texture_3d<T>, level: u32) -> vec3<u32> +fn textureDimensions(t: texture_cube<T>) -> vec2<u32> +fn textureDimensions(t: texture_cube<T>, level: u32) -> vec2<u32> +fn textureDimensions(t: texture_cube_array<T>) -> vec2<u32> +fn textureDimensions(t: texture_cube_array<T>, level: u32) -> vec2<u32> +fn textureDimensions(t: texture_multisampled_2d<T>)-> vec2<u32> + +Parameters: + * t: the sampled texture + * level: + - The mip level, with level 0 containing a full size version of the texture. + - If omitted, the dimensions of level 0 are returned. +` + ) + .params(u => + u + .combine('format', kAllTextureFormats) + .unless(p => kTextureFormatInfo[p.format].color?.type === 'unfilterable-float') + .expand('aspect', u => aspectsForFormat(u.format)) + .expand('samples', u => samplesForFormat(u.format)) + .beginSubcases() + .expand('dimensions', viewDimensions) + .expand('textureMipCount', textureMipCount) + .expand('baseMipLevel', baseMipLevel) + .expand('textureDimensionsLevel', textureDimensionsLevel) + ) + .beforeAllSubcases(t => { + const info = kTextureFormatInfo[t.params.format]; + t.skipIfTextureFormatNotSupported(t.params.format); + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(t => { + t.skipIfTextureViewDimensionNotSupported(t.params.dimensions); + const values = testValues(t.params); + const texture = t.device.createTexture({ + size: values.size, + dimension: textureDimensionsForViewDimensions(t.params.dimensions), + ...(t.isCompatibility && { textureBindingViewDimension: t.params.dimensions }), + usage: + t.params.samples === 1 + ? GPUTextureUsage.TEXTURE_BINDING + : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT, + format: t.params.format, + sampleCount: t.params.samples, + mipLevelCount: t.params.textureMipCount, + }); + const textureView = texture.createView({ + dimension: t.params.dimensions, + aspect: t.params.aspect, + baseMipLevel: t.params.baseMipLevel, + }); + + function wgslSampledTextureType(): string { + const base = t.params.samples !== 1 ? 'texture_multisampled' : 'texture'; + const dimensions = t.params.dimensions.replace('-', '_'); + const sampleType = sampleTypeForFormatAndAspect(t.params.format, t.params.aspect); + switch (sampleType) { + case 'depth': + case 'float': + return `${base}_${dimensions}<f32>`; + case 'uint': + return `${base}_${dimensions}<u32>`; + case 'sint': + return `${base}_${dimensions}<i32>`; + case 'unfilterable-float': + throw new Error(`'${t.params.format}' does not support sampling`); + } + } + + run(t, textureView, wgslSampledTextureType(), t.params.textureDimensionsLevel, values); + }); + +g.test('depth') + .specURL('https://www.w3.org/TR/WGSL/#texturedimensions') + .desc( + ` +fn textureDimensions(t: texture_depth_2d) -> vec2<u32> +fn textureDimensions(t: texture_depth_2d, level: u32) -> vec2<u32> +fn textureDimensions(t: texture_depth_2d_array) -> vec2<u32> +fn textureDimensions(t: texture_depth_2d_array, level: u32) -> vec2<u32> +fn textureDimensions(t: texture_depth_cube) -> vec2<u32> +fn textureDimensions(t: texture_depth_cube, level: u32) -> vec2<u32> +fn textureDimensions(t: texture_depth_cube_array) -> vec2<u32> +fn textureDimensions(t: texture_depth_cube_array, level: u32) -> vec2<u32> +fn textureDimensions(t: texture_depth_multisampled_2d)-> vec2<u32> + +Parameters: + * t: the depth or multisampled texture + * level: + - The mip level, with level 0 containing a full size version of the texture. + - If omitted, the dimensions of level 0 are returned. +` + ) + .params(u => + u + .combine('format', kAllTextureFormats) + .filter(p => !!kTextureFormatInfo[p.format].depth) + .expand('aspect', u => aspectsForFormat(u.format)) + .unless(u => u.aspect === 'stencil-only') + .expand('samples', u => samplesForFormat(u.format)) + .beginSubcases() + .expand('dimensions', viewDimensions) + .expand('textureMipCount', textureMipCount) + .expand('baseMipLevel', baseMipLevel) + .expand('textureDimensionsLevel', textureDimensionsLevel) + ) + .beforeAllSubcases(t => { + const info = kTextureFormatInfo[t.params.format]; + t.skipIfTextureFormatNotSupported(t.params.format); + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(t => { + t.skipIfTextureViewDimensionNotSupported(t.params.dimensions); + const values = testValues(t.params); + const texture = t.device.createTexture({ + size: values.size, + dimension: textureDimensionsForViewDimensions(t.params.dimensions), + ...(t.isCompatibility && { textureBindingViewDimension: t.params.dimensions }), + usage: + t.params.samples === 1 + ? GPUTextureUsage.TEXTURE_BINDING + : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT, + format: t.params.format, + sampleCount: t.params.samples, + mipLevelCount: t.params.textureMipCount, + }); + const textureView = texture.createView({ + dimension: t.params.dimensions, + aspect: t.params.aspect, + baseMipLevel: t.params.baseMipLevel, + }); + + function wgslDepthTextureType(): string { + const base = t.params.samples !== 1 ? 'texture_depth_multisampled' : 'texture_depth'; + const dimensions = t.params.dimensions.replace('-', '_'); + return `${base}_${dimensions}`; + } + + run(t, textureView, wgslDepthTextureType(), t.params.textureDimensionsLevel, values); + }); + +g.test('storage') + .specURL('https://www.w3.org/TR/WGSL/#texturedimensions') + .desc( + ` +F: rgba8unorm + rgba8snorm + rgba8uint + rgba8sint + rgba16uint + rgba16sint + rgba16float + r32uint + r32sint + r32float + rg32uint + rg32sint + rg32float + rgba32uint + rgba32sint + rgba32float +A: read, write, read_write + +fn textureDimensions(t: texture_storage_1d<F,A>) -> u32 +fn textureDimensions(t: texture_storage_2d<F,A>) -> vec2<u32> +fn textureDimensions(t: texture_storage_2d_array<F,A>) -> vec2<u32> +fn textureDimensions(t: texture_storage_3d<F,A>) -> vec3<u32> + +Parameters: + * t: the storage texture +` + ) + .params(u => + u + .combine('format', kColorTextureFormats) + .filter(p => kTextureFormatInfo[p.format].color?.storage === true) + .expand('aspect', u => aspectsForFormat(u.format)) + .beginSubcases() + .expand('dimensions', u => viewDimensions(u).filter(dimensionsValidForStorage)) + .expand('textureMipCount', textureMipCount) + .expand('baseMipLevel', baseMipLevel) + ) + .beforeAllSubcases(t => { + const info = kTextureFormatInfo[t.params.format]; + t.skipIfTextureFormatNotSupported(t.params.format); + t.skipIfTextureFormatNotUsableAsStorageTexture(t.params.format); + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(t => { + const values = testValues(t.params); + const texture = t.device.createTexture({ + size: values.size, + dimension: textureDimensionsForViewDimensions(t.params.dimensions), + usage: GPUTextureUsage.STORAGE_BINDING, + format: t.params.format, + mipLevelCount: t.params.textureMipCount, + }); + const textureView = texture.createView({ + dimension: t.params.dimensions, + aspect: t.params.aspect, + mipLevelCount: 1, + baseMipLevel: t.params.baseMipLevel, + }); + + function wgslStorageTextureType(): string { + const dimensions = t.params.dimensions.replace('-', '_'); + return `texture_storage_${dimensions}<${t.params.format}, write>`; + } + + run(t, textureView, wgslStorageTextureType(), undefined, values); + }); + +g.test('external') + .specURL('https://www.w3.org/TR/WGSL/#texturedimensions') + .desc( + ` +fn textureDimensions(t: texture_external) -> vec2<u32> + +Parameters: + * t: the external texture +` + ) + .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts index f5b01dfc63..7c743576e9 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts @@ -1,26 +1,24 @@ export const description = ` Samples a texture. - -Must only be used in a fragment shader stage. -Must only be invoked in uniform control flow. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; -import { GPUTest } from '../../../../../gpu_test.js'; +import { kEncodableTextureFormats, kTextureFormatInfo } from '../../../../../format_info.js'; +import { GPUTest, TextureTestMixin } from '../../../../../gpu_test.js'; +import { hashU32 } from '../../../../../util/math.js'; +import { kTexelRepresentationInfo } from '../../../../../util/texture/texel_data.js'; +import { + vec2, + createRandomTexelView, + TextureCall, + putDataInTextureThenDrawAndCheckResults, + generateSamplePoints, + kSamplePointMethods, +} from './texture_utils.js'; import { generateCoordBoundaries, generateOffsets } from './utils.js'; -export const g = makeTestGroup(GPUTest); - -g.test('stage') - .specURL('https://www.w3.org/TR/WGSL/#texturesample') - .desc( - ` -Tests that 'textureSample' can only be called in 'fragment' shaders. -` - ) - .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const)) - .unimplemented(); +export const g = makeTestGroup(TextureTestMixin(GPUTest)); g.test('control_flow') .specURL('https://www.w3.org/TR/WGSL/#texturesample') @@ -70,11 +68,74 @@ Parameters: Values outside of this range will result in a shader-creation error. ` ) - .paramsSubcasesOnly(u => + .params(u => u - .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const) - .combine('coords', generateCoordBoundaries(2)) - .combine('offset', generateOffsets(2)) + .combine('format', kEncodableTextureFormats) + .filter(t => { + const type = kTextureFormatInfo[t.format].color?.type; + return type === 'float' || type === 'unfilterable-float'; + }) + .combine('sample_points', kSamplePointMethods) + .combine('addressModeU', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const) + .combine('addressModeV', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const) + .combine('minFilter', ['nearest', 'linear'] as const) + .combine('offset', [false, true] as const) + ) + .beforeAllSubcases(t => { + const format = kTexelRepresentationInfo[t.params.format]; + t.skipIfTextureFormatNotSupported(t.params.format); + const hasFloat32 = format.componentOrder.some(c => { + const info = format.componentInfo[c]!; + return info.dataType === 'float' && info.bitLength === 32; + }); + if (hasFloat32) { + t.selectDeviceOrSkipTestCase('float32-filterable'); + } + }) + .fn(async t => { + const descriptor: GPUTextureDescriptor = { + format: t.params.format, + size: { width: 8, height: 8 }, + usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING, + }; + const texelView = createRandomTexelView(descriptor); + const calls: TextureCall<vec2>[] = generateSamplePoints(50, t.params.minFilter === 'nearest', { + method: t.params.sample_points, + textureWidth: 8, + textureHeight: 8, + }).map((c, i) => { + const hash = hashU32(i) & 0xff; + return { + builtin: 'textureSample', + coordType: 'f', + coords: c, + offset: t.params.offset ? [(hash & 15) - 8, (hash >> 4) - 8] : undefined, + }; + }); + const sampler: GPUSamplerDescriptor = { + addressModeU: t.params.addressModeU, + addressModeV: t.params.addressModeV, + minFilter: t.params.minFilter, + magFilter: t.params.minFilter, + }; + const res = await putDataInTextureThenDrawAndCheckResults( + t.device, + { texels: texelView, descriptor }, + sampler, + calls + ); + t.expectOK(res); + }); + +g.test('sampled_2d_coords,derivatives') + .specURL('https://www.w3.org/TR/WGSL/#texturesample') + .desc( + ` +fn textureSample(t: texture_2d<f32>, s: sampler, coords: vec2<f32>) -> vec4<f32> +fn textureSample(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32> + +test mip level selection based on derivatives + ` ) .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts index 786bce4830..1c61c1a5f2 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts @@ -2,8 +2,6 @@ export const description = ` Execution tests for the 'textureSampleBias' builtin function Samples a texture with a bias to the mip level. -Must only be used in a fragment shader stage. -Must only be invoked in uniform control flow. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; @@ -13,26 +11,6 @@ import { generateCoordBoundaries, generateOffsets } from './utils.js'; export const g = makeTestGroup(GPUTest); -g.test('stage') - .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias') - .desc( - ` -Tests that 'textureSampleBias' can only be called in 'fragment' shaders. -` - ) - .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const)) - .unimplemented(); - -g.test('control_flow') - .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias') - .desc( - ` -Tests that 'textureSampleBias' can only be called in uniform control flow. -` - ) - .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const)) - .unimplemented(); - g.test('sampled_2d_coords') .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias') .desc( diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts index 9f723fac2e..eae5098257 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts @@ -1,8 +1,5 @@ export const description = ` Samples a depth texture and compares the sampled depth values against a reference value. - -Must only be used in a fragment shader stage. -Must only be invoked in uniform control flow. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; @@ -12,26 +9,6 @@ import { generateCoordBoundaries, generateOffsets } from './utils.js'; export const g = makeTestGroup(GPUTest); -g.test('stage') - .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare') - .desc( - ` -Tests that 'textureSampleCompare' can only be called in 'fragment' shaders. -` - ) - .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const)) - .unimplemented(); - -g.test('control_flow') - .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare') - .desc( - ` -Tests that 'textureSampleCompare' can only be called in uniform control flow. -` - ) - .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const)) - .unimplemented(); - g.test('2d_coords') .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare') .desc( diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts new file mode 100644 index 0000000000..fe2da53d5a --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts @@ -0,0 +1,809 @@ +import { assert, range, unreachable } from '../../../../../../common/util/util.js'; +import { EncodableTextureFormat } from '../../../../../format_info.js'; +import { float32ToUint32 } from '../../../../../util/conversion.js'; +import { align, clamp, hashU32, lerp, quantizeToF32 } from '../../../../../util/math.js'; +import { + kTexelRepresentationInfo, + PerTexelComponent, + TexelRepresentationInfo, +} from '../../../../../util/texture/texel_data.js'; +import { TexelView } from '../../../../../util/texture/texel_view.js'; +import { createTextureFromTexelView } from '../../../../../util/texture.js'; +import { reifyExtent3D } from '../../../../../util/unions.js'; + +function getLimitValue(v: number) { + switch (v) { + case Number.POSITIVE_INFINITY: + return 1000; + case Number.NEGATIVE_INFINITY: + return -1000; + default: + return v; + } +} + +function getValueBetweenMinAndMaxTexelValueInclusive( + rep: TexelRepresentationInfo, + normalized: number +) { + return lerp( + getLimitValue(rep.numericRange!.min), + getLimitValue(rep.numericRange!.max), + normalized + ); +} + +/** + * Creates a TexelView filled with random values. + */ +export function createRandomTexelView(info: { + format: GPUTextureFormat; + size: GPUExtent3D; +}): TexelView { + const rep = kTexelRepresentationInfo[info.format as EncodableTextureFormat]; + const generator = (coords: Required<GPUOrigin3DDict>): Readonly<PerTexelComponent<number>> => { + const texel: PerTexelComponent<number> = {}; + for (const component of rep.componentOrder) { + const rnd = hashU32(coords.x, coords.y, coords.z, component.charCodeAt(0)); + const normalized = clamp(rnd / 0xffffffff, { min: 0, max: 1 }); + texel[component] = getValueBetweenMinAndMaxTexelValueInclusive(rep, normalized); + } + return quantize(texel, rep); + }; + return TexelView.fromTexelsAsColors(info.format as EncodableTextureFormat, generator); +} + +export type vec2 = [number, number]; +export type vec3 = [number, number, number]; +export type vec4 = [number, number, number, number]; +export type Dimensionality = number | vec2 | vec3; + +type TextureCallArgKeys = keyof TextureCallArgs<number>; +const kTextureCallArgNames: TextureCallArgKeys[] = [ + 'coords', + 'mipLevel', + 'arrayIndex', + 'ddx', + 'ddy', + 'offset', +]; + +export interface TextureCallArgs<T extends Dimensionality> { + coords?: T; + mipLevel?: number; + arrayIndex?: number; + ddx?: T; + ddy?: T; + offset?: T; +} + +export interface TextureCall<T extends Dimensionality> extends TextureCallArgs<T> { + builtin: 'textureSample' | 'textureLoad'; + coordType: 'f'; +} + +function toArray(coords: Dimensionality): number[] { + if (coords instanceof Array) { + return coords; + } + return [coords]; +} + +function quantize(texel: PerTexelComponent<number>, repl: TexelRepresentationInfo) { + return repl.bitsToNumber(repl.unpackBits(new Uint8Array(repl.pack(repl.encode(texel))))); +} + +function apply(a: number[], b: number[], op: (x: number, y: number) => number) { + assert(a.length === b.length, `apply(${a}, ${b}): arrays must have same length`); + return a.map((v, i) => op(v, b[i])); +} + +const add = (a: number[], b: number[]) => apply(a, b, (x, y) => x + y); + +export interface Texture { + texels: TexelView; + descriptor: GPUTextureDescriptor; +} + +/** + * Returns the expect value for a WGSL builtin texture function + */ +export function expected<T extends Dimensionality>( + call: TextureCall<T>, + texture: Texture, + sampler: GPUSamplerDescriptor +): PerTexelComponent<number> { + const rep = kTexelRepresentationInfo[texture.texels.format]; + const textureExtent = reifyExtent3D(texture.descriptor.size); + const textureSize = [textureExtent.width, textureExtent.height, textureExtent.depthOrArrayLayers]; + const addressMode = [ + sampler.addressModeU ?? 'clamp-to-edge', + sampler.addressModeV ?? 'clamp-to-edge', + sampler.addressModeW ?? 'clamp-to-edge', + ]; + + const load = (at: number[]) => + texture.texels.color({ + x: Math.floor(at[0]), + y: Math.floor(at[1] ?? 0), + z: Math.floor(at[2] ?? 0), + }); + + switch (call.builtin) { + case 'textureSample': { + const coords = toArray(call.coords!); + + // convert normalized to absolute texel coordinate + // ┌───┬───┬───┬───┐ + // │ a │ │ │ │ norm: a = 1/8, b = 7/8 + // ├───┼───┼───┼───┤ abs: a = 0, b = 3 + // │ │ │ │ │ + // ├───┼───┼───┼───┤ + // │ │ │ │ │ + // ├───┼───┼───┼───┤ + // │ │ │ │ b │ + // └───┴───┴───┴───┘ + let at = coords.map((v, i) => v * textureSize[i] - 0.5); + + // Apply offset in whole texel units + if (call.offset !== undefined) { + at = add(at, toArray(call.offset)); + } + + const samples: { at: number[]; weight: number }[] = []; + + const filter = sampler.minFilter; + switch (filter) { + case 'linear': { + // 'p0' is the lower texel for 'at' + const p0 = at.map(v => Math.floor(v)); + // 'p1' is the higher texel for 'at' + const p1 = p0.map(v => v + 1); + + // interpolation weights for p0 and p1 + const p1W = at.map((v, i) => v - p0[i]); + const p0W = p1W.map(v => 1 - v); + + switch (coords.length) { + case 1: + samples.push({ at: p0, weight: p0W[0] }); + samples.push({ at: p1, weight: p1W[0] }); + break; + case 2: { + samples.push({ at: p0, weight: p0W[0] * p0W[1] }); + samples.push({ at: [p1[0], p0[1]], weight: p1W[0] * p0W[1] }); + samples.push({ at: [p0[0], p1[1]], weight: p0W[0] * p1W[1] }); + samples.push({ at: p1, weight: p1W[0] * p1W[1] }); + break; + } + } + break; + } + case 'nearest': { + const p = at.map(v => Math.round(quantizeToF32(v))); + samples.push({ at: p, weight: 1 }); + break; + } + default: + unreachable(); + } + + const out: PerTexelComponent<number> = {}; + const ss = []; + for (const sample of samples) { + // Apply sampler address mode + const c = sample.at.map((v, i) => { + switch (addressMode[i]) { + case 'clamp-to-edge': + return clamp(v, { min: 0, max: textureSize[i] - 1 }); + case 'mirror-repeat': { + const n = Math.floor(v / textureSize[i]); + v = v - n * textureSize[i]; + return (n & 1) !== 0 ? textureSize[i] - v - 1 : v; + } + case 'repeat': + return v - Math.floor(v / textureSize[i]) * textureSize[i]; + default: + unreachable(); + } + }); + const v = load(c); + ss.push(v); + for (const component of rep.componentOrder) { + out[component] = (out[component] ?? 0) + v[component]! * sample.weight; + } + } + + return out; + } + case 'textureLoad': { + return load(toArray(call.coords!)); + } + } +} + +/** + * Puts random data in a texture, generates a shader that implements `calls` + * such that each call's result is written to the next consecutive texel of + * a rgba32float texture. It then checks the result of each call matches + * the expected result. + */ +export async function putDataInTextureThenDrawAndCheckResults<T extends Dimensionality>( + device: GPUDevice, + texture: Texture, + sampler: GPUSamplerDescriptor, + calls: TextureCall<T>[] +) { + const results = await doTextureCalls(device, texture, sampler, calls); + const errs: string[] = []; + const rep = kTexelRepresentationInfo[texture.texels.format]; + for (let callIdx = 0; callIdx < calls.length; callIdx++) { + const call = calls[callIdx]; + const got = results[callIdx]; + const expect = expected(call, texture, sampler); + + const gULP = rep.bitsToULPFromZero(rep.numberToBits(got)); + const eULP = rep.bitsToULPFromZero(rep.numberToBits(expect)); + for (const component of rep.componentOrder) { + const g = got[component]!; + const e = expect[component]!; + const absDiff = Math.abs(g - e); + const ulpDiff = Math.abs(gULP[component]! - eULP[component]!); + const relDiff = absDiff / Math.max(Math.abs(g), Math.abs(e)); + if (ulpDiff > 3 && relDiff > 0.03) { + const desc = describeTextureCall(call); + errs.push(`component was not as expected: + call: ${desc} + component: ${component} + got: ${g} + expected: ${e} + abs diff: ${absDiff.toFixed(4)} + rel diff: ${(relDiff * 100).toFixed(2)}% + ulp diff: ${ulpDiff} + sample points: +`); + const expectedSamplePoints = [ + 'expected:', + ...(await identifySamplePoints(texture.descriptor, (texels: TexelView) => { + return Promise.resolve( + expected(call, { texels, descriptor: texture.descriptor }, sampler) + ); + })), + ]; + const gotSamplePoints = [ + 'got:', + ...(await identifySamplePoints( + texture.descriptor, + async (texels: TexelView) => + ( + await doTextureCalls(device, { texels, descriptor: texture.descriptor }, sampler, [ + call, + ]) + )[0] + )), + ]; + errs.push(layoutTwoColumns(expectedSamplePoints, gotSamplePoints).join('\n')); + errs.push('', ''); + } + } + } + + return errs.length > 0 ? new Error(errs.join('\n')) : undefined; +} + +/** + * Generates a text art grid showing which texels were sampled + * followed by a list of the samples and the weights used for each + * component. + * + * Example: + * + * 0 1 2 3 4 5 6 7 + * ┌───┬───┬───┬───┬───┬───┬───┬───┐ + * 0 │ │ │ │ │ │ │ │ │ + * ├───┼───┼───┼───┼───┼───┼───┼───┤ + * 1 │ │ │ │ │ │ │ │ a │ + * ├───┼───┼───┼───┼───┼───┼───┼───┤ + * 2 │ │ │ │ │ │ │ │ b │ + * ├───┼───┼───┼───┼───┼───┼───┼───┤ + * 3 │ │ │ │ │ │ │ │ │ + * ├───┼───┼───┼───┼───┼───┼───┼───┤ + * 4 │ │ │ │ │ │ │ │ │ + * ├───┼───┼───┼───┼───┼───┼───┼───┤ + * 5 │ │ │ │ │ │ │ │ │ + * ├───┼───┼───┼───┼───┼───┼───┼───┤ + * 6 │ │ │ │ │ │ │ │ │ + * ├───┼───┼───┼───┼───┼───┼───┼───┤ + * 7 │ │ │ │ │ │ │ │ │ + * └───┴───┴───┴───┴───┴───┴───┴───┘ + * a: at: [7, 1], weights: [R: 0.75000] + * b: at: [7, 2], weights: [R: 0.25000] + */ +async function identifySamplePoints( + info: GPUTextureDescriptor, + run: (texels: TexelView) => Promise<PerTexelComponent<number>> +) { + const textureSize = reifyExtent3D(info.size); + const numTexels = textureSize.width * textureSize.height; + const rep = kTexelRepresentationInfo[info.format as EncodableTextureFormat]; + + // Identify all the texels that are sampled, and their weights. + const sampledTexelWeights = new Map<number, PerTexelComponent<number>>(); + const unclassifiedStack = [new Set<number>(range(numTexels, v => v))]; + while (unclassifiedStack.length > 0) { + // Pop the an unclassified texels stack + const unclassified = unclassifiedStack.pop()!; + + // Split unclassified texels evenly into two new sets + const setA = new Set<number>(); + const setB = new Set<number>(); + [...unclassified.keys()].forEach((t, i) => ((i & 1) === 0 ? setA : setB).add(t)); + + // Push setB to the unclassified texels stack + if (setB.size > 0) { + unclassifiedStack.push(setB); + } + + // See if any of the texels in setA were sampled. + const results = await run( + TexelView.fromTexelsAsColors( + info.format as EncodableTextureFormat, + (coords: Required<GPUOrigin3DDict>): Readonly<PerTexelComponent<number>> => { + const isCandidate = setA.has(coords.x + coords.y * textureSize.width); + const texel: PerTexelComponent<number> = {}; + for (const component of rep.componentOrder) { + texel[component] = isCandidate ? 1 : 0; + } + return texel; + } + ) + ); + if (rep.componentOrder.some(c => results[c] !== 0)) { + // One or more texels of setA were sampled. + if (setA.size === 1) { + // We identified a specific texel was sampled. + // As there was only one texel in the set, results holds the sampling weights. + setA.forEach(texel => sampledTexelWeights.set(texel, results)); + } else { + // More than one texel in the set. Needs splitting. + unclassifiedStack.push(setA); + } + } + } + + // ┌───┬───┬───┬───┐ + // │ a │ │ │ │ + // ├───┼───┼───┼───┤ + // │ │ │ │ │ + // ├───┼───┼───┼───┤ + // │ │ │ │ │ + // ├───┼───┼───┼───┤ + // │ │ │ │ b │ + // └───┴───┴───┴───┘ + const letter = (idx: number) => String.fromCharCode(97 + idx); // 97: 'a' + const orderedTexelIndices: number[] = []; + const lines: string[] = []; + { + let line = ' '; + for (let x = 0; x < textureSize.width; x++) { + line += ` ${x} `; + } + lines.push(line); + } + { + let line = ' ┌'; + for (let x = 0; x < textureSize.width; x++) { + line += x === textureSize.width - 1 ? '───┐' : '───┬'; + } + lines.push(line); + } + for (let y = 0; y < textureSize.height; y++) { + { + let line = `${y} │`; + for (let x = 0; x < textureSize.width; x++) { + const texelIdx = x + y * textureSize.height; + const weight = sampledTexelWeights.get(texelIdx); + if (weight !== undefined) { + line += ` ${letter(orderedTexelIndices.length)} │`; + orderedTexelIndices.push(texelIdx); + } else { + line += ' │'; + } + } + lines.push(line); + } + if (y < textureSize.height - 1) { + let line = ' ├'; + for (let x = 0; x < textureSize.width; x++) { + line += x === textureSize.width - 1 ? '───┤' : '───┼'; + } + lines.push(line); + } + } + { + let line = ' └'; + for (let x = 0; x < textureSize.width; x++) { + line += x === textureSize.width - 1 ? '───┘' : '───┴'; + } + lines.push(line); + } + + orderedTexelIndices.forEach((texelIdx, i) => { + const weights = sampledTexelWeights.get(texelIdx)!; + const y = Math.floor(texelIdx / textureSize.width); + const x = texelIdx - y * textureSize.height; + const w = rep.componentOrder.map(c => `${c}: ${weights[c]?.toFixed(5)}`).join(', '); + lines.push(`${letter(i)}: at: [${x}, ${y}], weights: [${w}]`); + }); + return lines; +} + +function layoutTwoColumns(columnA: string[], columnB: string[]) { + const widthA = Math.max(...columnA.map(l => l.length)); + const lines = Math.max(columnA.length, columnB.length); + const out: string[] = new Array<string>(lines); + for (let line = 0; line < lines; line++) { + const a = columnA[line] ?? ''; + const b = columnB[line] ?? ''; + out[line] = `${a}${' '.repeat(widthA - a.length)} | ${b}`; + } + return out; +} + +export const kSamplePointMethods = ['texel-centre', 'spiral'] as const; +export type SamplePointMethods = (typeof kSamplePointMethods)[number]; + +/** + * Generates an array of coordinates at which to sample a texture. + */ +export function generateSamplePoints( + n: number, + nearest: boolean, + args: + | { + method: 'texel-centre'; + textureWidth: number; + textureHeight: number; + } + | { + method: 'spiral'; + radius?: number; + loops?: number; + textureWidth: number; + textureHeight: number; + } +) { + const out: vec2[] = []; + switch (args.method) { + case 'texel-centre': { + for (let i = 0; i < n; i++) { + const r = hashU32(i); + const x = Math.floor(lerp(0, args.textureWidth - 1, (r & 0xffff) / 0xffff)) + 0.5; + const y = Math.floor(lerp(0, args.textureHeight - 1, (r >>> 16) / 0xffff)) + 0.5; + out.push([x / args.textureWidth, y / args.textureHeight]); + } + break; + } + case 'spiral': { + for (let i = 0; i < n; i++) { + const f = i / (Math.max(n, 2) - 1); + const r = (args.radius ?? 1.5) * f; + const a = (args.loops ?? 2) * 2 * Math.PI * f; + out.push([0.5 + r * Math.cos(a), 0.5 + r * Math.sin(a)]); + } + break; + } + } + // Samplers across devices use different methods to interpolate. + // Quantizing the texture coordinates seems to hit coords that produce + // comparable results to our computed results. + // Note: This value works with 8x8 textures. Other sizes have not been tested. + // Values that worked for reference: + // Win 11, NVidia 2070 Super: 16 + // Linux, AMD Radeon Pro WX 3200: 256 + // MacOS, M1 Mac: 256 + const kSubdivisionsPerTexel = 4; + const q = [args.textureWidth * kSubdivisionsPerTexel, args.textureHeight * kSubdivisionsPerTexel]; + return out.map( + c => + c.map((v, i) => { + // Quantize to kSubdivisionsPerPixel + const v1 = Math.floor(v * q[i]); + // If it's nearest and we're on the edge of a texel then move us off the edge + // since the edge could choose one texel or another in nearest mode + const v2 = nearest && v1 % kSubdivisionsPerTexel === 0 ? v1 + 1 : v1; + // Convert back to texture coords + return v2 / q[i]; + }) as vec2 + ); +} + +function wgslTypeFor(data: Dimensionality, type: 'f' | 'i' | 'u'): string { + if (data instanceof Array) { + switch (data.length) { + case 2: + return `vec2${type}`; + case 3: + return `vec3${type}`; + } + } + return '${type}32'; +} + +function wgslExpr(data: number | vec2 | vec3 | vec4): string { + if (data instanceof Array) { + switch (data.length) { + case 2: + return `vec2(${data.map(v => v.toString()).join(', ')})`; + case 3: + return `vec3(${data.map(v => v.toString()).join(', ')})`; + } + } + return data.toString(); +} + +function binKey<T extends Dimensionality>(call: TextureCall<T>): string { + const keys: string[] = []; + for (const name of kTextureCallArgNames) { + const value = call[name]; + if (value !== undefined) { + if (name === 'offset') { + // offset must be a constant expression + keys.push(`${name}: ${wgslExpr(value)}`); + } else { + keys.push(`${name}: ${wgslTypeFor(value, call.coordType)}`); + } + } + } + return `${call.builtin}(${keys.join(', ')})`; +} + +function buildBinnedCalls<T extends Dimensionality>(calls: TextureCall<T>[]) { + const args: string[] = ['T']; // All texture builtins take the texture as the first argument + const fields: string[] = []; + const data: number[] = []; + + const prototype = calls[0]; + if (prototype.builtin.startsWith('textureSample')) { + // textureSample*() builtins take a sampler as the second argument + args.push('S'); + } + + for (const name of kTextureCallArgNames) { + const value = prototype[name]; + if (value !== undefined) { + if (name === 'offset') { + args.push(`/* offset */ ${wgslExpr(value)}`); + } else { + args.push(`args.${name}`); + fields.push(`@align(16) ${name} : ${wgslTypeFor(value, prototype.coordType)}`); + } + } + } + + for (const call of calls) { + for (const name of kTextureCallArgNames) { + const value = call[name]; + assert( + (prototype[name] === undefined) === (value === undefined), + 'texture calls are not binned correctly' + ); + if (value !== undefined && name !== 'offset') { + const bitcastToU32 = (value: number) => { + if (calls[0].coordType === 'f') { + return float32ToUint32(value); + } + return value; + }; + if (value instanceof Array) { + for (const c of value) { + data.push(bitcastToU32(c)); + } + } else { + data.push(bitcastToU32(value)); + } + // All fields are aligned to 16 bytes. + while ((data.length & 3) !== 0) { + data.push(0); + } + } + } + } + + const expr = `${prototype.builtin}(${args.join(', ')})`; + + return { expr, fields, data }; +} + +function binCalls<T extends Dimensionality>(calls: TextureCall<T>[]): number[][] { + const map = new Map<string, number>(); // key to bin index + const bins: number[][] = []; + calls.forEach((call, callIdx) => { + const key = binKey(call); + const binIdx = map.get(key); + if (binIdx === undefined) { + map.set(key, bins.length); + bins.push([callIdx]); + } else { + bins[binIdx].push(callIdx); + } + }); + return bins; +} + +export function describeTextureCall<T extends Dimensionality>(call: TextureCall<T>): string { + const args: string[] = ['texture: T']; + if (call.builtin.startsWith('textureSample')) { + args.push('sampler: S'); + } + for (const name of kTextureCallArgNames) { + const value = call[name]; + if (value !== undefined) { + args.push(`${name}: ${wgslExpr(value)}`); + } + } + return `${call.builtin}(${args.join(', ')})`; +} + +/** + * Given a list of "calls", each one of which has a texture coordinate, + * generates a fragment shader that uses the fragment position as an index + * (position.y * 256 + position.x) That index is then used to look up a + * coordinate from a storage buffer which is used to call the WGSL texture + * function to read/sample the texture, and then write to an rgba32float + * texture. We then read the rgba32float texture for the per "call" results. + * + * Calls are "binned" by call parameters. Each bin has its own structure and + * field in the storage buffer. This allows the calls to be non-homogenous and + * each have their own data type for coordinates. + */ +export async function doTextureCalls<T extends Dimensionality>( + device: GPUDevice, + texture: Texture, + sampler: GPUSamplerDescriptor, + calls: TextureCall<T>[] +) { + let structs = ''; + let body = ''; + let dataFields = ''; + const data: number[] = []; + let callCount = 0; + const binned = binCalls(calls); + binned.forEach((binCalls, binIdx) => { + const b = buildBinnedCalls(binCalls.map(callIdx => calls[callIdx])); + structs += `struct Args${binIdx} { + ${b.fields.join(', \n')} +} +`; + dataFields += ` args${binIdx} : array<Args${binIdx}, ${binCalls.length}>, +`; + body += ` + { + let is_active = (frag_idx >= ${callCount}) & (frag_idx < ${callCount + binCalls.length}); + let args = data.args${binIdx}[frag_idx - ${callCount}]; + let call = ${b.expr}; + result = select(result, call, is_active); + } +`; + callCount += binCalls.length; + data.push(...b.data); + }); + + const dataBuffer = device.createBuffer({ + size: data.length * 4, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE, + }); + device.queue.writeBuffer(dataBuffer, 0, new Uint32Array(data)); + + const rtWidth = 256; + const renderTarget = device.createTexture({ + format: 'rgba32float', + size: { width: rtWidth, height: Math.ceil(calls.length / rtWidth) }, + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const code = ` +${structs} + +struct Data { +${dataFields} +} + +@vertex +fn vs_main(@builtin(vertex_index) vertex_index : u32) -> @builtin(position) vec4f { + let positions = array( + vec4f(-1, 1, 0, 1), vec4f( 1, 1, 0, 1), + vec4f(-1, -1, 0, 1), vec4f( 1, -1, 0, 1), + ); + return positions[vertex_index]; +} + +@group(0) @binding(0) var T : texture_2d<f32>; +@group(0) @binding(1) var S : sampler; +@group(0) @binding(2) var<storage> data : Data; + +@fragment +fn fs_main(@builtin(position) frag_pos : vec4f) -> @location(0) vec4f { + let frag_idx = u32(frag_pos.x) + u32(frag_pos.y) * ${renderTarget.width}; + var result : vec4f; +${body} + return result; +} +`; + const shaderModule = device.createShaderModule({ code }); + + const pipeline = device.createRenderPipeline({ + layout: 'auto', + vertex: { module: shaderModule, entryPoint: 'vs_main' }, + fragment: { + module: shaderModule, + entryPoint: 'fs_main', + targets: [{ format: renderTarget.format }], + }, + primitive: { topology: 'triangle-strip', cullMode: 'none' }, + }); + + const gpuTexture = createTextureFromTexelView(device, texture.texels, texture.descriptor); + const gpuSampler = device.createSampler(sampler); + + const bindGroup = device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: gpuTexture.createView() }, + { binding: 1, resource: gpuSampler }, + { binding: 2, resource: { buffer: dataBuffer } }, + ], + }); + + const bytesPerRow = align(16 * renderTarget.width, 256); + const resultBuffer = device.createBuffer({ + size: renderTarget.height * bytesPerRow, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, + }); + const encoder = device.createCommandEncoder(); + + const renderPass = encoder.beginRenderPass({ + colorAttachments: [{ view: renderTarget.createView(), loadOp: 'clear', storeOp: 'store' }], + }); + + renderPass.setPipeline(pipeline); + renderPass.setBindGroup(0, bindGroup); + renderPass.draw(4); + renderPass.end(); + encoder.copyTextureToBuffer( + { texture: renderTarget }, + { buffer: resultBuffer, bytesPerRow }, + { width: renderTarget.width, height: renderTarget.height } + ); + device.queue.submit([encoder.finish()]); + + await resultBuffer.mapAsync(GPUMapMode.READ); + + const view = TexelView.fromTextureDataByReference( + renderTarget.format as EncodableTextureFormat, + new Uint8Array(resultBuffer.getMappedRange()), + { + bytesPerRow, + rowsPerImage: renderTarget.height, + subrectOrigin: [0, 0, 0], + subrectSize: [renderTarget.width, renderTarget.height], + } + ); + + let outIdx = 0; + const out = new Array<PerTexelComponent<number>>(calls.length); + for (const bin of binned) { + for (const callIdx of bin) { + const x = outIdx % rtWidth; + const y = Math.floor(outIdx / rtWidth); + out[callIdx] = view.color({ x, y, z: 0 }); + outIdx++; + } + } + + renderTarget.destroy(); + gpuTexture.destroy(); + resultBuffer.destroy(); + + return out; +} diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.cache.ts new file mode 100644 index 0000000000..cb89e2d194 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.cache.ts @@ -0,0 +1,27 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract]_matCxR_[non_]const +// abstract_matCxR_non_const is empty and not used +const cases = (['f32', 'f16', 'abstract'] as const) + .flatMap(trait => + ([2, 3, 4] as const).flatMap(cols => + ([2, 3, 4] as const).flatMap(rows => + ([true, false] as const).map(nonConst => ({ + [`${trait}_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => { + if (trait === 'abstract' && nonConst) { + return []; + } + return FP[trait].generateMatrixToMatrixCases( + FP[trait].sparseMatrixRange(cols, rows), + nonConst ? 'unfiltered' : 'finite', + FP[trait].transposeInterval + ); + }, + })) + ) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('transpose', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts index 6fd4887f35..7a96906cfa 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts @@ -1,82 +1,21 @@ export const description = ` Execution tests for the 'transpose' builtin function -T is AbstractFloat, f32, or f16 +T is abstract-float, f32, or f16 @const transpose(e: matRxC<T> ) -> matCxR<T> Returns the transpose of e. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeAbstractFloat, TypeF16, TypeF32, TypeMat } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { - sparseMatrixF16Range, - sparseMatrixF32Range, - sparseMatrixF64Range, -} from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { abstractBuiltin, builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './transpose.cache.js'; export const g = makeTestGroup(GPUTest); -// Cases: f32_matCxR_[non_]const -const f32_cases = ([2, 3, 4] as const) - .flatMap(cols => - ([2, 3, 4] as const).flatMap(rows => - ([true, false] as const).map(nonConst => ({ - [`f32_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f32.generateMatrixToMatrixCases( - sparseMatrixF32Range(cols, rows), - nonConst ? 'unfiltered' : 'finite', - FP.f32.transposeInterval - ); - }, - })) - ) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -// Cases: f16_matCxR_[non_]const -const f16_cases = ([2, 3, 4] as const) - .flatMap(cols => - ([2, 3, 4] as const).flatMap(rows => - ([true, false] as const).map(nonConst => ({ - [`f16_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => { - return FP.f16.generateMatrixToMatrixCases( - sparseMatrixF16Range(cols, rows), - nonConst ? 'unfiltered' : 'finite', - FP.f16.transposeInterval - ); - }, - })) - ) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -// Cases: abstract_matCxR -const abstract_cases = ([2, 3, 4] as const) - .flatMap(cols => - ([2, 3, 4] as const).map(rows => ({ - [`abstract_mat${cols}x${rows}`]: () => { - return FP.abstract.generateMatrixToMatrixCases( - sparseMatrixF64Range(cols, rows), - 'finite', - FP.abstract.transposeInterval - ); - }, - })) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -export const d = makeCaseCache('transpose', { - ...f32_cases, - ...f16_cases, - ...abstract_cases, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions') .desc(`abstract float tests`) @@ -89,12 +28,12 @@ g.test('abstract_float') .fn(async t => { const cols = t.params.cols; const rows = t.params.rows; - const cases = await d.get(`abstract_mat${cols}x${rows}`); + const cases = await d.get(`abstract_mat${cols}x${rows}_const`); await run( t, - abstractBuiltin('transpose'), - [TypeMat(cols, rows, TypeAbstractFloat)], - TypeMat(rows, cols, TypeAbstractFloat), + abstractFloatBuiltin('transpose'), + [Type.mat(cols, rows, Type.abstractFloat)], + Type.mat(rows, cols, Type.abstractFloat), t.params, cases ); @@ -120,8 +59,8 @@ g.test('f32') await run( t, builtin('transpose'), - [TypeMat(cols, rows, TypeF32)], - TypeMat(rows, cols, TypeF32), + [Type.mat(cols, rows, Type.f32)], + Type.mat(rows, cols, Type.f32), t.params, cases ); @@ -150,8 +89,8 @@ g.test('f16') await run( t, builtin('transpose'), - [TypeMat(cols, rows, TypeF16)], - TypeMat(rows, cols, TypeF16), + [Type.mat(cols, rows, Type.f16)], + Type.mat(rows, cols, Type.f16), t.params, cases ); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.cache.ts new file mode 100644 index 0000000000..061c95b07f --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.cache.ts @@ -0,0 +1,17 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { makeCaseCache } from '../../case_cache.js'; + +// Cases: [f32|f16|abstract] +const cases = (['f32', 'f16', 'abstract'] as const) + .map(trait => ({ + [`${trait}`]: () => { + return FP[trait].generateScalarToIntervalCases( + FP[trait].scalarRange(), + 'unfiltered', + FP[trait].truncInterval + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('trunc', cases); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts index 63cd8470f5..9d05709fc6 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts @@ -1,7 +1,7 @@ export const description = ` Execution tests for the 'trunc' builtin function -S is AbstractFloat, f32, f16 +S is abstract-float, f32, f16 T is S or vecN<S> @const fn trunc(e: T ) -> T Returns the nearest whole number whose absolute value is less than or equal to e. @@ -10,32 +10,14 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF64Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { abstractBuiltin, builtin } from './builtin.js'; +import { abstractFloatBuiltin, builtin } from './builtin.js'; +import { d } from './trunc.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('trunc', { - f32: () => { - return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.truncInterval); - }, - f16: () => { - return FP.f16.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f16.truncInterval); - }, - abstract: () => { - return FP.abstract.generateScalarToIntervalCases( - fullF64Range(), - 'unfiltered', - FP.abstract.truncInterval - ); - }, -}); - g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) @@ -46,7 +28,14 @@ g.test('abstract_float') ) .fn(async t => { const cases = await d.get('abstract'); - await run(t, abstractBuiltin('trunc'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases); + await run( + t, + abstractFloatBuiltin('trunc'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases + ); }); g.test('f32') @@ -57,7 +46,7 @@ g.test('f32') ) .fn(async t => { const cases = await d.get('f32'); - await run(t, builtin('trunc'), [TypeF32], TypeF32, t.params, cases); + await run(t, builtin('trunc'), [Type.f32], Type.f32, t.params, cases); }); g.test('f16') @@ -71,5 +60,5 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, builtin('trunc'), [TypeF16], TypeF16, t.params, cases); + await run(t, builtin('trunc'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.cache.ts new file mode 100644 index 0000000000..79a7a568d2 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.cache.ts @@ -0,0 +1,20 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { fullU32Range } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +export const d = makeCaseCache('unpack2x16float', { + u32_const: () => { + return FP.f32.generateU32ToIntervalCases( + fullU32Range(), + 'finite', + FP.f32.unpack2x16floatInterval + ); + }, + u32_non_const: () => { + return FP.f32.generateU32ToIntervalCases( + fullU32Range(), + 'unfiltered', + FP.f32.unpack2x16floatInterval + ); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts index 4a0bf075e9..e145afca50 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts @@ -7,33 +7,14 @@ interpretation of bits 16×i through 16×i+15 of e as an IEEE-754 binary16 value import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullU32Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, run } from '../../expression.js'; import { builtin } from './builtin.js'; +import { d } from './unpack2x16float.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unpack2x16float', { - u32_const: () => { - return FP.f32.generateU32ToIntervalCases( - fullU32Range(), - 'finite', - FP.f32.unpack2x16floatInterval - ); - }, - u32_non_const: () => { - return FP.f32.generateU32ToIntervalCases( - fullU32Range(), - 'unfiltered', - FP.f32.unpack2x16floatInterval - ); - }, -}); - g.test('unpack') .specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions') .desc( @@ -44,5 +25,5 @@ g.test('unpack') .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const'); - await run(t, builtin('unpack2x16float'), [TypeU32], TypeVec(2, TypeF32), t.params, cases); + await run(t, builtin('unpack2x16float'), [Type.u32], Type.vec2f, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.cache.ts new file mode 100644 index 0000000000..89dfb475d3 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.cache.ts @@ -0,0 +1,20 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { fullU32Range } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +export const d = makeCaseCache('unpack2x16snorm', { + u32_const: () => { + return FP.f32.generateU32ToIntervalCases( + fullU32Range(), + 'finite', + FP.f32.unpack2x16snormInterval + ); + }, + u32_non_const: () => { + return FP.f32.generateU32ToIntervalCases( + fullU32Range(), + 'unfiltered', + FP.f32.unpack2x16snormInterval + ); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts index 195cfd9a01..059a5664f9 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts @@ -7,33 +7,14 @@ of bits 16×i through 16×i+15 of e as a twos-complement signed integer. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullU32Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, run } from '../../expression.js'; import { builtin } from './builtin.js'; +import { d } from './unpack2x16snorm.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unpack2x16snorm', { - u32_const: () => { - return FP.f32.generateU32ToIntervalCases( - fullU32Range(), - 'finite', - FP.f32.unpack2x16snormInterval - ); - }, - u32_non_const: () => { - return FP.f32.generateU32ToIntervalCases( - fullU32Range(), - 'unfiltered', - FP.f32.unpack2x16snormInterval - ); - }, -}); - g.test('unpack') .specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions') .desc( @@ -44,5 +25,5 @@ g.test('unpack') .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const'); - await run(t, builtin('unpack2x16snorm'), [TypeU32], TypeVec(2, TypeF32), t.params, cases); + await run(t, builtin('unpack2x16snorm'), [Type.u32], Type.vec2f, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.cache.ts new file mode 100644 index 0000000000..7f0fe84af6 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.cache.ts @@ -0,0 +1,20 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { fullU32Range } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +export const d = makeCaseCache('unpack2x16unorm', { + u32_const: () => { + return FP.f32.generateU32ToIntervalCases( + fullU32Range(), + 'finite', + FP.f32.unpack2x16unormInterval + ); + }, + u32_non_const: () => { + return FP.f32.generateU32ToIntervalCases( + fullU32Range(), + 'unfiltered', + FP.f32.unpack2x16unormInterval + ); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts index 16b4e6397c..971f384023 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts @@ -7,33 +7,14 @@ Component i of the result is v ÷ 65535, where v is the interpretation of bits import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullU32Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, run } from '../../expression.js'; import { builtin } from './builtin.js'; +import { d } from './unpack2x16unorm.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unpack2x16unorm', { - u32_const: () => { - return FP.f32.generateU32ToIntervalCases( - fullU32Range(), - 'finite', - FP.f32.unpack2x16unormInterval - ); - }, - u32_non_const: () => { - return FP.f32.generateU32ToIntervalCases( - fullU32Range(), - 'unfiltered', - FP.f32.unpack2x16unormInterval - ); - }, -}); - g.test('unpack') .specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions') .desc( @@ -44,5 +25,5 @@ g.test('unpack') .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const'); - await run(t, builtin('unpack2x16unorm'), [TypeU32], TypeVec(2, TypeF32), t.params, cases); + await run(t, builtin('unpack2x16unorm'), [Type.u32], Type.vec2f, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.cache.ts new file mode 100644 index 0000000000..3a4790f188 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.cache.ts @@ -0,0 +1,20 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { fullU32Range } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +export const d = makeCaseCache('unpack4x8snorm', { + u32_const: () => { + return FP.f32.generateU32ToIntervalCases( + fullU32Range(), + 'finite', + FP.f32.unpack4x8snormInterval + ); + }, + u32_non_const: () => { + return FP.f32.generateU32ToIntervalCases( + fullU32Range(), + 'unfiltered', + FP.f32.unpack4x8snormInterval + ); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts index 7ea8d51918..d6e533621b 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts @@ -7,33 +7,14 @@ bits 8×i through 8×i+7 of e as a twos-complement signed integer. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullU32Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, run } from '../../expression.js'; import { builtin } from './builtin.js'; +import { d } from './unpack4x8snorm.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unpack4x8snorm', { - u32_const: () => { - return FP.f32.generateU32ToIntervalCases( - fullU32Range(), - 'finite', - FP.f32.unpack4x8snormInterval - ); - }, - u32_non_const: () => { - return FP.f32.generateU32ToIntervalCases( - fullU32Range(), - 'unfiltered', - FP.f32.unpack4x8snormInterval - ); - }, -}); - g.test('unpack') .specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions') .desc( @@ -44,5 +25,5 @@ g.test('unpack') .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const'); - await run(t, builtin('unpack4x8snorm'), [TypeU32], TypeVec(4, TypeF32), t.params, cases); + await run(t, builtin('unpack4x8snorm'), [Type.u32], Type.vec4f, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.cache.ts new file mode 100644 index 0000000000..21390f74b1 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.cache.ts @@ -0,0 +1,20 @@ +import { FP } from '../../../../../util/floating_point.js'; +import { fullU32Range } from '../../../../../util/math.js'; +import { makeCaseCache } from '../../case_cache.js'; + +export const d = makeCaseCache('unpack4x8unorm', { + u32_const: () => { + return FP.f32.generateU32ToIntervalCases( + fullU32Range(), + 'finite', + FP.f32.unpack4x8unormInterval + ); + }, + u32_non_const: () => { + return FP.f32.generateU32ToIntervalCases( + fullU32Range(), + 'unfiltered', + FP.f32.unpack4x8unormInterval + ); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts index bf54d23c12..026043da1a 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts @@ -7,33 +7,14 @@ through 8×i+7 of e as an unsigned integer. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js'; -import { FP } from '../../../../../util/floating_point.js'; -import { fullU32Range } from '../../../../../util/math.js'; -import { makeCaseCache } from '../../case_cache.js'; +import { Type } from '../../../../../util/conversion.js'; import { allInputSources, run } from '../../expression.js'; import { builtin } from './builtin.js'; +import { d } from './unpack4x8unorm.cache.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unpack4x8unorm', { - u32_const: () => { - return FP.f32.generateU32ToIntervalCases( - fullU32Range(), - 'finite', - FP.f32.unpack4x8unormInterval - ); - }, - u32_non_const: () => { - return FP.f32.generateU32ToIntervalCases( - fullU32Range(), - 'unfiltered', - FP.f32.unpack4x8unormInterval - ); - }, -}); - g.test('unpack') .specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions') .desc( @@ -44,5 +25,5 @@ g.test('unpack') .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const'); - await run(t, builtin('unpack4x8unorm'), [TypeU32], TypeVec(4, TypeF32), t.params, cases); + await run(t, builtin('unpack4x8unorm'), [Type.u32], Type.vec4f, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xI8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xI8.spec.ts new file mode 100644 index 0000000000..4f7e4ed3a7 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xI8.spec.ts @@ -0,0 +1,56 @@ +export const description = ` +Execution tests for the 'unpack4xI8' builtin function + +@const fn unpack4xI8(e: u32) -> vec4<i32> +e is interpreted as a vector with four 8-bit signed integer components. Unpack e into a vec4<i32> +with sign extension. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { u32, toVector, i32, Type } from '../../../../../util/conversion.js'; +import { Case } from '../../case.js'; +import { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('basic') + .specURL('https://www.w3.org/TR/WGSL/#unpack4xI8-builtin') + .desc( + ` +@const fn unpack4xI8(e: u32) -> vec4<i32> + ` + ) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cfg: Config = t.params; + + const unpack4xI8 = (e: number) => { + const result: [number, number, number, number] = [0, 0, 0, 0]; + for (let i = 0; i < 4; ++i) { + let intValue = (e >> (8 * i)) & 0xff; + if (intValue > 127) { + intValue -= 256; + } + result[i] = intValue; + } + return result; + }; + + const testInputs = [ + 0, 0x01020304, 0xfcfdfeff, 0x040302ff, 0x0403fe01, 0x04fd0201, 0xfc030201, 0xfcfdfe01, + 0xfcfd02ff, 0xfc03feff, 0x04fdfeff, 0x0403feff, 0x04fd02ff, 0xfc0302ff, 0x04fdfe01, + 0xfc03fe01, 0xfcfd0201, 0x80817f7e, + ] as const; + + const makeCase = (e: number): Case => { + return { input: [u32(e)], expected: toVector(unpack4xI8(e), i32) }; + }; + const cases: Array<Case> = testInputs.flatMap(v => { + return [makeCase(v)]; + }); + + await run(t, builtin('unpack4xI8'), [Type.u32], Type.vec4i, cfg, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xU8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xU8.spec.ts new file mode 100644 index 0000000000..09849030eb --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xU8.spec.ts @@ -0,0 +1,48 @@ +export const description = ` +Execution tests for the 'unpack4xU8' builtin function + +@const fn unpack4xU8(e: u32) -> vec4<u32> +e is interpreted as a vector with four 8-bit unsigned integer components. Unpack e into a vec4<u32> +with zero extension. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { u32, toVector, Type } from '../../../../../util/conversion.js'; +import { Case } from '../../case.js'; +import { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('basic') + .specURL('https://www.w3.org/TR/WGSL/#unpack4xU8-builtin') + .desc( + ` +@const fn unpack4xU8(e: u32) -> vec4<u32> + ` + ) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cfg: Config = t.params; + + const unpack4xU8 = (e: number) => { + const result: [number, number, number, number] = [0, 0, 0, 0]; + for (let i = 0; i < 4; ++i) { + result[i] = (e >> (8 * i)) & 0xff; + } + return result; + }; + + const testInputs = [0, 0x08060402, 0xffffffff, 0xfefdfcfb] as const; + + const makeCase = (e: number): Case => { + return { input: [u32(e)], expected: toVector(unpack4xU8(e), u32) }; + }; + const cases: Array<Case> = testInputs.flatMap(v => { + return [makeCase(v)]; + }); + + await run(t, builtin('unpack4xU8'), [Type.u32], Type.vec4u, cfg, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupUniformLoad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupUniformLoad.spec.ts new file mode 100644 index 0000000000..099b54146d --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupUniformLoad.spec.ts @@ -0,0 +1,182 @@ +export const description = ` +Executes a control barrier synchronization function that affects memory and atomic operations in the workgroup address space. +`; + +// NOTE: The control barrier executed by this builtin is tested in the memory_model tests. + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { keysOf } from '../../../../../../common/util/data_tables.js'; +import { + TypedArrayBufferView, + TypedArrayBufferViewConstructor, + iterRange, +} from '../../../../../../common/util/util.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { checkElementsEqualGenerated } from '../../../../../util/check_contents.js'; + +export const g = makeTestGroup(GPUTest); + +interface TypeConfig { + // The value to store the workgroup variable. + store_val: string; + // The expected values once the variable has been copied back to the host. + expected: TypedArrayBufferView; + // The type used for the host-visible buffer, if different from the workgroup variable. + host_type?: string; + // A type conversion function, if the types are different. + to_host?: (x: string) => string; + // Any additional module-scope declarations needed by the type. + decls?: string; +} + +// A list of types configurations used for the workgroup variable. +const kTypes: Record<string, TypeConfig> = { + bool: { + store_val: `true`, + expected: new Uint32Array([1]), + host_type: 'u32', + to_host: (x: string) => `u32(${x})`, + }, + u32: { + store_val: `42`, + expected: new Uint32Array([42]), + }, + vec4u: { + store_val: `vec4u(42, 1, 0xffffffff, 777)`, + expected: new Uint32Array([42, 1, 0xffffffff, 777]), + }, + mat3x2f: { + store_val: `mat3x2(42, 1, 65536, -42, -1, -65536)`, + expected: new Float32Array([42, 1, 65536, -42, -1, -65536]), + }, + 'array<u32, 4>': { + store_val: `array(42, 1, 0xffffffff, 777)`, + expected: new Uint32Array([42, 1, 0xffffffff, 777]), + }, + SimpleStruct: { + decls: 'struct SimpleStruct { a: u32, b: u32, c: u32, d: u32, }', + store_val: `SimpleStruct(42, 1, 0xffffffff, 777)`, + expected: new Uint32Array([42, 1, 0xffffffff, 777]), + }, + ComplexStruct: { + decls: `struct Inner { v: vec4u, } + struct ComplexStruct { + a: array<Inner, 4>, + @size(28) b: vec4u, + c: u32 + } + const v = vec4(42, 1, 0xffffffff, 777); + const rhs = ComplexStruct( + array(Inner(v.xyzw), Inner(v.yzwx), Inner(v.zwxy), Inner(v.wxyz)), + v.xzxz, + 0x12345678, + );`, + store_val: `rhs`, + expected: new Uint32Array([ + // v.xyzw + 42, 1, 0xffffffff, 777, + // v.yzwx + 1, 0xffffffff, 777, 42, + // v.zwxy + 0xffffffff, 777, 42, 1, + // v.wxyz + 777, 42, 1, 0xffffffff, + // v.xzxz + 42, 0xffffffff, 42, 0xffffffff, + // 12 bytes of padding + 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0x12345678, + ]), + }, +}; + +g.test('types') + .specURL('https://gpuweb.github.io/gpuweb/wgsl/#workgroupUniformLoad-builtin') + .desc( + `Test that the result of a workgroupUniformLoad is the value previously stored to the workgroup variable, for a variety of types. + ` + ) + .params(u => + u.combine('type', keysOf(kTypes)).combine('wgsize', [ + [1, 1], + [3, 7], + [1, 128], + [16, 16], + ]) + ) + .fn(t => { + const type = kTypes[t.params.type]; + const wgsize_x = t.params.wgsize[0]; + const wgsize_y = t.params.wgsize[1]; + const num_invocations = wgsize_x * wgsize_y; + const num_words_per_invocation = type.expected.length; + const total_host_words = num_invocations * num_words_per_invocation; + + t.skipIf( + num_invocations > t.device.limits.maxComputeInvocationsPerWorkgroup, + `num_invocations (${num_invocations}) > maxComputeInvocationsPerWorkgroup (${t.device.limits.maxComputeInvocationsPerWorkgroup})` + ); + + let load = `workgroupUniformLoad(&wgvar)`; + if (type.to_host) { + load = type.to_host(load); + } + + // Construct a shader that stores a value to workgroup variable and then loads it using + // workgroupUniformLoad() in every invocation, copying the results back to a storage buffer. + const code = ` + ${type.decls ? type.decls : ''} + + @group(0) @binding(0) var<storage, read_write> buffer : array<${ + type.host_type ? type.host_type : t.params.type + }, ${num_invocations}>; + + var<workgroup> wgvar : ${t.params.type}; + + @compute @workgroup_size(${wgsize_x}, ${wgsize_y}) + fn main(@builtin(local_invocation_index) lid: u32) { + if (lid == ${num_invocations - 1}) { + wgvar = ${type.store_val}; + } + buffer[lid] = ${load}; + } + `; + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code }), + entryPoint: 'main', + }, + }); + + // Allocate a buffer and fill it with 0xdeadbeef values. + const outputBuffer = t.makeBufferWithContents( + new Uint32Array([...iterRange(total_host_words, _i => 0xdeadbeef)]), + GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC + ); + const bindGroup = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [{ binding: 0, resource: { buffer: outputBuffer } }], + }); + + // Run the shader. + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginComputePass(); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindGroup); + pass.dispatchWorkgroups(1); + pass.end(); + t.queue.submit([encoder.finish()]); + + // Check that the output matches the expected values for each invocation. + t.expectGPUBufferValuesPassCheck( + outputBuffer, + data => + checkElementsEqualGenerated(data, i => { + return Number(type.expected[i % num_words_per_invocation]); + }), + { + type: type.expected.constructor as TypedArrayBufferViewConstructor, + typedLength: total_host_words, + } + ); + }); |