diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin')
113 files changed, 17057 insertions, 0 deletions
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 new file mode 100644 index 0000000000..05d5242f73 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts @@ -0,0 +1,196 @@ +export const description = ` +Execution tests for the 'abs' builtin function + +S is AbstractInt, 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 +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). +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 { allInputSources, onlyConstInputSource, run } from '../../expression.js'; + +import { abstractBuiltin, 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) + ) + .unimplemented(); + +g.test('u32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`unsigned int tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + await run(t, builtin('abs'), [TypeU32], TypeU32, 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) }, + // Powers of 2: -2^i: 0 =< i =< 31 + { input: u32Bits(kBit.powTwo.to0), expected: u32Bits(kBit.powTwo.to0) }, + { input: u32Bits(kBit.powTwo.to1), expected: u32Bits(kBit.powTwo.to1) }, + { input: u32Bits(kBit.powTwo.to2), expected: u32Bits(kBit.powTwo.to2) }, + { input: u32Bits(kBit.powTwo.to3), expected: u32Bits(kBit.powTwo.to3) }, + { input: u32Bits(kBit.powTwo.to4), expected: u32Bits(kBit.powTwo.to4) }, + { input: u32Bits(kBit.powTwo.to5), expected: u32Bits(kBit.powTwo.to5) }, + { input: u32Bits(kBit.powTwo.to6), expected: u32Bits(kBit.powTwo.to6) }, + { input: u32Bits(kBit.powTwo.to7), expected: u32Bits(kBit.powTwo.to7) }, + { input: u32Bits(kBit.powTwo.to8), expected: u32Bits(kBit.powTwo.to8) }, + { input: u32Bits(kBit.powTwo.to9), expected: u32Bits(kBit.powTwo.to9) }, + { input: u32Bits(kBit.powTwo.to10), expected: u32Bits(kBit.powTwo.to10) }, + { input: u32Bits(kBit.powTwo.to11), expected: u32Bits(kBit.powTwo.to11) }, + { input: u32Bits(kBit.powTwo.to12), expected: u32Bits(kBit.powTwo.to12) }, + { input: u32Bits(kBit.powTwo.to13), expected: u32Bits(kBit.powTwo.to13) }, + { input: u32Bits(kBit.powTwo.to14), expected: u32Bits(kBit.powTwo.to14) }, + { input: u32Bits(kBit.powTwo.to15), expected: u32Bits(kBit.powTwo.to15) }, + { input: u32Bits(kBit.powTwo.to16), expected: u32Bits(kBit.powTwo.to16) }, + { input: u32Bits(kBit.powTwo.to17), expected: u32Bits(kBit.powTwo.to17) }, + { input: u32Bits(kBit.powTwo.to18), expected: u32Bits(kBit.powTwo.to18) }, + { input: u32Bits(kBit.powTwo.to19), expected: u32Bits(kBit.powTwo.to19) }, + { input: u32Bits(kBit.powTwo.to20), expected: u32Bits(kBit.powTwo.to20) }, + { input: u32Bits(kBit.powTwo.to21), expected: u32Bits(kBit.powTwo.to21) }, + { input: u32Bits(kBit.powTwo.to22), expected: u32Bits(kBit.powTwo.to22) }, + { input: u32Bits(kBit.powTwo.to23), expected: u32Bits(kBit.powTwo.to23) }, + { input: u32Bits(kBit.powTwo.to24), expected: u32Bits(kBit.powTwo.to24) }, + { input: u32Bits(kBit.powTwo.to25), expected: u32Bits(kBit.powTwo.to25) }, + { input: u32Bits(kBit.powTwo.to26), expected: u32Bits(kBit.powTwo.to26) }, + { input: u32Bits(kBit.powTwo.to27), expected: u32Bits(kBit.powTwo.to27) }, + { input: u32Bits(kBit.powTwo.to28), expected: u32Bits(kBit.powTwo.to28) }, + { input: u32Bits(kBit.powTwo.to29), expected: u32Bits(kBit.powTwo.to29) }, + { input: u32Bits(kBit.powTwo.to30), expected: u32Bits(kBit.powTwo.to30) }, + { input: u32Bits(kBit.powTwo.to31), expected: u32Bits(kBit.powTwo.to31) }, + ]); + }); + +g.test('i32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`signed int tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + await run(t, builtin('abs'), [TypeI32], TypeI32, 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) }, + { input: i32Bits(kBit.i32.negative.max), expected: i32Bits(kBit.i32.positive.min) }, + { input: i32Bits(kBit.i32.positive.max), expected: i32Bits(kBit.i32.positive.max) }, + { input: i32Bits(kBit.i32.positive.min), expected: i32Bits(kBit.i32.positive.min) }, + // input: -1 * pow(2, n), n = {-31, ..., 0 }, expected: pow(2, n), n = {-31, ..., 0}] + { input: i32Bits(kBit.negPowTwo.to0), expected: i32Bits(kBit.powTwo.to0) }, + { input: i32Bits(kBit.negPowTwo.to1), expected: i32Bits(kBit.powTwo.to1) }, + { input: i32Bits(kBit.negPowTwo.to2), expected: i32Bits(kBit.powTwo.to2) }, + { input: i32Bits(kBit.negPowTwo.to3), expected: i32Bits(kBit.powTwo.to3) }, + { input: i32Bits(kBit.negPowTwo.to4), expected: i32Bits(kBit.powTwo.to4) }, + { input: i32Bits(kBit.negPowTwo.to5), expected: i32Bits(kBit.powTwo.to5) }, + { input: i32Bits(kBit.negPowTwo.to6), expected: i32Bits(kBit.powTwo.to6) }, + { input: i32Bits(kBit.negPowTwo.to7), expected: i32Bits(kBit.powTwo.to7) }, + { input: i32Bits(kBit.negPowTwo.to8), expected: i32Bits(kBit.powTwo.to8) }, + { input: i32Bits(kBit.negPowTwo.to9), expected: i32Bits(kBit.powTwo.to9) }, + { input: i32Bits(kBit.negPowTwo.to10), expected: i32Bits(kBit.powTwo.to10) }, + { input: i32Bits(kBit.negPowTwo.to11), expected: i32Bits(kBit.powTwo.to11) }, + { input: i32Bits(kBit.negPowTwo.to12), expected: i32Bits(kBit.powTwo.to12) }, + { input: i32Bits(kBit.negPowTwo.to13), expected: i32Bits(kBit.powTwo.to13) }, + { input: i32Bits(kBit.negPowTwo.to14), expected: i32Bits(kBit.powTwo.to14) }, + { input: i32Bits(kBit.negPowTwo.to15), expected: i32Bits(kBit.powTwo.to15) }, + { input: i32Bits(kBit.negPowTwo.to16), expected: i32Bits(kBit.powTwo.to16) }, + { input: i32Bits(kBit.negPowTwo.to17), expected: i32Bits(kBit.powTwo.to17) }, + { input: i32Bits(kBit.negPowTwo.to18), expected: i32Bits(kBit.powTwo.to18) }, + { input: i32Bits(kBit.negPowTwo.to19), expected: i32Bits(kBit.powTwo.to19) }, + { input: i32Bits(kBit.negPowTwo.to20), expected: i32Bits(kBit.powTwo.to20) }, + { input: i32Bits(kBit.negPowTwo.to21), expected: i32Bits(kBit.powTwo.to21) }, + { input: i32Bits(kBit.negPowTwo.to22), expected: i32Bits(kBit.powTwo.to22) }, + { input: i32Bits(kBit.negPowTwo.to23), expected: i32Bits(kBit.powTwo.to23) }, + { input: i32Bits(kBit.negPowTwo.to24), expected: i32Bits(kBit.powTwo.to24) }, + { input: i32Bits(kBit.negPowTwo.to25), expected: i32Bits(kBit.powTwo.to25) }, + { input: i32Bits(kBit.negPowTwo.to26), expected: i32Bits(kBit.powTwo.to26) }, + { input: i32Bits(kBit.negPowTwo.to27), expected: i32Bits(kBit.powTwo.to27) }, + { input: i32Bits(kBit.negPowTwo.to28), expected: i32Bits(kBit.powTwo.to28) }, + { input: i32Bits(kBit.negPowTwo.to29), expected: i32Bits(kBit.powTwo.to29) }, + { input: i32Bits(kBit.negPowTwo.to30), expected: i32Bits(kBit.powTwo.to30) }, + { input: i32Bits(kBit.negPowTwo.to31), expected: i32Bits(kBit.powTwo.to31) }, + ]); + }); + +g.test('abstract_float') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`abstract float tests`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .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); + }); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`float 32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('abs'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('abs'), [TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..5755c07905 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts @@ -0,0 +1,78 @@ +export const description = ` +Execution tests for the 'acos' builtin function + +S is AbstractFloat, 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. +`; + +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 { 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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..cc78ce3eee --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts @@ -0,0 +1,81 @@ +export const description = ` +Execution tests for the 'acosh' builtin function + +S is AbstractFloat, 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. +Computes the non-negative functional inverse of cosh. +Component-wise when T is a vector. +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 { 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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..9a2938c1d5 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts @@ -0,0 +1,92 @@ +export const description = ` +Execution tests for the 'all' builtin function + +S is a bool +T is S or vecN<S> +@const fn all(e: T) -> bool +Returns e if e is scalar. +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 { allInputSources, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('bool') + .specURL('https://www.w3.org/TR/WGSL/#logical-builtin-functions') + .desc(`bool tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('overload', ['scalar', 'vec2', 'vec3', 'vec4'] as const) + ) + .fn(async t => { + const overloads = { + scalar: { + type: TypeBool, + cases: [ + { input: False, expected: False }, + { input: True, expected: True }, + ], + }, + vec2: { + type: TypeVec(2, TypeBool), + cases: [ + { input: vec2(False, False), expected: False }, + { input: vec2(True, False), expected: False }, + { input: vec2(False, True), expected: False }, + { input: vec2(True, True), expected: True }, + ], + }, + vec3: { + type: TypeVec(3, TypeBool), + cases: [ + { input: vec3(False, False, False), expected: False }, + { input: vec3(True, False, False), expected: False }, + { input: vec3(False, True, False), expected: False }, + { input: vec3(True, True, False), expected: False }, + { input: vec3(False, False, True), expected: False }, + { input: vec3(True, False, True), expected: False }, + { input: vec3(False, True, True), expected: False }, + { input: vec3(True, True, True), expected: True }, + ], + }, + vec4: { + type: TypeVec(4, TypeBool), + cases: [ + { input: vec4(False, False, False, False), expected: False }, + { input: vec4(False, True, False, False), expected: False }, + { input: vec4(False, False, True, False), expected: False }, + { input: vec4(False, True, True, False), expected: False }, + { input: vec4(False, False, False, True), expected: False }, + { input: vec4(False, True, False, True), expected: False }, + { input: vec4(False, False, True, True), expected: False }, + { input: vec4(False, True, True, True), expected: False }, + { input: vec4(True, False, False, False), expected: False }, + { input: vec4(True, False, False, True), expected: False }, + { input: vec4(True, False, True, False), expected: False }, + { input: vec4(True, False, True, True), expected: False }, + { input: vec4(True, True, False, False), expected: False }, + { input: vec4(True, True, False, True), expected: False }, + { input: vec4(True, True, True, False), expected: False }, + { input: vec4(True, True, True, True), expected: True }, + ], + }, + }; + const overload = overloads[t.params.overload]; + + await run(t, builtin('all'), [overload.type], TypeBool, 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 new file mode 100644 index 0000000000..19ed7d186f --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts @@ -0,0 +1,92 @@ +export const description = ` +Execution tests for the 'any' builtin function + +S is a bool +T is S or vecN<S> +@const fn all(e) -> bool +Returns e if e is scalar. +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 { allInputSources, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('bool') + .specURL('https://www.w3.org/TR/WGSL/#logical-builtin-functions') + .desc(`bool tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('overload', ['scalar', 'vec2', 'vec3', 'vec4'] as const) + ) + .fn(async t => { + const overloads = { + scalar: { + type: TypeBool, + cases: [ + { input: False, expected: False }, + { input: True, expected: True }, + ], + }, + vec2: { + type: TypeVec(2, TypeBool), + cases: [ + { input: vec2(False, False), expected: False }, + { input: vec2(True, False), expected: True }, + { input: vec2(False, True), expected: True }, + { input: vec2(True, True), expected: True }, + ], + }, + vec3: { + type: TypeVec(3, TypeBool), + cases: [ + { input: vec3(False, False, False), expected: False }, + { input: vec3(True, False, False), expected: True }, + { input: vec3(False, True, False), expected: True }, + { input: vec3(True, True, False), expected: True }, + { input: vec3(False, False, True), expected: True }, + { input: vec3(True, False, True), expected: True }, + { input: vec3(False, True, True), expected: True }, + { input: vec3(True, True, True), expected: True }, + ], + }, + vec4: { + type: TypeVec(4, TypeBool), + cases: [ + { input: vec4(False, False, False, False), expected: False }, + { input: vec4(False, True, False, False), expected: True }, + { input: vec4(False, False, True, False), expected: True }, + { input: vec4(False, True, True, False), expected: True }, + { input: vec4(False, False, False, True), expected: True }, + { input: vec4(False, True, False, True), expected: True }, + { input: vec4(False, False, True, True), expected: True }, + { input: vec4(False, True, True, True), expected: True }, + { input: vec4(True, False, False, False), expected: True }, + { input: vec4(True, False, False, True), expected: True }, + { input: vec4(True, False, True, False), expected: True }, + { input: vec4(True, False, True, True), expected: True }, + { input: vec4(True, True, False, False), expected: True }, + { input: vec4(True, True, False, True), expected: True }, + { input: vec4(True, True, True, False), expected: True }, + { input: vec4(True, True, True, True), expected: True }, + ], + }, + }; + const overload = overloads[t.params.overload]; + + await run(t, builtin('any'), [overload.type], TypeBool, t.params, overload.cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/arrayLength.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/arrayLength.spec.ts new file mode 100644 index 0000000000..e5c20391d8 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/arrayLength.spec.ts @@ -0,0 +1,306 @@ +export const description = ` +Execution tests for the 'arrayLength' builtin function. + +fn arrayLength(e: ptr<storage,array<T>> ) -> u32 +Returns the number of elements in the runtime-sized array. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { align } from '../../../../../util/math.js'; + +export const g = makeTestGroup(GPUTest); + +// List of array element types to test. +const kTestTypes = [ + { type: 'u32', stride: 4 }, + { type: 'i32', stride: 4 }, + { type: 'f32', stride: 4 }, + { type: 'f16', stride: 2 }, + { type: 'vec2<u32>', stride: 8 }, + { type: 'vec2<i32>', stride: 8 }, + { type: 'vec2<f32>', stride: 8 }, + { type: 'vec2<f16>', stride: 4 }, + { type: 'vec3<u32>', stride: 16 }, + { type: 'vec3<i32>', stride: 16 }, + { type: 'vec3<f32>', stride: 16 }, + { type: 'vec3<f16>', stride: 8 }, + { type: 'vec4<u32>', stride: 16 }, + { type: 'vec4<i32>', stride: 16 }, + { type: 'vec4<f32>', stride: 16 }, + { type: 'vec4<f16>', stride: 8 }, + { type: 'mat2x2<f32>', stride: 16 }, + { type: 'mat2x3<f32>', stride: 32 }, + { type: 'mat2x4<f32>', stride: 32 }, + { type: 'mat3x2<f32>', stride: 24 }, + { type: 'mat3x3<f32>', stride: 48 }, + { type: 'mat3x4<f32>', stride: 48 }, + { type: 'mat4x2<f32>', stride: 32 }, + { type: 'mat4x3<f32>', stride: 64 }, + { type: 'mat4x4<f32>', stride: 64 }, + { type: 'mat2x2<f16>', stride: 8 }, + { type: 'mat2x3<f16>', stride: 16 }, + { type: 'mat2x4<f16>', stride: 16 }, + { type: 'mat3x2<f16>', stride: 12 }, + { type: 'mat3x3<f16>', stride: 24 }, + { type: 'mat3x4<f16>', stride: 24 }, + { type: 'mat4x2<f16>', stride: 16 }, + { type: 'mat4x3<f16>', stride: 32 }, + { type: 'mat4x4<f16>', stride: 32 }, + { type: 'atomic<u32>', stride: 4 }, + { type: 'atomic<i32>', stride: 4 }, + { type: 'array<u32,4>', stride: 16 }, + { type: 'array<i32,4>', stride: 16 }, + { type: 'array<f32,4>', stride: 16 }, + { type: 'array<f16,4>', stride: 8 }, + // Structures - see declarations below. + { type: 'ElemStruct', stride: 4 }, + { type: 'ElemStruct_ImplicitPadding', stride: 16 }, + { type: 'ElemStruct_ExplicitPadding', stride: 32 }, +] as const; + +// Declarations for structures used as array element types. +const kWgslStructures = ` +struct ElemStruct { a : u32 } +struct ElemStruct_ImplicitPadding { a : vec3<u32> } +struct ElemStruct_ExplicitPadding { @align(32) a : u32 } +`; + +/** + * Run a shader and check that the array length is correct. + * + * @param t The test object + * @param wgsl The shader source + * @param stride The stride in bytes of the array element type + * @param offset The offset in bytes of the array from the start of the binding + * @param buffer_size The size in bytes of the buffer to allocate + * @param binding_size The size in bytes of the binding + * @param binding_offset The offset in bytes of the binding + * @param expected The array of expected values after running the shader + */ +function runShaderTest( + t: GPUTest, + wgsl: string, + stride: number, + offset: number, + buffer_size: number, + binding_size: number, + binding_offset: number +): void { + // Create the compute pipeline. + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + // Create the buffer that will contain the runtime-sized array. + const buffer = t.device.createBuffer({ + size: buffer_size, + usage: GPUBufferUsage.STORAGE, + }); + + // Create the buffer that will receive the array length. + const lengthBuffer = t.device.createBuffer({ + size: 4, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + + // Set up bindings. + const bindGroup = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer, size: binding_size, offset: binding_offset } }, + { binding: 1, resource: { buffer: lengthBuffer } }, + ], + }); + + // 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 the length. + const length = (binding_size - offset) / stride; + t.expectGPUBufferValuesEqual(lengthBuffer, new Uint32Array([length])); +} + +/** + * Test if a WGSL type string require using f16 extension. + * + * @param test_type The wgsl type for testing + */ +function typeRequiresF16(test_type: string): boolean { + return test_type.includes('f16'); +} + +/** + * Generate the necessary wgsl header for tested type, especially for f16 + * + * @param test_type The wgsl type for testing + */ +function shaderHeader(test_type: string): string { + return typeRequiresF16(test_type) ? 'enable f16;\n\n' : ''; +} + +g.test('single_element') + .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin') + .desc( + `Test the arrayLength() builtin with a binding that is just large enough for a single element. + + Test parameters: + - type: The WGSL type to use as the array element type. + - stride: The stride in bytes of the array element type. + ` + ) + .params(u => u.combineWithParams(kTestTypes)) + .beforeAllSubcases(t => { + if (typeRequiresF16(t.params.type)) { + t.selectDeviceOrSkipTestCase('shader-f16'); + } + }) + .fn(t => { + const wgsl = + shaderHeader(t.params.type) + + kWgslStructures + + ` + @group(0) @binding(0) var<storage, read_write> buffer : array<${t.params.type}>; + @group(0) @binding(1) var<storage, read_write> length : u32; + @compute @workgroup_size(1) + fn main() { + length = arrayLength(&buffer); + } + `; + let buffer_size: number = t.params.stride; + // Ensure that binding size is multiple of 4. + buffer_size = buffer_size + ((~buffer_size + 1) & 3); + runShaderTest(t, wgsl, t.params.stride, 0, buffer_size, buffer_size, 0); + }); + +g.test('multiple_elements') + .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin') + .desc( + `Test the arrayLength() builtin with a binding that is large enough for multiple elements. + + We test sizes that are not exact multiples of the array element strides, to test that the + length is correctly floored to the next whole element. + + Test parameters: + - buffer_size: The size in bytes of the buffer. + - type: The WGSL type to use as the array element type. + - stride: The stride in bytes of the array element type. + ` + ) + .params(u => + u.combine('buffer_size', [640, 1004, 1048576] as const).combineWithParams(kTestTypes) + ) + .beforeAllSubcases(t => { + if (typeRequiresF16(t.params.type)) { + t.selectDeviceOrSkipTestCase('shader-f16'); + } + }) + .fn(t => { + const wgsl = + shaderHeader(t.params.type) + + kWgslStructures + + ` + @group(0) @binding(0) var<storage, read_write> buffer : array<${t.params.type}>; + @group(0) @binding(1) var<storage, read_write> length : u32; + @compute @workgroup_size(1) + fn main() { + length = arrayLength(&buffer); + } + `; + runShaderTest(t, wgsl, t.params.stride, 0, t.params.buffer_size, t.params.buffer_size, 0); + }); + +g.test('struct_member') + .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin') + .desc( + `Test the arrayLength() builtin with an array that is inside a structure. + + We include offsets that are not exact multiples of the array element strides, to test that + the length is correctly floored to the next whole element. + + Test parameters: + - member_offset: The offset (in bytes) of the array member from the start of the struct. + - type: The WGSL type to use as the array element type. + - stride: The stride in bytes of the array element type. + ` + ) + .params(u => u.combine('member_offset', [0, 4, 20] as const).combineWithParams(kTestTypes)) + .beforeAllSubcases(t => { + if (typeRequiresF16(t.params.type)) { + t.selectDeviceOrSkipTestCase('shader-f16'); + } + }) + .fn(t => { + const member_offset = align(t.params.member_offset, t.params.stride); + const wgsl = + shaderHeader(t.params.type) + + kWgslStructures + + ` + alias ArrayType = array<${t.params.type}>; + struct Struct { + ${t.params.member_offset > 0 ? `@size(${member_offset}) padding : u32,` : ``} + arr : ArrayType, + } + @group(0) @binding(0) var<storage, read_write> buffer : Struct; + @group(0) @binding(1) var<storage, read_write> length : u32; + @compute @workgroup_size(1) + fn main() { + length = arrayLength(&buffer.arr); + } + `; + const buffer_size = 1048576; + runShaderTest(t, wgsl, t.params.stride, member_offset, buffer_size, buffer_size, 0); + }); + +g.test('binding_subregion') + .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin') + .desc( + `Test the arrayLength() builtin when used with a binding that starts at a non-zero offset and + does not fill the entire buffer. + ` + ) + .fn(t => { + const wgsl = ` + @group(0) @binding(0) var<storage, read_write> buffer : array<vec3<f32>>; + @group(0) @binding(1) var<storage, read_write> length : u32; + @compute @workgroup_size(1) + fn main() { + length = arrayLength(&buffer); + } + `; + const stride = 16; + const buffer_size = 1024; + const binding_size = 640; + const binding_offset = 256; + runShaderTest(t, wgsl, stride, 0, buffer_size, binding_size, binding_offset); + }); + +g.test('read_only') + .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin') + .desc( + `Test the arrayLength() builtin when used with a read-only storage buffer. + ` + ) + .fn(t => { + const wgsl = ` + @group(0) @binding(0) var<storage, read> buffer : array<vec3<f32>>; + @group(0) @binding(1) var<storage, read_write> length : u32; + @compute @workgroup_size(1) + fn main() { + length = arrayLength(&buffer); + } + `; + const stride = 16; + const buffer_size = 1024; + runShaderTest(t, wgsl, stride, 0, buffer_size, buffer_size, 0); + }); 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 new file mode 100644 index 0000000000..8d18ebb303 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts @@ -0,0 +1,78 @@ +export const description = ` +Execution tests for the 'asin' builtin function + +S is AbstractFloat, 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. +`; + +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 { 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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..9a8384e090 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts @@ -0,0 +1,65 @@ +export const description = ` +Execution tests for the 'sinh' builtin function + +S is AbstractFloat, f32, f16 +T is S or vecN<S> +@const fn asinh(e: T ) -> T +Returns the hyperbolic arc sine of e. +Computes the functional inverse of sinh. +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 { 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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('asinh'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('asinh'), [TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..3d0d3e6725 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts @@ -0,0 +1,80 @@ +export const description = ` +Execution tests for the 'atan' builtin function + +S is AbstractFloat, 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. + +`; + +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 { 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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +f32 tests + +TODO(#792): Decide what the ground-truth is for these tests. [1] +` + ) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..fbace73dd2 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts @@ -0,0 +1,83 @@ +export const description = ` +Execution tests for the 'atan2' builtin function + +S is AbstractFloat, 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. +`; + +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 { 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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +f32 tests + +TODO(#792): Decide what the ground-truth is for these tests. [1] +` + ) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..90f322a7ea --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts @@ -0,0 +1,87 @@ +export const description = ` +Execution tests for the 'atanh' builtin function + +S is AbstractFloat, 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. +Computes the functional inverse of tanh. +Component-wise when T is a vector. +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 { 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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..37d3ce5292 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts @@ -0,0 +1,101 @@ +export const description = ` +Atomically read, add and store value. + + * Load the original value pointed to by atomic_ptr. + * Obtains a new value by adding with the value v. + * Store the new value using atomic_ptr. + +Returns the original value stored in the atomic object. +`; + +import { makeTestGroup } from '../../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../../gpu_test.js'; + +import { + dispatchSizes, + workgroupSizes, + runStorageVariableTest, + runWorkgroupVariableTest, + typedArrayCtor, +} from './harness.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('add_storage') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize * t.params.dispatchSize; + // Allocate one extra element to ensure it doesn't get modified + const bufferNumElements = 2; + + const initValue = 0; + const op = `atomicAdd(&output[0], 1)`; + const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements); + expected[0] = numInvocations; + + runStorageVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + bufferNumElements, + initValue, + op, + expected, + }); + }); + +g.test('add_workgroup') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + // Allocate one extra element to ensure it doesn't get modified + const wgNumElements = 2; + + const initValue = 0; + const op = `atomicAdd(&wg[0], 1)`; + + const expected = new (typedArrayCtor(t.params.scalarType))( + wgNumElements * t.params.dispatchSize + ); + for (let d = 0; d < t.params.dispatchSize; ++d) { + const wg = expected.subarray(d * wgNumElements); + wg[0] = t.params.workgroupSize; + } + + runWorkgroupVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + wgNumElements, + initValue, + op, + expected, + }); + }); 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 new file mode 100644 index 0000000000..ed5cfa84a3 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts @@ -0,0 +1,135 @@ +export const description = ` +Atomically read, and and store value. + +* Load the original value pointed to by atomic_ptr. +* Obtains a new value by anding with the value v. +* Store the new value using atomic_ptr. + +Returns the original value stored in the atomic object. +`; + +import { makeTestGroup } from '../../../../../../../common/framework/test_group.js'; +import { keysOf } from '../../../../../../../common/util/data_tables.js'; +import { GPUTest } from '../../../../../../gpu_test.js'; + +import { + dispatchSizes, + workgroupSizes, + runStorageVariableTest, + runWorkgroupVariableTest, + kMapId, + typedArrayCtor, +} from './harness.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('and_storage') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize * t.params.dispatchSize; + + // Allocate an output buffer with bitsize of max invocations plus 1 for validation + const bufferNumElements = Math.max(1, numInvocations / 32) + 1; + + // Start with all bits high, then using atomicAnd to set mapped global id bit off. + // Note: Both WGSL and JS will shift left 1 by id modulo 32. + const initValue = 0xffffffff; + + const scalarType = t.params.scalarType; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations); // Defines map_id() + const op = ` + let i = map_id(u32(id)); + atomicAnd(&output[i / 32], ~(${scalarType}(1) << i)) + `; + + const expected = new (typedArrayCtor(scalarType))(bufferNumElements).fill(initValue); + for (let id = 0; id < numInvocations; ++id) { + const i = mapId.f(id, numInvocations); + expected[Math.floor(i / 32)] &= ~(1 << i); + } + + runStorageVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + bufferNumElements, + initValue, + op, + expected, + extra, + }); + }); + +g.test('and_workgroup') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize; + + // Allocate workgroup array with bitsize of max invocations plus 1 for validation + const wgNumElements = Math.max(1, numInvocations / 32) + 1; + + // Start with all bits high, then using atomicAnd to set mapped global id bit off. + // Note: Both WGSL and JS will shift left 1 by id modulo 32. + const initValue = 0xffffffff; + + const scalarType = t.params.scalarType; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations); // Defines map_id() + const op = ` + let i = map_id(u32(id)); + atomicAnd(&wg[i / 32], ~(${scalarType}(1) << i)) + `; + + const expected = new (typedArrayCtor(scalarType))(wgNumElements * t.params.dispatchSize).fill( + initValue + ); + for (let d = 0; d < t.params.dispatchSize; ++d) { + for (let id = 0; id < numInvocations; ++id) { + const wg = expected.subarray(d * wgNumElements); + const i = mapId.f(id, numInvocations); + wg[Math.floor(i / 32)] &= ~(1 << i); + } + } + + runWorkgroupVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + wgNumElements, + initValue, + op, + expected, + extra, + }); + }); 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 new file mode 100644 index 0000000000..2556bb744b --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts @@ -0,0 +1,742 @@ +export const description = ` +Performs the following steps atomically: + * Load the original value pointed to by atomic_ptr. + * Compare the original value to the value v using an equality operation. + * Store the value v only if the result of the equality comparison was true. + +Returns a two member structure, where the first member, old_value, is the original +value of the atomic object and the second member, exchanged, is whether or not +the comparison succeeded. + +Note: the equality comparison may spuriously fail on some implementations. +That is, the second component of the result vector may be false even if the first +component of the result vector equals cmp. +`; + +import { makeTestGroup } from '../../../../../../../common/framework/test_group.js'; +import { keysOf } from '../../../../../../../common/util/data_tables.js'; +import { assert } from '../../../../../../../common/util/util.js'; +import { GPUTest } from '../../../../../../gpu_test.js'; + +import { + dispatchSizes, + workgroupSizes, + typedArrayCtor, + kMapId, + onlyWorkgroupSizes, +} from './harness.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('compare_exchange_weak_storage_basic') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicCompareExchangeWeak(atomic_ptr: ptr<AS, atomic<T>, read_write>, cmp: T, v: T) -> __atomic_compare_exchange_result<T> + +struct __atomic_compare_exchange_result<T> { + old_value : T, // old value stored in the atomic + exchanged : bool, // true if the exchange was done +} +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(async t => { + const numInvocations = t.params.workgroupSize * t.params.dispatchSize; + const bufferNumElements = numInvocations; + const scalarType = t.params.scalarType; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id() + + const wgsl = + ` + @group(0) @binding(0) + var<storage, read_write> input : array<atomic<${scalarType}>>; + + @group(0) @binding(1) + var<storage, read_write> output : array<${scalarType}>; + + @group(0) @binding(2) + var<storage, read_write> exchanged : array<${scalarType}>; + + @compute @workgroup_size(${t.params.workgroupSize}) + fn main( + @builtin(global_invocation_id) global_invocation_id : vec3<u32>, + ) { + let id = ${scalarType}(global_invocation_id[0]); + + // Exchange every third value + var comp = id + 1; + if (id % 3 == 0) { + comp = id; + } + let r = atomicCompareExchangeWeak(&input[id], comp, map_id(id * 2)); + + // Store results + output[id] = r.old_value; + if (r.exchanged) { + exchanged[id] = 1; + } else { + exchanged[id] = 0; + } + } + ` + extra; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const arrayType = typedArrayCtor(scalarType); + + // Create input buffer with values [0..n] + const inputBuffer = t.device.createBuffer({ + size: bufferNumElements * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + mappedAtCreation: true, + }); + t.trackForCleanup(inputBuffer); + const data = new arrayType(inputBuffer.getMappedRange()); + data.forEach((_, i) => (data[i] = i)); + inputBuffer.unmap(); + + const outputBuffer = t.device.createBuffer({ + size: bufferNumElements * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(outputBuffer); + + const exchangedBuffer = t.device.createBuffer({ + size: bufferNumElements * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(exchangedBuffer); + + const bindGroup = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: inputBuffer } }, + { binding: 1, resource: { buffer: outputBuffer } }, + { binding: 2, resource: { buffer: exchangedBuffer } }, + ], + }); + + // Run the shader. + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginComputePass(); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindGroup); + pass.dispatchWorkgroups(t.params.dispatchSize); + pass.end(); + t.queue.submit([encoder.finish()]); + + // Output buffer should be the same as the initial input buffer as it contains + // values returned from atomicCompareExchangeWeak + const outputExpected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements); + outputExpected.forEach((_, i) => (outputExpected[i] = i)); + t.expectGPUBufferValuesEqual(outputBuffer, outputExpected); + + // Read back exchanged buffer + const exchangedBufferResult = await t.readGPUBufferRangeTyped(exchangedBuffer, { + type: arrayType, + typedLength: exchangedBuffer.size / arrayType.BYTES_PER_ELEMENT, + }); + + // The input buffer should have been modified to a computed value for every third value, + // unless the comparison spuriously failed. + const inputExpected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements); + inputExpected.forEach((_, i) => { + if (i % 3 === 0 && exchangedBufferResult.data[i]) { + inputExpected[i] = mapId.f(i * 2, numInvocations); + } else { + inputExpected[i] = i; // No change + } + }); + t.expectGPUBufferValuesEqual(inputBuffer, inputExpected); + }); + +g.test('compare_exchange_weak_workgroup_basic') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicCompareExchangeWeak(atomic_ptr: ptr<AS, atomic<T>, read_write>, cmp: T, v: T) -> __atomic_compare_exchange_result<T> + +struct __atomic_compare_exchange_result<T> { + old_value : T, // old value stored in the atomic + exchanged : bool, // true if the exchange was done +} +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(async t => { + const numInvocations = t.params.workgroupSize; + const wgNumElements = numInvocations; + const scalarType = t.params.scalarType; + const dispatchSize = t.params.dispatchSize; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id() + + const wgsl = + ` + var<workgroup> wg: array<atomic<${scalarType}>, ${wgNumElements}>; + + @group(0) @binding(0) + var<storage, read_write> output: array<${scalarType}, ${wgNumElements * dispatchSize}>; + + @group(0) @binding(1) + var<storage, read_write> exchanged: array<${scalarType}, ${wgNumElements * dispatchSize}>; + + // Result of each workgroup is written to output[workgroup_id.x] + @group(0) @binding(2) + var<storage, read_write> wg_copy: array<${scalarType}, ${wgNumElements * dispatchSize}>; + + @compute @workgroup_size(${t.params.workgroupSize}) + fn main( + @builtin(local_invocation_index) local_invocation_index: u32, + @builtin(workgroup_id) workgroup_id : vec3<u32> + ) { + let id = ${scalarType}(local_invocation_index); + let global_id = ${scalarType}(workgroup_id.x * ${wgNumElements} + local_invocation_index); + + // Initialize wg[id] with this invocations global id + atomicStore(&wg[id], global_id); + + // Exchange every third value + var comp = global_id + 1; + if (global_id % 3 == 0) { + comp = global_id; + } + let r = atomicCompareExchangeWeak(&wg[id], comp, map_id(global_id * 2)); + + // Store results + output[global_id] = r.old_value; + if (r.exchanged) { + exchanged[global_id] = 1; + } else { + exchanged[global_id] = 0; + } + + // Copy new value into wg_copy + wg_copy[global_id] = atomicLoad(&wg[id]); + } + ` + extra; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const arrayType = typedArrayCtor(scalarType); + + const outputBuffer = t.device.createBuffer({ + size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(outputBuffer); + + const wgCopyBuffer = t.device.createBuffer({ + size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(outputBuffer); + + const exchangedBuffer = t.device.createBuffer({ + size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(exchangedBuffer); + + const bindGroup = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: outputBuffer } }, + { binding: 1, resource: { buffer: exchangedBuffer } }, + { binding: 2, resource: { buffer: wgCopyBuffer } }, + ], + }); + + // Run the shader. + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginComputePass(); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindGroup); + pass.dispatchWorkgroups(dispatchSize); + pass.end(); + t.queue.submit([encoder.finish()]); + + // Output buffer should be the same as the initial wg buffer as it contains + // values returned from atomicCompareExchangeWeak + const outputExpected = new (typedArrayCtor(t.params.scalarType))(wgNumElements * dispatchSize); + outputExpected.forEach((_, i) => (outputExpected[i] = i)); + t.expectGPUBufferValuesEqual(outputBuffer, outputExpected); + + // Read back exchanged buffer + const exchangedBufferResult = await t.readGPUBufferRangeTyped(exchangedBuffer, { + type: arrayType, + typedLength: exchangedBuffer.size / arrayType.BYTES_PER_ELEMENT, + }); + + // And the wg copy buffer should have been modified to a computed value for every third value, + // unless the comparison spuriously failed. + const wgCopyBufferExpected = new (typedArrayCtor(t.params.scalarType))( + wgNumElements * dispatchSize + ); + wgCopyBufferExpected.forEach((_, i) => { + if (i % 3 === 0 && exchangedBufferResult.data[i]) { + wgCopyBufferExpected[i] = mapId.f(i * 2, numInvocations); + } else { + wgCopyBufferExpected[i] = i; // No change + } + }); + t.expectGPUBufferValuesEqual(wgCopyBuffer, wgCopyBufferExpected); + }); + +g.test('compare_exchange_weak_storage_advanced') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicCompareExchangeWeak(atomic_ptr: ptr<AS, atomic<T>, read_write>, cmp: T, v: T) -> __atomic_compare_exchange_result<T> + +struct __atomic_compare_exchange_result<T> { + old_value : T, // old value stored in the atomic + exchanged : bool, // true if the exchange was done +} +` + ) + .params(u => + u + .combine('workgroupSize', onlyWorkgroupSizes) // + .combine('scalarType', ['u32', 'i32']) + ) + .fn(async t => { + const numInvocations = t.params.workgroupSize; + const scalarType = t.params.scalarType; + + // Number of times each workgroup attempts to exchange the same value to the same memory address + const numWrites = 4; + + const bufferNumElements = numInvocations * numWrites; + const pingPongValues = [24, 68]; + + const wgsl = ` + @group(0) @binding(0) + var<storage, read_write> data : atomic<${scalarType}>; + + @group(0) @binding(1) + var<storage, read_write> old_values : array<${scalarType}>; + + @group(0) @binding(2) + var<storage, read_write> exchanged : array<${scalarType}>; + + fn ping_pong_value(i: u32) -> ${scalarType} { + if (i % 2 == 0) { + return ${pingPongValues[0]}; + } else { + return ${pingPongValues[1]}; + } + } + + @compute @workgroup_size(${t.params.workgroupSize}) + fn main( + @builtin(global_invocation_id) global_invocation_id : vec3<u32>, + ) { + let id = ${scalarType}(global_invocation_id[0]); + + // Each invocation attempts to write an alternating (ping-pong) value, once per loop. + // The data value is initialized with the first of the two ping-pong values. + // Only one invocation per loop iteration should succeed. Note the workgroupBarrier() used + // to synchronize each invocation in the loop. + // The reason we alternate is in case atomicCompareExchangeWeak spurioulsy fails: + // If all invocations of one iteration spuriously fail, the very next iteration will also + // fail since the value will not have been exchanged; however, the subsequent one will succeed + // (assuming not all iterations spuriously fail yet again). + + for (var i = 0u; i < ${numWrites}u; i++) { + let compare = ping_pong_value(i); + let next = ping_pong_value(i + 1); + + let r = atomicCompareExchangeWeak(&data, compare, next); + + let slot = i * ${numInvocations}u + u32(id); + old_values[slot] = r.old_value; + if (r.exchanged) { + exchanged[slot] = 1; + } else { + exchanged[slot] = 0; + } + + workgroupBarrier(); + } + } + `; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const arrayType = typedArrayCtor(scalarType); + const defaultValue = 99999999; + + // Create single-value data buffer initialized to the first ping-pong value + const dataBuffer = t.device.createBuffer({ + size: 1 * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + mappedAtCreation: true, + }); + { + const data = new arrayType(dataBuffer.getMappedRange()); + data[0] = pingPongValues[0]; + dataBuffer.unmap(); + } + t.trackForCleanup(dataBuffer); + + const oldValuesBuffer = t.device.createBuffer({ + size: bufferNumElements * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + mappedAtCreation: true, + }); + t.trackForCleanup(oldValuesBuffer); + { + const data = new arrayType(oldValuesBuffer.getMappedRange()); + data.fill(defaultValue); + oldValuesBuffer.unmap(); + } + + const exchangedBuffer = t.device.createBuffer({ + size: bufferNumElements * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + mappedAtCreation: true, + }); + t.trackForCleanup(exchangedBuffer); + { + const data = new arrayType(exchangedBuffer.getMappedRange()); + data.fill(defaultValue); + exchangedBuffer.unmap(); + } + + const bindGroup = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: dataBuffer } }, + { binding: 1, resource: { buffer: oldValuesBuffer } }, + { binding: 2, resource: { buffer: exchangedBuffer } }, + ], + }); + + // 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()]); + + // Read back buffers + const oldValuesBufferResult = ( + await t.readGPUBufferRangeTyped(oldValuesBuffer, { + type: arrayType, + typedLength: oldValuesBuffer.size / arrayType.BYTES_PER_ELEMENT, + }) + ).data; + const exchangedBufferResult = ( + await t.readGPUBufferRangeTyped(exchangedBuffer, { + type: arrayType, + typedLength: exchangedBuffer.size / arrayType.BYTES_PER_ELEMENT, + }) + ).data; + + for (let w = 0; w < numWrites; ++w) { + const offset = w * numInvocations; + const exchanged = exchangedBufferResult.subarray(offset, offset + numInvocations); + const oldValues = oldValuesBufferResult.subarray(offset, offset + numInvocations); + + const dumpValues = () => { + return ` + For write: ${w} + exchanged: ${exchanged} + oldValues: ${oldValues}`; + }; + + // Only one of the invocations should have succeeded to exchange - or none if spurious failures occured + const noExchanges = exchanged.every(v => v === 0); + if (noExchanges) { + // Spurious failure, all values in oldValues should be the default value + if (!oldValues.every(v => v === defaultValue)) { + t.fail( + `Spurious failure detected, expected only default value of ${defaultValue} in oldValues buffer.${dumpValues()}` + ); + return; + } + } else { + // Only one invocation should have exchanged its value + if (exchanged.filter(v => v === 1).length !== 1) { + t.fail(`More than one invocation exchanged its value.${dumpValues()}`); + return; + } + + // Get its index + const idx = exchanged.findIndex(v => v === 1); + assert(idx !== -1); + + // Its output should contain the old value after exchange + const oldValue = pingPongValues[w % 2]; + if (oldValues[idx] !== oldValue) { + t.fail( + `oldValues[${idx}] expected to contain old value from exchange: ${oldValue}.${dumpValues()}'` + ); + return; + } + + // The rest of oldValues should either contain the old value or the newly exchanged value, + // depending on whether they executed atomicCompareExchangWeak before or after invocation 'idx'. + const oldValuesRest = oldValues.filter((_, i) => i !== idx); + if (!oldValuesRest.every(v => pingPongValues.includes(v))) { + t.fail( + `Values in oldValues buffer should be one of '${pingPongValues}', except at index '${idx} where it is '${oldValue}'.${dumpValues()}` + ); + return; + } + } + } + }); + +g.test('compare_exchange_weak_workgroup_advanced') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicCompareExchangeWeak(atomic_ptr: ptr<AS, atomic<T>, read_write>, cmp: T, v: T) -> __atomic_compare_exchange_result<T> + +struct __atomic_compare_exchange_result<T> { + old_value : T, // old value stored in the atomic + exchanged : bool, // true if the exchange was done +} +` + ) + .params(u => + u + .combine('workgroupSize', onlyWorkgroupSizes) // + .combine('scalarType', ['u32', 'i32']) + ) + .fn(async t => { + const numInvocations = t.params.workgroupSize; + const scalarType = t.params.scalarType; + + // Number of times each workgroup attempts to exchange the same value to the same memory address + const numWrites = 4; + + const bufferNumElements = numInvocations * numWrites; + const pingPongValues = [24, 68]; + + const wgsl = ` + var<workgroup> wg: atomic<${scalarType}>; + + @group(0) @binding(0) + var<storage, read_write> old_values : array<${scalarType}>; + + @group(0) @binding(1) + var<storage, read_write> exchanged : array<${scalarType}>; + + fn ping_pong_value(i: u32) -> ${scalarType} { + if (i % 2 == 0) { + return ${pingPongValues[0]}; + } else { + return ${pingPongValues[1]}; + } + } + + @compute @workgroup_size(${t.params.workgroupSize}) + fn main( + @builtin(local_invocation_index) local_invocation_index: u32, + @builtin(workgroup_id) workgroup_id : vec3<u32> + ) { + let id = ${scalarType}(local_invocation_index); + + // Each invocation attempts to write an alternating (ping-pong) value, once per loop. + // The input value is initialized with the first of the two ping-pong values. + // Only one invocation per loop iteration should succeed. Note the workgroupBarrier() used + // to synchronize each invocation in the loop. + // The reason we alternate is in case atomicCompareExchangeWeak spurioulsy fails: + // If all invocations of one iteration spuriously fail, the very next iteration will also + // fail since the value will not have been exchanged; however, the subsequent one will succeed + // (assuming not all iterations spuriously fail yet again). + + // Initialize wg + if (local_invocation_index == 0) { + atomicStore(&wg, ${pingPongValues[0]}); + } + workgroupBarrier(); + + for (var i = 0u; i < ${numWrites}u; i++) { + let compare = ping_pong_value(i); + let next = ping_pong_value(i + 1); + + let r = atomicCompareExchangeWeak(&wg, compare, next); + + let slot = i * ${numInvocations}u + u32(id); + old_values[slot] = r.old_value; + if (r.exchanged) { + exchanged[slot] = 1; + } else { + exchanged[slot] = 0; + } + + workgroupBarrier(); + } + } + `; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const arrayType = typedArrayCtor(scalarType); + const defaultValue = 99999999; + + const oldValuesBuffer = t.device.createBuffer({ + size: bufferNumElements * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + mappedAtCreation: true, + }); + t.trackForCleanup(oldValuesBuffer); + { + const data = new arrayType(oldValuesBuffer.getMappedRange()); + data.fill(defaultValue); + oldValuesBuffer.unmap(); + } + + const exchangedBuffer = t.device.createBuffer({ + size: bufferNumElements * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + mappedAtCreation: true, + }); + t.trackForCleanup(exchangedBuffer); + { + const data = new arrayType(exchangedBuffer.getMappedRange()); + data.fill(defaultValue); + exchangedBuffer.unmap(); + } + + const bindGroup = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: oldValuesBuffer } }, + { binding: 1, resource: { buffer: exchangedBuffer } }, + ], + }); + + // 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()]); + + // Read back buffers + const oldValuesBufferResult = ( + await t.readGPUBufferRangeTyped(oldValuesBuffer, { + type: arrayType, + typedLength: oldValuesBuffer.size / arrayType.BYTES_PER_ELEMENT, + }) + ).data; + const exchangedBufferResult = ( + await t.readGPUBufferRangeTyped(exchangedBuffer, { + type: arrayType, + typedLength: exchangedBuffer.size / arrayType.BYTES_PER_ELEMENT, + }) + ).data; + + for (let w = 0; w < numWrites; ++w) { + const offset = w * numInvocations; + const exchanged = exchangedBufferResult.subarray(offset, offset + numInvocations); + const oldValues = oldValuesBufferResult.subarray(offset, offset + numInvocations); + + const dumpValues = () => { + return ` + For write: ${w} + exchanged: ${exchanged} + oldValues: ${oldValues}`; + }; + + // Only one of the invocations should have succeeded to exchange - or none if spurious failures occured + const noExchanges = exchanged.every(v => v === 0); + if (noExchanges) { + // Spurious failure, all values in oldValues should be the default value + if (!oldValues.every(v => v === defaultValue)) { + t.fail( + `Spurious failure detected, expected only default value of ${defaultValue} in oldValues buffer.${dumpValues()}` + ); + return; + } + } else { + // Only one invocation should have exchanged its value + if (exchanged.filter(v => v === 1).length !== 1) { + t.fail(`More than one invocation exchanged its value.${dumpValues()}`); + return; + } + + // Get its index + const idx = exchanged.findIndex(v => v === 1); + assert(idx !== -1); + + // Its output should contain the old value after exchange + const oldValue = pingPongValues[w % 2]; + if (oldValues[idx] !== oldValue) { + t.fail( + `oldValues[${idx}] expected to contain old value from exchange: ${oldValue}.${dumpValues()}'` + ); + return; + } + + // The rest of oldValues should either contain the old value or the newly exchanged value, + // depending on whether they executed atomicCompareExchangWeak before or after invocation 'idx'. + const oldValuesRest = oldValues.filter((_, i) => i !== idx); + if (!oldValuesRest.every(v => pingPongValues.includes(v))) { + t.fail( + `Values in oldValues buffer should be one of '${pingPongValues}', except at index '${idx} where it is '${oldValue}'.${dumpValues()}` + ); + return; + } + } + } + }); 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 new file mode 100644 index 0000000000..540ac16b07 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts @@ -0,0 +1,470 @@ +export const description = ` +Atomically stores the value v in the atomic object pointed to atomic_ptr and returns the original value stored in the atomic object. +`; + +import { makeTestGroup } from '../../../../../../../common/framework/test_group.js'; +import { keysOf } from '../../../../../../../common/util/data_tables.js'; +import { GPUTest } from '../../../../../../gpu_test.js'; +import { checkElementsEqual } from '../../../../../../util/check_contents.js'; + +import { dispatchSizes, workgroupSizes, typedArrayCtor, kMapId } from './harness.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('exchange_storage_basic') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize * t.params.dispatchSize; + const bufferNumElements = numInvocations; + const scalarType = t.params.scalarType; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id() + + const wgsl = + ` + @group(0) @binding(0) + var<storage, read_write> input : array<atomic<${scalarType}>>; + + @group(0) @binding(1) + var<storage, read_write> output : array<${scalarType}>; + + @compute @workgroup_size(${t.params.workgroupSize}) + fn main( + @builtin(global_invocation_id) global_invocation_id : vec3<u32>, + ) { + let id = ${scalarType}(global_invocation_id[0]); + + output[id] = atomicExchange(&input[id], map_id(id * 2)); + } + ` + extra; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const arrayType = typedArrayCtor(scalarType); + + // Create input buffer with values [0..n] + const inputBuffer = t.device.createBuffer({ + size: bufferNumElements * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + mappedAtCreation: true, + }); + t.trackForCleanup(inputBuffer); + const data = new arrayType(inputBuffer.getMappedRange()); + data.forEach((_, i) => (data[i] = i)); + inputBuffer.unmap(); + + const outputBuffer = t.device.createBuffer({ + size: bufferNumElements * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(outputBuffer); + + const bindGroup = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: inputBuffer } }, + { binding: 1, resource: { buffer: outputBuffer } }, + ], + }); + + // Run the shader. + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginComputePass(); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindGroup); + pass.dispatchWorkgroups(t.params.dispatchSize); + pass.end(); + t.queue.submit([encoder.finish()]); + + // Output buffer should be the same as the initial input buffer as it contains + // values returned from atomicExchange + const outputExpected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements); + outputExpected.forEach((_, i) => (outputExpected[i] = i)); + t.expectGPUBufferValuesEqual(outputBuffer, outputExpected); + + // And the input buffer should have been modified to a computed value + const inputExpected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements); + inputExpected.forEach((_, i) => (inputExpected[i] = mapId.f(i * 2, numInvocations))); + t.expectGPUBufferValuesEqual(inputBuffer, inputExpected); + }); + +g.test('exchange_workgroup_basic') + .specURL('https://www.w3.org/TR/WGSL/#atomic-load') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T + +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize; + const wgNumElements = numInvocations; + const scalarType = t.params.scalarType; + const dispatchSize = t.params.dispatchSize; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id() + + const wgsl = + ` + var<workgroup> wg: array<atomic<${scalarType}>, ${wgNumElements}>; + + // Result of each workgroup is written to output[workgroup_id.x] + @group(0) @binding(0) + var<storage, read_write> output: array<${scalarType}, ${wgNumElements * dispatchSize}>; + + @group(0) @binding(1) + var<storage, read_write> wg_copy: array<${scalarType}, ${wgNumElements * dispatchSize}>; + + @compute @workgroup_size(${t.params.workgroupSize}) + fn main( + @builtin(local_invocation_index) local_invocation_index: u32, + @builtin(workgroup_id) workgroup_id : vec3<u32> + ) { + let id = ${scalarType}(local_invocation_index); + let global_id = ${scalarType}(workgroup_id.x * ${wgNumElements} + local_invocation_index); + + // Initialize wg[id] with this invocations global id + atomicStore(&wg[id], global_id); + workgroupBarrier(); + + // Test atomicExchange, storing old value into output + output[global_id] = atomicExchange(&wg[id], map_id(global_id * 2)); + + // Copy new value into wg_copy + wg_copy[global_id] = atomicLoad(&wg[id]); + } + ` + extra; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const arrayType = typedArrayCtor(scalarType); + + const outputBuffer = t.device.createBuffer({ + size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(outputBuffer); + + const wgCopyBuffer = t.device.createBuffer({ + size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(outputBuffer); + + const bindGroup = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: outputBuffer } }, + { binding: 1, resource: { buffer: wgCopyBuffer } }, + ], + }); + + // Run the shader. + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginComputePass(); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindGroup); + pass.dispatchWorkgroups(dispatchSize); + pass.end(); + t.queue.submit([encoder.finish()]); + + // Output buffer should be the same as the initial wg buffer as it contains + // values returned from atomicExchange + const outputExpected = new (typedArrayCtor(t.params.scalarType))(wgNumElements * dispatchSize); + outputExpected.forEach((_, i) => (outputExpected[i] = i)); + t.expectGPUBufferValuesEqual(outputBuffer, outputExpected); + + // And the wg copy buffer should have been modified to a computed value + const wgCopyBufferExpected = new (typedArrayCtor(t.params.scalarType))( + wgNumElements * dispatchSize + ); + wgCopyBufferExpected.forEach( + (_, i) => (wgCopyBufferExpected[i] = mapId.f(i * 2, numInvocations)) + ); + t.expectGPUBufferValuesEqual(wgCopyBuffer, wgCopyBufferExpected); + }); + +g.test('exchange_storage_advanced') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(async t => { + const numInvocations = t.params.workgroupSize * t.params.dispatchSize; + const bufferNumElements = numInvocations; + const scalarType = t.params.scalarType; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id() + + const wgsl = + ` + @group(0) @binding(0) + var<storage, read_write> input : atomic<${scalarType}>; + + @group(0) @binding(1) + var<storage, read_write> output : array<${scalarType}>; + + @compute @workgroup_size(${t.params.workgroupSize}) + fn main( + @builtin(global_invocation_id) global_invocation_id : vec3<u32>, + ) { + let id = ${scalarType}(global_invocation_id[0]); + + // All invocations exchange with same single memory address, and we store + // the old value at the current invocation's location in the output buffer. + output[id] = atomicExchange(&input, map_id(id)); + } + ` + extra; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const arrayType = typedArrayCtor(scalarType); + + // Create input buffer of size 1 with initial value 0 + const inputBuffer = t.device.createBuffer({ + size: 1 * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(inputBuffer); + + const outputBuffer = t.device.createBuffer({ + size: bufferNumElements * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(outputBuffer); + + const bindGroup = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: inputBuffer } }, + { binding: 1, resource: { buffer: outputBuffer } }, + ], + }); + + // Run the shader. + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginComputePass(); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindGroup); + pass.dispatchWorkgroups(t.params.dispatchSize); + pass.end(); + t.queue.submit([encoder.finish()]); + + // Read back buffers + const inputBufferResult = await t.readGPUBufferRangeTyped(inputBuffer, { + type: arrayType, + typedLength: inputBuffer.size / arrayType.BYTES_PER_ELEMENT, + }); + const outputBufferResult = await t.readGPUBufferRangeTyped(outputBuffer, { + type: arrayType, + typedLength: outputBuffer.size / arrayType.BYTES_PER_ELEMENT, + }); + + // The one value in the input buffer plus all values in the output buffer + // should contain initial value 0 plus map_id(0..n), unsorted. + const values = new arrayType([...inputBufferResult.data, ...outputBufferResult.data]); + + const expected = new arrayType(values.length); + expected.forEach((_, i) => { + if (i === 0) { + expected[0] = 0; + } else { + expected[i] = mapId.f(i - 1, numInvocations); + } + }); + + // Sort both arrays and compare + values.sort(); + expected.sort(); // Sort because we store hashed results when mapId == 'remap' + t.expectOK(checkElementsEqual(values, expected)); + }); + +g.test('exchange_workgroup_advanced') + .specURL('https://www.w3.org/TR/WGSL/#atomic-load') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T + +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(async t => { + const numInvocations = t.params.workgroupSize; + const scalarType = t.params.scalarType; + const dispatchSize = t.params.dispatchSize; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id() + + const wgsl = + ` + var<workgroup> wg: atomic<${scalarType}>; + + // Will contain the atomicExchange result for each invocation at global index + @group(0) @binding(0) + var<storage, read_write> output: array<${scalarType}, ${numInvocations * dispatchSize}>; + + // Will contain the final value in wg in wg_copy for this dispatch + @group(0) @binding(1) + var<storage, read_write> wg_copy: array<${scalarType}, ${dispatchSize}>; + + @compute @workgroup_size(${t.params.workgroupSize}) + fn main( + @builtin(local_invocation_index) local_invocation_index: u32, + @builtin(workgroup_id) workgroup_id : vec3<u32> + ) { + let id = ${scalarType}(local_invocation_index); + let global_id = ${scalarType}(workgroup_id.x * ${numInvocations} + local_invocation_index); + + // All invocations exchange with same single memory address, and we store + // the old value at the current invocation's location in the output buffer. + output[global_id] = atomicExchange(&wg, map_id(id)); + + // Once all invocations have completed, the first one copies the final exchanged value + // to wg_copy for this dispatch (workgroup_id.x) + workgroupBarrier(); + if (local_invocation_index == 0u) { + wg_copy[workgroup_id.x] = atomicLoad(&wg); + } + } + ` + extra; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const arrayType = typedArrayCtor(scalarType); + + const outputBuffer = t.device.createBuffer({ + size: numInvocations * dispatchSize * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(outputBuffer); + + const wgCopyBuffer = t.device.createBuffer({ + size: dispatchSize * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(outputBuffer); + + const bindGroup = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: outputBuffer } }, + { binding: 1, resource: { buffer: wgCopyBuffer } }, + ], + }); + + // Run the shader. + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginComputePass(); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindGroup); + pass.dispatchWorkgroups(dispatchSize); + pass.end(); + t.queue.submit([encoder.finish()]); + + // Read back buffers + const outputBufferResult = await t.readGPUBufferRangeTyped(outputBuffer, { + type: arrayType, + typedLength: outputBuffer.size / arrayType.BYTES_PER_ELEMENT, + }); + const wgCopyBufferResult = await t.readGPUBufferRangeTyped(wgCopyBuffer, { + type: arrayType, + typedLength: wgCopyBuffer.size / arrayType.BYTES_PER_ELEMENT, + }); + + // For each dispatch, the one value in wgCopyBuffer plus all values in the output buffer + // should contain initial value 0 plus map_id(0..n), unsorted. + + // Expected values for each dispatch + const expected = new arrayType(numInvocations + 1); + expected.forEach((_, i) => { + if (i === 0) { + expected[0] = 0; + } else { + expected[i] = mapId.f(i - 1, numInvocations); + } + }); + expected.sort(); // Sort because we store hashed results when mapId == 'remap' + + // Test values for each dispatch + for (let d = 0; d < dispatchSize; ++d) { + // Get values for this dispatch + const dispatchOffset = d * numInvocations; + const values = new arrayType([ + wgCopyBufferResult.data[d], // Last 'wg' value for this dispatch + ...outputBufferResult.data.subarray(dispatchOffset, dispatchOffset + numInvocations), // Rest of the returned values + ]); + + values.sort(); + t.expectOK(checkElementsEqual(values, expected)); + } + }); 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 new file mode 100644 index 0000000000..2aac7bb9b9 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts @@ -0,0 +1,192 @@ +export const description = ` +Returns the atomically loaded the value pointed to by atomic_ptr. It does not modify the object. +`; + +import { makeTestGroup } from '../../../../../../../common/framework/test_group.js'; +import { keysOf } from '../../../../../../../common/util/data_tables.js'; +import { GPUTest } from '../../../../../../gpu_test.js'; + +import { dispatchSizes, workgroupSizes, typedArrayCtor, kMapId } from './harness.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('load_storage') + .specURL('https://www.w3.org/TR/WGSL/#atomic-load') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T + +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize * t.params.dispatchSize; + const bufferNumElements = numInvocations; + const scalarType = t.params.scalarType; + const mapId = kMapId[t.params.mapId]; + + const wgsl = ` + @group(0) @binding(0) + var<storage, read_write> input : array<atomic<${scalarType}>>; + + @group(0) @binding(1) + var<storage, read_write> output : array<${scalarType}>; + + @compute @workgroup_size(${t.params.workgroupSize}) + fn main( + @builtin(global_invocation_id) global_invocation_id : vec3<u32>, + ) { + let id = ${scalarType}(global_invocation_id[0]); + output[id] = atomicLoad(&input[id]); + } + `; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const arrayType = typedArrayCtor(scalarType); + + // Create input buffer with values [map_id(0)..map_id(n)] + const inputBuffer = t.device.createBuffer({ + size: bufferNumElements * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + mappedAtCreation: true, + }); + t.trackForCleanup(inputBuffer); + const data = new arrayType(inputBuffer.getMappedRange()); + data.forEach((_, i) => (data[i] = mapId.f(i, numInvocations))); + inputBuffer.unmap(); + + const outputBuffer = t.device.createBuffer({ + size: bufferNumElements * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(outputBuffer); + + const bindGroup = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: inputBuffer } }, + { binding: 1, resource: { buffer: outputBuffer } }, + ], + }); + + // Run the shader. + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginComputePass(); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindGroup); + pass.dispatchWorkgroups(t.params.dispatchSize); + pass.end(); + t.queue.submit([encoder.finish()]); + + // Both input and output buffer should be the same now + const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements); + expected.forEach((_, i) => (expected[i] = mapId.f(i, numInvocations))); + t.expectGPUBufferValuesEqual(inputBuffer, expected); + t.expectGPUBufferValuesEqual(outputBuffer, expected); + }); + +g.test('load_workgroup') + .specURL('https://www.w3.org/TR/WGSL/#atomic-load') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T + +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize; + const wgNumElements = numInvocations; + const scalarType = t.params.scalarType; + const dispatchSize = t.params.dispatchSize; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id() + + const wgsl = + ` + var<workgroup> wg: array<atomic<${scalarType}>, ${wgNumElements}>; + + // Result of each workgroup is written to output[workgroup_id.x] + @group(0) @binding(0) + var<storage, read_write> output: array<${scalarType}, ${wgNumElements * dispatchSize}>; + + @compute @workgroup_size(${t.params.workgroupSize}) + fn main( + @builtin(local_invocation_index) local_invocation_index: u32, + @builtin(workgroup_id) workgroup_id : vec3<u32> + ) { + let id = ${scalarType}(local_invocation_index); + let global_id = ${scalarType}(workgroup_id.x * ${wgNumElements} + local_invocation_index); + + // Initialize wg[id] with this invocations global id (mapped) + atomicStore(&wg[id], map_id(global_id)); + workgroupBarrier(); + + // Test atomic loading of value at wg[id] and store result in output[global_id] + output[global_id] = atomicLoad(&wg[id]); + } + ` + extra; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const arrayType = typedArrayCtor(scalarType); + + const outputBuffer = t.device.createBuffer({ + size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(outputBuffer); + + 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(dispatchSize); + pass.end(); + t.queue.submit([encoder.finish()]); + + // Expected values should be map_id(0..n) + const expected = new (typedArrayCtor(t.params.scalarType))( + wgNumElements * t.params.dispatchSize + ); + expected.forEach((_, i) => (expected[i] = mapId.f(i, numInvocations))); + + t.expectGPUBufferValuesEqual(outputBuffer, expected); + }); 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 new file mode 100644 index 0000000000..066d673018 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts @@ -0,0 +1,101 @@ +export const description = ` +Atomically read, max and store value. + +* Load the original value pointed to by atomic_ptr. +* Obtains a new value by taking the max with the value v. +* Store the new value using atomic_ptr. + +Returns the original value stored in the atomic object. +`; + +import { makeTestGroup } from '../../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../../gpu_test.js'; + +import { + dispatchSizes, + workgroupSizes, + runStorageVariableTest, + runWorkgroupVariableTest, + typedArrayCtor, +} from './harness.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('max_storage') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize * t.params.dispatchSize; + // Allocate one extra element to ensure it doesn't get modified + const bufferNumElements = 2; + + const initValue = 0; + const op = `atomicMax(&output[0], id)`; + const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements); + expected[0] = numInvocations - 1; + + runStorageVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + bufferNumElements, + initValue, + op, + expected, + }); + }); + +g.test('max_workgroup') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + // Allocate one extra element to ensure it doesn't get modified + const wgNumElements = 2; + + const initValue = 0; + const op = `atomicMax(&wg[0], id)`; + + const expected = new (typedArrayCtor(t.params.scalarType))( + wgNumElements * t.params.dispatchSize + ).fill(initValue); + for (let d = 0; d < t.params.dispatchSize; ++d) { + const wg = expected.subarray(d * wgNumElements); + wg[0] = t.params.workgroupSize - 1; + } + + runWorkgroupVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + wgNumElements, + initValue, + op, + expected, + }); + }); 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 new file mode 100644 index 0000000000..ad880c4182 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts @@ -0,0 +1,100 @@ +export const description = ` +Atomically read, min and store value. + +* Load the original value pointed to by atomic_ptr. + * Obtains a new value by take the min with the value v. + * Store the new value using atomic_ptr. + +Returns the original value stored in the atomic object. +`; + +import { makeTestGroup } from '../../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../../gpu_test.js'; + +import { + dispatchSizes, + workgroupSizes, + runStorageVariableTest, + runWorkgroupVariableTest, + typedArrayCtor, +} from './harness.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('min_storage') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + // Allocate one extra element to ensure it doesn't get modified + const bufferNumElements = 2; + + const initValue = t.params.scalarType === 'u32' ? 0xffffffff : 0x7fffffff; + const op = `atomicMin(&output[0], id)`; + const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements).fill(initValue); + expected[0] = 0; + + runStorageVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + bufferNumElements, + initValue, + op, + expected, + }); + }); + +g.test('min_workgroup') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + // Allocate one extra element to ensure it doesn't get modified + const wgNumElements = 2; + + const initValue = t.params.scalarType === 'u32' ? 0xffffffff : 0x7fffffff; + const op = `atomicMin(&wg[0], id)`; + + const expected = new (typedArrayCtor(t.params.scalarType))( + wgNumElements * t.params.dispatchSize + ).fill(initValue); + for (let d = 0; d < t.params.dispatchSize; ++d) { + const wg = expected.subarray(d * wgNumElements); + wg[0] = 0; + } + + runWorkgroupVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + wgNumElements, + initValue, + op, + expected, + }); + }); 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 new file mode 100644 index 0000000000..3892d41b38 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts @@ -0,0 +1,131 @@ +export const description = ` +Atomically read, or and store value. + +* Load the original value pointed to by atomic_ptr. +* Obtains a new value by or'ing with the value v. +* Store the new value using atomic_ptr. + +Returns the original value stored in the atomic object. +`; + +import { makeTestGroup } from '../../../../../../../common/framework/test_group.js'; +import { keysOf } from '../../../../../../../common/util/data_tables.js'; +import { GPUTest } from '../../../../../../gpu_test.js'; + +import { + dispatchSizes, + workgroupSizes, + runStorageVariableTest, + runWorkgroupVariableTest, + kMapId, + typedArrayCtor, +} from './harness.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('or_storage') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize * t.params.dispatchSize; + + // Allocate an output buffer with bitsize of max invocations plus 1 for validation + const bufferNumElements = Math.max(1, numInvocations / 32) + 1; + + // Start with all bits low, then using atomicOr to set mapped global id bit on. + // Note: Both WGSL and JS will shift left 1 by id modulo 32. + const initValue = 0; + + const scalarType = t.params.scalarType; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations); // Defines map_id() + const op = ` + let i = map_id(u32(id)); + atomicOr(&output[i / 32], ${scalarType}(1) << i) + `; + const expected = new (typedArrayCtor(scalarType))(bufferNumElements); + for (let id = 0; id < numInvocations; ++id) { + const i = mapId.f(id, numInvocations); + expected[Math.floor(i / 32)] |= 1 << i; + } + + runStorageVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + bufferNumElements, + initValue, + op, + expected, + extra, + }); + }); + +g.test('or_workgroup') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize; + + // Allocate workgroup array with bitsize of max invocations plus 1 for validation + const wgNumElements = Math.max(1, numInvocations / 32) + 1; + + // Start with all bits low, then using atomicOr to set mapped local id bit on. + // Note: Both WGSL and JS will shift left 1 by id modulo 32. + const initValue = 0; + + const scalarType = t.params.scalarType; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations); // Defines map_id() + const op = ` + let i = map_id(u32(id)); + atomicOr(&wg[i / 32], ${scalarType}(1) << i) + `; + const expected = new (typedArrayCtor(scalarType))(wgNumElements * t.params.dispatchSize); + for (let d = 0; d < t.params.dispatchSize; ++d) { + for (let id = 0; id < numInvocations; ++id) { + const wg = expected.subarray(d * wgNumElements); + const i = mapId.f(id, numInvocations); + wg[Math.floor(i / 32)] |= 1 << i; + } + } + + runWorkgroupVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + wgNumElements, + initValue, + op, + expected, + extra, + }); + }); 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 new file mode 100644 index 0000000000..18ff72975d --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts @@ -0,0 +1,301 @@ +export const description = ` +Atomically stores the value v in the atomic object pointed to by atomic_ptr. +`; + +import { makeTestGroup } from '../../../../../../../common/framework/test_group.js'; +import { keysOf } from '../../../../../../../common/util/data_tables.js'; +import { GPUTest } from '../../../../../../gpu_test.js'; + +import { + dispatchSizes, + workgroupSizes, + runStorageVariableTest, + runWorkgroupVariableTest, + typedArrayCtor, + kMapId, +} from './harness.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('store_storage_basic') + .specURL('https://www.w3.org/TR/WGSL/#atomic-store') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize * t.params.dispatchSize; + const bufferNumElements = numInvocations; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id() + + const initValue = 0; + const op = `atomicStore(&output[id], map_id(id))`; + const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements); + expected.forEach((_, i) => (expected[i] = mapId.f(i, numInvocations))); + + runStorageVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + bufferNumElements, + initValue, + op, + expected, + extra, + }); + }); + +g.test('store_workgroup_basic') + .specURL('https://www.w3.org/TR/WGSL/#atomic-store') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize; + const wgNumElements = numInvocations; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id() + + const initValue = 0; + const op = `atomicStore(&wg[id], map_id(global_id))`; + const expected = new (typedArrayCtor(t.params.scalarType))( + wgNumElements * t.params.dispatchSize + ); + expected.forEach((_, i) => (expected[i] = mapId.f(i, numInvocations))); + + runWorkgroupVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + wgNumElements, + initValue, + op, + expected, + extra, + }); + }); + +g.test('store_storage_advanced') + .specURL('https://www.w3.org/TR/WGSL/#atomic-store') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) + +Tests that multiple invocations of atomicStore to the same location returns +one of the values written. +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(async t => { + const numInvocations = t.params.workgroupSize * t.params.dispatchSize; + const scalarType = t.params.scalarType; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id() + + const wgsl = + ` + @group(0) @binding(0) + var<storage, read_write> output : array<atomic<${scalarType}>>; + + @compute @workgroup_size(${t.params.workgroupSize}) + fn main( + @builtin(global_invocation_id) global_invocation_id : vec3<u32>, + ) { + let id = ${scalarType}(global_invocation_id[0]); + + // All invocations store to the same location + atomicStore(&output[0], map_id(id)); + } + ` + extra; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const arrayType = typedArrayCtor(scalarType); + + // Output buffer has only 1 element + const outputBuffer = t.device.createBuffer({ + size: 1 * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(outputBuffer); + + 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(t.params.dispatchSize); + pass.end(); + t.queue.submit([encoder.finish()]); + + // Read back the buffer + const outputBufferResult = ( + await t.readGPUBufferRangeTyped(outputBuffer, { + type: arrayType, + typedLength: outputBuffer.size / arrayType.BYTES_PER_ELEMENT, + }) + ).data; + + // All invocations wrote to the output[0], so validate that it contains one + // of the possible computed values. + const expected_one_of = new arrayType(numInvocations); + expected_one_of.forEach((_, i) => (expected_one_of[i] = mapId.f(i, numInvocations))); + + if (!expected_one_of.includes(outputBufferResult[0])) { + t.fail( + `Unexpected value in output[0]: '${outputBufferResult[0]}, expected value to be one of: ${expected_one_of}` + ); + } + }); + +g.test('store_workgroup_advanced') + .specURL('https://www.w3.org/TR/WGSL/#atomic-store') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) + +Tests that multiple invocations of atomicStore to the same location returns +one of the values written. +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(async t => { + const numInvocations = t.params.workgroupSize; + const scalarType = t.params.scalarType; + const dispatchSize = t.params.dispatchSize; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id() + + const wgsl = + ` + var<workgroup> wg: atomic<${scalarType}>; + + // Result of each workgroup is written to output[workgroup_id.x] + @group(0) @binding(0) + var<storage, read_write> output: array<${scalarType}, ${dispatchSize}>; + + @compute @workgroup_size(${t.params.workgroupSize}) + fn main( + @builtin(local_invocation_index) local_invocation_index: u32, + @builtin(workgroup_id) workgroup_id : vec3<u32> + ) { + let id = ${scalarType}(local_invocation_index); + + // All invocations of a given dispatch store to the same location. + // In the end, the final value should be randomly equal to one of the ids. + atomicStore(&wg, map_id(id)); + + // Once all invocations have completed, the first one copies the result + // to output for this dispatch (workgroup_id.x) + workgroupBarrier(); + if (local_invocation_index == 0u) { + output[workgroup_id.x] = atomicLoad(&wg); + } + } + ` + extra; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const arrayType = typedArrayCtor(scalarType); + + const outputBuffer = t.device.createBuffer({ + size: dispatchSize * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(outputBuffer); + + 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(dispatchSize); + pass.end(); + t.queue.submit([encoder.finish()]); + + // Read back the buffer + const outputBufferResult = ( + await t.readGPUBufferRangeTyped(outputBuffer, { + type: arrayType, + typedLength: outputBuffer.size / arrayType.BYTES_PER_ELEMENT, + }) + ).data; + + // Each dispatch wrote to a single atomic workgroup var that was copied + // to outputBuffer[dispatch]. Validate that each value in the output buffer + // is one of the possible computed values. + const expected_one_of = new arrayType(numInvocations); + expected_one_of.forEach((_, i) => (expected_one_of[i] = mapId.f(i, numInvocations))); + + for (let d = 0; d < dispatchSize; d++) { + if (!expected_one_of.includes(outputBufferResult[d])) { + t.fail( + `Unexpected value in output[d] for dispatch d '${d}': '${outputBufferResult[d]}', expected value to be one of: ${expected_one_of}` + ); + } + } + }); 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 new file mode 100644 index 0000000000..6cea190299 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts @@ -0,0 +1,101 @@ +export const description = ` +Atomically read, subtract and store value. + +* Load the original value pointed to by atomic_ptr. +* Obtains a new value by subtracting with the value v. +* Store the new value using atomic_ptr. + +Returns the original value stored in the atomic object. +`; + +import { makeTestGroup } from '../../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../../gpu_test.js'; + +import { + dispatchSizes, + workgroupSizes, + runStorageVariableTest, + runWorkgroupVariableTest, + typedArrayCtor, +} from './harness.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('sub_storage') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize * t.params.dispatchSize; + // Allocate one extra element to ensure it doesn't get modified + const bufferNumElements = 2; + + const initValue = 0; + const op = `atomicSub(&output[0], 1)`; + const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements); + expected[0] = -1 * numInvocations; + + runStorageVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + bufferNumElements, + initValue, + op, + expected, + }); + }); + +g.test('sub_workgroup') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + // Allocate one extra element to ensure it doesn't get modified + const wgNumElements = 2; + + const initValue = 0; + const op = `atomicSub(&wg[0], 1)`; + + const expected = new (typedArrayCtor(t.params.scalarType))( + wgNumElements * t.params.dispatchSize + ); + for (let d = 0; d < t.params.dispatchSize; ++d) { + const wg = expected.subarray(d * wgNumElements); + wg[0] = -1 * t.params.workgroupSize; + } + + runWorkgroupVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + wgNumElements, + initValue, + op, + expected, + }); + }); 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 new file mode 100644 index 0000000000..99192fd9fe --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts @@ -0,0 +1,135 @@ +export const description = ` +Atomically read, xor and store value. + +* Load the original value pointed to by atomic_ptr. +* Obtains a new value by xor'ing with the value v. +* Store the new value using atomic_ptr. + +Returns the original value stored in the atomic object. +`; + +import { makeTestGroup } from '../../../../../../../common/framework/test_group.js'; +import { keysOf } from '../../../../../../../common/util/data_tables.js'; +import { GPUTest } from '../../../../../../gpu_test.js'; + +import { + dispatchSizes, + workgroupSizes, + runStorageVariableTest, + runWorkgroupVariableTest, + kMapId, + typedArrayCtor, +} from './harness.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('xor_storage') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize * t.params.dispatchSize; + + // Allocate an output buffer with bitsize of max invocations plus 1 for validation + const bufferNumElements = Math.max(1, numInvocations / 32) + 1; + + // Start with all bits set to some random value for each u32 in the buffer, then atomicXor each mapped global id bit. + // Note: Both WGSL and JS will shift left 1 by id modulo 32. + const initValue = 0b11000011010110100000111100111100; + + const scalarType = t.params.scalarType; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations); // Defines map_id() + const op = ` + let i = map_id(u32(id)); + atomicXor(&output[i / 32], ${scalarType}(1) << i) + `; + + const expected = new (typedArrayCtor(scalarType))(bufferNumElements).fill(initValue); + for (let id = 0; id < numInvocations; ++id) { + const i = mapId.f(id, numInvocations); + expected[Math.floor(i / 32)] ^= 1 << i; + } + + runStorageVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + bufferNumElements, + initValue, + op, + expected, + extra, + }); + }); + +g.test('xor_workgroup') + .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw') + .desc( + ` +AS is storage or workgroup +T is i32 or u32 + +fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T +` + ) + .params(u => + u + .combine('workgroupSize', workgroupSizes) + .combine('dispatchSize', dispatchSizes) + .combine('mapId', keysOf(kMapId)) + .combine('scalarType', ['u32', 'i32']) + ) + .fn(t => { + const numInvocations = t.params.workgroupSize; + + // Allocate workgroup array with bitsize of max invocations plus 1 for validation + const wgNumElements = Math.max(1, numInvocations / 32) + 1; + + // Start with all bits set to some random value for each u32 in the buffer, then atomicXor each mapped global id bit. + // Note: Both WGSL and JS will shift left 1 by id modulo 32. + const initValue = 0b11000011010110100000111100111100; + + const scalarType = t.params.scalarType; + const mapId = kMapId[t.params.mapId]; + const extra = mapId.wgsl(numInvocations); // Defines map_id() + const op = ` + let i = map_id(u32(id)); + atomicXor(&wg[i / 32], ${scalarType}(1) << i) + `; + + const expected = new (typedArrayCtor(scalarType))(wgNumElements * t.params.dispatchSize).fill( + initValue + ); + for (let d = 0; d < t.params.dispatchSize; ++d) { + for (let id = 0; id < numInvocations; ++id) { + const wg = expected.subarray(d * wgNumElements); + const i = mapId.f(id, numInvocations); + wg[Math.floor(i / 32)] ^= 1 << i; + } + } + + runWorkgroupVariableTest({ + t, + workgroupSize: t.params.workgroupSize, + dispatchSize: t.params.dispatchSize, + wgNumElements, + initValue, + op, + expected, + extra, + }); + }); 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 new file mode 100644 index 0000000000..ed02467f80 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts @@ -0,0 +1,208 @@ +import { + assert, + TypedArrayBufferView, + TypedArrayBufferViewConstructor, +} from '../../../../../../../common/util/util.js'; +import { GPUTest } from '../../../../../../gpu_test.js'; + +// Use these in combination. +export const workgroupSizes = [1, 2, 32, 64]; +export const dispatchSizes = [1, 4, 8, 16]; + +// Use this alone - dispatch size should be 1. +export const onlyWorkgroupSizes = [1, 2, 4, 8, 16, 32, 64, 128, 256]; + +export const kMapId = { + passthrough: { + f: (id: number, _max: number) => id, + wgsl: (_max: number, scalarType = 'u32') => + `fn map_id(id: ${scalarType}) -> ${scalarType} { return id; }`, + }, + remap: { + f: (id: number, max: number) => (((id >>> 0) * 14957) ^ (((id >>> 0) * 26561) >> 2)) % max, + wgsl: (max: number, scalarType = 'u32') => + `fn map_id(id: ${scalarType}) -> ${scalarType} { return ((id * 14957) ^ ((id * 26561) >> 2)) % ${max}; }`, + }, +}; + +export function typedArrayCtor(scalarType: string): TypedArrayBufferViewConstructor { + switch (scalarType) { + case 'u32': + return Uint32Array; + case 'i32': + return Int32Array; + default: + assert(false, 'Atomic variables can only by u32 or i32'); + return Uint8Array; + } +} + +export function runStorageVariableTest({ + t, + workgroupSize, // Workgroup X-size + dispatchSize, // Dispatch X-size + bufferNumElements, // Number of 32-bit elements in output buffer + initValue, // 32-bit initial value used to fill output buffer + // Atomic op source executed by the compute shader, NOTE: 'id' is global_invocation_id.x, + // and `output` is a storage array of atomics. + op, + expected, // Expected values array to compare against output buffer + extra, // Optional extra WGSL source +}: { + t: GPUTest; + workgroupSize: number; + dispatchSize: number; + bufferNumElements: number; + initValue: number; + op: string; + expected: TypedArrayBufferView; + extra?: string; +}) { + assert(expected.length === bufferNumElements, "'expected' buffer size is incorrect"); + + const scalarType = expected instanceof Uint32Array ? 'u32' : 'i32'; + const arrayType = typedArrayCtor(scalarType); + + const wgsl = ` + @group(0) @binding(0) + var<storage, read_write> output : array<atomic<${scalarType}>>; + + @compute @workgroup_size(${workgroupSize}) + fn main( + @builtin(global_invocation_id) global_invocation_id : vec3<u32>, + ) { + let id = ${scalarType}(global_invocation_id[0]); + ${op}; + } + ${extra || ''} + `; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const outputBuffer = t.device.createBuffer({ + size: bufferNumElements * arrayType.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + mappedAtCreation: true, + }); + // Fill with initial value + t.trackForCleanup(outputBuffer); + const data = new arrayType(outputBuffer.getMappedRange()); + data.fill(initValue); + outputBuffer.unmap(); + + 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(dispatchSize); + pass.end(); + t.queue.submit([encoder.finish()]); + + t.expectGPUBufferValuesEqual(outputBuffer, expected); +} + +export function runWorkgroupVariableTest({ + t, + workgroupSize, // Workgroup X-size + dispatchSize, // Dispatch X-size + wgNumElements, // Number of 32-bit elements in 'wg' array. Output buffer is sized to wgNumElements * dispatchSize. + initValue, // 32-bit initial value used to fill 'wg' array + // Atomic op source executed by the compute shader, NOTE: 'id' is local_invocation_index, + // `wg` is a workgroup array of atomics of size `workgroupSize`, `output` is a storage array of non-atomics of size + // `workgroupSize * dispatcSize` to which each dispatch of `wg` gets copied to (dispatch 0 to first workgroupSize elements, + // dispatch 1 to second workgroupSize elements, etc.). + op, + expected, // Expected values array to compare against output buffer + extra, // Optional extra WGSL source +}: { + t: GPUTest; + workgroupSize: number; + dispatchSize: number; + wgNumElements: number; + initValue: number; + op: string; + expected: TypedArrayBufferView; + extra?: string; +}) { + assert(expected.length === wgNumElements * dispatchSize, "'expected' buffer size is incorrect"); + + const scalarType = expected instanceof Uint32Array ? 'u32' : 'i32'; + const arrayType = typedArrayCtor(scalarType); + + const wgsl = ` + var<workgroup> wg: array<atomic<${scalarType}>, ${wgNumElements}>; + + // Result of each workgroup is written to output[workgroup_id.x] + @group(0) @binding(0) + var<storage, read_write> output: array<${scalarType}, ${wgNumElements * dispatchSize}>; + + @compute @workgroup_size(${workgroupSize}) + fn main( + @builtin(local_invocation_index) local_invocation_index: u32, + @builtin(workgroup_id) workgroup_id : vec3<u32> + ) { + let id = ${scalarType}(local_invocation_index); + let global_id = ${scalarType}(workgroup_id.x * ${wgNumElements} + local_invocation_index); + + // Initialize workgroup array + if (local_invocation_index == 0) { + for (var i = 0u; i < ${wgNumElements}; i++) { + atomicStore(&wg[i], bitcast<${scalarType}>(${initValue}u)); + } + } + workgroupBarrier(); + + ${op}; + + // Copy results to output buffer + workgroupBarrier(); + if (local_invocation_index == 0) { + for (var i = 0u; i < ${wgNumElements}; i++) { + output[(workgroup_id.x * ${wgNumElements}) + i] = atomicLoad(&wg[i]); + } + } + } + ${extra || ''} + `; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ code: wgsl }), + entryPoint: 'main', + }, + }); + + const outputBuffer = t.device.createBuffer({ + size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT, + usage: 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(dispatchSize); + pass.end(); + t.queue.submit([encoder.finish()]); + + t.expectGPUBufferValuesEqual(outputBuffer, expected); +} 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 new file mode 100644 index 0000000000..390129f2c7 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts @@ -0,0 +1,1275 @@ +export const description = ` +Execution tests for the 'bitcast' builtin function + +@const @must_use fn bitcast<T>(e: T ) -> T +T is concrete numeric scalar or concerete numeric vector +Identity function. + +@const @must_use fn bitcast<T>(e: S ) -> T +@const @must_use fn bitcast<vecN<T>>(e: vecN<S> ) -> vecN<T> +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<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> +@const @must_use fn bitcast<vec4<f16>>(e: vec2<T> ) -> vec4<f16> +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 { + f32, + i32, + u32, + f16, + TypeF32, + TypeI32, + TypeU32, + TypeF16, + TypeVec, + Vector, + Scalar, + toVector, +} 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 { 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 + * a WGSL type alias. + */ +function bitcastBuilder(canonicalDestType: string, params: TestParams): ShaderBuilder { + const destType = params.vectorize + ? `vec${params.vectorize}<${canonicalDestType}>` + : canonicalDestType; + + return builtinWithPredeclaration( + `bitcast<${destType}>`, + params.alias ? `alias myalias = ${destType};` : '' + ); +} + +// Identity cases +g.test('i32_to_i32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast i32 to i32 tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('alias', [false, true]) + ) + .fn(async t => { + const cases = await d.get('i32_to_i32'); + await run(t, bitcastBuilder('i32', t.params), [TypeI32], TypeI32, t.params, cases); + }); + +g.test('u32_to_u32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast u32 to u32 tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('alias', [false, true]) + ) + .fn(async t => { + const cases = await d.get('u32_to_u32'); + await run(t, bitcastBuilder('u32', t.params), [TypeU32], TypeU32, t.params, cases); + }); + +g.test('f32_to_f32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast f32 to f32 tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('alias', [false, true]) + ) + .fn(async t => { + const cases = await d.get( + // 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); + }); + +// To i32 from u32, f32 +g.test('u32_to_i32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast u32 to i32 tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('alias', [false, true]) + ) + .fn(async t => { + const cases = await d.get('u32_to_i32'); + await run(t, bitcastBuilder('i32', t.params), [TypeU32], TypeI32, t.params, cases); + }); + +g.test('f32_to_i32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast f32 to i32 tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('alias', [false, true]) + ) + .fn(async t => { + const cases = await d.get( + // 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); + }); + +// To u32 from i32, f32 +g.test('i32_to_u32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast i32 to u32 tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('alias', [false, true]) + ) + .fn(async t => { + const cases = await d.get('i32_to_u32'); + await run(t, bitcastBuilder('u32', t.params), [TypeI32], TypeU32, t.params, cases); + }); + +g.test('f32_to_u32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast f32 to i32 tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('alias', [false, true]) + ) + .fn(async t => { + const cases = await d.get( + // 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); + }); + +// To f32 from i32, u32 +g.test('i32_to_f32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast i32 to f32 tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('alias', [false, true]) + ) + .fn(async t => { + const cases = await d.get( + // 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); + }); + +g.test('u32_to_f32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast u32 to f32 tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('alias', [false, true]) + ) + .fn(async t => { + const cases = await d.get( + // 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); + }); + +// 16 bit types + +// f16 cases + +// f16: Identity +g.test('f16_to_f16') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast f16 to f16 tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('alias', [false, true]) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + // 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); + }); + +// f16: 32-bit scalar numeric to vec2<f16> +g.test('i32_to_vec2h') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast i32 to vec2h tests`) + .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + // 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 + ); + }); + +g.test('u32_to_vec2h') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast u32 to vec2h tests`) + .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + // 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 + ); + }); + +g.test('f32_to_vec2h') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast u32 to vec2h tests`) + .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + // 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 + ); + }); + +// f16: vec2<32-bit scalar numeric> to vec4<f16> +g.test('vec2i_to_vec4h') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast vec2i to vec4h tests`) + .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + // 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 + ); + }); + +g.test('vec2u_to_vec4h') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast vec2u to vec4h tests`) + .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + // 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 + ); + }); + +g.test('vec2f_to_vec4h') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast vec2f to vec2h tests`) + .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + // Infinities and NaNs are errors in const-eval. + t.params.inputSource === 'const' + ? '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 + ); + }); + +// f16: vec2<f16> to 32-bit scalar numeric +g.test('vec2h_to_i32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast vec2h to i32 tests`) + .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + // 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); + }); + +g.test('vec2h_to_u32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast vec2h to u32 tests`) + .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + // 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); + }); + +g.test('vec2h_to_f32') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast vec2h to f32 tests`) + .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + // 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); + }); + +// f16: vec4<f16> to vec2<32-bit scalar numeric> +g.test('vec4h_to_vec2i') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast vec4h to vec2i tests`) + .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + // 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 + ); + }); + +g.test('vec4h_to_vec2u') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast vec4h to vec2u tests`) + .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + // 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 + ); + }); + +g.test('vec4h_to_vec2f') + .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin') + .desc(`bitcast vec4h to vec2f tests`) + .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true])) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + // Infinities and NaNs are errors in const-eval. + t.params.inputSource === 'const' + ? 'vec4_f16_to_vec2_f32_finite' + : 'vec4_f16_inf_nan_to_vec2_f32' + ); + await run( + t, + bitcastBuilder('vec2<f32>', t.params), + [TypeVec(4, TypeF16)], + TypeVec(2, TypeF32), + 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 new file mode 100644 index 0000000000..282feea703 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts @@ -0,0 +1,24 @@ +import { + abstractFloatShaderBuilder, + basicExpressionBuilder, + basicExpressionWithPredeclarationBuilder, + ShaderBuilder, +} from '../../expression.js'; + +/* @returns a ShaderBuilder that calls the builtin with the given name */ +export function builtin(name: string): ShaderBuilder { + return basicExpressionBuilder(values => `${name}(${values.join(', ')})`); +} + +/* @returns a ShaderBuilder that calls the builtin with the given name that returns AbstractFloats */ +export function abstractBuiltin(name: string): ShaderBuilder { + return abstractFloatShaderBuilder(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( + values => `${name}(${values.join(', ')})`, + predeclaration + ); +} 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 new file mode 100644 index 0000000000..6cdf90986b --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts @@ -0,0 +1,101 @@ +export const description = ` +Execution tests for the 'ceil' builtin function + +S is AbstractFloat, 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. + +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('ceil'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('ceil'), [TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..0113fd656f --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts @@ -0,0 +1,195 @@ +export const description = ` +Execution tests for the 'clamp' builtin function + +S is AbstractInt, 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 +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. +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 { abstractBuiltin, builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('u32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`u32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('i32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`i32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('abstract_float') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`abstract float tests`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractBuiltin('clamp'), + [TypeAbstractFloat, TypeAbstractFloat, TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..723bca2efd --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts @@ -0,0 +1,84 @@ +export const description = ` +Execution tests for the 'cos' builtin function + +S is AbstractFloat, 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. +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +f32 tests + +TODO(#792): Decide what the ground-truth is for these tests. [1] +` + ) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('cos'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('cos'), [TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..37fb961c98 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts @@ -0,0 +1,68 @@ +export const description = ` +Execution tests for the 'cosh' builtin function + +S is AbstractFloat, 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 +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..cfae4bb6e0 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts @@ -0,0 +1,250 @@ +export const description = ` +Execution tests for the 'countLeadingZeros' builtin function + +S is i32 or u32 +T is S or vecN<S> +@const fn countLeadingZeros(e: T ) -> T +The number of consecutive 0 bits starting from the most significant bit of e, +when T is a scalar type. +Component-wise when T is a vector. +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 { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('u32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`u32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + await run(t, builtin('countLeadingZeros'), [TypeU32], TypeU32, cfg, [ + // Zero + { input: u32Bits(0b00000000000000000000000000000000), expected: u32(32) }, + + // One + { input: u32Bits(0b00000000000000000000000000000001), expected: u32(31) }, + + // 0's after leading 1 + { input: u32Bits(0b00000000000000000000000000000010), expected: u32(30) }, + { input: u32Bits(0b00000000000000000000000000000100), expected: u32(29) }, + { input: u32Bits(0b00000000000000000000000000001000), expected: u32(28) }, + { input: u32Bits(0b00000000000000000000000000010000), expected: u32(27) }, + { input: u32Bits(0b00000000000000000000000000100000), expected: u32(26) }, + { input: u32Bits(0b00000000000000000000000001000000), expected: u32(25) }, + { input: u32Bits(0b00000000000000000000000010000000), expected: u32(24) }, + { input: u32Bits(0b00000000000000000000000100000000), expected: u32(23) }, + { input: u32Bits(0b00000000000000000000001000000000), expected: u32(22) }, + { input: u32Bits(0b00000000000000000000010000000000), expected: u32(21) }, + { input: u32Bits(0b00000000000000000000100000000000), expected: u32(20) }, + { input: u32Bits(0b00000000000000000001000000000000), expected: u32(19) }, + { input: u32Bits(0b00000000000000000010000000000000), expected: u32(18) }, + { input: u32Bits(0b00000000000000000100000000000000), expected: u32(17) }, + { input: u32Bits(0b00000000000000001000000000000000), expected: u32(16) }, + { input: u32Bits(0b00000000000000010000000000000000), expected: u32(15) }, + { input: u32Bits(0b00000000000000100000000000000000), expected: u32(14) }, + { input: u32Bits(0b00000000000001000000000000000000), expected: u32(13) }, + { input: u32Bits(0b00000000000010000000000000000000), expected: u32(12) }, + { input: u32Bits(0b00000000000100000000000000000000), expected: u32(11) }, + { input: u32Bits(0b00000000001000000000000000000000), expected: u32(10) }, + { input: u32Bits(0b00000000010000000000000000000000), expected: u32(9) }, + { input: u32Bits(0b00000000100000000000000000000000), expected: u32(8) }, + { input: u32Bits(0b00000001000000000000000000000000), expected: u32(7) }, + { input: u32Bits(0b00000010000000000000000000000000), expected: u32(6) }, + { input: u32Bits(0b00000100000000000000000000000000), expected: u32(5) }, + { input: u32Bits(0b00001000000000000000000000000000), expected: u32(4) }, + { input: u32Bits(0b00010000000000000000000000000000), expected: u32(3) }, + { input: u32Bits(0b00100000000000000000000000000000), expected: u32(2) }, + { input: u32Bits(0b01000000000000000000000000000000), expected: u32(1) }, + { input: u32Bits(0b10000000000000000000000000000000), expected: u32(0) }, + + // 1's after leading 1 + { input: u32Bits(0b00000000000000000000000000000011), expected: u32(30) }, + { input: u32Bits(0b00000000000000000000000000000111), expected: u32(29) }, + { input: u32Bits(0b00000000000000000000000000001111), expected: u32(28) }, + { input: u32Bits(0b00000000000000000000000000011111), expected: u32(27) }, + { input: u32Bits(0b00000000000000000000000000111111), expected: u32(26) }, + { input: u32Bits(0b00000000000000000000000001111111), expected: u32(25) }, + { input: u32Bits(0b00000000000000000000000011111111), expected: u32(24) }, + { input: u32Bits(0b00000000000000000000000111111111), expected: u32(23) }, + { input: u32Bits(0b00000000000000000000001111111111), expected: u32(22) }, + { input: u32Bits(0b00000000000000000000011111111111), expected: u32(21) }, + { input: u32Bits(0b00000000000000000000111111111111), expected: u32(20) }, + { input: u32Bits(0b00000000000000000001111111111111), expected: u32(19) }, + { input: u32Bits(0b00000000000000000011111111111111), expected: u32(18) }, + { input: u32Bits(0b00000000000000000111111111111111), expected: u32(17) }, + { input: u32Bits(0b00000000000000001111111111111111), expected: u32(16) }, + { input: u32Bits(0b00000000000000011111111111111111), expected: u32(15) }, + { input: u32Bits(0b00000000000000111111111111111111), expected: u32(14) }, + { input: u32Bits(0b00000000000001111111111111111111), expected: u32(13) }, + { input: u32Bits(0b00000000000011111111111111111111), expected: u32(12) }, + { input: u32Bits(0b00000000000111111111111111111111), expected: u32(11) }, + { input: u32Bits(0b00000000001111111111111111111111), expected: u32(10) }, + { input: u32Bits(0b00000000011111111111111111111111), expected: u32(9) }, + { input: u32Bits(0b00000000111111111111111111111111), expected: u32(8) }, + { input: u32Bits(0b00000001111111111111111111111111), expected: u32(7) }, + { input: u32Bits(0b00000011111111111111111111111111), expected: u32(6) }, + { input: u32Bits(0b00000111111111111111111111111111), expected: u32(5) }, + { input: u32Bits(0b00001111111111111111111111111111), expected: u32(4) }, + { input: u32Bits(0b00011111111111111111111111111111), expected: u32(3) }, + { input: u32Bits(0b00111111111111111111111111111111), expected: u32(2) }, + { input: u32Bits(0b01111111111111111111111111111111), expected: u32(1) }, + { input: u32Bits(0b11111111111111111111111111111111), expected: u32(0) }, + + // random after leading 1 + { input: u32Bits(0b00000000000000000000000000000110), expected: u32(29) }, + { input: u32Bits(0b00000000000000000000000000001101), expected: u32(28) }, + { input: u32Bits(0b00000000000000000000000000011101), expected: u32(27) }, + { input: u32Bits(0b00000000000000000000000000111001), expected: u32(26) }, + { input: u32Bits(0b00000000000000000000000001101111), expected: u32(25) }, + { input: u32Bits(0b00000000000000000000000011111111), expected: u32(24) }, + { input: u32Bits(0b00000000000000000000000111101111), expected: u32(23) }, + { input: u32Bits(0b00000000000000000000001111111111), expected: u32(22) }, + { input: u32Bits(0b00000000000000000000011111110001), expected: u32(21) }, + { input: u32Bits(0b00000000000000000000111011011101), expected: u32(20) }, + { input: u32Bits(0b00000000000000000001101101111111), expected: u32(19) }, + { input: u32Bits(0b00000000000000000011111111011111), expected: u32(18) }, + { input: u32Bits(0b00000000000000000101111001110101), expected: u32(17) }, + { input: u32Bits(0b00000000000000001101111011110111), expected: u32(16) }, + { input: u32Bits(0b00000000000000011111111111110011), expected: u32(15) }, + { input: u32Bits(0b00000000000000111111111110111111), expected: u32(14) }, + { input: u32Bits(0b00000000000001111111011111111111), expected: u32(13) }, + { input: u32Bits(0b00000000000011111111111111111111), expected: u32(12) }, + { input: u32Bits(0b00000000000111110101011110111111), expected: u32(11) }, + { input: u32Bits(0b00000000001111101111111111110111), expected: u32(10) }, + { input: u32Bits(0b00000000011111111111010000101111), expected: u32(9) }, + { input: u32Bits(0b00000000111111111111001111111011), expected: u32(8) }, + { input: u32Bits(0b00000001111111011111101111111111), expected: u32(7) }, + { input: u32Bits(0b00000011101011111011110111111011), expected: u32(6) }, + { input: u32Bits(0b00000111111110111111111111111111), expected: u32(5) }, + { input: u32Bits(0b00001111000000011011011110111111), expected: u32(4) }, + { input: u32Bits(0b00011110101111011111111111111111), expected: u32(3) }, + { input: u32Bits(0b00110110111111100111111110111101), expected: u32(2) }, + { input: u32Bits(0b01010111111101111111011111011111), expected: u32(1) }, + { input: u32Bits(0b11100010011110101101101110101111), expected: u32(0) }, + ]); + }); + +g.test('i32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`i32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + await run(t, builtin('countLeadingZeros'), [TypeI32], TypeI32, cfg, [ + // Zero + { input: i32Bits(0b00000000000000000000000000000000), expected: i32(32) }, + + // One + { input: i32Bits(0b00000000000000000000000000000001), expected: i32(31) }, + + // 0's after leading 1 + { input: i32Bits(0b00000000000000000000000000000010), expected: i32(30) }, + { input: i32Bits(0b00000000000000000000000000000100), expected: i32(29) }, + { input: i32Bits(0b00000000000000000000000000001000), expected: i32(28) }, + { input: i32Bits(0b00000000000000000000000000010000), expected: i32(27) }, + { input: i32Bits(0b00000000000000000000000000100000), expected: i32(26) }, + { input: i32Bits(0b00000000000000000000000001000000), expected: i32(25) }, + { input: i32Bits(0b00000000000000000000000010000000), expected: i32(24) }, + { input: i32Bits(0b00000000000000000000000100000000), expected: i32(23) }, + { input: i32Bits(0b00000000000000000000001000000000), expected: i32(22) }, + { input: i32Bits(0b00000000000000000000010000000000), expected: i32(21) }, + { input: i32Bits(0b00000000000000000000100000000000), expected: i32(20) }, + { input: i32Bits(0b00000000000000000001000000000000), expected: i32(19) }, + { input: i32Bits(0b00000000000000000010000000000000), expected: i32(18) }, + { input: i32Bits(0b00000000000000000100000000000000), expected: i32(17) }, + { input: i32Bits(0b00000000000000001000000000000000), expected: i32(16) }, + { input: i32Bits(0b00000000000000010000000000000000), expected: i32(15) }, + { input: i32Bits(0b00000000000000100000000000000000), expected: i32(14) }, + { input: i32Bits(0b00000000000001000000000000000000), expected: i32(13) }, + { input: i32Bits(0b00000000000010000000000000000000), expected: i32(12) }, + { input: i32Bits(0b00000000000100000000000000000000), expected: i32(11) }, + { input: i32Bits(0b00000000001000000000000000000000), expected: i32(10) }, + { input: i32Bits(0b00000000010000000000000000000000), expected: i32(9) }, + { input: i32Bits(0b00000000100000000000000000000000), expected: i32(8) }, + { input: i32Bits(0b00000001000000000000000000000000), expected: i32(7) }, + { input: i32Bits(0b00000010000000000000000000000000), expected: i32(6) }, + { input: i32Bits(0b00000100000000000000000000000000), expected: i32(5) }, + { input: i32Bits(0b00001000000000000000000000000000), expected: i32(4) }, + { input: i32Bits(0b00010000000000000000000000000000), expected: i32(3) }, + { input: i32Bits(0b00100000000000000000000000000000), expected: i32(2) }, + { input: i32Bits(0b01000000000000000000000000000000), expected: i32(1) }, + { input: i32Bits(0b10000000000000000000000000000000), expected: i32(0) }, + + // 1's after leading 1 + { input: i32Bits(0b00000000000000000000000000000011), expected: i32(30) }, + { input: i32Bits(0b00000000000000000000000000000111), expected: i32(29) }, + { input: i32Bits(0b00000000000000000000000000001111), expected: i32(28) }, + { input: i32Bits(0b00000000000000000000000000011111), expected: i32(27) }, + { input: i32Bits(0b00000000000000000000000000111111), expected: i32(26) }, + { input: i32Bits(0b00000000000000000000000001111111), expected: i32(25) }, + { input: i32Bits(0b00000000000000000000000011111111), expected: i32(24) }, + { input: i32Bits(0b00000000000000000000000111111111), expected: i32(23) }, + { input: i32Bits(0b00000000000000000000001111111111), expected: i32(22) }, + { input: i32Bits(0b00000000000000000000011111111111), expected: i32(21) }, + { input: i32Bits(0b00000000000000000000111111111111), expected: i32(20) }, + { input: i32Bits(0b00000000000000000001111111111111), expected: i32(19) }, + { input: i32Bits(0b00000000000000000011111111111111), expected: i32(18) }, + { input: i32Bits(0b00000000000000000111111111111111), expected: i32(17) }, + { input: i32Bits(0b00000000000000001111111111111111), expected: i32(16) }, + { input: i32Bits(0b00000000000000011111111111111111), expected: i32(15) }, + { input: i32Bits(0b00000000000000111111111111111111), expected: i32(14) }, + { input: i32Bits(0b00000000000001111111111111111111), expected: i32(13) }, + { input: i32Bits(0b00000000000011111111111111111111), expected: i32(12) }, + { input: i32Bits(0b00000000000111111111111111111111), expected: i32(11) }, + { input: i32Bits(0b00000000001111111111111111111111), expected: i32(10) }, + { input: i32Bits(0b00000000011111111111111111111111), expected: i32(9) }, + { input: i32Bits(0b00000000111111111111111111111111), expected: i32(8) }, + { input: i32Bits(0b00000001111111111111111111111111), expected: i32(7) }, + { input: i32Bits(0b00000011111111111111111111111111), expected: i32(6) }, + { input: i32Bits(0b00000111111111111111111111111111), expected: i32(5) }, + { input: i32Bits(0b00001111111111111111111111111111), expected: i32(4) }, + { input: i32Bits(0b00011111111111111111111111111111), expected: i32(3) }, + { input: i32Bits(0b00111111111111111111111111111111), expected: i32(2) }, + { input: i32Bits(0b01111111111111111111111111111111), expected: i32(1) }, + { input: i32Bits(0b11111111111111111111111111111111), expected: i32(0) }, + + // random after leading 1 + { input: i32Bits(0b00000000000000000000000000000110), expected: i32(29) }, + { input: i32Bits(0b00000000000000000000000000001101), expected: i32(28) }, + { input: i32Bits(0b00000000000000000000000000011101), expected: i32(27) }, + { input: i32Bits(0b00000000000000000000000000111001), expected: i32(26) }, + { input: i32Bits(0b00000000000000000000000001101111), expected: i32(25) }, + { input: i32Bits(0b00000000000000000000000011111111), expected: i32(24) }, + { input: i32Bits(0b00000000000000000000000111101111), expected: i32(23) }, + { input: i32Bits(0b00000000000000000000001111111111), expected: i32(22) }, + { input: i32Bits(0b00000000000000000000011111110001), expected: i32(21) }, + { input: i32Bits(0b00000000000000000000111011011101), expected: i32(20) }, + { input: i32Bits(0b00000000000000000001101101111111), expected: i32(19) }, + { input: i32Bits(0b00000000000000000011111111011111), expected: i32(18) }, + { input: i32Bits(0b00000000000000000101111001110101), expected: i32(17) }, + { input: i32Bits(0b00000000000000001101111011110111), expected: i32(16) }, + { input: i32Bits(0b00000000000000011111111111110011), expected: i32(15) }, + { input: i32Bits(0b00000000000000111111111110111111), expected: i32(14) }, + { input: i32Bits(0b00000000000001111111011111111111), expected: i32(13) }, + { input: i32Bits(0b00000000000011111111111111111111), expected: i32(12) }, + { input: i32Bits(0b00000000000111110101011110111111), expected: i32(11) }, + { input: i32Bits(0b00000000001111101111111111110111), expected: i32(10) }, + { input: i32Bits(0b00000000011111111111010000101111), expected: i32(9) }, + { input: i32Bits(0b00000000111111111111001111111011), expected: i32(8) }, + { input: i32Bits(0b00000001111111011111101111111111), expected: i32(7) }, + { input: i32Bits(0b00000011101011111011110111111011), expected: i32(6) }, + { input: i32Bits(0b00000111111110111111111111111111), expected: i32(5) }, + { input: i32Bits(0b00001111000000011011011110111111), expected: i32(4) }, + { input: i32Bits(0b00011110101111011111111111111111), expected: i32(3) }, + { input: i32Bits(0b00110110111111100111111110111101), expected: i32(2) }, + { input: i32Bits(0b01010111111101111111011111011111), expected: i32(1) }, + { input: i32Bits(0b11100010011110101101101110101111), expected: i32(0) }, + ]); + }); 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 new file mode 100644 index 0000000000..f0be916285 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts @@ -0,0 +1,249 @@ +export const description = ` +Execution tests for the 'countOneBits' builtin function + +S is i32 or u32 +T is S or vecN<S> +@const fn countOneBits(e: T ) -> T +The number of 1 bits in the representation of e. +Also known as "population count". +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 { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('u32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`u32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + await run(t, builtin('countOneBits'), [TypeU32], TypeU32, cfg, [ + // Zero + { input: u32Bits(0b00000000000000000000000000000000), expected: u32(0) }, + + // One + { input: u32Bits(0b00000000000000000000000000000001), expected: u32(1) }, + + // 0's after leading 1 + { input: u32Bits(0b00000000000000000000000000000010), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000000000000100), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000000000001000), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000000000010000), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000000000100000), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000000001000000), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000000010000000), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000000100000000), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000001000000000), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000010000000000), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000100000000000), expected: u32(1) }, + { input: u32Bits(0b00000000000000000001000000000000), expected: u32(1) }, + { input: u32Bits(0b00000000000000000010000000000000), expected: u32(1) }, + { input: u32Bits(0b00000000000000000100000000000000), expected: u32(1) }, + { input: u32Bits(0b00000000000000001000000000000000), expected: u32(1) }, + { input: u32Bits(0b00000000000000010000000000000000), expected: u32(1) }, + { input: u32Bits(0b00000000000000100000000000000000), expected: u32(1) }, + { input: u32Bits(0b00000000000001000000000000000000), expected: u32(1) }, + { input: u32Bits(0b00000000000010000000000000000000), expected: u32(1) }, + { input: u32Bits(0b00000000000100000000000000000000), expected: u32(1) }, + { input: u32Bits(0b00000000001000000000000000000000), expected: u32(1) }, + { input: u32Bits(0b00000000010000000000000000000000), expected: u32(1) }, + { input: u32Bits(0b00000000100000000000000000000000), expected: u32(1) }, + { input: u32Bits(0b00000001000000000000000000000000), expected: u32(1) }, + { input: u32Bits(0b00000010000000000000000000000000), expected: u32(1) }, + { input: u32Bits(0b00000100000000000000000000000000), expected: u32(1) }, + { input: u32Bits(0b00001000000000000000000000000000), expected: u32(1) }, + { input: u32Bits(0b00010000000000000000000000000000), expected: u32(1) }, + { input: u32Bits(0b00100000000000000000000000000000), expected: u32(1) }, + { input: u32Bits(0b01000000000000000000000000000000), expected: u32(1) }, + { input: u32Bits(0b10000000000000000000000000000000), expected: u32(1) }, + + // 1's after leading 1 + { input: u32Bits(0b00000000000000000000000000000011), expected: u32(2) }, + { input: u32Bits(0b00000000000000000000000000000111), expected: u32(3) }, + { input: u32Bits(0b00000000000000000000000000001111), expected: u32(4) }, + { input: u32Bits(0b00000000000000000000000000011111), expected: u32(5) }, + { input: u32Bits(0b00000000000000000000000000111111), expected: u32(6) }, + { input: u32Bits(0b00000000000000000000000001111111), expected: u32(7) }, + { input: u32Bits(0b00000000000000000000000011111111), expected: u32(8) }, + { input: u32Bits(0b00000000000000000000000111111111), expected: u32(9) }, + { input: u32Bits(0b00000000000000000000001111111111), expected: u32(10) }, + { input: u32Bits(0b00000000000000000000011111111111), expected: u32(11) }, + { input: u32Bits(0b00000000000000000000111111111111), expected: u32(12) }, + { input: u32Bits(0b00000000000000000001111111111111), expected: u32(13) }, + { input: u32Bits(0b00000000000000000011111111111111), expected: u32(14) }, + { input: u32Bits(0b00000000000000000111111111111111), expected: u32(15) }, + { input: u32Bits(0b00000000000000001111111111111111), expected: u32(16) }, + { input: u32Bits(0b00000000000000011111111111111111), expected: u32(17) }, + { input: u32Bits(0b00000000000000111111111111111111), expected: u32(18) }, + { input: u32Bits(0b00000000000001111111111111111111), expected: u32(19) }, + { input: u32Bits(0b00000000000011111111111111111111), expected: u32(20) }, + { input: u32Bits(0b00000000000111111111111111111111), expected: u32(21) }, + { input: u32Bits(0b00000000001111111111111111111111), expected: u32(22) }, + { input: u32Bits(0b00000000011111111111111111111111), expected: u32(23) }, + { input: u32Bits(0b00000000111111111111111111111111), expected: u32(24) }, + { input: u32Bits(0b00000001111111111111111111111111), expected: u32(25) }, + { input: u32Bits(0b00000011111111111111111111111111), expected: u32(26) }, + { input: u32Bits(0b00000111111111111111111111111111), expected: u32(27) }, + { input: u32Bits(0b00001111111111111111111111111111), expected: u32(28) }, + { input: u32Bits(0b00011111111111111111111111111111), expected: u32(29) }, + { input: u32Bits(0b00111111111111111111111111111111), expected: u32(30) }, + { input: u32Bits(0b01111111111111111111111111111111), expected: u32(31) }, + { input: u32Bits(0b11111111111111111111111111111111), expected: u32(32) }, + + // random after leading 1 + { input: u32Bits(0b00000000000000000000000000000110), expected: u32(2) }, + { input: u32Bits(0b00000000000000000000000000001101), expected: u32(3) }, + { input: u32Bits(0b00000000000000000000000000011101), expected: u32(4) }, + { input: u32Bits(0b00000000000000000000000000111001), expected: u32(4) }, + { input: u32Bits(0b00000000000000000000000001101111), expected: u32(6) }, + { input: u32Bits(0b00000000000000000000000011111111), expected: u32(8) }, + { input: u32Bits(0b00000000000000000000000111101111), expected: u32(8) }, + { input: u32Bits(0b00000000000000000000001111111111), expected: u32(10) }, + { input: u32Bits(0b00000000000000000000011111110001), expected: u32(8) }, + { input: u32Bits(0b00000000000000000000111011011101), expected: u32(9) }, + { input: u32Bits(0b00000000000000000001101101111111), expected: u32(11) }, + { input: u32Bits(0b00000000000000000011111111011111), expected: u32(13) }, + { input: u32Bits(0b00000000000000000101111001110101), expected: u32(10) }, + { input: u32Bits(0b00000000000000001101111011110111), expected: u32(13) }, + { input: u32Bits(0b00000000000000011111111111110011), expected: u32(15) }, + { input: u32Bits(0b00000000000000111111111110111111), expected: u32(17) }, + { input: u32Bits(0b00000000000001111111011111111111), expected: u32(18) }, + { input: u32Bits(0b00000000000011111111111111111111), expected: u32(20) }, + { input: u32Bits(0b00000000000111110101011110111111), expected: u32(17) }, + { input: u32Bits(0b00000000001111101111111111110111), expected: u32(20) }, + { input: u32Bits(0b00000000011111111111010000101111), expected: u32(17) }, + { input: u32Bits(0b00000000111111111111001111111011), expected: u32(21) }, + { input: u32Bits(0b00000001111111011111101111111111), expected: u32(23) }, + { input: u32Bits(0b00000011101011111011110111111011), expected: u32(21) }, + { input: u32Bits(0b00000111111110111111111111111111), expected: u32(26) }, + { input: u32Bits(0b00001111000000011011011110111111), expected: u32(18) }, + { input: u32Bits(0b00011110101111011111111111111111), expected: u32(26) }, + { input: u32Bits(0b00110110111111100111111110111101), expected: u32(24) }, + { input: u32Bits(0b01010111111101111111011111011111), expected: u32(26) }, + { input: u32Bits(0b11100010011110101101101110101111), expected: u32(21) }, + ]); + }); + +g.test('i32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`i32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + await run(t, builtin('countOneBits'), [TypeI32], TypeI32, cfg, [ + // Zero + { input: i32Bits(0b00000000000000000000000000000000), expected: i32(0) }, + + // One + { input: i32Bits(0b00000000000000000000000000000001), expected: i32(1) }, + + // 0's after leading 1 + { input: i32Bits(0b00000000000000000000000000000010), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000000000000100), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000000000001000), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000000000010000), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000000000100000), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000000001000000), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000000010000000), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000000100000000), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000001000000000), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000010000000000), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000100000000000), expected: i32(1) }, + { input: i32Bits(0b00000000000000000001000000000000), expected: i32(1) }, + { input: i32Bits(0b00000000000000000010000000000000), expected: i32(1) }, + { input: i32Bits(0b00000000000000000100000000000000), expected: i32(1) }, + { input: i32Bits(0b00000000000000001000000000000000), expected: i32(1) }, + { input: i32Bits(0b00000000000000010000000000000000), expected: i32(1) }, + { input: i32Bits(0b00000000000000100000000000000000), expected: i32(1) }, + { input: i32Bits(0b00000000000001000000000000000000), expected: i32(1) }, + { input: i32Bits(0b00000000000010000000000000000000), expected: i32(1) }, + { input: i32Bits(0b00000000000100000000000000000000), expected: i32(1) }, + { input: i32Bits(0b00000000001000000000000000000000), expected: i32(1) }, + { input: i32Bits(0b00000000010000000000000000000000), expected: i32(1) }, + { input: i32Bits(0b00000000100000000000000000000000), expected: i32(1) }, + { input: i32Bits(0b00000001000000000000000000000000), expected: i32(1) }, + { input: i32Bits(0b00000010000000000000000000000000), expected: i32(1) }, + { input: i32Bits(0b00000100000000000000000000000000), expected: i32(1) }, + { input: i32Bits(0b00001000000000000000000000000000), expected: i32(1) }, + { input: i32Bits(0b00010000000000000000000000000000), expected: i32(1) }, + { input: i32Bits(0b00100000000000000000000000000000), expected: i32(1) }, + { input: i32Bits(0b01000000000000000000000000000000), expected: i32(1) }, + { input: i32Bits(0b10000000000000000000000000000000), expected: i32(1) }, + + // 1's after leading 1 + { input: i32Bits(0b00000000000000000000000000000011), expected: i32(2) }, + { input: i32Bits(0b00000000000000000000000000000111), expected: i32(3) }, + { input: i32Bits(0b00000000000000000000000000001111), expected: i32(4) }, + { input: i32Bits(0b00000000000000000000000000011111), expected: i32(5) }, + { input: i32Bits(0b00000000000000000000000000111111), expected: i32(6) }, + { input: i32Bits(0b00000000000000000000000001111111), expected: i32(7) }, + { input: i32Bits(0b00000000000000000000000011111111), expected: i32(8) }, + { input: i32Bits(0b00000000000000000000000111111111), expected: i32(9) }, + { input: i32Bits(0b00000000000000000000001111111111), expected: i32(10) }, + { input: i32Bits(0b00000000000000000000011111111111), expected: i32(11) }, + { input: i32Bits(0b00000000000000000000111111111111), expected: i32(12) }, + { input: i32Bits(0b00000000000000000001111111111111), expected: i32(13) }, + { input: i32Bits(0b00000000000000000011111111111111), expected: i32(14) }, + { input: i32Bits(0b00000000000000000111111111111111), expected: i32(15) }, + { input: i32Bits(0b00000000000000001111111111111111), expected: i32(16) }, + { input: i32Bits(0b00000000000000011111111111111111), expected: i32(17) }, + { input: i32Bits(0b00000000000000111111111111111111), expected: i32(18) }, + { input: i32Bits(0b00000000000001111111111111111111), expected: i32(19) }, + { input: i32Bits(0b00000000000011111111111111111111), expected: i32(20) }, + { input: i32Bits(0b00000000000111111111111111111111), expected: i32(21) }, + { input: i32Bits(0b00000000001111111111111111111111), expected: i32(22) }, + { input: i32Bits(0b00000000011111111111111111111111), expected: i32(23) }, + { input: i32Bits(0b00000000111111111111111111111111), expected: i32(24) }, + { input: i32Bits(0b00000001111111111111111111111111), expected: i32(25) }, + { input: i32Bits(0b00000011111111111111111111111111), expected: i32(26) }, + { input: i32Bits(0b00000111111111111111111111111111), expected: i32(27) }, + { input: i32Bits(0b00001111111111111111111111111111), expected: i32(28) }, + { input: i32Bits(0b00011111111111111111111111111111), expected: i32(29) }, + { input: i32Bits(0b00111111111111111111111111111111), expected: i32(30) }, + { input: i32Bits(0b01111111111111111111111111111111), expected: i32(31) }, + { input: i32Bits(0b11111111111111111111111111111111), expected: i32(32) }, + + // random after leading 1 + { input: i32Bits(0b00000000000000000000000000000110), expected: i32(2) }, + { input: i32Bits(0b00000000000000000000000000001101), expected: i32(3) }, + { input: i32Bits(0b00000000000000000000000000011101), expected: i32(4) }, + { input: i32Bits(0b00000000000000000000000000111001), expected: i32(4) }, + { input: i32Bits(0b00000000000000000000000001101111), expected: i32(6) }, + { input: i32Bits(0b00000000000000000000000011111111), expected: i32(8) }, + { input: i32Bits(0b00000000000000000000000111101111), expected: i32(8) }, + { input: i32Bits(0b00000000000000000000001111111111), expected: i32(10) }, + { input: i32Bits(0b00000000000000000000011111110001), expected: i32(8) }, + { input: i32Bits(0b00000000000000000000111011011101), expected: i32(9) }, + { input: i32Bits(0b00000000000000000001101101111111), expected: i32(11) }, + { input: i32Bits(0b00000000000000000011111111011111), expected: i32(13) }, + { input: i32Bits(0b00000000000000000101111001110101), expected: i32(10) }, + { input: i32Bits(0b00000000000000001101111011110111), expected: i32(13) }, + { input: i32Bits(0b00000000000000011111111111110011), expected: i32(15) }, + { input: i32Bits(0b00000000000000111111111110111111), expected: i32(17) }, + { input: i32Bits(0b00000000000001111111011111111111), expected: i32(18) }, + { input: i32Bits(0b00000000000011111111111111111111), expected: i32(20) }, + { input: i32Bits(0b00000000000111110101011110111111), expected: i32(17) }, + { input: i32Bits(0b00000000001111101111111111110111), expected: i32(20) }, + { input: i32Bits(0b00000000011111111111010000101111), expected: i32(17) }, + { input: i32Bits(0b00000000111111111111001111111011), expected: i32(21) }, + { input: i32Bits(0b00000001111111011111101111111111), expected: i32(23) }, + { input: i32Bits(0b00000011101011111011110111111011), expected: i32(21) }, + { input: i32Bits(0b00000111111110111111111111111111), expected: i32(26) }, + { input: i32Bits(0b00001111000000011011011110111111), expected: i32(18) }, + { input: i32Bits(0b00011110101111011111111111111111), expected: i32(26) }, + { input: i32Bits(0b00110110111111100111111110111101), expected: i32(24) }, + { input: i32Bits(0b01010111111101111111011111011111), expected: i32(26) }, + { input: i32Bits(0b11100010011110101101101110101111), expected: i32(21) }, + ]); + }); 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 new file mode 100644 index 0000000000..d0b3198f49 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts @@ -0,0 +1,250 @@ +export const description = ` +Execution tests for the 'countTrailingZeros' builtin function + +S is i32 or u32 +T is S or vecN<S> +@const fn countTrailingZeros(e: T ) -> T +The number of consecutive 0 bits starting from the least significant bit of e, +when T is a scalar type. +Component-wise when T is a vector. +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 { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('u32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`u32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + await run(t, builtin('countTrailingZeros'), [TypeU32], TypeU32, cfg, [ + // Zero + { input: u32Bits(0b00000000000000000000000000000000), expected: u32(32) }, + + // High bit + { input: u32Bits(0b10000000000000000000000000000000), expected: u32(31) }, + + // 0's before trailing 1 + { input: u32Bits(0b00000000000000000000000000000001), expected: u32(0) }, + { input: u32Bits(0b00000000000000000000000000000010), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000000000000100), expected: u32(2) }, + { input: u32Bits(0b00000000000000000000000000001000), expected: u32(3) }, + { input: u32Bits(0b00000000000000000000000000010000), expected: u32(4) }, + { input: u32Bits(0b00000000000000000000000000100000), expected: u32(5) }, + { input: u32Bits(0b00000000000000000000000001000000), expected: u32(6) }, + { input: u32Bits(0b00000000000000000000000010000000), expected: u32(7) }, + { input: u32Bits(0b00000000000000000000000100000000), expected: u32(8) }, + { input: u32Bits(0b00000000000000000000001000000000), expected: u32(9) }, + { input: u32Bits(0b00000000000000000000010000000000), expected: u32(10) }, + { input: u32Bits(0b00000000000000000000100000000000), expected: u32(11) }, + { input: u32Bits(0b00000000000000000001000000000000), expected: u32(12) }, + { input: u32Bits(0b00000000000000000010000000000000), expected: u32(13) }, + { input: u32Bits(0b00000000000000000100000000000000), expected: u32(14) }, + { input: u32Bits(0b00000000000000001000000000000000), expected: u32(15) }, + { input: u32Bits(0b00000000000000010000000000000000), expected: u32(16) }, + { input: u32Bits(0b00000000000000100000000000000000), expected: u32(17) }, + { input: u32Bits(0b00000000000001000000000000000000), expected: u32(18) }, + { input: u32Bits(0b00000000000010000000000000000000), expected: u32(19) }, + { input: u32Bits(0b00000000000100000000000000000000), expected: u32(20) }, + { input: u32Bits(0b00000000001000000000000000000000), expected: u32(21) }, + { input: u32Bits(0b00000000010000000000000000000000), expected: u32(22) }, + { input: u32Bits(0b00000000100000000000000000000000), expected: u32(23) }, + { input: u32Bits(0b00000001000000000000000000000000), expected: u32(24) }, + { input: u32Bits(0b00000010000000000000000000000000), expected: u32(25) }, + { input: u32Bits(0b00000100000000000000000000000000), expected: u32(26) }, + { input: u32Bits(0b00001000000000000000000000000000), expected: u32(27) }, + { input: u32Bits(0b00010000000000000000000000000000), expected: u32(28) }, + { input: u32Bits(0b00100000000000000000000000000000), expected: u32(29) }, + { input: u32Bits(0b01000000000000000000000000000000), expected: u32(30) }, + + // 1's before trailing 1 + { input: u32Bits(0b11111111111111111111111111111111), expected: u32(0) }, + { input: u32Bits(0b11111111111111111111111111111110), expected: u32(1) }, + { input: u32Bits(0b11111111111111111111111111111100), expected: u32(2) }, + { input: u32Bits(0b11111111111111111111111111111000), expected: u32(3) }, + { input: u32Bits(0b11111111111111111111111111110000), expected: u32(4) }, + { input: u32Bits(0b11111111111111111111111111100000), expected: u32(5) }, + { input: u32Bits(0b11111111111111111111111111000000), expected: u32(6) }, + { input: u32Bits(0b11111111111111111111111110000000), expected: u32(7) }, + { input: u32Bits(0b11111111111111111111111100000000), expected: u32(8) }, + { input: u32Bits(0b11111111111111111111111000000000), expected: u32(9) }, + { input: u32Bits(0b11111111111111111111110000000000), expected: u32(10) }, + { input: u32Bits(0b11111111111111111111100000000000), expected: u32(11) }, + { input: u32Bits(0b11111111111111111111000000000000), expected: u32(12) }, + { input: u32Bits(0b11111111111111111110000000000000), expected: u32(13) }, + { input: u32Bits(0b11111111111111111100000000000000), expected: u32(14) }, + { input: u32Bits(0b11111111111111111000000000000000), expected: u32(15) }, + { input: u32Bits(0b11111111111111110000000000000000), expected: u32(16) }, + { input: u32Bits(0b11111111111111100000000000000000), expected: u32(17) }, + { input: u32Bits(0b11111111111111000000000000000000), expected: u32(18) }, + { input: u32Bits(0b11111111111110000000000000000000), expected: u32(19) }, + { input: u32Bits(0b11111111111100000000000000000000), expected: u32(20) }, + { input: u32Bits(0b11111111111000000000000000000000), expected: u32(21) }, + { input: u32Bits(0b11111111110000000000000000000000), expected: u32(22) }, + { input: u32Bits(0b11111111100000000000000000000000), expected: u32(23) }, + { input: u32Bits(0b11111111000000000000000000000000), expected: u32(24) }, + { input: u32Bits(0b11111110000000000000000000000000), expected: u32(25) }, + { input: u32Bits(0b11111100000000000000000000000000), expected: u32(26) }, + { input: u32Bits(0b11111000000000000000000000000000), expected: u32(27) }, + { input: u32Bits(0b11110000000000000000000000000000), expected: u32(28) }, + { input: u32Bits(0b11100000000000000000000000000000), expected: u32(29) }, + { input: u32Bits(0b11000000000000000000000000000000), expected: u32(30) }, + + // random before trailing 1 + { input: u32Bits(0b11110000001111111101111010001111), expected: u32(0) }, + { input: u32Bits(0b11011110111111100101110011110010), expected: u32(1) }, + { input: u32Bits(0b11110111011011111111010000111100), expected: u32(2) }, + { input: u32Bits(0b11010011011101111111010011101000), expected: u32(3) }, + { input: u32Bits(0b11010111110111110001111110110000), expected: u32(4) }, + { input: u32Bits(0b11111101111101111110101111100000), expected: u32(5) }, + { input: u32Bits(0b11111001111011111001111011000000), expected: u32(6) }, + { input: u32Bits(0b11001110110111110111111010000000), expected: u32(7) }, + { input: u32Bits(0b11101111011111101110101100000000), expected: u32(8) }, + { input: u32Bits(0b11111101111011111111111000000000), expected: u32(9) }, + { input: u32Bits(0b10011111011101110110110000000000), expected: u32(10) }, + { input: u32Bits(0b11111111101101111011100000000000), expected: u32(11) }, + { input: u32Bits(0b11111011010110111011000000000000), expected: u32(12) }, + { input: u32Bits(0b00111101010000111010000000000000), expected: u32(13) }, + { input: u32Bits(0b11111011110001101100000000000000), expected: u32(14) }, + { input: u32Bits(0b10111111010111111000000000000000), expected: u32(15) }, + { input: u32Bits(0b11011101111010110000000000000000), expected: u32(16) }, + { input: u32Bits(0b01110100110110100000000000000000), expected: u32(17) }, + { input: u32Bits(0b11100111001011000000000000000000), expected: u32(18) }, + { input: u32Bits(0b11111001110110000000000000000000), expected: u32(19) }, + { input: u32Bits(0b00110100100100000000000000000000), expected: u32(20) }, + { input: u32Bits(0b11111010011000000000000000000000), expected: u32(21) }, + { input: u32Bits(0b00000010110000000000000000000000), expected: u32(22) }, + { input: u32Bits(0b11100111100000000000000000000000), expected: u32(23) }, + { input: u32Bits(0b00101101000000000000000000000000), expected: u32(24) }, + { input: u32Bits(0b11011010000000000000000000000000), expected: u32(25) }, + { input: u32Bits(0b11010100000000000000000000000000), expected: u32(26) }, + { input: u32Bits(0b10111000000000000000000000000000), expected: u32(27) }, + { input: u32Bits(0b01110000000000000000000000000000), expected: u32(28) }, + { input: u32Bits(0b10100000000000000000000000000000), expected: u32(29) }, + ]); + }); + +g.test('i32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`i32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + await run(t, builtin('countTrailingZeros'), [TypeI32], TypeI32, cfg, [ + // Zero + { input: i32Bits(0b00000000000000000000000000000000), expected: i32(32) }, + + // High bit + { input: i32Bits(0b10000000000000000000000000000000), expected: i32(31) }, + + // 0's before trailing 1 + { input: i32Bits(0b00000000000000000000000000000001), expected: i32(0) }, + { input: i32Bits(0b00000000000000000000000000000010), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000000000000100), expected: i32(2) }, + { input: i32Bits(0b00000000000000000000000000001000), expected: i32(3) }, + { input: i32Bits(0b00000000000000000000000000010000), expected: i32(4) }, + { input: i32Bits(0b00000000000000000000000000100000), expected: i32(5) }, + { input: i32Bits(0b00000000000000000000000001000000), expected: i32(6) }, + { input: i32Bits(0b00000000000000000000000010000000), expected: i32(7) }, + { input: i32Bits(0b00000000000000000000000100000000), expected: i32(8) }, + { input: i32Bits(0b00000000000000000000001000000000), expected: i32(9) }, + { input: i32Bits(0b00000000000000000000010000000000), expected: i32(10) }, + { input: i32Bits(0b00000000000000000000100000000000), expected: i32(11) }, + { input: i32Bits(0b00000000000000000001000000000000), expected: i32(12) }, + { input: i32Bits(0b00000000000000000010000000000000), expected: i32(13) }, + { input: i32Bits(0b00000000000000000100000000000000), expected: i32(14) }, + { input: i32Bits(0b00000000000000001000000000000000), expected: i32(15) }, + { input: i32Bits(0b00000000000000010000000000000000), expected: i32(16) }, + { input: i32Bits(0b00000000000000100000000000000000), expected: i32(17) }, + { input: i32Bits(0b00000000000001000000000000000000), expected: i32(18) }, + { input: i32Bits(0b00000000000010000000000000000000), expected: i32(19) }, + { input: i32Bits(0b00000000000100000000000000000000), expected: i32(20) }, + { input: i32Bits(0b00000000001000000000000000000000), expected: i32(21) }, + { input: i32Bits(0b00000000010000000000000000000000), expected: i32(22) }, + { input: i32Bits(0b00000000100000000000000000000000), expected: i32(23) }, + { input: i32Bits(0b00000001000000000000000000000000), expected: i32(24) }, + { input: i32Bits(0b00000010000000000000000000000000), expected: i32(25) }, + { input: i32Bits(0b00000100000000000000000000000000), expected: i32(26) }, + { input: i32Bits(0b00001000000000000000000000000000), expected: i32(27) }, + { input: i32Bits(0b00010000000000000000000000000000), expected: i32(28) }, + { input: i32Bits(0b00100000000000000000000000000000), expected: i32(29) }, + { input: i32Bits(0b01000000000000000000000000000000), expected: i32(30) }, + + // 1's before trailing 1 + { input: i32Bits(0b11111111111111111111111111111111), expected: i32(0) }, + { input: i32Bits(0b11111111111111111111111111111110), expected: i32(1) }, + { input: i32Bits(0b11111111111111111111111111111100), expected: i32(2) }, + { input: i32Bits(0b11111111111111111111111111111000), expected: i32(3) }, + { input: i32Bits(0b11111111111111111111111111110000), expected: i32(4) }, + { input: i32Bits(0b11111111111111111111111111100000), expected: i32(5) }, + { input: i32Bits(0b11111111111111111111111111000000), expected: i32(6) }, + { input: i32Bits(0b11111111111111111111111110000000), expected: i32(7) }, + { input: i32Bits(0b11111111111111111111111100000000), expected: i32(8) }, + { input: i32Bits(0b11111111111111111111111000000000), expected: i32(9) }, + { input: i32Bits(0b11111111111111111111110000000000), expected: i32(10) }, + { input: i32Bits(0b11111111111111111111100000000000), expected: i32(11) }, + { input: i32Bits(0b11111111111111111111000000000000), expected: i32(12) }, + { input: i32Bits(0b11111111111111111110000000000000), expected: i32(13) }, + { input: i32Bits(0b11111111111111111100000000000000), expected: i32(14) }, + { input: i32Bits(0b11111111111111111000000000000000), expected: i32(15) }, + { input: i32Bits(0b11111111111111110000000000000000), expected: i32(16) }, + { input: i32Bits(0b11111111111111100000000000000000), expected: i32(17) }, + { input: i32Bits(0b11111111111111000000000000000000), expected: i32(18) }, + { input: i32Bits(0b11111111111110000000000000000000), expected: i32(19) }, + { input: i32Bits(0b11111111111100000000000000000000), expected: i32(20) }, + { input: i32Bits(0b11111111111000000000000000000000), expected: i32(21) }, + { input: i32Bits(0b11111111110000000000000000000000), expected: i32(22) }, + { input: i32Bits(0b11111111100000000000000000000000), expected: i32(23) }, + { input: i32Bits(0b11111111000000000000000000000000), expected: i32(24) }, + { input: i32Bits(0b11111110000000000000000000000000), expected: i32(25) }, + { input: i32Bits(0b11111100000000000000000000000000), expected: i32(26) }, + { input: i32Bits(0b11111000000000000000000000000000), expected: i32(27) }, + { input: i32Bits(0b11110000000000000000000000000000), expected: i32(28) }, + { input: i32Bits(0b11100000000000000000000000000000), expected: i32(29) }, + { input: i32Bits(0b11000000000000000000000000000000), expected: i32(30) }, + + // random before trailing 1 + { input: i32Bits(0b11110000001111111101111010001111), expected: i32(0) }, + { input: i32Bits(0b11011110111111100101110011110010), expected: i32(1) }, + { input: i32Bits(0b11110111011011111111010000111100), expected: i32(2) }, + { input: i32Bits(0b11010011011101111111010011101000), expected: i32(3) }, + { input: i32Bits(0b11010111110111110001111110110000), expected: i32(4) }, + { input: i32Bits(0b11111101111101111110101111100000), expected: i32(5) }, + { input: i32Bits(0b11111001111011111001111011000000), expected: i32(6) }, + { input: i32Bits(0b11001110110111110111111010000000), expected: i32(7) }, + { input: i32Bits(0b11101111011111101110101100000000), expected: i32(8) }, + { input: i32Bits(0b11111101111011111111111000000000), expected: i32(9) }, + { input: i32Bits(0b10011111011101110110110000000000), expected: i32(10) }, + { input: i32Bits(0b11111111101101111011100000000000), expected: i32(11) }, + { input: i32Bits(0b11111011010110111011000000000000), expected: i32(12) }, + { input: i32Bits(0b00111101010000111010000000000000), expected: i32(13) }, + { input: i32Bits(0b11111011110001101100000000000000), expected: i32(14) }, + { input: i32Bits(0b10111111010111111000000000000000), expected: i32(15) }, + { input: i32Bits(0b11011101111010110000000000000000), expected: i32(16) }, + { input: i32Bits(0b01110100110110100000000000000000), expected: i32(17) }, + { input: i32Bits(0b11100111001011000000000000000000), expected: i32(18) }, + { input: i32Bits(0b11111001110110000000000000000000), expected: i32(19) }, + { input: i32Bits(0b00110100100100000000000000000000), expected: i32(20) }, + { input: i32Bits(0b11111010011000000000000000000000), expected: i32(21) }, + { input: i32Bits(0b00000010110000000000000000000000), expected: i32(22) }, + { input: i32Bits(0b11100111100000000000000000000000), expected: i32(23) }, + { input: i32Bits(0b00101101000000000000000000000000), expected: i32(24) }, + { input: i32Bits(0b11011010000000000000000000000000), expected: i32(25) }, + { input: i32Bits(0b11010100000000000000000000000000), expected: i32(26) }, + { input: i32Bits(0b10111000000000000000000000000000), expected: i32(27) }, + { input: i32Bits(0b01110000000000000000000000000000), expected: i32(28) }, + { input: i32Bits(0b10100000000000000000000000000000), expected: i32(29) }, + ]); + }); 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 new file mode 100644 index 0000000000..2b0b3e58ce --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts @@ -0,0 +1,113 @@ +export const description = ` +Execution tests for the 'cross' builtin function + +T is AbstractFloat, 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 { allInputSources, onlyConstInputSource, run } from '../../expression.js'; + +import { abstractBuiltin, builtin } from './builtin.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'); + await run( + t, + abstractBuiltin('cross'), + [TypeVec(3, TypeAbstractFloat), TypeVec(3, TypeAbstractFloat)], + TypeVec(3, TypeAbstractFloat), + t.params, + cases + ); + }); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .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 + ); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-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 + ); + }); 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 new file mode 100644 index 0000000000..f82153ffca --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts @@ -0,0 +1,95 @@ +export const description = ` +Execution tests for the 'degrees' builtin function + +S is AbstractFloat, 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 +`; + +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 { allInputSources, onlyConstInputSource, run } from '../../expression.js'; + +import { abstractBuiltin, builtin } from './builtin.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`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractBuiltin('degrees'), + [TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..f08f4f0b6b --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts @@ -0,0 +1,137 @@ +export const description = ` +Execution tests for the 'determinant' builtin function + +T is AbstractFloat, 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 { builtin } from './builtin.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(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions') + .desc(`f32 tests`) + .params(u => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4] as const)) + .fn(async t => { + const dim = t.params.dim; + const cases = await d.get( + t.params.inputSource === 'const' + ? `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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions') + .desc(`f16 tests`) + .params(u => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4] as const)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const dim = t.params.dim; + const cases = await d.get( + t.params.inputSource === 'const' + ? `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); + }); 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 new file mode 100644 index 0000000000..13cddf6403 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts @@ -0,0 +1,241 @@ +export const description = ` +Execution tests for the 'distance' builtin function + +S is AbstractFloat, 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)). + +`; + +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'; + +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 + ); + }, + ...f32_vec_cases, + f16_const: () => { + return FP.f16.generateScalarPairToIntervalCases( + fullF16Range(), + fullF16Range(), + 'finite', + FP.f16.distanceInterval + ); + }, + f16_non_const: () => { + return FP.f16.generateScalarPairToIntervalCases( + fullF16Range(), + fullF16Range(), + 'unfiltered', + FP.f16.distanceInterval + ); + }, + ...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('f32') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests`) + .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); + }); + +g.test('f32_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-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('distance'), + [TypeVec(2, TypeF32), TypeVec(2, TypeF32)], + TypeF32, + t.params, + cases + ); + }); + +g.test('f32_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + 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 + ); + }); + +g.test('f32_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + 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 + ); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); + +g.test('f16_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec2s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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 + ); + }); + +g.test('f16_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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 + ); + }); + +g.test('f16_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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 + ); + }); 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 new file mode 100644 index 0000000000..2726546183 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts @@ -0,0 +1,182 @@ +export const description = ` +Execution tests for the 'dot' builtin function + +T is AbstractInt, 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 { builtin } from './builtin.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`) + .params(u => u.combine('inputSource', allInputSources)) + .unimplemented(); + +g.test('i32') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`i32 tests`) + .params(u => u.combine('inputSource', allInputSources)) + .unimplemented(); + +g.test('u32') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`u32 tests`) + .params(u => u.combine('inputSource', allInputSources)) + .unimplemented(); + +g.test('abstract_float') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`abstract float test`) + .params(u => u.combine('inputSource', allInputSources)) + .unimplemented(); + +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'), + [TypeVec(2, TypeF32), TypeVec(2, TypeF32)], + TypeF32, + t.params, + cases + ); + }); + +g.test('f32_vec3') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`f32 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + 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 + ); + }); + +g.test('f32_vec4') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`f32 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + 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 + ); + }); + +g.test('f16_vec2') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`f16 tests using vec2s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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 + ); + }); + +g.test('f16_vec3') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`f16 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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 + ); + }); + +g.test('f16_vec4') + .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions') + .desc(`f16 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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 + ); + }); 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 new file mode 100644 index 0000000000..287a51c699 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts @@ -0,0 +1,23 @@ +export const description = ` +Execution tests for the 'dpdx' builtin function + +T is f32 or vecN<f32> +fn dpdx(e:T) -> T +Partial derivative of e with respect to window x coordinates. +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'; + +export const g = makeTestGroup(GPUTest); + +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) + ) + .unimplemented(); 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 new file mode 100644 index 0000000000..67a75bb010 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts @@ -0,0 +1,22 @@ +export const description = ` +Execution tests for the 'dpdxCoarse' builtin function + +T is f32 or vecN<f32> +fn dpdxCoarse(e:T) ->T +Returns the partial derivative of e with respect to window x coordinates using local differences. +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'; + +export const g = makeTestGroup(GPUTest); + +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) + ) + .unimplemented(); 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 new file mode 100644 index 0000000000..91d65b990b --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts @@ -0,0 +1,21 @@ +export const description = ` +Execution tests for the 'dpdxFine' builtin function + +T is f32 or vecN<f32> +fn dpdxFine(e:T) ->T +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'; + +export const g = makeTestGroup(GPUTest); + +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) + ) + .unimplemented(); 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 new file mode 100644 index 0000000000..0cd9cafdb9 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts @@ -0,0 +1,22 @@ +export const description = ` +Execution tests for the 'dpdy' builtin function + +T is f32 or vecN<f32> +fn dpdy(e:T) ->T +Partial derivative of e with respect to window y coordinates. +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'; + +export const g = makeTestGroup(GPUTest); + +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) + ) + .unimplemented(); 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 new file mode 100644 index 0000000000..f06869fdc2 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts @@ -0,0 +1,22 @@ +export const description = ` +Execution tests for the 'dpdyCoarse' builtin function + +T is f32 or vecN<f32> +fn dpdyCoarse(e:T) ->T +Returns the partial derivative of e with respect to window y coordinates using local differences. +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'; + +export const g = makeTestGroup(GPUTest); + +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) + ) + .unimplemented(); 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 new file mode 100644 index 0000000000..e09761de95 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts @@ -0,0 +1,21 @@ +export const description = ` +Execution tests for the 'dpdyFine' builtin function + +T is f32 or vecN<f32> +fn dpdyFine(e:T) ->T +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'; + +export const g = makeTestGroup(GPUTest); + +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) + ) + .unimplemented(); 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 new file mode 100644 index 0000000000..8b1ced3cab --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts @@ -0,0 +1,90 @@ +export const description = ` +Execution tests for the 'exp' builtin function + +S is AbstractFloat, 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. +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..67e123cb30 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts @@ -0,0 +1,90 @@ +export const description = ` +Execution tests for the 'exp2' builtin function + +S is AbstractFloat, 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. +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..d535bf5d74 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts @@ -0,0 +1,337 @@ +export const description = ` +Execution tests for the 'extractBits' builtin function + +T is u32 or vecN<u32> +@const fn extractBits(e: T, offset: u32, count: u32) -> T +Reads bits from an integer, without sign extension. + +When T is a scalar type, then: + w is the bit width of T + o = min(offset,w) + c = min(count, w - o) + +The result is 0 if c is 0. +Otherwise, bits 0..c-1 of the result are copied from bits o..o+c-1 of e. +Other bits of the result are 0. +Component-wise when T is a vector. + + +T is i32 or vecN<i32> +@const fn extractBits(e: T, offset: u32, count: u32) -> T +Reads bits from an integer, with sign extension. + +When T is a scalar type, then: + w is the bit width of T + o = min(offset,w) + c = min(count, w - o) + +The result is 0 if c is 0. +Otherwise, bits 0..c-1 of the result are copied from bits o..o+c-1 of e. +Other bits of the result are the same as bit c-1 of the result. +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 { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('u32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`u32 tests`) + .params(u => u.combine('inputSource', allInputSources).combine('width', [1, 2, 3, 4])) + .fn(async t => { + const cfg: Config = t.params; + + const T = t.params.width === 1 ? TypeU32 : TypeVec(t.params.width, TypeU32); + + const V = (x: number, y?: number, z?: number, w?: number) => { + y = y === undefined ? x : y; + z = z === undefined ? x : z; + w = w === undefined ? x : w; + + switch (t.params.width) { + case 1: + return u32Bits(x); + case 2: + return vec2(u32Bits(x), u32Bits(y)); + case 3: + return vec3(u32Bits(x), u32Bits(y), u32Bits(z)); + default: + return vec4(u32Bits(x), u32Bits(y), u32Bits(z), u32Bits(w)); + } + }; + + const all_1 = V(0b11111111111111111111111111111111); + const all_0 = V(0b00000000000000000000000000000000); + const low_1 = V(0b00000000000000000000000000000001); + const high_1 = V(0b10000000000000000000000000000000); + const pattern = V( + 0b00000000000111011100000000000000, + 0b11111111111000000011111111111111, + 0b00000000010101010101000000000000, + 0b00000000001010101010100000000000 + ); + + const cases = [ + { input: [all_0, u32(0), u32(32)], expected: all_0 }, + { input: [all_0, u32(1), u32(10)], expected: all_0 }, + { input: [all_0, u32(2), u32(5)], expected: all_0 }, + { input: [all_0, u32(0), u32(1)], expected: all_0 }, + { input: [all_0, u32(31), u32(1)], expected: all_0 }, + + { input: [all_1, u32(0), u32(32)], expected: all_1 }, + { + input: [all_1, u32(1), u32(10)], + expected: V(0b00000000000000000000001111111111), + }, + { + input: [all_1, u32(2), u32(5)], + expected: V(0b00000000000000000000000000011111), + }, + { input: [all_1, u32(0), u32(1)], expected: low_1 }, + { input: [all_1, u32(31), u32(1)], expected: low_1 }, + + // Patterns + { input: [pattern, u32(0), u32(32)], expected: pattern }, + { + input: [pattern, u32(1), u32(31)], + expected: V( + 0b00000000000011101110000000000000, + 0b01111111111100000001111111111111, + 0b00000000001010101010100000000000, + 0b00000000000101010101010000000000 + ), + }, + { + input: [pattern, u32(14), u32(18)], + expected: V( + 0b00000000000000000000000001110111, + 0b00000000000000111111111110000000, + 0b00000000000000000000000101010101, + 0b00000000000000000000000010101010 + ), + }, + { + input: [pattern, u32(14), u32(7)], + expected: V( + 0b00000000000000000000000001110111, + 0b00000000000000000000000000000000, + 0b00000000000000000000000001010101, + 0b00000000000000000000000000101010 + ), + }, + { + input: [pattern, u32(14), u32(4)], + expected: V( + 0b00000000000000000000000000000111, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000101, + 0b00000000000000000000000000001010 + ), + }, + { + input: [pattern, u32(14), u32(3)], + expected: V( + 0b00000000000000000000000000000111, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000101, + 0b00000000000000000000000000000010 + ), + }, + { + input: [pattern, u32(18), u32(3)], + expected: V( + 0b00000000000000000000000000000111, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000101, + 0b00000000000000000000000000000010 + ), + }, + { input: [low_1, u32(0), u32(1)], expected: low_1 }, + { input: [high_1, u32(31), u32(1)], expected: low_1 }, + + // Zero count + { input: [all_1, u32(0), u32(0)], expected: all_0 }, + { input: [all_0, u32(0), u32(0)], expected: all_0 }, + { input: [low_1, u32(0), u32(0)], expected: all_0 }, + { input: [high_1, u32(31), u32(0)], expected: all_0 }, + { input: [pattern, u32(0), u32(0)], expected: all_0 }, + ]; + + if (t.params.inputSource !== 'const') { + cases.push( + ...[ + // End overflow + { input: [low_1, u32(0), u32(99)], expected: low_1 }, + { input: [high_1, u32(31), u32(99)], expected: low_1 }, + { input: [pattern, u32(0), u32(99)], expected: pattern }, + { + input: [pattern, u32(14), u32(99)], + expected: V( + 0b00000000000000000000000001110111, + 0b00000000000000111111111110000000, + 0b00000000000000000000000101010101, + 0b00000000000000000000000010101010 + ), + }, + ] + ); + } + + await run(t, builtin('extractBits'), [T, TypeU32, TypeU32], T, cfg, cases); + }); + +g.test('i32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`i32 tests`) + .params(u => u.combine('inputSource', allInputSources).combine('width', [1, 2, 3, 4])) + .fn(async t => { + const cfg: Config = t.params; + + const T = t.params.width === 1 ? TypeI32 : TypeVec(t.params.width, TypeI32); + + const V = (x: number, y?: number, z?: number, w?: number) => { + y = y === undefined ? x : y; + z = z === undefined ? x : z; + w = w === undefined ? x : w; + + switch (t.params.width) { + case 1: + return i32Bits(x); + case 2: + return vec2(i32Bits(x), i32Bits(y)); + case 3: + return vec3(i32Bits(x), i32Bits(y), i32Bits(z)); + default: + return vec4(i32Bits(x), i32Bits(y), i32Bits(z), i32Bits(w)); + } + }; + + const all_1 = V(0b11111111111111111111111111111111); + const all_0 = V(0b00000000000000000000000000000000); + const low_1 = V(0b00000000000000000000000000000001); + const high_1 = V(0b10000000000000000000000000000000); + const pattern = V( + 0b00000000000111011100000000000000, + 0b11111111111000000011111111111111, + 0b00000000010101010101000000000000, + 0b00000000001010101010100000000000 + ); + + const cases = [ + { input: [all_0, u32(0), u32(32)], expected: all_0 }, + { input: [all_0, u32(1), u32(10)], expected: all_0 }, + { input: [all_0, u32(2), u32(5)], expected: all_0 }, + { input: [all_0, u32(0), u32(1)], expected: all_0 }, + { input: [all_0, u32(31), u32(1)], expected: all_0 }, + + { input: [all_1, u32(0), u32(32)], expected: all_1 }, + { input: [all_1, u32(1), u32(10)], expected: all_1 }, + { input: [all_1, u32(2), u32(5)], expected: all_1 }, + { input: [all_1, u32(0), u32(1)], expected: all_1 }, + { input: [all_1, u32(31), u32(1)], expected: all_1 }, + + // Patterns + { input: [pattern, u32(0), u32(32)], expected: pattern }, + { + input: [pattern, u32(1), u32(31)], + expected: V( + 0b00000000000011101110000000000000, + 0b11111111111100000001111111111111, + 0b00000000001010101010100000000000, + 0b00000000000101010101010000000000 + ), + }, + { + input: [pattern, u32(14), u32(18)], + expected: V( + 0b00000000000000000000000001110111, + 0b11111111111111111111111110000000, + 0b00000000000000000000000101010101, + 0b00000000000000000000000010101010 + ), + }, + { + input: [pattern, u32(14), u32(7)], + expected: V( + 0b11111111111111111111111111110111, + 0b00000000000000000000000000000000, + 0b11111111111111111111111111010101, + 0b00000000000000000000000000101010 + ), + }, + { + input: [pattern, u32(14), u32(4)], + expected: V( + 0b00000000000000000000000000000111, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000101, + 0b11111111111111111111111111111010 + ), + }, + { + input: [pattern, u32(14), u32(3)], + expected: V( + 0b11111111111111111111111111111111, + 0b00000000000000000000000000000000, + 0b11111111111111111111111111111101, + 0b00000000000000000000000000000010 + ), + }, + { + input: [pattern, u32(18), u32(3)], + expected: V( + 0b11111111111111111111111111111111, + 0b00000000000000000000000000000000, + 0b11111111111111111111111111111101, + 0b00000000000000000000000000000010 + ), + }, + { input: [low_1, u32(0), u32(1)], expected: all_1 }, + { input: [high_1, u32(31), u32(1)], expected: all_1 }, + + // Zero count + { input: [all_1, u32(0), u32(0)], expected: all_0 }, + { input: [all_0, u32(0), u32(0)], expected: all_0 }, + { input: [low_1, u32(0), u32(0)], expected: all_0 }, + { input: [high_1, u32(31), u32(0)], expected: all_0 }, + { input: [pattern, u32(0), u32(0)], expected: all_0 }, + ]; + + if (t.params.inputSource !== 'const') { + cases.push( + ...[ + // End overflow + { input: [low_1, u32(0), u32(99)], expected: low_1 }, + { input: [high_1, u32(31), u32(99)], expected: all_1 }, + { input: [pattern, u32(0), u32(99)], expected: pattern }, + { + input: [pattern, u32(14), u32(99)], + expected: V( + 0b00000000000000000000000001110111, + 0b11111111111111111111111110000000, + 0b00000000000000000000000101010101, + 0b00000000000000000000000010101010 + ), + }, + ] + ); + } + + await run(t, builtin('extractBits'), [T, TypeU32, TypeU32], T, cfg, 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 new file mode 100644 index 0000000000..6b6794fb9f --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts @@ -0,0 +1,256 @@ +export const description = ` +Execution tests for the 'faceForward' builtin function + +T is vecN<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 { builtin } from './builtin.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 }), {}); + +export const d = makeCaseCache('faceForward', { + ...f32_vec_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', [2, 3, 4] as const)) + .unimplemented(); + +g.test('f32_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-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('faceForward'), + [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeVec(2, TypeF32)], + TypeVec(2, TypeF32), + t.params, + cases + ); + }); + +g.test('f32_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const' + ); + await run( + t, + builtin('faceForward'), + [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeVec(3, TypeF32)], + TypeVec(3, TypeF32), + t.params, + cases + ); + }); + +g.test('f32_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const' + ); + await run( + t, + builtin('faceForward'), + [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeVec(4, TypeF32)], + TypeVec(4, TypeF32), + t.params, + cases + ); + }); + +g.test('f16_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec2s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const' + ); + await run( + t, + builtin('faceForward'), + [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeVec(2, TypeF16)], + TypeVec(2, TypeF16), + t.params, + cases + ); + }); + +g.test('f16_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const' + ); + await run( + t, + builtin('faceForward'), + [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeVec(3, TypeF16)], + TypeVec(3, TypeF16), + t.params, + cases + ); + }); + +g.test('f16_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const' + ); + await run( + t, + builtin('faceForward'), + [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeVec(4, TypeF16)], + TypeVec(4, TypeF16), + 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 new file mode 100644 index 0000000000..26216563cd --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts @@ -0,0 +1,350 @@ +export const description = ` +Execution tests for the 'firstLeadingBit' builtin function + +T is u32 or vecN<u32> +@const fn firstLeadingBit(e: T ) -> T +For scalar T, the result is: T(-1) if e is zero. +Otherwise the position of the most significant 1 bit in e. +Component-wise when T is a vector. + +T is i32 or vecN<i32> +@const fn firstLeadingBit(e: T ) -> T +For scalar T, the result is: -1 if e is 0 or -1. +Otherwise the position of the most significant bit in e that is different from e’s sign bit. +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 { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('u32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`u32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + await run(t, builtin('firstLeadingBit'), [TypeU32], TypeU32, cfg, [ + // Zero + { input: u32Bits(0b00000000000000000000000000000000), expected: u32(-1) }, + + // One + { input: u32Bits(0b00000000000000000000000000000001), expected: u32(0) }, + + // 0's after leading 1 + { input: u32Bits(0b00000000000000000000000000000010), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000000000000100), expected: u32(2) }, + { input: u32Bits(0b00000000000000000000000000001000), expected: u32(3) }, + { input: u32Bits(0b00000000000000000000000000010000), expected: u32(4) }, + { input: u32Bits(0b00000000000000000000000000100000), expected: u32(5) }, + { input: u32Bits(0b00000000000000000000000001000000), expected: u32(6) }, + { input: u32Bits(0b00000000000000000000000010000000), expected: u32(7) }, + { input: u32Bits(0b00000000000000000000000100000000), expected: u32(8) }, + { input: u32Bits(0b00000000000000000000001000000000), expected: u32(9) }, + { input: u32Bits(0b00000000000000000000010000000000), expected: u32(10) }, + { input: u32Bits(0b00000000000000000000100000000000), expected: u32(11) }, + { input: u32Bits(0b00000000000000000001000000000000), expected: u32(12) }, + { input: u32Bits(0b00000000000000000010000000000000), expected: u32(13) }, + { input: u32Bits(0b00000000000000000100000000000000), expected: u32(14) }, + { input: u32Bits(0b00000000000000001000000000000000), expected: u32(15) }, + { input: u32Bits(0b00000000000000010000000000000000), expected: u32(16) }, + { input: u32Bits(0b00000000000000100000000000000000), expected: u32(17) }, + { input: u32Bits(0b00000000000001000000000000000000), expected: u32(18) }, + { input: u32Bits(0b00000000000010000000000000000000), expected: u32(19) }, + { input: u32Bits(0b00000000000100000000000000000000), expected: u32(20) }, + { input: u32Bits(0b00000000001000000000000000000000), expected: u32(21) }, + { input: u32Bits(0b00000000010000000000000000000000), expected: u32(22) }, + { input: u32Bits(0b00000000100000000000000000000000), expected: u32(23) }, + { input: u32Bits(0b00000001000000000000000000000000), expected: u32(24) }, + { input: u32Bits(0b00000010000000000000000000000000), expected: u32(25) }, + { input: u32Bits(0b00000100000000000000000000000000), expected: u32(26) }, + { input: u32Bits(0b00001000000000000000000000000000), expected: u32(27) }, + { input: u32Bits(0b00010000000000000000000000000000), expected: u32(28) }, + { input: u32Bits(0b00100000000000000000000000000000), expected: u32(29) }, + { input: u32Bits(0b01000000000000000000000000000000), expected: u32(30) }, + { input: u32Bits(0b10000000000000000000000000000000), expected: u32(31) }, + + // 1's after leading 1 + { input: u32Bits(0b00000000000000000000000000000011), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000000000000111), expected: u32(2) }, + { input: u32Bits(0b00000000000000000000000000001111), expected: u32(3) }, + { input: u32Bits(0b00000000000000000000000000011111), expected: u32(4) }, + { input: u32Bits(0b00000000000000000000000000111111), expected: u32(5) }, + { input: u32Bits(0b00000000000000000000000001111111), expected: u32(6) }, + { input: u32Bits(0b00000000000000000000000011111111), expected: u32(7) }, + { input: u32Bits(0b00000000000000000000000111111111), expected: u32(8) }, + { input: u32Bits(0b00000000000000000000001111111111), expected: u32(9) }, + { input: u32Bits(0b00000000000000000000011111111111), expected: u32(10) }, + { input: u32Bits(0b00000000000000000000111111111111), expected: u32(11) }, + { input: u32Bits(0b00000000000000000001111111111111), expected: u32(12) }, + { input: u32Bits(0b00000000000000000011111111111111), expected: u32(13) }, + { input: u32Bits(0b00000000000000000111111111111111), expected: u32(14) }, + { input: u32Bits(0b00000000000000001111111111111111), expected: u32(15) }, + { input: u32Bits(0b00000000000000011111111111111111), expected: u32(16) }, + { input: u32Bits(0b00000000000000111111111111111111), expected: u32(17) }, + { input: u32Bits(0b00000000000001111111111111111111), expected: u32(18) }, + { input: u32Bits(0b00000000000011111111111111111111), expected: u32(19) }, + { input: u32Bits(0b00000000000111111111111111111111), expected: u32(20) }, + { input: u32Bits(0b00000000001111111111111111111111), expected: u32(21) }, + { input: u32Bits(0b00000000011111111111111111111111), expected: u32(22) }, + { input: u32Bits(0b00000000111111111111111111111111), expected: u32(23) }, + { input: u32Bits(0b00000001111111111111111111111111), expected: u32(24) }, + { input: u32Bits(0b00000011111111111111111111111111), expected: u32(25) }, + { input: u32Bits(0b00000111111111111111111111111111), expected: u32(26) }, + { input: u32Bits(0b00001111111111111111111111111111), expected: u32(27) }, + { input: u32Bits(0b00011111111111111111111111111111), expected: u32(28) }, + { input: u32Bits(0b00111111111111111111111111111111), expected: u32(29) }, + { input: u32Bits(0b01111111111111111111111111111111), expected: u32(30) }, + { input: u32Bits(0b11111111111111111111111111111111), expected: u32(31) }, + + // random after leading 1 + { input: u32Bits(0b00000000000000000000000000000110), expected: u32(2) }, + { input: u32Bits(0b00000000000000000000000000001101), expected: u32(3) }, + { input: u32Bits(0b00000000000000000000000000011101), expected: u32(4) }, + { input: u32Bits(0b00000000000000000000000000111001), expected: u32(5) }, + { input: u32Bits(0b00000000000000000000000001101111), expected: u32(6) }, + { input: u32Bits(0b00000000000000000000000011111111), expected: u32(7) }, + { input: u32Bits(0b00000000000000000000000111101111), expected: u32(8) }, + { input: u32Bits(0b00000000000000000000001111111111), expected: u32(9) }, + { input: u32Bits(0b00000000000000000000011111110001), expected: u32(10) }, + { input: u32Bits(0b00000000000000000000111011011101), expected: u32(11) }, + { input: u32Bits(0b00000000000000000001101101111111), expected: u32(12) }, + { input: u32Bits(0b00000000000000000011111111011111), expected: u32(13) }, + { input: u32Bits(0b00000000000000000101111001110101), expected: u32(14) }, + { input: u32Bits(0b00000000000000001101111011110111), expected: u32(15) }, + { input: u32Bits(0b00000000000000011111111111110011), expected: u32(16) }, + { input: u32Bits(0b00000000000000111111111110111111), expected: u32(17) }, + { input: u32Bits(0b00000000000001111111011111111111), expected: u32(18) }, + { input: u32Bits(0b00000000000011111111111111111111), expected: u32(19) }, + { input: u32Bits(0b00000000000111110101011110111111), expected: u32(20) }, + { input: u32Bits(0b00000000001111101111111111110111), expected: u32(21) }, + { input: u32Bits(0b00000000011111111111010000101111), expected: u32(22) }, + { input: u32Bits(0b00000000111111111111001111111011), expected: u32(23) }, + { input: u32Bits(0b00000001111111011111101111111111), expected: u32(24) }, + { input: u32Bits(0b00000011101011111011110111111011), expected: u32(25) }, + { input: u32Bits(0b00000111111110111111111111111111), expected: u32(26) }, + { input: u32Bits(0b00001111000000011011011110111111), expected: u32(27) }, + { input: u32Bits(0b00011110101111011111111111111111), expected: u32(28) }, + { input: u32Bits(0b00110110111111100111111110111101), expected: u32(29) }, + { input: u32Bits(0b01010111111101111111011111011111), expected: u32(30) }, + { input: u32Bits(0b11100010011110101101101110101111), expected: u32(31) }, + ]); + }); + +g.test('i32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`i32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + await run(t, builtin('firstLeadingBit'), [TypeI32], TypeI32, cfg, [ + // Zero + { input: i32Bits(0b00000000000000000000000000000000), expected: i32(-1) }, + + // Negative One + { input: i32Bits(0b11111111111111111111111111111111), expected: i32(-1) }, + + // One + { input: i32Bits(0b00000000000000000000000000000001), expected: i32(0) }, + + // Positive: 0's after leading 1 + { input: i32Bits(0b00000000000000000000000000000010), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000000000000100), expected: i32(2) }, + { input: i32Bits(0b00000000000000000000000000001000), expected: i32(3) }, + { input: i32Bits(0b00000000000000000000000000010000), expected: i32(4) }, + { input: i32Bits(0b00000000000000000000000000100000), expected: i32(5) }, + { input: i32Bits(0b00000000000000000000000001000000), expected: i32(6) }, + { input: i32Bits(0b00000000000000000000000010000000), expected: i32(7) }, + { input: i32Bits(0b00000000000000000000000100000000), expected: i32(8) }, + { input: i32Bits(0b00000000000000000000001000000000), expected: i32(9) }, + { input: i32Bits(0b00000000000000000000010000000000), expected: i32(10) }, + { input: i32Bits(0b00000000000000000000100000000000), expected: i32(11) }, + { input: i32Bits(0b00000000000000000001000000000000), expected: i32(12) }, + { input: i32Bits(0b00000000000000000010000000000000), expected: i32(13) }, + { input: i32Bits(0b00000000000000000100000000000000), expected: i32(14) }, + { input: i32Bits(0b00000000000000001000000000000000), expected: i32(15) }, + { input: i32Bits(0b00000000000000010000000000000000), expected: i32(16) }, + { input: i32Bits(0b00000000000000100000000000000000), expected: i32(17) }, + { input: i32Bits(0b00000000000001000000000000000000), expected: i32(18) }, + { input: i32Bits(0b00000000000010000000000000000000), expected: i32(19) }, + { input: i32Bits(0b00000000000100000000000000000000), expected: i32(20) }, + { input: i32Bits(0b00000000001000000000000000000000), expected: i32(21) }, + { input: i32Bits(0b00000000010000000000000000000000), expected: i32(22) }, + { input: i32Bits(0b00000000100000000000000000000000), expected: i32(23) }, + { input: i32Bits(0b00000001000000000000000000000000), expected: i32(24) }, + { input: i32Bits(0b00000010000000000000000000000000), expected: i32(25) }, + { input: i32Bits(0b00000100000000000000000000000000), expected: i32(26) }, + { input: i32Bits(0b00001000000000000000000000000000), expected: i32(27) }, + { input: i32Bits(0b00010000000000000000000000000000), expected: i32(28) }, + { input: i32Bits(0b00100000000000000000000000000000), expected: i32(29) }, + { input: i32Bits(0b01000000000000000000000000000000), expected: i32(30) }, + + // Negative: 0's after leading 0 + { input: i32Bits(0b11111111111111111111111111111110), expected: i32(0) }, + { input: i32Bits(0b11111111111111111111111111111100), expected: i32(1) }, + { input: i32Bits(0b11111111111111111111111111111000), expected: i32(2) }, + { input: i32Bits(0b11111111111111111111111111110000), expected: i32(3) }, + { input: i32Bits(0b11111111111111111111111111100000), expected: i32(4) }, + { input: i32Bits(0b11111111111111111111111111000000), expected: i32(5) }, + { input: i32Bits(0b11111111111111111111111110000000), expected: i32(6) }, + { input: i32Bits(0b11111111111111111111111100000000), expected: i32(7) }, + { input: i32Bits(0b11111111111111111111111000000000), expected: i32(8) }, + { input: i32Bits(0b11111111111111111111110000000000), expected: i32(9) }, + { input: i32Bits(0b11111111111111111111100000000000), expected: i32(10) }, + { input: i32Bits(0b11111111111111111111000000000000), expected: i32(11) }, + { input: i32Bits(0b11111111111111111110000000000000), expected: i32(12) }, + { input: i32Bits(0b11111111111111111100000000000000), expected: i32(13) }, + { input: i32Bits(0b11111111111111111000000000000000), expected: i32(14) }, + { input: i32Bits(0b11111111111111110000000000000000), expected: i32(15) }, + { input: i32Bits(0b11111111111111100000000000000000), expected: i32(16) }, + { input: i32Bits(0b11111111111111000000000000000000), expected: i32(17) }, + { input: i32Bits(0b11111111111110000000000000000000), expected: i32(18) }, + { input: i32Bits(0b11111111111100000000000000000000), expected: i32(19) }, + { input: i32Bits(0b11111111111000000000000000000000), expected: i32(20) }, + { input: i32Bits(0b11111111110000000000000000000000), expected: i32(21) }, + { input: i32Bits(0b11111111100000000000000000000000), expected: i32(22) }, + { input: i32Bits(0b11111111000000000000000000000000), expected: i32(23) }, + { input: i32Bits(0b11111110000000000000000000000000), expected: i32(24) }, + { input: i32Bits(0b11111100000000000000000000000000), expected: i32(25) }, + { input: i32Bits(0b11111000000000000000000000000000), expected: i32(26) }, + { input: i32Bits(0b11110000000000000000000000000000), expected: i32(27) }, + { input: i32Bits(0b11100000000000000000000000000000), expected: i32(28) }, + { input: i32Bits(0b11000000000000000000000000000000), expected: i32(29) }, + { input: i32Bits(0b10000000000000000000000000000000), expected: i32(30) }, + + // Positive: 1's after leading 1 + { input: i32Bits(0b00000000000000000000000000000011), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000000000000111), expected: i32(2) }, + { input: i32Bits(0b00000000000000000000000000001111), expected: i32(3) }, + { input: i32Bits(0b00000000000000000000000000011111), expected: i32(4) }, + { input: i32Bits(0b00000000000000000000000000111111), expected: i32(5) }, + { input: i32Bits(0b00000000000000000000000001111111), expected: i32(6) }, + { input: i32Bits(0b00000000000000000000000011111111), expected: i32(7) }, + { input: i32Bits(0b00000000000000000000000111111111), expected: i32(8) }, + { input: i32Bits(0b00000000000000000000001111111111), expected: i32(9) }, + { input: i32Bits(0b00000000000000000000011111111111), expected: i32(10) }, + { input: i32Bits(0b00000000000000000000111111111111), expected: i32(11) }, + { input: i32Bits(0b00000000000000000001111111111111), expected: i32(12) }, + { input: i32Bits(0b00000000000000000011111111111111), expected: i32(13) }, + { input: i32Bits(0b00000000000000000111111111111111), expected: i32(14) }, + { input: i32Bits(0b00000000000000001111111111111111), expected: i32(15) }, + { input: i32Bits(0b00000000000000011111111111111111), expected: i32(16) }, + { input: i32Bits(0b00000000000000111111111111111111), expected: i32(17) }, + { input: i32Bits(0b00000000000001111111111111111111), expected: i32(18) }, + { input: i32Bits(0b00000000000011111111111111111111), expected: i32(19) }, + { input: i32Bits(0b00000000000111111111111111111111), expected: i32(20) }, + { input: i32Bits(0b00000000001111111111111111111111), expected: i32(21) }, + { input: i32Bits(0b00000000011111111111111111111111), expected: i32(22) }, + { input: i32Bits(0b00000000111111111111111111111111), expected: i32(23) }, + { input: i32Bits(0b00000001111111111111111111111111), expected: i32(24) }, + { input: i32Bits(0b00000011111111111111111111111111), expected: i32(25) }, + { input: i32Bits(0b00000111111111111111111111111111), expected: i32(26) }, + { input: i32Bits(0b00001111111111111111111111111111), expected: i32(27) }, + { input: i32Bits(0b00011111111111111111111111111111), expected: i32(28) }, + { input: i32Bits(0b00111111111111111111111111111111), expected: i32(29) }, + { input: i32Bits(0b01111111111111111111111111111111), expected: i32(30) }, + + // Negative: 1's after leading 0 + { input: i32Bits(0b11111111111111111111111111111101), expected: i32(1) }, + { input: i32Bits(0b11111111111111111111111111111011), expected: i32(2) }, + { input: i32Bits(0b11111111111111111111111111110111), expected: i32(3) }, + { input: i32Bits(0b11111111111111111111111111101111), expected: i32(4) }, + { input: i32Bits(0b11111111111111111111111111011111), expected: i32(5) }, + { input: i32Bits(0b11111111111111111111111110111111), expected: i32(6) }, + { input: i32Bits(0b11111111111111111111111101111111), expected: i32(7) }, + { input: i32Bits(0b11111111111111111111111011111111), expected: i32(8) }, + { input: i32Bits(0b11111111111111111111110111111111), expected: i32(9) }, + { input: i32Bits(0b11111111111111111111101111111111), expected: i32(10) }, + { input: i32Bits(0b11111111111111111111011111111111), expected: i32(11) }, + { input: i32Bits(0b11111111111111111110111111111111), expected: i32(12) }, + { input: i32Bits(0b11111111111111111101111111111111), expected: i32(13) }, + { input: i32Bits(0b11111111111111111011111111111111), expected: i32(14) }, + { input: i32Bits(0b11111111111111110111111111111111), expected: i32(15) }, + { input: i32Bits(0b11111111111111101111111111111111), expected: i32(16) }, + { input: i32Bits(0b11111111111111011111111111111111), expected: i32(17) }, + { input: i32Bits(0b11111111111110111111111111111111), expected: i32(18) }, + { input: i32Bits(0b11111111111101111111111111111111), expected: i32(19) }, + { input: i32Bits(0b11111111111011111111111111111111), expected: i32(20) }, + { input: i32Bits(0b11111111110111111111111111111111), expected: i32(21) }, + { input: i32Bits(0b11111111101111111111111111111111), expected: i32(22) }, + { input: i32Bits(0b11111111011111111111111111111111), expected: i32(23) }, + { input: i32Bits(0b11111110111111111111111111111111), expected: i32(24) }, + { input: i32Bits(0b11111101111111111111111111111111), expected: i32(25) }, + { input: i32Bits(0b11111011111111111111111111111111), expected: i32(26) }, + { input: i32Bits(0b11110111111111111111111111111111), expected: i32(27) }, + { input: i32Bits(0b11101111111111111111111111111111), expected: i32(28) }, + { input: i32Bits(0b11011111111111111111111111111111), expected: i32(29) }, + { input: i32Bits(0b10111111111111111111111111111111), expected: i32(30) }, + + // Positive: random after leading 1 + { input: i32Bits(0b00000000000000000000000000000110), expected: i32(2) }, + { input: i32Bits(0b00000000000000000000000000001101), expected: i32(3) }, + { input: i32Bits(0b00000000000000000000000000011101), expected: i32(4) }, + { input: i32Bits(0b00000000000000000000000000111001), expected: i32(5) }, + { input: i32Bits(0b00000000000000000000000001101111), expected: i32(6) }, + { input: i32Bits(0b00000000000000000000000011111111), expected: i32(7) }, + { input: i32Bits(0b00000000000000000000000111101111), expected: i32(8) }, + { input: i32Bits(0b00000000000000000000001111111111), expected: i32(9) }, + { input: i32Bits(0b00000000000000000000011111110001), expected: i32(10) }, + { input: i32Bits(0b00000000000000000000111011011101), expected: i32(11) }, + { input: i32Bits(0b00000000000000000001101101111111), expected: i32(12) }, + { input: i32Bits(0b00000000000000000011111111011111), expected: i32(13) }, + { input: i32Bits(0b00000000000000000101111001110101), expected: i32(14) }, + { input: i32Bits(0b00000000000000001101111011110111), expected: i32(15) }, + { input: i32Bits(0b00000000000000011111111111110011), expected: i32(16) }, + { input: i32Bits(0b00000000000000111111111110111111), expected: i32(17) }, + { input: i32Bits(0b00000000000001111111011111111111), expected: i32(18) }, + { input: i32Bits(0b00000000000011111111111111111111), expected: i32(19) }, + { input: i32Bits(0b00000000000111110101011110111111), expected: i32(20) }, + { input: i32Bits(0b00000000001111101111111111110111), expected: i32(21) }, + { input: i32Bits(0b00000000011111111111010000101111), expected: i32(22) }, + { input: i32Bits(0b00000000111111111111001111111011), expected: i32(23) }, + { input: i32Bits(0b00000001111111011111101111111111), expected: i32(24) }, + { input: i32Bits(0b00000011101011111011110111111011), expected: i32(25) }, + { input: i32Bits(0b00000111111110111111111111111111), expected: i32(26) }, + { input: i32Bits(0b00001111000000011011011110111111), expected: i32(27) }, + { input: i32Bits(0b00011110101111011111111111111111), expected: i32(28) }, + { input: i32Bits(0b00110110111111100111111110111101), expected: i32(29) }, + { input: i32Bits(0b01010111111101111111011111011111), expected: i32(30) }, + + // Negative: random after leading 0 + { input: i32Bits(0b11111111111111111111111111111010), expected: i32(2) }, + { input: i32Bits(0b11111111111111111111111111110110), expected: i32(3) }, + { input: i32Bits(0b11111111111111111111111111101101), expected: i32(4) }, + { input: i32Bits(0b11111111111111111111111111011101), expected: i32(5) }, + { input: i32Bits(0b11111111111111111111111110111001), expected: i32(6) }, + { input: i32Bits(0b11111111111111111111111101101111), expected: i32(7) }, + { input: i32Bits(0b11111111111111111111111011111111), expected: i32(8) }, + { input: i32Bits(0b11111111111111111111110111101111), expected: i32(9) }, + { input: i32Bits(0b11111111111111111111101111111111), expected: i32(10) }, + { input: i32Bits(0b11111111111111111111011111110001), expected: i32(11) }, + { input: i32Bits(0b11111111111111111110111011011101), expected: i32(12) }, + { input: i32Bits(0b11111111111111111101101101111111), expected: i32(13) }, + { input: i32Bits(0b11111111111111111011111111011111), expected: i32(14) }, + { input: i32Bits(0b11111111111111110101111001110101), expected: i32(15) }, + { input: i32Bits(0b11111111111111101101111011110111), expected: i32(16) }, + { input: i32Bits(0b11111111111111011111111111110011), expected: i32(17) }, + { input: i32Bits(0b11111111111110111111111110111111), expected: i32(18) }, + { input: i32Bits(0b11111111111101111111011111111111), expected: i32(19) }, + { input: i32Bits(0b11111111111011111111111111111111), expected: i32(20) }, + { input: i32Bits(0b11111111110111110101011110111111), expected: i32(21) }, + { input: i32Bits(0b11111111101111101111111111110111), expected: i32(22) }, + { input: i32Bits(0b11111111011111111111010000101111), expected: i32(23) }, + { input: i32Bits(0b11111110111111111111001111111011), expected: i32(24) }, + { input: i32Bits(0b11111101111111011111101111111111), expected: i32(25) }, + { input: i32Bits(0b11111011101011111011110111111011), expected: i32(26) }, + { input: i32Bits(0b11110111111110111111111111111111), expected: i32(27) }, + { input: i32Bits(0b11101111000000011011011110111111), expected: i32(28) }, + { input: i32Bits(0b11011110101111011111111111111111), expected: i32(29) }, + { input: i32Bits(0b10110110111111100111111110111101), expected: i32(30) }, + ]); + }); 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 new file mode 100644 index 0000000000..5c65f59d28 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts @@ -0,0 +1,250 @@ +export const description = ` +Execution tests for the 'firstTrailingBit' builtin function + +S is i32, u32 +T is S or vecN<S> +@const fn firstTrailingBit(e: T ) -> T +For scalar T, the result is: T(-1) if e is zero. +Otherwise the position of the least significant 1 bit in e. +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 { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('u32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`u32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + await run(t, builtin('firstTrailingBit'), [TypeU32], TypeU32, cfg, [ + // Zero + { input: u32Bits(0b00000000000000000000000000000000), expected: u32(-1) }, + + // High bit + { input: u32Bits(0b10000000000000000000000000000000), expected: u32(31) }, + + // 0's before trailing 1 + { input: u32Bits(0b00000000000000000000000000000001), expected: u32(0) }, + { input: u32Bits(0b00000000000000000000000000000010), expected: u32(1) }, + { input: u32Bits(0b00000000000000000000000000000100), expected: u32(2) }, + { input: u32Bits(0b00000000000000000000000000001000), expected: u32(3) }, + { input: u32Bits(0b00000000000000000000000000010000), expected: u32(4) }, + { input: u32Bits(0b00000000000000000000000000100000), expected: u32(5) }, + { input: u32Bits(0b00000000000000000000000001000000), expected: u32(6) }, + { input: u32Bits(0b00000000000000000000000010000000), expected: u32(7) }, + { input: u32Bits(0b00000000000000000000000100000000), expected: u32(8) }, + { input: u32Bits(0b00000000000000000000001000000000), expected: u32(9) }, + { input: u32Bits(0b00000000000000000000010000000000), expected: u32(10) }, + { input: u32Bits(0b00000000000000000000100000000000), expected: u32(11) }, + { input: u32Bits(0b00000000000000000001000000000000), expected: u32(12) }, + { input: u32Bits(0b00000000000000000010000000000000), expected: u32(13) }, + { input: u32Bits(0b00000000000000000100000000000000), expected: u32(14) }, + { input: u32Bits(0b00000000000000001000000000000000), expected: u32(15) }, + { input: u32Bits(0b00000000000000010000000000000000), expected: u32(16) }, + { input: u32Bits(0b00000000000000100000000000000000), expected: u32(17) }, + { input: u32Bits(0b00000000000001000000000000000000), expected: u32(18) }, + { input: u32Bits(0b00000000000010000000000000000000), expected: u32(19) }, + { input: u32Bits(0b00000000000100000000000000000000), expected: u32(20) }, + { input: u32Bits(0b00000000001000000000000000000000), expected: u32(21) }, + { input: u32Bits(0b00000000010000000000000000000000), expected: u32(22) }, + { input: u32Bits(0b00000000100000000000000000000000), expected: u32(23) }, + { input: u32Bits(0b00000001000000000000000000000000), expected: u32(24) }, + { input: u32Bits(0b00000010000000000000000000000000), expected: u32(25) }, + { input: u32Bits(0b00000100000000000000000000000000), expected: u32(26) }, + { input: u32Bits(0b00001000000000000000000000000000), expected: u32(27) }, + { input: u32Bits(0b00010000000000000000000000000000), expected: u32(28) }, + { input: u32Bits(0b00100000000000000000000000000000), expected: u32(29) }, + { input: u32Bits(0b01000000000000000000000000000000), expected: u32(30) }, + + // 1's before trailing 1 + { input: u32Bits(0b11111111111111111111111111111111), expected: u32(0) }, + { input: u32Bits(0b11111111111111111111111111111110), expected: u32(1) }, + { input: u32Bits(0b11111111111111111111111111111100), expected: u32(2) }, + { input: u32Bits(0b11111111111111111111111111111000), expected: u32(3) }, + { input: u32Bits(0b11111111111111111111111111110000), expected: u32(4) }, + { input: u32Bits(0b11111111111111111111111111100000), expected: u32(5) }, + { input: u32Bits(0b11111111111111111111111111000000), expected: u32(6) }, + { input: u32Bits(0b11111111111111111111111110000000), expected: u32(7) }, + { input: u32Bits(0b11111111111111111111111100000000), expected: u32(8) }, + { input: u32Bits(0b11111111111111111111111000000000), expected: u32(9) }, + { input: u32Bits(0b11111111111111111111110000000000), expected: u32(10) }, + { input: u32Bits(0b11111111111111111111100000000000), expected: u32(11) }, + { input: u32Bits(0b11111111111111111111000000000000), expected: u32(12) }, + { input: u32Bits(0b11111111111111111110000000000000), expected: u32(13) }, + { input: u32Bits(0b11111111111111111100000000000000), expected: u32(14) }, + { input: u32Bits(0b11111111111111111000000000000000), expected: u32(15) }, + { input: u32Bits(0b11111111111111110000000000000000), expected: u32(16) }, + { input: u32Bits(0b11111111111111100000000000000000), expected: u32(17) }, + { input: u32Bits(0b11111111111111000000000000000000), expected: u32(18) }, + { input: u32Bits(0b11111111111110000000000000000000), expected: u32(19) }, + { input: u32Bits(0b11111111111100000000000000000000), expected: u32(20) }, + { input: u32Bits(0b11111111111000000000000000000000), expected: u32(21) }, + { input: u32Bits(0b11111111110000000000000000000000), expected: u32(22) }, + { input: u32Bits(0b11111111100000000000000000000000), expected: u32(23) }, + { input: u32Bits(0b11111111000000000000000000000000), expected: u32(24) }, + { input: u32Bits(0b11111110000000000000000000000000), expected: u32(25) }, + { input: u32Bits(0b11111100000000000000000000000000), expected: u32(26) }, + { input: u32Bits(0b11111000000000000000000000000000), expected: u32(27) }, + { input: u32Bits(0b11110000000000000000000000000000), expected: u32(28) }, + { input: u32Bits(0b11100000000000000000000000000000), expected: u32(29) }, + { input: u32Bits(0b11000000000000000000000000000000), expected: u32(30) }, + + // random before trailing 1 + { input: u32Bits(0b11110000001111111101111010001111), expected: u32(0) }, + { input: u32Bits(0b11011110111111100101110011110010), expected: u32(1) }, + { input: u32Bits(0b11110111011011111111010000111100), expected: u32(2) }, + { input: u32Bits(0b11010011011101111111010011101000), expected: u32(3) }, + { input: u32Bits(0b11010111110111110001111110110000), expected: u32(4) }, + { input: u32Bits(0b11111101111101111110101111100000), expected: u32(5) }, + { input: u32Bits(0b11111001111011111001111011000000), expected: u32(6) }, + { input: u32Bits(0b11001110110111110111111010000000), expected: u32(7) }, + { input: u32Bits(0b11101111011111101110101100000000), expected: u32(8) }, + { input: u32Bits(0b11111101111011111111111000000000), expected: u32(9) }, + { input: u32Bits(0b10011111011101110110110000000000), expected: u32(10) }, + { input: u32Bits(0b11111111101101111011100000000000), expected: u32(11) }, + { input: u32Bits(0b11111011010110111011000000000000), expected: u32(12) }, + { input: u32Bits(0b00111101010000111010000000000000), expected: u32(13) }, + { input: u32Bits(0b11111011110001101100000000000000), expected: u32(14) }, + { input: u32Bits(0b10111111010111111000000000000000), expected: u32(15) }, + { input: u32Bits(0b11011101111010110000000000000000), expected: u32(16) }, + { input: u32Bits(0b01110100110110100000000000000000), expected: u32(17) }, + { input: u32Bits(0b11100111001011000000000000000000), expected: u32(18) }, + { input: u32Bits(0b11111001110110000000000000000000), expected: u32(19) }, + { input: u32Bits(0b00110100100100000000000000000000), expected: u32(20) }, + { input: u32Bits(0b11111010011000000000000000000000), expected: u32(21) }, + { input: u32Bits(0b00000010110000000000000000000000), expected: u32(22) }, + { input: u32Bits(0b11100111100000000000000000000000), expected: u32(23) }, + { input: u32Bits(0b00101101000000000000000000000000), expected: u32(24) }, + { input: u32Bits(0b11011010000000000000000000000000), expected: u32(25) }, + { input: u32Bits(0b11010100000000000000000000000000), expected: u32(26) }, + { input: u32Bits(0b10111000000000000000000000000000), expected: u32(27) }, + { input: u32Bits(0b01110000000000000000000000000000), expected: u32(28) }, + { input: u32Bits(0b10100000000000000000000000000000), expected: u32(29) }, + ]); + }); + +g.test('i32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`i32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + await run(t, builtin('firstTrailingBit'), [TypeI32], TypeI32, cfg, [ + // Zero + { input: i32Bits(0b00000000000000000000000000000000), expected: i32(-1) }, + + // High bit + { input: i32Bits(0b10000000000000000000000000000000), expected: i32(31) }, + + // 0's before trailing 1 + { input: i32Bits(0b00000000000000000000000000000001), expected: i32(0) }, + { input: i32Bits(0b00000000000000000000000000000010), expected: i32(1) }, + { input: i32Bits(0b00000000000000000000000000000100), expected: i32(2) }, + { input: i32Bits(0b00000000000000000000000000001000), expected: i32(3) }, + { input: i32Bits(0b00000000000000000000000000010000), expected: i32(4) }, + { input: i32Bits(0b00000000000000000000000000100000), expected: i32(5) }, + { input: i32Bits(0b00000000000000000000000001000000), expected: i32(6) }, + { input: i32Bits(0b00000000000000000000000010000000), expected: i32(7) }, + { input: i32Bits(0b00000000000000000000000100000000), expected: i32(8) }, + { input: i32Bits(0b00000000000000000000001000000000), expected: i32(9) }, + { input: i32Bits(0b00000000000000000000010000000000), expected: i32(10) }, + { input: i32Bits(0b00000000000000000000100000000000), expected: i32(11) }, + { input: i32Bits(0b00000000000000000001000000000000), expected: i32(12) }, + { input: i32Bits(0b00000000000000000010000000000000), expected: i32(13) }, + { input: i32Bits(0b00000000000000000100000000000000), expected: i32(14) }, + { input: i32Bits(0b00000000000000001000000000000000), expected: i32(15) }, + { input: i32Bits(0b00000000000000010000000000000000), expected: i32(16) }, + { input: i32Bits(0b00000000000000100000000000000000), expected: i32(17) }, + { input: i32Bits(0b00000000000001000000000000000000), expected: i32(18) }, + { input: i32Bits(0b00000000000010000000000000000000), expected: i32(19) }, + { input: i32Bits(0b00000000000100000000000000000000), expected: i32(20) }, + { input: i32Bits(0b00000000001000000000000000000000), expected: i32(21) }, + { input: i32Bits(0b00000000010000000000000000000000), expected: i32(22) }, + { input: i32Bits(0b00000000100000000000000000000000), expected: i32(23) }, + { input: i32Bits(0b00000001000000000000000000000000), expected: i32(24) }, + { input: i32Bits(0b00000010000000000000000000000000), expected: i32(25) }, + { input: i32Bits(0b00000100000000000000000000000000), expected: i32(26) }, + { input: i32Bits(0b00001000000000000000000000000000), expected: i32(27) }, + { input: i32Bits(0b00010000000000000000000000000000), expected: i32(28) }, + { input: i32Bits(0b00100000000000000000000000000000), expected: i32(29) }, + { input: i32Bits(0b01000000000000000000000000000000), expected: i32(30) }, + + // 1's before trailing 1 + { input: i32Bits(0b11111111111111111111111111111111), expected: i32(0) }, + { input: i32Bits(0b11111111111111111111111111111110), expected: i32(1) }, + { input: i32Bits(0b11111111111111111111111111111100), expected: i32(2) }, + { input: i32Bits(0b11111111111111111111111111111000), expected: i32(3) }, + { input: i32Bits(0b11111111111111111111111111110000), expected: i32(4) }, + { input: i32Bits(0b11111111111111111111111111100000), expected: i32(5) }, + { input: i32Bits(0b11111111111111111111111111000000), expected: i32(6) }, + { input: i32Bits(0b11111111111111111111111110000000), expected: i32(7) }, + { input: i32Bits(0b11111111111111111111111100000000), expected: i32(8) }, + { input: i32Bits(0b11111111111111111111111000000000), expected: i32(9) }, + { input: i32Bits(0b11111111111111111111110000000000), expected: i32(10) }, + { input: i32Bits(0b11111111111111111111100000000000), expected: i32(11) }, + { input: i32Bits(0b11111111111111111111000000000000), expected: i32(12) }, + { input: i32Bits(0b11111111111111111110000000000000), expected: i32(13) }, + { input: i32Bits(0b11111111111111111100000000000000), expected: i32(14) }, + { input: i32Bits(0b11111111111111111000000000000000), expected: i32(15) }, + { input: i32Bits(0b11111111111111110000000000000000), expected: i32(16) }, + { input: i32Bits(0b11111111111111100000000000000000), expected: i32(17) }, + { input: i32Bits(0b11111111111111000000000000000000), expected: i32(18) }, + { input: i32Bits(0b11111111111110000000000000000000), expected: i32(19) }, + { input: i32Bits(0b11111111111100000000000000000000), expected: i32(20) }, + { input: i32Bits(0b11111111111000000000000000000000), expected: i32(21) }, + { input: i32Bits(0b11111111110000000000000000000000), expected: i32(22) }, + { input: i32Bits(0b11111111100000000000000000000000), expected: i32(23) }, + { input: i32Bits(0b11111111000000000000000000000000), expected: i32(24) }, + { input: i32Bits(0b11111110000000000000000000000000), expected: i32(25) }, + { input: i32Bits(0b11111100000000000000000000000000), expected: i32(26) }, + { input: i32Bits(0b11111000000000000000000000000000), expected: i32(27) }, + { input: i32Bits(0b11110000000000000000000000000000), expected: i32(28) }, + { input: i32Bits(0b11100000000000000000000000000000), expected: i32(29) }, + { input: i32Bits(0b11000000000000000000000000000000), expected: i32(30) }, + + // random before trailing 1 + { input: i32Bits(0b11110000001111111101111010001111), expected: i32(0) }, + { input: i32Bits(0b11011110111111100101110011110010), expected: i32(1) }, + { input: i32Bits(0b11110111011011111111010000111100), expected: i32(2) }, + { input: i32Bits(0b11010011011101111111010011101000), expected: i32(3) }, + { input: i32Bits(0b11010111110111110001111110110000), expected: i32(4) }, + { input: i32Bits(0b11111101111101111110101111100000), expected: i32(5) }, + { input: i32Bits(0b11111001111011111001111011000000), expected: i32(6) }, + { input: i32Bits(0b11001110110111110111111010000000), expected: i32(7) }, + { input: i32Bits(0b11101111011111101110101100000000), expected: i32(8) }, + { input: i32Bits(0b11111101111011111111111000000000), expected: i32(9) }, + { input: i32Bits(0b10011111011101110110110000000000), expected: i32(10) }, + { input: i32Bits(0b11111111101101111011100000000000), expected: i32(11) }, + { input: i32Bits(0b11111011010110111011000000000000), expected: i32(12) }, + { input: i32Bits(0b00111101010000111010000000000000), expected: i32(13) }, + { input: i32Bits(0b11111011110001101100000000000000), expected: i32(14) }, + { input: i32Bits(0b10111111010111111000000000000000), expected: i32(15) }, + { input: i32Bits(0b11011101111010110000000000000000), expected: i32(16) }, + { input: i32Bits(0b01110100110110100000000000000000), expected: i32(17) }, + { input: i32Bits(0b11100111001011000000000000000000), expected: i32(18) }, + { input: i32Bits(0b11111001110110000000000000000000), expected: i32(19) }, + { input: i32Bits(0b00110100100100000000000000000000), expected: i32(20) }, + { input: i32Bits(0b11111010011000000000000000000000), expected: i32(21) }, + { input: i32Bits(0b00000010110000000000000000000000), expected: i32(22) }, + { input: i32Bits(0b11100111100000000000000000000000), expected: i32(23) }, + { input: i32Bits(0b00101101000000000000000000000000), expected: i32(24) }, + { input: i32Bits(0b11011010000000000000000000000000), expected: i32(25) }, + { input: i32Bits(0b11010100000000000000000000000000), expected: i32(26) }, + { input: i32Bits(0b10111000000000000000000000000000), expected: i32(27) }, + { input: i32Bits(0b01110000000000000000000000000000), expected: i32(28) }, + { input: i32Bits(0b10100000000000000000000000000000), expected: i32(29) }, + ]); + }); 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 new file mode 100644 index 0000000000..873a6772c3 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts @@ -0,0 +1,96 @@ +export const description = ` +Execution tests for the 'floor' builtin function + +S is AbstractFloat, 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. +`; + +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 { allInputSources, onlyConstInputSource, run } from '../../expression.js'; + +import { abstractBuiltin, builtin } from './builtin.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`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstract'); + await run(t, abstractBuiltin('floor'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases); + }); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('floor'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('floor'), [TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..701f9d7ca9 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts @@ -0,0 +1,113 @@ +export const description = ` +Execution tests for the 'fma' builtin function + +S is AbstractFloat, 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. +`; + +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 { allInputSources, onlyConstInputSource, run } from '../../expression.js'; + +import { abstractBuiltin, builtin } from './builtin.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`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractBuiltin('fma'), + [TypeAbstractFloat, TypeAbstractFloat, TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-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); + }); 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 new file mode 100644 index 0000000000..44ea31fde2 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts @@ -0,0 +1,103 @@ +export const description = ` +Execution tests for the 'fract' builtin function + +S is AbstractFloat, 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). +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('fract'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('fract'), [TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..ffe672b08c --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts @@ -0,0 +1,475 @@ +export const description = ` +Execution tests for the 'frexp' builtin function + +S is f32 or f16 +T is S or vecN<S> + +@const fn frexp(e: T) -> result_struct + +Splits e into a significand and exponent of the form significand * 2^exponent. +Returns the result_struct for the appropriate overload. + + +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 { + allInputSources, + basicExpressionBuilder, + Case, + run, + ShaderBuilder, +} from '../../expression.js'; + +export const g = makeTestGroup(GPUTest); + +/* @returns an ShaderBuilder that evaluates frexp and returns .fract from the result structure */ +function fractBuilder(): ShaderBuilder { + return basicExpressionBuilder(value => `frexp(${value}).fract`); +} + +/* @returns an ShaderBuilder that evaluates frexp and returns .exp from the result structure */ +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; + }); + + return { input: toInput(v), expected: toOutput(fs) }; +} + +/* @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; + }); + + return { input: toInput(v), expected: toOutput(fs) }; +} + +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')); + }, +}); + +g.test('f32_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is f32 + +struct __frexp_result_f32 { + fract : f32, // fract part + exp : i32 // exponent part +} +` + ) + .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); + }); + +g.test('f32_exp') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is f32 + +struct __frexp_result_f32 { + fract : f32, // fract part + exp : i32 // exponent part +} +` + ) + .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); + }); + +g.test('f32_vec2_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec2<f32> + +struct __frexp_result_vec2_f32 { + fract : vec2<f32>, // fract part + exp : vec2<i32> // exponent part +} +` + ) + .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); + }); + +g.test('f32_vec2_exp') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec2<f32> + +struct __frexp_result_vec2_f32 { + fract : vec2<f32>, // fractional part + exp : vec2<i32> // exponent part +} +` + ) + .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); + }); + +g.test('f32_vec3_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec3<f32> + +struct __frexp_result_vec3_f32 { + fract : vec3<f32>, // fractional part + exp : vec3<i32> // exponent part +} +` + ) + .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); + }); + +g.test('f32_vec3_exp') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec3<f32> + +struct __frexp_result_vec3_f32 { + fract : vec3<f32>, // fractional part + exp : vec3<i32> // exponent part +} +` + ) + .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); + }); + +g.test('f32_vec4_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec4<f32> + +struct __frexp_result_vec4_f32 { + fract : vec4<f32>, // fractional part + exp : vec4<i32> // exponent part +} +` + ) + .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); + }); + +g.test('f32_vec4_exp') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec4<f32> + +struct __frexp_result_vec4_f32 { + fract : vec4<f32>, // fractional part + exp : vec4<i32> // exponent part +} +` + ) + .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); + }); + +g.test('f16_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is f16 + +struct __frexp_result_f16 { + fract : f16, // fract part + exp : i32 // exponent part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16_fract'); + await run(t, fractBuilder(), [TypeF16], TypeF16, t.params, cases); + }); + +g.test('f16_exp') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is f16 + +struct __frexp_result_f16 { + fract : f16, // fract part + exp : i32 // exponent part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16_exp'); + await run(t, expBuilder(), [TypeF16], TypeI32, t.params, cases); + }); + +g.test('f16_vec2_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec2<f16> + +struct __frexp_result_vec2_f16 { + fract : vec2<f16>, // fract part + exp : vec2<i32> // exponent part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); + +g.test('f16_vec2_exp') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec2<f16> + +struct __frexp_result_vec2_f16 { + fract : vec2<f16>, // fractional part + exp : vec2<i32> // exponent part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); + +g.test('f16_vec3_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec3<f16> + +struct __frexp_result_vec3_f16 { + fract : vec3<f16>, // fractional part + exp : vec3<i32> // exponent part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); + +g.test('f16_vec3_exp') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec3<f16> + +struct __frexp_result_vec3_f16 { + fract : vec3<f16>, // fractional part + exp : vec3<i32> // exponent part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); + +g.test('f16_vec4_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec4<f16> + +struct __frexp_result_vec4_f16 { + fract : vec4<f16>, // fractional part + exp : vec4<i32> // exponent part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); + +g.test('f16_vec4_exp') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec4<f16> + +struct __frexp_result_vec4_f16 { + fract : vec4<f16>, // fractional part + exp : vec4<i32> // exponent part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidth.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidth.spec.ts new file mode 100644 index 0000000000..7c6f0232a9 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidth.spec.ts @@ -0,0 +1,21 @@ +export const description = ` +Execution tests for the 'fwidth' builtin function + +T is f32 or vecN<f32> +fn fwidth(e:T) ->T +Returns abs(dpdx(e)) + abs(dpdy(e)). +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { allInputSources } from '../../expression.js'; + +export const g = makeTestGroup(GPUTest); + +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) + ) + .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthCoarse.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthCoarse.spec.ts new file mode 100644 index 0000000000..9f93237934 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthCoarse.spec.ts @@ -0,0 +1,21 @@ +export const description = ` +Execution tests for the 'fwidthCoarse' builtin function + +T is f32 or vecN<f32> +fn fwidthCoarse(e:T) ->T +Returns abs(dpdxCoarse(e)) + abs(dpdyCoarse(e)). +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { allInputSources } from '../../expression.js'; + +export const g = makeTestGroup(GPUTest); + +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) + ) + .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthFine.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthFine.spec.ts new file mode 100644 index 0000000000..b08c293228 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthFine.spec.ts @@ -0,0 +1,21 @@ +export const description = ` +Execution tests for the 'fwidthFine' builtin function + +T is f32 or vecN<f32> +fn fwidthFine(e:T) ->T +Returns abs(dpdxFine(e)) + abs(dpdyFine(e)). +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { allInputSources } from '../../expression.js'; + +export const g = makeTestGroup(GPUTest); + +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) + ) + .unimplemented(); 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 new file mode 100644 index 0000000000..1068e76252 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts @@ -0,0 +1,386 @@ +export const description = ` +Execution tests for the 'insertBits' builtin function + +S is i32 or u32 +T is S or vecN<S> +@const fn insertBits(e: T, newbits:T, offset: u32, count: u32) -> T Sets bits in an integer. + +When T is a scalar type, then: + w is the bit width of T + o = min(offset,w) + c = min(count, w - o) + +The result is e if c is 0. +Otherwise, bits o..o+c-1 of the result are copied from bits 0..c-1 of newbits. +Other bits of the result are copied from e. +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 { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('integer') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`integer tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('signed', [false, true]) + .combine('width', [1, 2, 3, 4]) + ) + .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 V = (x: number, y?: number, z?: number, w?: number) => { + y = y === undefined ? x : y; + z = z === undefined ? x : z; + w = w === undefined ? x : w; + + if (t.params.signed) { + switch (t.params.width) { + case 1: + return i32Bits(x); + case 2: + return vec2(i32Bits(x), i32Bits(y)); + case 3: + return vec3(i32Bits(x), i32Bits(y), i32Bits(z)); + default: + return vec4(i32Bits(x), i32Bits(y), i32Bits(z), i32Bits(w)); + } + } else { + switch (t.params.width) { + case 1: + return u32Bits(x); + case 2: + return vec2(u32Bits(x), u32Bits(y)); + case 3: + return vec3(u32Bits(x), u32Bits(y), u32Bits(z)); + default: + return vec4(u32Bits(x), u32Bits(y), u32Bits(z), u32Bits(w)); + } + } + }; + + const all_1 = V(0b11111111111111111111111111111111); + const all_0 = V(0b00000000000000000000000000000000); + const low_1 = V(0b00000000000000000000000000000001); + const low_0 = V(0b11111111111111111111111111111110); + const high_1 = V(0b10000000000000000000000000000000); + const high_0 = V(0b01111111111111111111111111111111); + const pattern = V( + 0b10001001010100100010010100100010, + 0b11001110001100111000110011100011, + 0b10101010101010101010101010101010, + 0b01010101010101010101010101010101 + ); + + const cases = [ + { input: [all_0, all_0, u32(0), u32(32)], expected: all_0 }, + { input: [all_0, all_0, u32(1), u32(10)], expected: all_0 }, + { input: [all_0, all_0, u32(2), u32(5)], expected: all_0 }, + { input: [all_0, all_0, u32(0), u32(1)], expected: all_0 }, + { input: [all_0, all_0, u32(31), u32(1)], expected: all_0 }, + + { input: [all_0, all_1, u32(0), u32(32)], expected: all_1 }, + { input: [all_1, all_0, u32(0), u32(32)], expected: all_0 }, + { input: [all_0, all_1, u32(0), u32(1)], expected: low_1 }, + { input: [all_1, all_0, u32(0), u32(1)], expected: low_0 }, + { input: [all_0, all_1, u32(31), u32(1)], expected: high_1 }, + { input: [all_1, all_0, u32(31), u32(1)], expected: high_0 }, + { input: [all_0, all_1, u32(1), u32(10)], expected: V(0b00000000000000000000011111111110) }, + { input: [all_1, all_0, u32(1), u32(10)], expected: V(0b11111111111111111111100000000001) }, + { input: [all_0, all_1, u32(2), u32(5)], expected: V(0b00000000000000000000000001111100) }, + { input: [all_1, all_0, u32(2), u32(5)], expected: V(0b11111111111111111111111110000011) }, + + // Patterns + { input: [all_0, pattern, u32(0), u32(32)], expected: pattern }, + { input: [all_1, pattern, u32(0), u32(32)], expected: pattern }, + { + input: [all_0, pattern, u32(1), u32(31)], + expected: V( + 0b00010010101001000100101001000100, + 0b10011100011001110001100111000110, + 0b01010101010101010101010101010100, + 0b10101010101010101010101010101010 + ), + }, + { + input: [all_1, pattern, u32(1), u32(31)], + expected: V( + 0b00010010101001000100101001000101, + 0b10011100011001110001100111000111, + 0b01010101010101010101010101010101, + 0b10101010101010101010101010101011 + ), + }, + { + input: [all_0, pattern, u32(14), u32(18)], + expected: V( + 0b10001001010010001000000000000000, + 0b11100011001110001100000000000000, + 0b10101010101010101000000000000000, + 0b01010101010101010100000000000000 + ), + }, + { + input: [all_1, pattern, u32(14), u32(18)], + expected: V( + 0b10001001010010001011111111111111, + 0b11100011001110001111111111111111, + 0b10101010101010101011111111111111, + 0b01010101010101010111111111111111 + ), + }, + { + input: [all_0, pattern, u32(14), u32(7)], + expected: V( + 0b00000000000010001000000000000000, + 0b00000000000110001100000000000000, + 0b00000000000010101000000000000000, + 0b00000000000101010100000000000000 + ), + }, + { + input: [all_1, pattern, u32(14), u32(7)], + expected: V( + 0b11111111111010001011111111111111, + 0b11111111111110001111111111111111, + 0b11111111111010101011111111111111, + 0b11111111111101010111111111111111 + ), + }, + { + input: [all_0, pattern, u32(14), u32(4)], + expected: V( + 0b00000000000000001000000000000000, + 0b00000000000000001100000000000000, + 0b00000000000000101000000000000000, + 0b00000000000000010100000000000000 + ), + }, + { + input: [all_1, pattern, u32(14), u32(4)], + expected: V( + 0b11111111111111001011111111111111, + 0b11111111111111001111111111111111, + 0b11111111111111101011111111111111, + 0b11111111111111010111111111111111 + ), + }, + { + input: [all_0, pattern, u32(14), u32(3)], + expected: V( + 0b00000000000000001000000000000000, + 0b00000000000000001100000000000000, + 0b00000000000000001000000000000000, + 0b00000000000000010100000000000000 + ), + }, + { + input: [all_1, pattern, u32(14), u32(3)], + expected: V( + 0b11111111111111101011111111111111, + 0b11111111111111101111111111111111, + 0b11111111111111101011111111111111, + 0b11111111111111110111111111111111 + ), + }, + { + input: [all_0, pattern, u32(18), u32(3)], + expected: V( + 0b00000000000010000000000000000000, + 0b00000000000011000000000000000000, + 0b00000000000010000000000000000000, + 0b00000000000101000000000000000000 + ), + }, + { + input: [all_1, pattern, u32(18), u32(3)], + expected: V( + 0b11111111111010111111111111111111, + 0b11111111111011111111111111111111, + 0b11111111111010111111111111111111, + 0b11111111111101111111111111111111 + ), + }, + { + input: [pattern, all_0, u32(1), u32(31)], + expected: V( + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000001, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000001 + ), + }, + { + input: [pattern, all_1, u32(1), u32(31)], + expected: V( + 0b11111111111111111111111111111110, + 0b11111111111111111111111111111111, + 0b11111111111111111111111111111110, + 0b11111111111111111111111111111111 + ), + }, + { + input: [pattern, all_0, u32(14), u32(18)], + expected: V( + 0b00000000000000000010010100100010, + 0b00000000000000000000110011100011, + 0b00000000000000000010101010101010, + 0b00000000000000000001010101010101 + ), + }, + { + input: [pattern, all_1, u32(14), u32(18)], + expected: V( + 0b11111111111111111110010100100010, + 0b11111111111111111100110011100011, + 0b11111111111111111110101010101010, + 0b11111111111111111101010101010101 + ), + }, + { + input: [pattern, all_0, u32(14), u32(7)], + expected: V( + 0b10001001010000000010010100100010, + 0b11001110001000000000110011100011, + 0b10101010101000000010101010101010, + 0b01010101010000000001010101010101 + ), + }, + { + input: [pattern, all_1, u32(14), u32(7)], + expected: V( + 0b10001001010111111110010100100010, + 0b11001110001111111100110011100011, + 0b10101010101111111110101010101010, + 0b01010101010111111101010101010101 + ), + }, + { + input: [pattern, all_0, u32(14), u32(4)], + expected: V( + 0b10001001010100000010010100100010, + 0b11001110001100000000110011100011, + 0b10101010101010000010101010101010, + 0b01010101010101000001010101010101 + ), + }, + { + input: [pattern, all_1, u32(14), u32(4)], + expected: V( + 0b10001001010100111110010100100010, + 0b11001110001100111100110011100011, + 0b10101010101010111110101010101010, + 0b01010101010101111101010101010101 + ), + }, + { + input: [pattern, all_0, u32(14), u32(3)], + expected: V( + 0b10001001010100100010010100100010, + 0b11001110001100100000110011100011, + 0b10101010101010100010101010101010, + 0b01010101010101000001010101010101 + ), + }, + { + input: [pattern, all_1, u32(14), u32(3)], + expected: V( + 0b10001001010100111110010100100010, + 0b11001110001100111100110011100011, + 0b10101010101010111110101010101010, + 0b01010101010101011101010101010101 + ), + }, + { + input: [pattern, all_0, u32(18), u32(3)], + expected: V( + 0b10001001010000100010010100100010, + 0b11001110001000111000110011100011, + 0b10101010101000101010101010101010, + 0b01010101010000010101010101010101 + ), + }, + { + input: [pattern, all_1, u32(18), u32(3)], + expected: V( + 0b10001001010111100010010100100010, + 0b11001110001111111000110011100011, + 0b10101010101111101010101010101010, + 0b01010101010111010101010101010101 + ), + }, + { + input: [pattern, pattern, u32(18), u32(3)], + expected: V( + 0b10001001010010100010010100100010, + 0b11001110001011111000110011100011, + 0b10101010101010101010101010101010, + 0b01010101010101010101010101010101 + ), + }, + { + input: [pattern, pattern, u32(14), u32(7)], + expected: V( + 0b10001001010010001010010100100010, + 0b11001110001110001100110011100011, + 0b10101010101010101010101010101010, + 0b01010101010101010101010101010101 + ), + }, + + // Zero count + { input: [pattern, all_1, u32(0), u32(0)], expected: pattern }, + { input: [pattern, all_1, u32(1), u32(0)], expected: pattern }, + { input: [pattern, all_1, u32(2), u32(0)], expected: pattern }, + { input: [pattern, all_1, u32(31), u32(0)], expected: pattern }, + { input: [pattern, all_1, u32(32), u32(0)], expected: pattern }, + { input: [pattern, all_1, u32(0), u32(0)], expected: pattern }, + ]; + + if (t.params.inputSource !== 'const') { + cases.push( + ...[ + // Start overflow + { input: [all_0, pattern, u32(50), u32(3)], expected: all_0 }, + { input: [all_1, pattern, u32(50), u32(3)], expected: all_1 }, + { input: [pattern, pattern, u32(50), u32(3)], expected: pattern }, + + // End overflow + { input: [all_0, pattern, u32(0), u32(99)], expected: pattern }, + { input: [all_1, pattern, u32(0), u32(99)], expected: pattern }, + { input: [all_0, low_1, u32(31), u32(99)], expected: high_1 }, + { + input: [pattern, pattern, u32(20), u32(99)], + expected: V( + 0b01010010001000100010010100100010, + 0b11001110001100111000110011100011, + 0b10101010101010101010101010101010, + 0b01010101010101010101010101010101 + ), + }, + ] + ); + } + + await run(t, builtin('insertBits'), [T, T, TypeU32, TypeU32], T, cfg, cases); + }); 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 new file mode 100644 index 0000000000..3e83816387 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts @@ -0,0 +1,81 @@ +export const description = ` +Execution tests for the 'inverseSqrt' builtin function + +S is AbstractFloat, 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. +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('inverseSqrt'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('inverseSqrt'), [TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..3829867752 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts @@ -0,0 +1,121 @@ +export const description = ` +Execution tests for the 'ldexp' builtin function + +S is AbstractFloat, f32, f16 +T is S or vecN<S> + +K is 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 + +@const fn ldexp(e1: T ,e2: I ) -> T +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 { builtin } from './builtin.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( + ` +` + ) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..85c1f85169 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts @@ -0,0 +1,178 @@ +export const description = ` +Execution tests for the 'length' builtin function + +S is AbstractFloat, 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). +`; + +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'; + +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 + ); + }, + ...f32_vec_cases, + f16: () => { + return FP.f16.generateScalarToIntervalCases( + fullF16Range(), + 'unfiltered', + FP.f16.lengthInterval + ); + }, + ...f16_vec_cases, +}); + +g.test('abstract_float') + .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(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests`) + .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); + }); + +g.test('f32_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-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('length'), [TypeVec(2, TypeF32)], TypeF32, t.params, cases); + }); + +g.test('f32_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + 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); + }); + +g.test('f32_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + 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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('length'), [TypeF16], TypeF16, t.params, cases); + }); + +g.test('f16_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec2s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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); + }); + +g.test('f16_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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); + }); + +g.test('f16_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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); + }); 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 new file mode 100644 index 0000000000..ac60e2b1bc --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts @@ -0,0 +1,89 @@ +export const description = ` +Execution tests for the 'log' builtin function + +S is AbstractFloat, 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. +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +f32 tests + +TODO(#792): Decide what the ground-truth is for these tests. [1] +` + ) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..37931579b9 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts @@ -0,0 +1,89 @@ +export const description = ` +Execution tests for the 'log2' builtin function + +S is AbstractFloat, 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. +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +f32 tests + +TODO(#792): Decide what the ground-truth is for these tests. [1] +` + ) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..6654b4951c --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts @@ -0,0 +1,165 @@ +export const description = ` +Execution tests for the 'max' builtin function + +S is AbstractInt, 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 +T is vecN<S> +@const fn max(e1: T ,e2: T) -> T +Returns e2 if e1 is less than e2, and e1 otherwise. +If one operand is a NaN, the other is returned. +If both operands are NaNs, a NaN is returned. +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'; + +/** 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)); + }); + }); + 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) + ) + .unimplemented(); + +g.test('u32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`u32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const makeCase = (x: number, y: number): Case => { + return { input: [u32(x), u32(y)], expected: u32(Math.max(x, y)) }; + }; + + const test_values: Array<number> = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff]; + const cases = generateTestCases(test_values, makeCase); + + await run(t, builtin('max'), [TypeU32, TypeU32], TypeU32, t.params, cases); + }); + +g.test('i32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`i32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const makeCase = (x: number, y: number): Case => { + 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 cases = generateTestCases(test_values, makeCase); + + await run(t, builtin('max'), [TypeI32, TypeI32], TypeI32, 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', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractBuiltin('max'), + [TypeAbstractFloat, TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('max'), [TypeF32, TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('max'), [TypeF16, TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..6c05319546 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts @@ -0,0 +1,164 @@ +export const description = ` +Execution tests for the 'min' builtin function + +S is AbstractInt, 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 +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. +If one operand is a NaN, the other is returned. +If both operands are NaNs, a NaN is returned. +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'; + +export const g = makeTestGroup(GPUTest); + +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 + ); + }, +}); + +/** 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)); + }); + }); + 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) + ) + .unimplemented(); + +g.test('u32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`u32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const makeCase = (x: number, y: number): Case => { + return { input: [u32(x), u32(y)], expected: u32(Math.min(x, y)) }; + }; + + const test_values: Array<number> = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff]; + const cases = generateTestCases(test_values, makeCase); + + await run(t, builtin('min'), [TypeU32, TypeU32], TypeU32, t.params, cases); + }); + +g.test('i32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`i32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const makeCase = (x: number, y: number): Case => { + 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 cases = generateTestCases(test_values, makeCase); + + await run(t, builtin('min'), [TypeI32, TypeI32], TypeI32, 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', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractBuiltin('min'), + [TypeAbstractFloat, TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('min'), [TypeF32, TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('min'), [TypeF16, TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..95e9f6b310 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts @@ -0,0 +1,275 @@ +export const description = ` +Execution tests for the 'mix' builtin function + +S is AbstractFloat, 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 +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. +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +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(); + +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(); + +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(); + +g.test('f32_matching') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 test with matching third param`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f32_nonmatching_vec2') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests with two vec2<f32> params and scalar third param`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + 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 + ); + }); + +g.test('f32_nonmatching_vec3') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests with two vec3<f32> params and scalar third param`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + 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 + ); + }); + +g.test('f32_nonmatching_vec4') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests with two vec4<f32> params and scalar third param`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + 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 + ); + }); + +g.test('f16_matching') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 test with matching third param`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .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); + }); + +g.test('f16_nonmatching_vec2') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests with two vec2<f16> params and scalar third param`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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 + ); + }); + +g.test('f16_nonmatching_vec3') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests with two vec3<f16> params and scalar third param`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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 + ); + }); + +g.test('f16_nonmatching_vec4') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests with two vec4<f16> params and scalar third param`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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 + ); + }); 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 new file mode 100644 index 0000000000..1a3d8a2850 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts @@ -0,0 +1,661 @@ +export const description = ` +Execution tests for the 'modf' builtin function + +T is f32 or f16 or 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 +T is vecN<S> +@const fn modf(e:T) -> result_struct +Splits the components of |e| into fractional and whole number parts. +The |i|'th component of the whole and fractional parts equal the whole and fractional parts of modf(e[i]). +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 { + abstractFloatShaderBuilder, + allInputSources, + basicExpressionBuilder, + Case, + onlyConstInputSource, + run, + ShaderBuilder, +} from '../../expression.js'; + +export const g = makeTestGroup(GPUTest); + +/** @returns an ShaderBuilder that evaluates modf and returns .whole from the result structure */ +function wholeBuilder(): ShaderBuilder { + return basicExpressionBuilder(value => `modf(${value}).whole`); +} + +/** @returns an ShaderBuilder that evaluates modf and returns .fract from the result structure */ +function fractBuilder(): ShaderBuilder { + return basicExpressionBuilder(value => `modf(${value}).fract`); +} + +/** @returns an ShaderBuilder that evaluates modf and returns .whole from the result structure for AbstractFloats */ +function abstractWholeBuilder(): ShaderBuilder { + return abstractFloatShaderBuilder(value => `modf(${value}).whole`); +} + +/** @returns an ShaderBuilder that evaluates modf and returns .fract from the result structure for AbstractFloats */ +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( + ` +T is f32 + +struct __modf_result_f32 { + fract : f32, // fractional part + whole : f32 // whole part +} +` + ) + .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); + }); + +g.test('f32_whole') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is f32 + +struct __modf_result_f32 { + fract : f32, // fractional part + whole : f32 // whole part +} +` + ) + .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); + }); + +g.test('f32_vec2_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec2<f32> + +struct __modf_result_vec2_f32 { + fract : vec2<f32>, // fractional part + whole : vec2<f32> // whole part +} +` + ) + .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); + }); + +g.test('f32_vec2_whole') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec2<f32> + +struct __modf_result_vec2_f32 { + fract : vec2<f32>, // fractional part + whole : vec2<f32> // whole part +} +` + ) + .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); + }); + +g.test('f32_vec3_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec3<f32> + +struct __modf_result_vec3_f32 { + fract : vec3<f32>, // fractional part + whole : vec3<f32> // whole part +} +` + ) + .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); + }); + +g.test('f32_vec3_whole') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec3<f32> + +struct __modf_result_vec3_f32 { + fract : vec3<f32>, // fractional part + whole : vec3<f32> // whole part +} +` + ) + .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); + }); + +g.test('f32_vec4_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec4<f32> + +struct __modf_result_vec4_f32 { + fract : vec4<f32>, // fractional part + whole : vec4<f32> // whole part +} +` + ) + .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); + }); + +g.test('f32_vec4_whole') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec4<f32> + +struct __modf_result_vec4_f32 { + fract : vec4<f32>, // fractional part + whole : vec4<f32> // whole part +} +` + ) + .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); + }); + +g.test('f16_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is f16 + +struct __modf_result_f16 { + fract : f16, // fractional part + whole : f16 // whole part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + }) + .fn(async t => { + const cases = await d.get('f16_fract'); + await run(t, fractBuilder(), [TypeF16], TypeF16, t.params, cases); + }); + +g.test('f16_whole') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is f16 + +struct __modf_result_f16 { + fract : f16, // fractional part + whole : f16 // whole part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + }) + .fn(async t => { + const cases = await d.get('f16_whole'); + await run(t, wholeBuilder(), [TypeF16], TypeF16, t.params, cases); + }); + +g.test('f16_vec2_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec2<f16> + +struct __modf_result_vec2_f16 { + fract : vec2<f16>, // fractional part + whole : vec2<f16> // whole part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-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); + }); + +g.test('f16_vec2_whole') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec2<f16> + +struct __modf_result_vec2_f16 { + fract : vec2<f16>, // fractional part + whole : vec2<f16> // whole part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-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); + }); + +g.test('f16_vec3_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec3<f16> + +struct __modf_result_vec3_f16 { + fract : vec3<f16>, // fractional part + whole : vec3<f16> // whole part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-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); + }); + +g.test('f16_vec3_whole') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec3<f16> + +struct __modf_result_vec3_f16 { + fract : vec3<f16>, // fractional part + whole : vec3<f16> // whole part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-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); + }); + +g.test('f16_vec4_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec4<f16> + +struct __modf_result_vec4_f16 { + fract : vec4<f16>, // fractional part + whole : vec4<f16> // whole part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-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); + }); + +g.test('f16_vec4_whole') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec4<f16> + +struct __modf_result_vec4_f16 { + fract : vec4<f16>, // fractional part + whole : vec4<f16> // whole part +} +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-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); + }); + +g.test('abstract_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is AbstractFloat + +struct __modf_result_abstract { + fract : AbstractFloat, // fractional part + whole : 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); + }); + +g.test('abstract_whole') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is AbstractFloat + +struct __modf_result_abstract { + fract : AbstractFloat, // fractional part + whole : 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); + }); + +g.test('abstract_vec2_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec2<abstract> + +struct __modf_result_vec2_abstract { + fract : vec2<abstract>, // fractional part + whole : vec2<abstract> // whole part +} +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec2_fract'); + await run( + t, + abstractFractBuilder(), + [TypeVec(2, TypeAbstractFloat)], + TypeVec(2, TypeAbstractFloat), + t.params, + cases + ); + }); + +g.test('abstract_vec2_whole') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec2<abstract> + +struct __modf_result_vec2_abstract { + fract : vec2<abstract>, // fractional part + whole : vec2<abstract> // whole part +} +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec2_whole'); + await run( + t, + abstractWholeBuilder(), + [TypeVec(2, TypeAbstractFloat)], + TypeVec(2, TypeAbstractFloat), + t.params, + cases + ); + }); + +g.test('abstract_vec3_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec3<abstract> + +struct __modf_result_vec3_abstract { + fract : vec3<abstract>, // fractional part + whole : vec3<abstract> // whole part +} +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec3_fract'); + await run( + t, + abstractFractBuilder(), + [TypeVec(3, TypeAbstractFloat)], + TypeVec(3, TypeAbstractFloat), + t.params, + cases + ); + }); + +g.test('abstract_vec3_whole') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec3<abstract> + +struct __modf_result_vec3_abstract { + fract : vec3<abstract>, // fractional part + whole : vec3<abstract> // whole part +} +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec3_whole'); + await run( + t, + abstractWholeBuilder(), + [TypeVec(3, TypeAbstractFloat)], + TypeVec(3, TypeAbstractFloat), + t.params, + cases + ); + }); + +g.test('abstract_vec4_fract') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec4<abstract> + +struct __modf_result_vec4_abstract { + fract : vec4<abstract>, // fractional part + whole : vec4<abstract> // whole part +} +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec4_fract'); + await run( + t, + abstractFractBuilder(), + [TypeVec(4, TypeAbstractFloat)], + TypeVec(4, TypeAbstractFloat), + t.params, + cases + ); + }); + +g.test('abstract_vec4_whole') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +T is vec4<abstract> + +struct __modf_result_vec4_abstract { + fract : vec4<abstract>, // fractional part + whole : vec4<abstract> // whole part +} +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec4_whole'); + await run( + t, + abstractWholeBuilder(), + [TypeVec(4, TypeAbstractFloat)], + TypeVec(4, TypeAbstractFloat), + t.params, + 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 new file mode 100644 index 0000000000..615617b448 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts @@ -0,0 +1,137 @@ +export const description = ` +Execution tests for the 'normalize' builtin function + +T is AbstractFloat, 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 { builtin } from './builtin.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 }), {}); + +export const d = makeCaseCache('normalize', { + ...f32_vec_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('f32_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-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('normalize'), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases); + }); + +g.test('f32_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + 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); + }); + +g.test('f32_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + 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); + }); + +g.test('f16_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec2s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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); + }); + +g.test('f16_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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); + }); + +g.test('f16_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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); + }); 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 new file mode 100644 index 0000000000..790e54720c --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts @@ -0,0 +1,88 @@ +export const description = ` +Converts two floating point values to half-precision floating point numbers, and then combines them into one u32 value. +Component e[i] of the input is converted to a IEEE-754 binary16 value, +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 { builtin } from './builtin.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( + ` +@const fn pack2x16float(e: vec2<f32>) -> u32 +` + ) + .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); + }); 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 new file mode 100644 index 0000000000..54bb21f6c6 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts @@ -0,0 +1,55 @@ +export const description = ` +Converts two normalized floating point values to 16-bit signed integers, and then combines them into one u32 value. +Component e[i] of the input is converted to a 16-bit twos complement integer value +⌊ 0.5 + 32767 × min(1, max(-1, e[i])) ⌋ 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 { kValue } from '../../../../../util/constants.js'; +import { + f32, + pack2x16snorm, + TypeF32, + TypeU32, + TypeVec, + u32, + vec2, +} from '../../../../../util/conversion.js'; +import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js'; +import { allInputSources, Case, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('pack') + .specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions') + .desc( + ` +@const fn pack2x16snorm(e: vec2<f32>) -> u32 +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const makeCase = (x: number, y: number): Case => { + x = quantizeToF32(x); + y = quantizeToF32(y); + return { input: [vec2(f32(x), f32(y))], expected: u32(pack2x16snorm(x, y)) }; + }; + + // Returns a value normalized to [-1, 1]. + const normalizeF32 = (n: number): number => { + return n / kValue.f32.positive.max; + }; + + const cases: Array<Case> = vectorF32Range(2).flatMap(v => { + return [ + makeCase(...(v as [number, number])), + makeCase(...(v.map(normalizeF32) as [number, number])), + ]; + }); + + await run(t, builtin('pack2x16snorm'), [TypeVec(2, TypeF32)], TypeU32, 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 new file mode 100644 index 0000000000..a875a9c7e1 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts @@ -0,0 +1,55 @@ +export const description = ` +Converts two normalized floating point values to 16-bit unsigned integers, and then combines them into one u32 value. +Component e[i] of the input is converted to a 16-bit unsigned integer value +⌊ 0.5 + 65535 × min(1, max(0, e[i])) ⌋ 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 { kValue } from '../../../../../util/constants.js'; +import { + f32, + pack2x16unorm, + TypeF32, + TypeU32, + TypeVec, + u32, + vec2, +} from '../../../../../util/conversion.js'; +import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js'; +import { allInputSources, Case, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('pack') + .specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions') + .desc( + ` +@const fn pack2x16unorm(e: vec2<f32>) -> u32 +` + ) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const makeCase = (x: number, y: number): Case => { + x = quantizeToF32(x); + y = quantizeToF32(y); + return { input: [vec2(f32(x), f32(y))], expected: u32(pack2x16unorm(x, y)) }; + }; + + // Returns a value normalized to [0, 1]. + const normalizeF32 = (n: number): number => { + return n > 0 ? n / kValue.f32.positive.max : n / kValue.f32.negative.min; + }; + + const cases: Array<Case> = vectorF32Range(2).flatMap(v => { + return [ + makeCase(...(v as [number, number])), + makeCase(...(v.map(normalizeF32) as [number, number])), + ]; + }); + + await run(t, builtin('pack2x16unorm'), [TypeVec(2, TypeF32)], TypeU32, 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 new file mode 100644 index 0000000000..de0463e9fc --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts @@ -0,0 +1,60 @@ +export const description = ` +Converts four normalized floating point values to 8-bit signed integers, and then combines them into one u32 value. +Component e[i] of the input is converted to an 8-bit twos complement integer value +⌊ 0.5 + 127 × min(1, max(-1, e[i])) ⌋ which is then placed in +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 { quantizeToF32, vectorF32Range } from '../../../../../util/math.js'; +import { allInputSources, Case, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('pack') + .specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions') + .desc( + ` +@const fn pack4x8snorm(e: vec4<f32>) -> u32 +` + ) + .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]; + for (const idx in vals) { + vals[idx] = quantizeToF32(vals[idx]); + vals_f32[idx] = f32(vals[idx]); + } + + return { input: [vec4(...vals_f32)], expected: u32(pack4x8snorm(...vals)) }; + }; + + // Returns a value normalized to [-1, 1]. + const normalizeF32 = (n: number): number => { + return n / kValue.f32.positive.max; + }; + + const cases: Array<Case> = vectorF32Range(4).flatMap(v => { + return [ + makeCase(v as [number, number, number, number]), + makeCase(v.map(normalizeF32) as [number, number, number, number]), + ]; + }); + + await run(t, builtin('pack4x8snorm'), [TypeVec(4, TypeF32)], TypeU32, 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 new file mode 100644 index 0000000000..b670e92fbb --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts @@ -0,0 +1,60 @@ +export const description = ` +Converts four normalized floating point values to 8-bit unsigned integers, and then combines them into one u32 value. +Component e[i] of the input is converted to an 8-bit unsigned integer value +⌊ 0.5 + 255 × min(1, max(0, e[i])) ⌋ which is then placed in +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 { quantizeToF32, vectorF32Range } from '../../../../../util/math.js'; +import { allInputSources, Case, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('pack') + .specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions') + .desc( + ` +@const fn pack4x8unorm(e: vec4<f32>) -> u32 +` + ) + .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]; + for (const idx in vals) { + vals[idx] = quantizeToF32(vals[idx]); + vals_f32[idx] = f32(vals[idx]); + } + + return { input: [vec4(...vals_f32)], expected: u32(pack4x8unorm(...vals)) }; + }; + + // Returns a value normalized to [0, 1]. + const normalizeF32 = (n: number): number => { + return n > 0 ? n / kValue.f32.positive.max : n / kValue.f32.negative.min; + }; + + const cases: Array<Case> = vectorF32Range(4).flatMap(v => { + return [ + makeCase(v as [number, number, number, number]), + makeCase(v.map(normalizeF32) as [number, number, number, number]), + ]; + }); + + await run(t, builtin('pack4x8unorm'), [TypeVec(4, TypeF32)], TypeU32, t.params, 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 new file mode 100644 index 0000000000..f9b4fe1cfa --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts @@ -0,0 +1,88 @@ +export const description = ` +Execution tests for the 'pow' builtin function + +S is AbstractFloat, 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. +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..b37d4c5afb --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts @@ -0,0 +1,70 @@ +export const description = ` +Execution tests for the 'quantizeToF16' builtin function + +T is f32 or vecN<f32> +@const fn quantizeToF16(e: T ) -> T +Quantizes a 32-bit floating point value e as if e were converted to a IEEE 754 +binary16 value, and then converted back to a IEEE 754 binary32 value. +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 { allInputSources, run } from '../../expression.js'; + +import { builtin } from './builtin.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`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); 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 new file mode 100644 index 0000000000..63ae45b656 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts @@ -0,0 +1,90 @@ +export const description = ` +Execution tests for the 'radians' builtin function + +S is AbstractFloat, f32, f16 +T is S or vecN<S> +@const fn radians(e1: T ) -> T +Converts degrees to radians, approximating e1 * π / 180. +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 { allInputSources, onlyConstInputSource, run } from '../../expression.js'; + +import { abstractBuiltin, builtin } from './builtin.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`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractBuiltin('radians'), + [TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('radians'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('radians'), [TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..2614c4e686 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts @@ -0,0 +1,180 @@ +export const description = ` +Execution tests for the 'reflect' builtin function + +T is vecN<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. +`; + +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 { builtin } from './builtin.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 }), {}); + +export const d = makeCaseCache('reflect', { + ...f32_vec_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', [2, 3, 4] as const)) + .unimplemented(); + +g.test('f32_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-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('reflect'), + [TypeVec(2, TypeF32), TypeVec(2, TypeF32)], + TypeVec(2, TypeF32), + t.params, + cases + ); + }); + +g.test('f32_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + 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 + ); + }); + +g.test('f32_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + 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 + ); + }); + +g.test('f16_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec2s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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 + ); + }); + +g.test('f16_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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 + ); + }); + +g.test('f16_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + 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 + ); + }); 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 new file mode 100644 index 0000000000..be1a76b437 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts @@ -0,0 +1,253 @@ +export const description = ` +Execution tests for the 'refract' builtin function + +T is vecN<I> +I is AbstractFloat, 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)). +If k < 0.0, returns the refraction vector 0.0, otherwise return the refraction +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 { builtin } from './builtin.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 }), {}); + +export const d = makeCaseCache('refract', { + ...f32_vec_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', [2, 3, 4] as const)) + .unimplemented(); + +g.test('f32_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-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('refract'), + [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeF32], + TypeVec(2, TypeF32), + t.params, + cases + ); + }); + +g.test('f32_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const' + ); + await run( + t, + builtin('refract'), + [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeF32], + TypeVec(3, TypeF32), + t.params, + cases + ); + }); + +g.test('f32_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f32 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const' + ); + await run( + t, + builtin('refract'), + [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeF32], + TypeVec(4, TypeF32), + t.params, + cases + ); + }); + +g.test('f16_vec2') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec2s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const' + ); + await run( + t, + builtin('refract'), + [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeF16], + TypeVec(2, TypeF16), + t.params, + cases + ); + }); + +g.test('f16_vec3') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec3s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const' + ); + await run( + t, + builtin('refract'), + [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeF16], + TypeVec(3, TypeF16), + t.params, + cases + ); + }); + +g.test('f16_vec4') + .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions') + .desc(`f16 tests using vec4s`) + .params(u => u.combine('inputSource', allInputSources)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const' + ); + await run( + t, + builtin('refract'), + [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeF16], + TypeVec(4, TypeF16), + 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 new file mode 100644 index 0000000000..6acb359822 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts @@ -0,0 +1,250 @@ +export const description = ` +Execution tests for the 'reversBits' builtin function + +S is i32, u32 +T is S or vecN<S> +@const fn reverseBits(e: T ) -> T +Reverses the bits in e: The bit at position k of the result equals the bit at position 31-k of e. +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 { allInputSources, Config, run } from '../../expression.js'; + +import { builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('u32') + .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions') + .desc(`u32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + // prettier-ignore + await run(t, builtin('reverseBits'), [TypeU32], TypeU32, cfg, [ + // Zero + { input: u32Bits(0b00000000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000000) }, + + // One + { input: u32Bits(0b00000000000000000000000000000001), expected: u32Bits(0b10000000000000000000000000000000) }, + + // 0's after leading 1 + { input: u32Bits(0b00000000000000000000000000000010), expected: u32Bits(0b01000000000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000000000100), expected: u32Bits(0b00100000000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000000001000), expected: u32Bits(0b00010000000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000000010000), expected: u32Bits(0b00001000000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000000100000), expected: u32Bits(0b00000100000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000001000000), expected: u32Bits(0b00000010000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000010000000), expected: u32Bits(0b00000001000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000100000000), expected: u32Bits(0b00000000100000000000000000000000) }, + { input: u32Bits(0b00000000000000000000001000000000), expected: u32Bits(0b00000000010000000000000000000000) }, + { input: u32Bits(0b00000000000000000000010000000000), expected: u32Bits(0b00000000001000000000000000000000) }, + { input: u32Bits(0b00000000000000000000100000000000), expected: u32Bits(0b00000000000100000000000000000000) }, + { input: u32Bits(0b00000000000000000001000000000000), expected: u32Bits(0b00000000000010000000000000000000) }, + { input: u32Bits(0b00000000000000000010000000000000), expected: u32Bits(0b00000000000001000000000000000000) }, + { input: u32Bits(0b00000000000000000100000000000000), expected: u32Bits(0b00000000000000100000000000000000) }, + { input: u32Bits(0b00000000000000001000000000000000), expected: u32Bits(0b00000000000000010000000000000000) }, + { input: u32Bits(0b00000000000000010000000000000000), expected: u32Bits(0b00000000000000001000000000000000) }, + { input: u32Bits(0b00000000000000100000000000000000), expected: u32Bits(0b00000000000000000100000000000000) }, + { input: u32Bits(0b00000000000001000000000000000000), expected: u32Bits(0b00000000000000000010000000000000) }, + { input: u32Bits(0b00000000000010000000000000000000), expected: u32Bits(0b00000000000000000001000000000000) }, + { input: u32Bits(0b00000000000100000000000000000000), expected: u32Bits(0b00000000000000000000100000000000) }, + { input: u32Bits(0b00000000001000000000000000000000), expected: u32Bits(0b00000000000000000000010000000000) }, + { input: u32Bits(0b00000000010000000000000000000000), expected: u32Bits(0b00000000000000000000001000000000) }, + { input: u32Bits(0b00000000100000000000000000000000), expected: u32Bits(0b00000000000000000000000100000000) }, + { input: u32Bits(0b00000001000000000000000000000000), expected: u32Bits(0b00000000000000000000000010000000) }, + { input: u32Bits(0b00000010000000000000000000000000), expected: u32Bits(0b00000000000000000000000001000000) }, + { input: u32Bits(0b00000100000000000000000000000000), expected: u32Bits(0b00000000000000000000000000100000) }, + { input: u32Bits(0b00001000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000010000) }, + { input: u32Bits(0b00010000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000001000) }, + { input: u32Bits(0b00100000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000100) }, + { input: u32Bits(0b01000000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000010) }, + { input: u32Bits(0b10000000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000001) }, + + // 1's after leading 1 + { input: u32Bits(0b00000000000000000000000000000011), expected: u32Bits(0b11000000000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000000000111), expected: u32Bits(0b11100000000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000000001111), expected: u32Bits(0b11110000000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000000011111), expected: u32Bits(0b11111000000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000000111111), expected: u32Bits(0b11111100000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000001111111), expected: u32Bits(0b11111110000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000011111111), expected: u32Bits(0b11111111000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000111111111), expected: u32Bits(0b11111111100000000000000000000000) }, + { input: u32Bits(0b00000000000000000000001111111111), expected: u32Bits(0b11111111110000000000000000000000) }, + { input: u32Bits(0b00000000000000000000011111111111), expected: u32Bits(0b11111111111000000000000000000000) }, + { input: u32Bits(0b00000000000000000000111111111111), expected: u32Bits(0b11111111111100000000000000000000) }, + { input: u32Bits(0b00000000000000000001111111111111), expected: u32Bits(0b11111111111110000000000000000000) }, + { input: u32Bits(0b00000000000000000011111111111111), expected: u32Bits(0b11111111111111000000000000000000) }, + { input: u32Bits(0b00000000000000000111111111111111), expected: u32Bits(0b11111111111111100000000000000000) }, + { input: u32Bits(0b00000000000000001111111111111111), expected: u32Bits(0b11111111111111110000000000000000) }, + { input: u32Bits(0b00000000000000011111111111111111), expected: u32Bits(0b11111111111111111000000000000000) }, + { input: u32Bits(0b00000000000000111111111111111111), expected: u32Bits(0b11111111111111111100000000000000) }, + { input: u32Bits(0b00000000000001111111111111111111), expected: u32Bits(0b11111111111111111110000000000000) }, + { input: u32Bits(0b00000000000011111111111111111111), expected: u32Bits(0b11111111111111111111000000000000) }, + { input: u32Bits(0b00000000000111111111111111111111), expected: u32Bits(0b11111111111111111111100000000000) }, + { input: u32Bits(0b00000000001111111111111111111111), expected: u32Bits(0b11111111111111111111110000000000) }, + { input: u32Bits(0b00000000011111111111111111111111), expected: u32Bits(0b11111111111111111111111000000000) }, + { input: u32Bits(0b00000000111111111111111111111111), expected: u32Bits(0b11111111111111111111111100000000) }, + { input: u32Bits(0b00000001111111111111111111111111), expected: u32Bits(0b11111111111111111111111110000000) }, + { input: u32Bits(0b00000011111111111111111111111111), expected: u32Bits(0b11111111111111111111111111000000) }, + { input: u32Bits(0b00000111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111100000) }, + { input: u32Bits(0b00001111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111110000) }, + { input: u32Bits(0b00011111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111111000) }, + { input: u32Bits(0b00111111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111111100) }, + { input: u32Bits(0b01111111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111111110) }, + { input: u32Bits(0b11111111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111111111) }, + + // random after leading 1 + { input: u32Bits(0b00000000000000000000000000000110), expected: u32Bits(0b01100000000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000000001101), expected: u32Bits(0b10110000000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000000011101), expected: u32Bits(0b10111000000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000000111001), expected: u32Bits(0b10011100000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000001101111), expected: u32Bits(0b11110110000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000011111111), expected: u32Bits(0b11111111000000000000000000000000) }, + { input: u32Bits(0b00000000000000000000000111101111), expected: u32Bits(0b11110111100000000000000000000000) }, + { input: u32Bits(0b00000000000000000000001111111111), expected: u32Bits(0b11111111110000000000000000000000) }, + { input: u32Bits(0b00000000000000000000011111110001), expected: u32Bits(0b10001111111000000000000000000000) }, + { input: u32Bits(0b00000000000000000000111011011101), expected: u32Bits(0b10111011011100000000000000000000) }, + { input: u32Bits(0b00000000000000000001101101111111), expected: u32Bits(0b11111110110110000000000000000000) }, + { input: u32Bits(0b00000000000000000011111111011111), expected: u32Bits(0b11111011111111000000000000000000) }, + { input: u32Bits(0b00000000000000000101111001110101), expected: u32Bits(0b10101110011110100000000000000000) }, + { input: u32Bits(0b00000000000000001101111011110111), expected: u32Bits(0b11101111011110110000000000000000) }, + { input: u32Bits(0b00000000000000011111111111110011), expected: u32Bits(0b11001111111111111000000000000000) }, + { input: u32Bits(0b00000000000000111111111110111111), expected: u32Bits(0b11111101111111111100000000000000) }, + { input: u32Bits(0b00000000000001111111011111111111), expected: u32Bits(0b11111111111011111110000000000000) }, + { input: u32Bits(0b00000000000011111111111111111111), expected: u32Bits(0b11111111111111111111000000000000) }, + { input: u32Bits(0b00000000000111110101011110111111), expected: u32Bits(0b11111101111010101111100000000000) }, + { input: u32Bits(0b00000000001111101111111111110111), expected: u32Bits(0b11101111111111110111110000000000) }, + { input: u32Bits(0b00000000011111111111010000101111), expected: u32Bits(0b11110100001011111111111000000000) }, + { input: u32Bits(0b00000000111111111111001111111011), expected: u32Bits(0b11011111110011111111111100000000) }, + { input: u32Bits(0b00000001111111011111101111111111), expected: u32Bits(0b11111111110111111011111110000000) }, + { input: u32Bits(0b00000011101011111011110111111011), expected: u32Bits(0b11011111101111011111010111000000) }, + { input: u32Bits(0b00000111111110111111111111111111), expected: u32Bits(0b11111111111111111101111111100000) }, + { input: u32Bits(0b00001111000000011011011110111111), expected: u32Bits(0b11111101111011011000000011110000) }, + { input: u32Bits(0b00011110101111011111111111111111), expected: u32Bits(0b11111111111111111011110101111000) }, + { input: u32Bits(0b00110110111111100111111110111101), expected: u32Bits(0b10111101111111100111111101101100) }, + { input: u32Bits(0b01010111111101111111011111011111), expected: u32Bits(0b11111011111011111110111111101010) }, + { input: u32Bits(0b11100010011110101101101110101111), expected: u32Bits(0b11110101110110110101111001000111) }, + ]); + }); + +g.test('i32') + .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions') + .desc(`i32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cfg: Config = t.params; + // prettier-ignore + await run(t, builtin('reverseBits'), [TypeI32], TypeI32, cfg, [ + // Zero + { input: i32Bits(0b00000000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000000) }, + + // One + { input: i32Bits(0b00000000000000000000000000000001), expected: i32Bits(0b10000000000000000000000000000000) }, + + // 0's after leading 1 + { input: i32Bits(0b00000000000000000000000000000010), expected: i32Bits(0b01000000000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000000000100), expected: i32Bits(0b00100000000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000000001000), expected: i32Bits(0b00010000000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000000010000), expected: i32Bits(0b00001000000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000000100000), expected: i32Bits(0b00000100000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000001000000), expected: i32Bits(0b00000010000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000010000000), expected: i32Bits(0b00000001000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000100000000), expected: i32Bits(0b00000000100000000000000000000000) }, + { input: i32Bits(0b00000000000000000000001000000000), expected: i32Bits(0b00000000010000000000000000000000) }, + { input: i32Bits(0b00000000000000000000010000000000), expected: i32Bits(0b00000000001000000000000000000000) }, + { input: i32Bits(0b00000000000000000000100000000000), expected: i32Bits(0b00000000000100000000000000000000) }, + { input: i32Bits(0b00000000000000000001000000000000), expected: i32Bits(0b00000000000010000000000000000000) }, + { input: i32Bits(0b00000000000000000010000000000000), expected: i32Bits(0b00000000000001000000000000000000) }, + { input: i32Bits(0b00000000000000000100000000000000), expected: i32Bits(0b00000000000000100000000000000000) }, + { input: i32Bits(0b00000000000000001000000000000000), expected: i32Bits(0b00000000000000010000000000000000) }, + { input: i32Bits(0b00000000000000010000000000000000), expected: i32Bits(0b00000000000000001000000000000000) }, + { input: i32Bits(0b00000000000000100000000000000000), expected: i32Bits(0b00000000000000000100000000000000) }, + { input: i32Bits(0b00000000000001000000000000000000), expected: i32Bits(0b00000000000000000010000000000000) }, + { input: i32Bits(0b00000000000010000000000000000000), expected: i32Bits(0b00000000000000000001000000000000) }, + { input: i32Bits(0b00000000000100000000000000000000), expected: i32Bits(0b00000000000000000000100000000000) }, + { input: i32Bits(0b00000000001000000000000000000000), expected: i32Bits(0b00000000000000000000010000000000) }, + { input: i32Bits(0b00000000010000000000000000000000), expected: i32Bits(0b00000000000000000000001000000000) }, + { input: i32Bits(0b00000000100000000000000000000000), expected: i32Bits(0b00000000000000000000000100000000) }, + { input: i32Bits(0b00000001000000000000000000000000), expected: i32Bits(0b00000000000000000000000010000000) }, + { input: i32Bits(0b00000010000000000000000000000000), expected: i32Bits(0b00000000000000000000000001000000) }, + { input: i32Bits(0b00000100000000000000000000000000), expected: i32Bits(0b00000000000000000000000000100000) }, + { input: i32Bits(0b00001000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000010000) }, + { input: i32Bits(0b00010000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000001000) }, + { input: i32Bits(0b00100000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000100) }, + { input: i32Bits(0b01000000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000010) }, + { input: i32Bits(0b10000000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000001) }, + + // 1's after leading 1 + { input: i32Bits(0b00000000000000000000000000000011), expected: i32Bits(0b11000000000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000000000111), expected: i32Bits(0b11100000000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000000001111), expected: i32Bits(0b11110000000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000000011111), expected: i32Bits(0b11111000000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000000111111), expected: i32Bits(0b11111100000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000001111111), expected: i32Bits(0b11111110000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000011111111), expected: i32Bits(0b11111111000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000111111111), expected: i32Bits(0b11111111100000000000000000000000) }, + { input: i32Bits(0b00000000000000000000001111111111), expected: i32Bits(0b11111111110000000000000000000000) }, + { input: i32Bits(0b00000000000000000000011111111111), expected: i32Bits(0b11111111111000000000000000000000) }, + { input: i32Bits(0b00000000000000000000111111111111), expected: i32Bits(0b11111111111100000000000000000000) }, + { input: i32Bits(0b00000000000000000001111111111111), expected: i32Bits(0b11111111111110000000000000000000) }, + { input: i32Bits(0b00000000000000000011111111111111), expected: i32Bits(0b11111111111111000000000000000000) }, + { input: i32Bits(0b00000000000000000111111111111111), expected: i32Bits(0b11111111111111100000000000000000) }, + { input: i32Bits(0b00000000000000001111111111111111), expected: i32Bits(0b11111111111111110000000000000000) }, + { input: i32Bits(0b00000000000000011111111111111111), expected: i32Bits(0b11111111111111111000000000000000) }, + { input: i32Bits(0b00000000000000111111111111111111), expected: i32Bits(0b11111111111111111100000000000000) }, + { input: i32Bits(0b00000000000001111111111111111111), expected: i32Bits(0b11111111111111111110000000000000) }, + { input: i32Bits(0b00000000000011111111111111111111), expected: i32Bits(0b11111111111111111111000000000000) }, + { input: i32Bits(0b00000000000111111111111111111111), expected: i32Bits(0b11111111111111111111100000000000) }, + { input: i32Bits(0b00000000001111111111111111111111), expected: i32Bits(0b11111111111111111111110000000000) }, + { input: i32Bits(0b00000000011111111111111111111111), expected: i32Bits(0b11111111111111111111111000000000) }, + { input: i32Bits(0b00000000111111111111111111111111), expected: i32Bits(0b11111111111111111111111100000000) }, + { input: i32Bits(0b00000001111111111111111111111111), expected: i32Bits(0b11111111111111111111111110000000) }, + { input: i32Bits(0b00000011111111111111111111111111), expected: i32Bits(0b11111111111111111111111111000000) }, + { input: i32Bits(0b00000111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111100000) }, + { input: i32Bits(0b00001111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111110000) }, + { input: i32Bits(0b00011111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111111000) }, + { input: i32Bits(0b00111111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111111100) }, + { input: i32Bits(0b01111111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111111110) }, + { input: i32Bits(0b11111111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111111111) }, + + // random after leading 1 + { input: i32Bits(0b00000000000000000000000000000110), expected: i32Bits(0b01100000000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000000001101), expected: i32Bits(0b10110000000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000000011101), expected: i32Bits(0b10111000000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000000111001), expected: i32Bits(0b10011100000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000001101111), expected: i32Bits(0b11110110000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000011111111), expected: i32Bits(0b11111111000000000000000000000000) }, + { input: i32Bits(0b00000000000000000000000111101111), expected: i32Bits(0b11110111100000000000000000000000) }, + { input: i32Bits(0b00000000000000000000001111111111), expected: i32Bits(0b11111111110000000000000000000000) }, + { input: i32Bits(0b00000000000000000000011111110001), expected: i32Bits(0b10001111111000000000000000000000) }, + { input: i32Bits(0b00000000000000000000111011011101), expected: i32Bits(0b10111011011100000000000000000000) }, + { input: i32Bits(0b00000000000000000001101101111111), expected: i32Bits(0b11111110110110000000000000000000) }, + { input: i32Bits(0b00000000000000000011111111011111), expected: i32Bits(0b11111011111111000000000000000000) }, + { input: i32Bits(0b00000000000000000101111001110101), expected: i32Bits(0b10101110011110100000000000000000) }, + { input: i32Bits(0b00000000000000001101111011110111), expected: i32Bits(0b11101111011110110000000000000000) }, + { input: i32Bits(0b00000000000000011111111111110011), expected: i32Bits(0b11001111111111111000000000000000) }, + { input: i32Bits(0b00000000000000111111111110111111), expected: i32Bits(0b11111101111111111100000000000000) }, + { input: i32Bits(0b00000000000001111111011111111111), expected: i32Bits(0b11111111111011111110000000000000) }, + { input: i32Bits(0b00000000000011111111111111111111), expected: i32Bits(0b11111111111111111111000000000000) }, + { input: i32Bits(0b00000000000111110101011110111111), expected: i32Bits(0b11111101111010101111100000000000) }, + { input: i32Bits(0b00000000001111101111111111110111), expected: i32Bits(0b11101111111111110111110000000000) }, + { input: i32Bits(0b00000000011111111111010000101111), expected: i32Bits(0b11110100001011111111111000000000) }, + { input: i32Bits(0b00000000111111111111001111111011), expected: i32Bits(0b11011111110011111111111100000000) }, + { input: i32Bits(0b00000001111111011111101111111111), expected: i32Bits(0b11111111110111111011111110000000) }, + { input: i32Bits(0b00000011101011111011110111111011), expected: i32Bits(0b11011111101111011111010111000000) }, + { input: i32Bits(0b00000111111110111111111111111111), expected: i32Bits(0b11111111111111111101111111100000) }, + { input: i32Bits(0b00001111000000011011011110111111), expected: i32Bits(0b11111101111011011000000011110000) }, + { input: i32Bits(0b00011110101111011111111111111111), expected: i32Bits(0b11111111111111111011110101111000) }, + { input: i32Bits(0b00110110111111100111111110111101), expected: i32Bits(0b10111101111111100111111101101100) }, + { input: i32Bits(0b01010111111101111111011111011111), expected: i32Bits(0b11111011111011111110111111101010) }, + { input: i32Bits(0b11100010011110101101101110101111), expected: i32Bits(0b11110101110110110101111001000111) }, + ]); + }); 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 new file mode 100644 index 0000000000..bd40ed4b2a --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts @@ -0,0 +1,79 @@ +export const description = ` +Execution tests for the 'round' builtin function + +S is AbstractFloat, 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. +When e lies halfway between integers k and k+1, the result is k when k is even, +and k+1 when k is odd. +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('round'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('round'), [TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..2f16502921 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts @@ -0,0 +1,100 @@ +export const description = ` +Execution tests for the 'saturate' builtin function + +S is AbstractFloat, 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. +`; + +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 { allInputSources, onlyConstInputSource, run } from '../../expression.js'; + +import { abstractBuiltin, builtin } from './builtin.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`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractBuiltin('saturate'), + [TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('saturate'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('saturate'), [TypeF16], TypeF16, 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 new file mode 100644 index 0000000000..c64f989f42 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts @@ -0,0 +1,253 @@ +export const description = ` +Execution tests for the 'select' builtin function + +T is scalar, abstract numeric type, or vector +@const fn select(f: T, t: T, cond: bool) -> T +Returns t when cond is true, and f otherwise. + +T is scalar or abstract numeric type +@const fn select(f: vecN<T>, t: vecN<T>, cond: vecN<bool>) -> vecN<T> +Component-wise selection. Result component i is evaluated as select(f[i],t[i],cond[i]). +`; + +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, + u32, + False, + True, + bool, + vec2, + vec3, + vec4, + abstractFloat, + TypeAbstractFloat, +} from '../../../../../util/conversion.js'; +import { run, CaseList, allInputSources } from '../../expression.js'; + +import { abstractBuiltin, builtin } from './builtin.js'; + +export const g = makeTestGroup(GPUTest); + +function makeBool(n: number) { + return bool((n & 1) === 1); +} + +type scalarKind = 'b' | 'af' | 'f' | 'h' | 'i' | 'u'; + +const dataType = { + b: { + type: TypeBool, + constructor: makeBool, + }, + af: { + type: TypeAbstractFloat, + constructor: abstractFloat, + }, + f: { + type: TypeF32, + constructor: f32, + }, + h: { + type: TypeF16, + constructor: f16, + }, + i: { + type: TypeI32, + constructor: i32, + }, + u: { + type: TypeU32, + constructor: u32, + }, +}; + +g.test('scalar') + .specURL('https://www.w3.org/TR/WGSL/#logical-builtin-functions') + .desc(`scalar tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('component', ['b', 'af', 'f', 'h', 'i', 'u'] as const) + .combine('overload', ['scalar', 'vec2', 'vec3', 'vec4'] as const) + ) + .beforeAllSubcases(t => { + if (t.params.component === 'h') { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + } + t.skipIf(t.params.component === 'af' && 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; + + // Create the scalar values that will be selected from, either as scalars + // or vectors. + // + // 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)); + // 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 overloads = { + scalar: { + type: componentType, + cases: [ + { input: [c[0], c[1], False], expected: c[0] }, + { input: [c[0], c[1], True], expected: c[1] }, + ], + }, + vec2: { + type: TypeVec(2, componentType), + cases: [ + { input: [v2a, v2b, False], expected: v2a }, + { input: [v2a, v2b, True], expected: v2b }, + ], + }, + vec3: { + type: TypeVec(3, componentType), + cases: [ + { input: [v3a, v3b, False], expected: v3a }, + { input: [v3a, v3b, True], expected: v3b }, + ], + }, + vec4: { + type: TypeVec(4, componentType), + cases: [ + { input: [v4a, v4b, False], expected: v4a }, + { input: [v4a, v4b, True], expected: v4b }, + ], + }, + }; + const overload = overloads[t.params.overload]; + + await run( + t, + t.params.component === 'af' ? abstractBuiltin('select') : builtin('select'), + [overload.type, overload.type, TypeBool], + overload.type, + t.params, + overload.cases + ); + }); + +g.test('vector') + .specURL('https://www.w3.org/TR/WGSL/#logical-builtin-functions') + .desc(`vector tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('component', ['b', 'af', 'f', 'h', 'i', 'u'] as const) + .combine('overload', ['vec2', 'vec3', 'vec4'] as const) + ) + .beforeAllSubcases(t => { + if (t.params.component === 'h') { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + } + t.skipIf(t.params.component === 'af' && 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; + + // 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 T = True; + const F = False; + + let tests: { dataType: VectorType; boolType: VectorType; cases: CaseList }; + + switch (t.params.overload) { + case 'vec2': { + const a = vec2(c[0], c[1]); + const b = vec2(c[4], c[5]); + tests = { + dataType: TypeVec(2, componentType), + boolType: TypeVec(2, TypeBool), + 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) }, + { input: [a, b, vec2(T, F)], expected: vec2(b.x, a.y) }, + { input: [a, b, vec2(T, T)], expected: vec2(b.x, b.y) }, + ], + }; + break; + } + case 'vec3': { + const a = vec3(c[0], c[1], c[2]); + const b = vec3(c[4], c[5], c[6]); + tests = { + dataType: TypeVec(3, componentType), + boolType: TypeVec(3, TypeBool), + 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) }, + { input: [a, b, vec3(F, T, F)], expected: vec3(a.x, b.y, a.z) }, + { input: [a, b, vec3(F, T, T)], expected: vec3(a.x, b.y, b.z) }, + { input: [a, b, vec3(T, F, F)], expected: vec3(b.x, a.y, a.z) }, + { input: [a, b, vec3(T, F, T)], expected: vec3(b.x, a.y, b.z) }, + { input: [a, b, vec3(T, T, F)], expected: vec3(b.x, b.y, a.z) }, + { input: [a, b, vec3(T, T, T)], expected: vec3(b.x, b.y, b.z) }, + ], + }; + 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]); + tests = { + dataType: TypeVec(4, componentType), + boolType: TypeVec(4, TypeBool), + 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) }, + { input: [a, b, vec4(F, F, T, F)], expected: vec4(a.x, a.y, b.z, a.w) }, + { input: [a, b, vec4(F, F, T, T)], expected: vec4(a.x, a.y, b.z, b.w) }, + { input: [a, b, vec4(F, T, F, F)], expected: vec4(a.x, b.y, a.z, a.w) }, + { input: [a, b, vec4(F, T, F, T)], expected: vec4(a.x, b.y, a.z, b.w) }, + { input: [a, b, vec4(F, T, T, F)], expected: vec4(a.x, b.y, b.z, a.w) }, + { input: [a, b, vec4(F, T, T, T)], expected: vec4(a.x, b.y, b.z, b.w) }, + { input: [a, b, vec4(T, F, F, F)], expected: vec4(b.x, a.y, a.z, a.w) }, + { input: [a, b, vec4(T, F, F, T)], expected: vec4(b.x, a.y, a.z, b.w) }, + { input: [a, b, vec4(T, F, T, F)], expected: vec4(b.x, a.y, b.z, a.w) }, + { input: [a, b, vec4(T, F, T, T)], expected: vec4(b.x, a.y, b.z, b.w) }, + { input: [a, b, vec4(T, T, F, F)], expected: vec4(b.x, b.y, a.z, a.w) }, + { input: [a, b, vec4(T, T, F, T)], expected: vec4(b.x, b.y, a.z, b.w) }, + { input: [a, b, vec4(T, T, T, F)], expected: vec4(b.x, b.y, b.z, a.w) }, + { input: [a, b, vec4(T, T, T, T)], expected: vec4(b.x, b.y, b.z, b.w) }, + ], + }; + break; + } + } + + await run( + t, + t.params.component === 'af' ? abstractBuiltin('select') : builtin('select'), + [tests.dataType, tests.dataType, tests.boolType], + tests.dataType, + t.params, + tests.cases + ); + }); 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 new file mode 100644 index 0000000000..a147acf6fb --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts @@ -0,0 +1,109 @@ +export const description = ` +Execution tests for the 'sign' builtin function + +S is AbstractFloat, 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. +`; + +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 { allInputSources, onlyConstInputSource, run } from '../../expression.js'; + +import { abstractBuiltin, builtin } from './builtin.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`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstract_float'); + await run(t, abstractBuiltin('sign'), [TypeAbstractFloat], TypeAbstractFloat, 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) + ) + .unimplemented(); + +g.test('i32') + .specURL('https://www.w3.org/TR/WGSL/#sign-builtin') + .desc(`i32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('i32'); + await run(t, builtin('sign'), [TypeI32], TypeI32, t.params, cases); + }); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#sign-builtin') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('sign'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#sign-builtin') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('sign'), [TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..4ab3ae7a3d --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts @@ -0,0 +1,84 @@ +export const description = ` +Execution tests for the 'sin' builtin function + +S is AbstractFloat, 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. +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc( + ` +f32 tests + +TODO(#792): Decide what the ground-truth is for these tests. [1] +` + ) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('sin'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('sin'), [TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..d9b93a3dc8 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts @@ -0,0 +1,68 @@ +export const description = ` +Execution tests for the 'sinh' builtin function + +S is AbstractFloat, 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. +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..20d2a4edbc --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts @@ -0,0 +1,94 @@ +export const description = ` +Execution tests for the 'smoothstep' builtin function + +S is AbstractFloat, 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. +Component-wise when T is a vector. +For scalar T, the result is t * t * (3.0 - 2.0 * t), where t = clamp((x - low) / (high - low), 0.0, 1.0). +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..a092438043 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts @@ -0,0 +1,68 @@ +export const description = ` +Execution tests for the 'sqrt' builtin function + +S is AbstractFloat, 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. +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .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); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-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); + }); 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 new file mode 100644 index 0000000000..752e2676e6 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts @@ -0,0 +1,87 @@ +export const description = ` +Execution tests for the 'step' builtin function + +S is AbstractFloat, 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. +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('step'), [TypeF32, TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('step'), [TypeF16, TypeF16], TypeF16, t.params, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/storageBarrier.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/storageBarrier.spec.ts new file mode 100644 index 0000000000..f376db4472 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/storageBarrier.spec.ts @@ -0,0 +1,38 @@ +export const description = ` +'storageBarrier' affects memory and atomic operations in the storage address space. + +All synchronization functions execute a control barrier with Acquire/Release memory ordering. +That is, all synchronization functions, and affected memory and atomic operations are ordered +in program order relative to the synchronization function. Additionally, the affected memory +and atomic operations program-ordered before the synchronization function must be visible to +all other threads in the workgroup before any affected memory or atomic operation program-ordered +after the synchronization function is executed by a member of the workgroup. All synchronization +functions use the Workgroup memory scope. All synchronization functions have a Workgroup +execution scope. + +All synchronization functions must only be used in the compute shader stage. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('stage') + .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions') + .desc( + ` +All synchronization functions must only be used in the compute shader stage. +` + ) + .params(u => u.combine('stage', ['vertex', 'fragment', 'compute'] as const)) + .unimplemented(); + +g.test('barrier') + .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions') + .desc( + ` +fn storageBarrier() +` + ) + .unimplemented(); 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 new file mode 100644 index 0000000000..be3bdee046 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts @@ -0,0 +1,78 @@ +export const description = ` +Execution tests for the 'tan' builtin function + +S is AbstractFloat, 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. +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('tan'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('tan'), [TypeF16], TypeF16, t.params, 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 new file mode 100644 index 0000000000..3aca5b924b --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts @@ -0,0 +1,62 @@ +export const description = ` +Execution tests for the 'tanh' builtin function + +S is AbstractFloat, 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. +`; + +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 { builtin } from './builtin.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) + ) + .unimplemented(); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('tanh'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('tanh'), [TypeF16], TypeF16, 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 new file mode 100644 index 0000000000..0ecb9964cf --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts @@ -0,0 +1,160 @@ +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/textureGather.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureGather.spec.ts new file mode 100644 index 0000000000..40b331efab --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureGather.spec.ts @@ -0,0 +1,270 @@ +export const description = ` +Execution tests for the 'textureGather' builtin function + +A texture gather operation reads from a 2D, 2D array, cube, or cube array texture, computing a four-component vector as follows: + * Find the four texels that would be used in a sampling operation with linear filtering, from mip level 0: + - Use the specified coordinate, array index (when present), and offset (when present). + - The texels are adjacent, forming a square, when considering their texture space coordinates (u,v). + - Selected texels at the texture edge, cube face edge, or cube corners are handled as in ordinary texture sampling. + * For each texel, read one channel and convert it into a scalar value. + - For non-depth textures, a zero-based component parameter specifies the channel to use. + * If the texture format supports the specified channel, i.e. has more than component channels: + - Yield scalar value v[component] when the texel value is v. + * Otherwise: + - Yield 0.0 when component is 1 or 2. + - Yield 1.0 when component is 3 (the alpha channel). + - For depth textures, yield the texel value. (Depth textures only have one channel.) + * Yield the four-component vector, arranging scalars produced by the previous step into components according to the relative coordinates of the texels, as follows: + - Result component Relative texel coordinate + x (umin,vmax) + y (umax,vmax) + z (umax,vmin) + w (umin,vmin) +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; + +import { generateCoordBoundaries, generateOffsets } from './utils.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('sampled_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturegather') + .desc( + ` +C: i32, u32 +T: i32, u32, f32 + +fn textureGather(component: C, t: texture_2d<T>, s: sampler, coords: vec2<f32>) -> vec4<T> +fn textureGather(component: C, t: texture_2d<T>, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<T> + +Parameters: + * component: + - The index of the channel to read from the selected texels. + - When provided, the component expression must a creation-time expression (e.g. 1). + - Its value must be at least 0 and at most 3. Values outside of this range will result in a shader-creation error. + * t: The sampled texture to read from + * s: The sampler type + * coords: The texture coordinates + * offset: + - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + This offset is applied before applying any texture wrapping modes. + - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + - Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('T', ['f32-only', 'i32', 'u32'] as const) + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('coords', generateCoordBoundaries(2)) + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('sampled_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturegather') + .desc( + ` +C: i32, u32 +T: i32, u32, f32 + +fn textureGather(component: C, t: texture_cube<T>, s: sampler, coords: vec3<f32>) -> vec4<T> + +Parameters: + * component: + - The index of the channel to read from the selected texels. + - When provided, the component expression must a creation-time expression (e.g. 1). + - Its value must be at least 0 and at most 3. Values outside of this range will result in a shader-creation error. + * t: The sampled texture to read from + * s: The sampler type + * coords: The texture coordinates +` + ) + .paramsSubcasesOnly(u => + u + .combine('T', ['f32-only', 'i32', 'u32'] as const) + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('coords', generateCoordBoundaries(3)) + ) + .unimplemented(); + +g.test('sampled_array_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturegather') + .desc( + ` +C: i32, u32 +T: i32, u32, f32 + +fn textureGather(component: C, t: texture_2d_array<T>, s: sampler, coords: vec2<f32>, array_index: C) -> vec4<T> +fn textureGather(component: C, t: texture_2d_array<T>, s: sampler, coords: vec2<f32>, array_index: C, offset: vec2<i32>) -> vec4<T> + +Parameters: + * component: + - The index of the channel to read from the selected texels. + - When provided, the component expression must a creation-time expression (e.g. 1). + - Its value must be at least 0 and at most 3. Values outside of this range will result in a shader-creation error. + * t: The sampled texture to read from + * s: The sampler type + * coords: The texture coordinates + * array_index: The 0-based texture array index + * offset: + - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + This offset is applied before applying any texture wrapping modes. + - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + - Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('T', ['f32-only', 'i32', 'u32'] as const) + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('coords', generateCoordBoundaries(2)) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('sampled_array_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturegather') + .desc( + ` +C: i32, u32 +T: i32, u32, f32 + +fn textureGather(component: C, t: texture_cube_array<T>, s: sampler, coords: vec3<f32>, array_index: C) -> vec4<T> + +Parameters: + * component: + - The index of the channel to read from the selected texels. + - When provided, the component expression must a creation-time expression (e.g. 1). + - Its value must be at least 0 and at most 3. Values outside of this range will result in a shader-creation error. + * t: The sampled texture to read from + * s: The sampler type + * coords: The texture coordinates + * array_index: The 0-based texture array index +` + ) + .paramsSubcasesOnly( + u => + u + .combine('T', ['f32-only', 'i32', 'u32'] as const) + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('coords', generateCoordBoundaries(3)) + /* array_index not param'd as out-of-bounds is implementation specific */ + ) + .unimplemented(); + +g.test('depth_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturegather') + .desc( + ` +fn textureGather(t: texture_depth_2d, s: sampler, coords: vec2<f32>) -> vec4<f32> +fn textureGather(t: texture_depth_2d, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32> + +Parameters: + * t: The depth texture to read from + * s: The sampler type + * coords: The texture coordinates + * offset: + - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + This offset is applied before applying any texture wrapping modes. + - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + - Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(2)) + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('depth_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturegather') + .desc( + ` +fn textureGather(t: texture_depth_cube, s: sampler, coords: vec3<f32>) -> vec4<f32> + +Parameters: + * t: The depth texture to read from + * s: The sampler type + * coords: The texture coordinates +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(3)) + ) + .unimplemented(); + +g.test('depth_array_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturegather') + .desc( + ` +C: i32, u32 + +fn textureGather(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C) -> vec4<f32> +fn textureGather(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C, offset: vec2<i32>) -> vec4<f32> + +Parameters: + * t: The depth texture to read from + * s: The sampler type + * coords: The texture coordinates + * array_index: The 0-based texture array index + * offset: + - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + This offset is applied before applying any texture wrapping modes. + - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + - Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('coords', generateCoordBoundaries(2)) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('depth_array_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturegather') + .desc( + ` +C: i32, u32 + +fn textureGather(t: texture_depth_cube_array, s: sampler, coords: vec3<f32>, array_index: C) -> vec4<f32> + +Parameters: + * t: The depth texture to read from + * s: The sampler type + * coords: The texture coordinates + * array_index: The 0-based texture array index +` + ) + .paramsSubcasesOnly( + u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('coords', generateCoordBoundaries(3)) + /* array_index not param'd as out-of-bounds is implementation specific */ + ) + .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureGatherCompare.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureGatherCompare.spec.ts new file mode 100644 index 0000000000..c743883ce8 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureGatherCompare.spec.ts @@ -0,0 +1,134 @@ +export const description = ` +Execution tests for the 'textureGatherCompare' builtin function + +A texture gather compare operation performs a depth comparison on four texels in a depth texture and collects the results into a single vector, as follows: + * Find the four texels that would be used in a depth sampling operation with linear filtering, from mip level 0: + - Use the specified coordinate, array index (when present), and offset (when present). + - The texels are adjacent, forming a square, when considering their texture space coordinates (u,v). + - Selected texels at the texture edge, cube face edge, or cube corners are handled as in ordinary texture sampling. + * For each texel, perform a comparison against the depth reference value, yielding a 0.0 or 1.0 value, as controlled by the comparison sampler parameters. + * Yield the four-component vector where the components are the comparison results with the texels with relative texel coordinates as follows: + + Result component Relative texel coordinate + x (umin,vmax) + y (umax,vmax) + z (umax,vmin) + w (umin,vmin) +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; + +import { generateCoordBoundaries, generateOffsets } from './utils.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('array_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturegathercompare') + .desc( + ` +C: i32, u32 + +fn textureGatherCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32) -> vec4<f32> +fn textureGatherCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32, offset: vec2<i32>) -> vec4<f32> + +Parameters: + * t: The depth texture to read from + * s: The sampler_comparison + * coords: The texture coordinates + * array_index: The 0-based array index. + * depth_ref: The reference value to compare the sampled depth value against + * offset: + - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + This offset is applied before applying any texture wrapping modes. + - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + - Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4]) + .combine('coords', generateCoordBoundaries(2)) + .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const) + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('array_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturegathercompare') + .desc( + ` +C: i32, u32 + +fn textureGatherCompare(t: texture_depth_cube_array, s: sampler_comparison, coords: vec3<f32>, array_index: C, depth_ref: f32) -> vec4<f32> + +Parameters: + * t: The depth texture to read from + * s: The sampler_comparison + * coords: The texture coordinates + * array_index: The 0-based array index. + * depth_ref: The reference value to compare the sampled depth value against +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4]) + .combine('coords', generateCoordBoundaries(3)) + .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const) + ) + .unimplemented(); + +g.test('sampled_array_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturegathercompare') + .desc( + ` +fn textureGatherCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> vec4<f32> +fn textureGatherCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> vec4<f32> + +Parameters: + * t: The depth texture to read from + * s: The sampler_comparison + * coords: The texture coordinates + * depth_ref: The reference value to compare the sampled depth value against + * offset: + - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + This offset is applied before applying any texture wrapping modes. + - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + - Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(2)) + .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const) + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('sampled_array_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturegathercompare') + .desc( + ` +fn textureGatherCompare(t: texture_depth_cube, s: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> vec4<f32> + +Parameters: + * t: The depth texture to read from + * s: The sampler_comparison + * coords: The texture coordinates + * depth_ref: The reference value to compare the sampled depth value against +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(3)) + .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const) + ) + .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.ts new file mode 100644 index 0000000000..30cc4fff52 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.ts @@ -0,0 +1,185 @@ +export const description = ` +Execution tests for the 'textureLoad' builtin function + +Reads a single texel from a texture without sampling or filtering. + +Returns the unfiltered texel data. + +An out of bounds access occurs if: + * any element of coords is outside the range [0, textureDimensions(t, level)) for the corresponding element, or + * array_index is outside the range [0, textureNumLayers(t)), or + * level is outside the range [0, textureNumLevels(t)) + +If an out of bounds access occurs, the built-in function returns one of: + * The data for some texel within bounds of the texture + * A vector (0,0,0,0) or (0,0,0,1) of the appropriate type for non-depth textures + * 0.0 for depth textures +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; + +import { generateCoordBoundaries } from './utils.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('sampled_1d') + .specURL('https://www.w3.org/TR/WGSL/#textureload') + .desc( + ` +C is i32 or u32 + +fn textureLoad(t: texture_1d<T>, coords: C, level: C) -> vec4<T> + +Parameters: + * t: The sampled texture to read from + * coords: The 0-based texel coordinate + * level: The mip level, with level 0 containing a full size version of the texture +` + ) + .params(u => + u + .combine('C', ['i32', 'u32'] as const) + .combine('coords', generateCoordBoundaries(1)) + .combine('level', [-1, 0, `numlevels-1`, `numlevels`] as const) + ) + .unimplemented(); + +g.test('sampled_2d') + .specURL('https://www.w3.org/TR/WGSL/#textureload') + .desc( + ` +C is i32 or u32 + +fn textureLoad(t: texture_2d<T>, coords: vec2<C>, level: C) -> vec4<T> + +Parameters: + * t: The sampled texture to read from + * coords: The 0-based texel coordinate + * level: The mip level, with level 0 containing a full size version of the texture +` + ) + .params(u => + u + .combine('C', ['i32', 'u32'] as const) + .combine('coords', generateCoordBoundaries(2)) + .combine('level', [-1, 0, `numlevels-1`, `numlevels`] as const) + ) + .unimplemented(); + +g.test('sampled_3d') + .specURL('https://www.w3.org/TR/WGSL/#textureload') + .desc( + ` +C is i32 or u32 + +fn textureLoad(t: texture_3d<T>, coords: vec3<C>, level: C) -> vec4<T> + +Parameters: + * t: The sampled texture to read from + * coords: The 0-based texel coordinate + * level: The mip level, with level 0 containing a full size version of the texture +` + ) + .params(u => + u + .combine('C', ['i32', 'u32'] as const) + .combine('coords', generateCoordBoundaries(3)) + .combine('level', [-1, 0, `numlevels-1`, `numlevels`] as const) + ) + .unimplemented(); + +g.test('multisampled') + .specURL('https://www.w3.org/TR/WGSL/#textureload') + .desc( + ` +C is i32 or u32 + +fn textureLoad(t: texture_multisampled_2d<T>, coords: vec2<C>, sample_index: C)-> vec4<T> +fn textureLoad(t: texture_depth_multisampled_2d, coords: vec2<C>, sample_index: C)-> f32 + +Parameters: + * t: The sampled texture to read from + * coords: The 0-based texel coordinate + * sample_index: The 0-based sample index of the multisampled texture +` + ) + .params(u => + u + .combine('texture_type', [ + 'texture_multisampled_2d', + 'texture_depth_multisampled_2d', + ] as const) + .beginSubcases() + .combine('C', ['i32', 'u32'] as const) + .combine('coords', generateCoordBoundaries(2)) + .combine('sample_index', [-1, 0, `sampleCount-1`, `sampleCount`] as const) + ) + .unimplemented(); + +g.test('depth') + .specURL('https://www.w3.org/TR/WGSL/#textureload') + .desc( + ` +C is i32 or u32 + +fn textureLoad(t: texture_depth_2d, coords: vec2<C>, level: C) -> f32 + +Parameters: + * t: The sampled texture to read from + * coords: The 0-based texel coordinate + * level: The mip level, with level 0 containing a full size version of the texture +` + ) + .paramsSubcasesOnly(u => + u + .combine('C', ['i32', 'u32'] as const) + .combine('coords', generateCoordBoundaries(2)) + .combine('level', [-1, 0, `numlevels-1`, `numlevels`] as const) + ) + .unimplemented(); + +g.test('external') + .specURL('https://www.w3.org/TR/WGSL/#textureload') + .desc( + ` +C is i32 or u32 + +fn textureLoad(t: texture_external, coords: vec2<C>) -> vec4<f32> + +Parameters: + * t: The sampled texture to read from + * coords: The 0-based texel coordinate +` + ) + .paramsSubcasesOnly(u => + u.combine('C', ['i32', 'u32'] as const).combine('coords', generateCoordBoundaries(2)) + ) + .unimplemented(); + +g.test('arrayed') + .specURL('https://www.w3.org/TR/WGSL/#textureload') + .desc( + ` +C is i32 or u32 + +fn textureLoad(t: texture_2d_array<T>, coords: vec2<C>, array_index: C, level: C) -> vec4<T> +fn textureLoad(t: texture_depth_2d_array, coords: vec2<C>, array_index: C, level: C) -> f32 + +Parameters: + * t: The sampled texture to read from + * coords: The 0-based texel coordinate + * array_index: The 0-based texture array index + * level: The mip level, with level 0 containing a full size version of the texture +` + ) + .params(u => + u + .combine('texture_type', ['texture_2d_array', 'texture_depth_2d_array'] as const) + .beginSubcases() + .combine('C', ['i32', 'u32'] as const) + .combine('coords', generateCoordBoundaries(2)) + .combine('array_index', [-1, 0, `numlayers-1`, `numlayers`] as const) + .combine('level', [-1, 0, `numlevels-1`, `numlevels`] as const) + ) + .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLayers.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLayers.spec.ts new file mode 100644 index 0000000000..b845301161 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLayers.spec.ts @@ -0,0 +1,100 @@ +export const description = ` +Execution tests for the 'textureNumLayers' builtin function + +Returns the number of layers (elements) of an array texture. +`; + +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/#texturenumlayers') + .desc( + ` +T, a sampled type. + +fn textureNumLayers(t: texture_2d_array<T>) -> u32 +fn textureNumLayers(t: texture_cube_array<T>) -> u32 + +Parameters + * t The sampled array texture. +` + ) + .params(u => + u + .combine('texture_type', ['texture_2d_array', 'texture_cube_array'] as const) + .beginSubcases() + .combine('sampled_type', ['f32-only', 'i32', 'u32'] as const) + ) + .unimplemented(); + +g.test('arrayed') + .specURL('https://www.w3.org/TR/WGSL/#texturenumlayers') + .desc( + ` +fn textureNumLayers(t: texture_depth_2d_array) -> u32 +fn textureNumLayers(t: texture_depth_cube_array) -> u32 + +Parameters + * t The depth array texture. +` + ) + .params(u => + u.combine('texture_type', ['texture_depth_2d_array', 'texture_depth_cube_array'] as const) + ) + .unimplemented(); + +g.test('storage') + .specURL('https://www.w3.org/TR/WGSL/#texturenumlayers') + .desc( + ` +F: rgba8unorm + rgba8snorm + rgba8uint + rgba8sint + rgba16uint + rgba16sint + rgba16float + r32uint + r32sint + r32float + rg32uint + rg32sint + rg32float + rgba32uint + rgba32sint + rgba32float +A: read, write, read_write + +fn textureNumLayers(t: texture_storage_2d_array<F,A>) -> u32 + +Parameters + * t The sampled storage array texture. +` + ) + .params(u => + u + .beginSubcases() + .combine('texel_format', [ + 'rgba8unorm', + 'rgba8snorm', + 'rgba8uint', + 'rgba8sint', + 'rgba16uint', + 'rgba16sint', + 'rgba16float', + 'r32uint', + 'r32sint', + 'r32float', + 'rg32uint', + 'rg32sint', + 'rg32float', + 'rgba32uint', + 'rgba32sint', + 'rgba32float', + ] as const) + .combine('access_mode', ['read', 'write', 'read_write'] as const) + ) + .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLevels.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLevels.spec.ts new file mode 100644 index 0000000000..4204397b23 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLevels.spec.ts @@ -0,0 +1,65 @@ +export const description = ` +Execution tests for the 'textureNumLevels' builtin function + +Returns the number of mip levels of a texture. +`; + +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/#texturenumlevels') + .desc( + ` +T, a sampled type. + +fn textureNumLevels(t: texture_1d<T>) -> u32 +fn textureNumLevels(t: texture_2d<T>) -> u32 +fn textureNumLevels(t: texture_2d_array<T>) -> u32 +fn textureNumLevels(t: texture_3d<T>) -> u32 +fn textureNumLevels(t: texture_cube<T>) -> u32 +fn textureNumLevels(t: texture_cube_array<T>) -> u32 + +Parameters + * t The sampled array texture. +` + ) + .params(u => + u + .combine('texture_type', [ + 'texture_1d', + 'texture_2d', + 'texture_2d_array', + 'texture_3d', + 'texture_cube', + 'texture_cube_array`', + ] as const) + .beginSubcases() + .combine('sampled_type', ['f32-only', 'i32', 'u32'] as const) + ) + .unimplemented(); + +g.test('depth') + .specURL('https://www.w3.org/TR/WGSL/#texturenumlevels') + .desc( + ` +fn textureNumLevels(t: texture_depth_2d) -> u32 +fn textureNumLevels(t: texture_depth_2d_array) -> u32 +fn textureNumLevels(t: texture_depth_cube) -> u32 +fn textureNumLevels(t: texture_depth_cube_array) -> u32 + +Parameters + * t The depth array texture. +` + ) + .params(u => + u.combine('texture_type', [ + 'texture_depth_2d', + 'texture_depth_2d_array', + 'texture_depth_cube', + 'texture_depth_cube_array', + ] as const) + ) + .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumSamples.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumSamples.spec.ts new file mode 100644 index 0000000000..26bda6cd48 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumSamples.spec.ts @@ -0,0 +1,37 @@ +export const description = ` +Execution tests for the 'textureNumSamples' builtin function + +Returns the number samples per texel in a multisampled texture. +`; + +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/#texturenumsamples') + .desc( + ` +T, a sampled type. + +fn textureNumSamples(t: texture_multisampled_2d<T>) -> u32 + +Parameters + * t The multisampled texture. +` + ) + .params(u => u.beginSubcases().combine('sampled_type', ['f32-only', 'i32', 'u32'] as const)) + .unimplemented(); + +g.test('depth') + .specURL('https://www.w3.org/TR/WGSL/#texturenumsamples') + .desc( + ` +fn textureNumSamples(t: texture_depth_multisampled_2d) -> u32 + +Parameters + * t The multisampled 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 new file mode 100644 index 0000000000..f5b01dfc63 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts @@ -0,0 +1,273 @@ +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 { 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(); + +g.test('control_flow') + .specURL('https://www.w3.org/TR/WGSL/#texturesample') + .desc( + ` +Tests that 'textureSample' can only be called in uniform control flow. +` + ) + .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const)) + .unimplemented(); + +g.test('sampled_1d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesample') + .desc( + ` +fn textureSample(t: texture_1d<f32>, s: sampler, coords: f32) -> vec4<f32> + +Parameters: + * t The sampled, depth, or external texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const) + .combine('coords', generateCoordBoundaries(1)) + ) + .unimplemented(); + +g.test('sampled_2d_coords') + .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> + +Parameters: + * t The sampled, depth, or external texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const) + .combine('coords', generateCoordBoundaries(2)) + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('sampled_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesample') + .desc( + ` +fn textureSample(t: texture_3d<f32>, s: sampler, coords: vec3<f32>) -> vec4<f32> +fn textureSample(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, offset: vec3<i32>) -> vec4<f32> +fn textureSample(t: texture_cube<f32>, s: sampler, coords: vec3<f32>) -> vec4<f32> + +Parameters: + * t The sampled, depth, or external texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .params(u => + u + .combine('texture_type', ['texture_3d', 'texture_cube'] as const) + .beginSubcases() + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const) + .combine('coords', generateCoordBoundaries(3)) + .combine('offset', generateOffsets(3)) + ) + .unimplemented(); + +g.test('depth_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesample') + .desc( + ` +fn textureSample(t: texture_depth_2d, s: sampler, coords: vec2<f32>) -> f32 +fn textureSample(t: texture_depth_2d, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> f32 + +Parameters: + * t The sampled, depth, or external texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const) + .combine('coords', generateCoordBoundaries(2)) + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('sampled_array_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesample') + .desc( + ` +C is i32 or u32 + +fn textureSample(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C) -> vec4<f32> +fn textureSample(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, offset: vec2<i32>) -> vec4<f32> + +Parameters: + * t The sampled, depth, or external texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * array_index The 0-based texture array index to sample. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const) + .combine('coords', generateCoordBoundaries(2)) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('sampled_array_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesample') + .desc( + ` +C is i32 or u32 + +fn textureSample(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: C) -> vec4<f32> + +Parameters: + * t The sampled, depth, or external texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * array_index The 0-based texture array index to sample. +` + ) + .paramsSubcasesOnly( + u => + u + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const) + .combine('coords', generateCoordBoundaries(3)) + /* array_index not param'd as out-of-bounds is implementation specific */ + ) + .unimplemented(); + +g.test('depth_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesample') + .desc( + ` +fn textureSample(t: texture_depth_cube, s: sampler, coords: vec3<f32>) -> f32 + +Parameters: + * t The sampled, depth, or external texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const) + .combine('coords', generateCoordBoundaries(3)) + ) + .unimplemented(); + +g.test('depth_array_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesample') + .desc( + ` +C is i32 or u32 + +fn textureSample(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C) -> f32 +fn textureSample(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C, offset: vec2<i32>) -> f32 + +Parameters: + * t The sampled, depth, or external texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * array_index The 0-based texture array index to sample. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const) + .combine('coords', generateCoordBoundaries(2)) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('depth_array_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesample') + .desc( + ` +C is i32 or u32 + +fn textureSample(t: texture_depth_cube_array, s: sampler, coords: vec3<f32>, array_index: C) -> f32 + +Parameters: + * t The sampled, depth, or external texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * array_index The 0-based texture array index to sample. +` + ) + .paramsSubcasesOnly( + u => + u + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const) + .combine('coords', generateCoordBoundaries(3)) + /* array_index not param'd as out-of-bounds is implementation specific */ + ) + .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 new file mode 100644 index 0000000000..786bce4830 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts @@ -0,0 +1,163 @@ +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'; +import { GPUTest } from '../../../../../gpu_test.js'; + +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( + ` +fn textureSampleBias(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, bias: f32) -> vec4<f32> +fn textureSampleBias(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, bias: f32, offset: vec2<i32>) -> vec4<f32> + +Parameters: + * t: The sampled texture to read from + * s: The sampler type + * coords: The texture coordinates + * bias: The bias to apply to the mip level before sampling. bias must be between -16.0 and 15.99. + * offset: + - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + This offset is applied before applying any texture wrapping modes. + - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + - Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(2)) + .combine('bias', [-16.1, -16, 0, 1, 15.99, 16] as const) + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('sampled_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias') + .desc( + ` +fn textureSampleBias(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, bias: f32) -> vec4<f32> +fn textureSampleBias(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, bias: f32, offset: vec3<i32>) -> vec4<f32> +fn textureSampleBias(t: texture_cube<f32>, s: sampler, coords: vec3<f32>, bias: f32) -> vec4<f32> + +Parameters: + * t: The sampled texture to read from + * s: The sampler type + * coords: The texture coordinates + * bias: The bias to apply to the mip level before sampling. bias must be between -16.0 and 15.99. + * offset: + - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + This offset is applied before applying any texture wrapping modes. + - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + - Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .params(u => + u + .combine('texture_type', ['texture_3d', 'texture_cube'] as const) + .beginSubcases() + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(3)) + .combine('bias', [-16.1, -16, 0, 1, 15.99, 16] as const) + .combine('offset', generateOffsets(3)) + ) + .unimplemented(); + +g.test('arrayed_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias') + .desc( + ` +C: i32, u32 + +fn textureSampleBias(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, bias: f32) -> vec4<f32> +fn textureSampleBias(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, bias: f32, offset: vec2<i32>) -> vec4<f32> + +Parameters: + * t: The sampled texture to read from + * s: The sampler type + * coords: The texture coordinates + * array_index: The 0-based texture array index to sample. + * bias: The bias to apply to the mip level before sampling. bias must be between -16.0 and 15.99. + * offset: + - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + This offset is applied before applying any texture wrapping modes. + - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + - Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(2)) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('bias', [-16.1, -16, 0, 1, 15.99, 16] as const) + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('arrayed_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias') + .desc( + ` +C: i32, u32 + +fn textureSampleBias(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: C, bias: f32) -> vec4<f32> + +Parameters: + * t: The sampled texture to read from + * s: The sampler type + * coords: The texture coordinates + * array_index: The 0-based texture array index to sample. + * bias: The bias to apply to the mip level before sampling. bias must be between -16.0 and 15.99. + * offset: + - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + This offset is applied before applying any texture wrapping modes. + - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + - Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(3)) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('bias', [-16.1, -16, 0, 1, 15.99, 16] as const) + ) + .unimplemented(); 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 new file mode 100644 index 0000000000..9f723fac2e --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts @@ -0,0 +1,145 @@ +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'; +import { GPUTest } from '../../../../../gpu_test.js'; + +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( + ` +fn textureSampleCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> f32 +fn textureSampleCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> f32 + +Parameters: + * t The depth texture to sample. + * s The sampler_comparision type. + * coords The texture coordinates used for sampling. + * depth_ref The reference value to compare the sampled depth value against. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(2)) + .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const) + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare') + .desc( + ` +fn textureSampleCompare(t: texture_depth_cube, s: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> f32 + +Parameters: + * t The depth texture to sample. + * s The sampler_comparision type. + * coords The texture coordinates used for sampling. + * depth_ref The reference value to compare the sampled depth value against. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(3)) + .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const) + ) + .unimplemented(); + +g.test('arrayed_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare') + .desc( + ` +C is i32 or u32 + +fn textureSampleCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32) -> f32 +fn textureSampleCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32, offset: vec2<i32>) -> f32 + +Parameters: + * t The depth texture to sample. + * s The sampler_comparision type. + * coords The texture coordinates used for sampling. + * array_index: The 0-based texture array index to sample. + * depth_ref The reference value to compare the sampled depth value against. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(2)) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const) + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('arrayed_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare') + .desc( + ` +C is i32 or u32 + +fn textureSampleCompare(t: texture_depth_cube_array, s: sampler_comparison, coords: vec3<f32>, array_index: C, depth_ref: f32) -> f32 + +Parameters: + * t The depth texture to sample. + * s The sampler_comparision type. + * coords The texture coordinates used for sampling. + * array_index: The 0-based texture array index to sample. + * depth_ref The reference value to compare the sampled depth value against. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(3)) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const) + ) + .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel.spec.ts new file mode 100644 index 0000000000..500df8a6ec --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel.spec.ts @@ -0,0 +1,149 @@ +export const description = ` +Samples a depth texture and compares the sampled depth values against a reference value. + +The textureSampleCompareLevel function is the same as textureSampleCompare, except that: + + * textureSampleCompareLevel always samples texels from mip level 0. + * The function does not compute derivatives. + * There is no requirement for textureSampleCompareLevel to be invoked in uniform control flow. + * textureSampleCompareLevel may be invoked in any shader stage. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; + +import { generateCoordBoundaries, generateOffsets } from './utils.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('stage') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel') + .desc( + ` +Tests that 'textureSampleCompareLevel' maybe called in any shader stage. +` + ) + .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const)) + .unimplemented(); + +g.test('control_flow') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel') + .desc( + ` +Tests that 'textureSampleCompareLevel' maybe called in non-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/#texturesamplecomparelevel') + .desc( + ` +fn textureSampleCompareLevel(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> f32 +fn textureSampleCompareLevel(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> f32 + +Parameters: + * t The depth texture to sample. + * s The sampler_comparision type. + * coords The texture coordinates used for sampling. + * depth_ref The reference value to compare the sampled depth value against. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(2)) + .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const) + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel') + .desc( + ` +fn textureSampleCompareLevel(t: texture_depth_cube, s: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> f32 + +Parameters: + * t The depth texture to sample. + * s The sampler_comparision type. + * coords The texture coordinates used for sampling. + * depth_ref The reference value to compare the sampled depth value against. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(3)) + .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const) + ) + .unimplemented(); + +g.test('arrayed_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel') + .desc( + ` +C is i32 or u32 + +fn textureSampleCompareLevel(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32) -> f32 +fn textureSampleCompareLevel(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32, offset: vec2<i32>) -> f32 + +Parameters: + * t The depth texture to sample. + * s The sampler_comparision type. + * coords The texture coordinates used for sampling. + * array_index: The 0-based texture array index to sample. + * depth_ref The reference value to compare the sampled depth value against. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(2)) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const) + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('arrayed_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel') + .desc( + ` +C is i32 or u32 + +fn textureSampleCompareLevel(t: texture_depth_cube_array, s: sampler_comparison, coords: vec3<f32>, array_index: C, depth_ref: f32) -> f32 + +Parameters: + * t The depth texture to sample. + * s The sampler_comparision type. + * coords The texture coordinates used for sampling. + * array_index: The 0-based texture array index to sample. + * depth_ref The reference value to compare the sampled depth value against. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(3)) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const) + ) + .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleGrad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleGrad.spec.ts new file mode 100644 index 0000000000..e0d754ece3 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleGrad.spec.ts @@ -0,0 +1,136 @@ +export const description = ` +Samples a texture using explicit gradients. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; + +import { generateCoordBoundaries, generateOffsets } from './utils.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('sampled_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplegrad') + .desc( + ` +fn textureSampleGrad(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, ddx: vec2<f32>, ddy: vec2<f32>) -> vec4<f32> +fn textureSampleGrad(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, ddx: vec2<f32>, ddy: vec2<f32>, offset: vec2<i32>) -> vec4<f32> + +Parameters: + * t The sampled texture. + * s The sampler type. + * coords The texture coordinates used for sampling. + * ddx The x direction derivative vector used to compute the sampling locations + * ddy The y direction derivative vector used to compute the sampling locations + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(2)) + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('sampled_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplegrad') + .desc( + ` +fn textureSampleGrad(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32> +fn textureSampleGrad(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>, offset: vec3<i32>) -> vec4<f32> +fn textureSampleGrad(t: texture_cube<f32>, s: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32> + +Parameters: + * t The sampled texture. + * s The sampler type. + * ddx The x direction derivative vector used to compute the sampling locations + * ddy The y direction derivative vector used to compute the sampling locations + * coords The texture coordinates used for sampling. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(3)) + .combine('offset', generateOffsets(3)) + ) + .unimplemented(); + +g.test('sampled_array_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplegrad') + .desc( + ` +C is i32 or u32 + +fn textureSampleGrad(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, ddx: vec2<f32>, ddy: vec2<f32>) -> vec4<f32> +fn textureSampleGrad(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, ddx: vec2<f32>, ddy: vec2<f32>, offset: vec2<i32>) -> vec4<f32> + +Parameters: + * t The sampled texture. + * s The sampler type. + * coords The texture coordinates used for sampling. + * array_index The 0-based texture array index to sample. + * ddx The x direction derivative vector used to compute the sampling locations + * ddy The y direction derivative vector used to compute the sampling locations + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('coords', generateCoordBoundaries(2)) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('offset', generateOffsets(2)) + ) + .unimplemented(); + +g.test('sampled_array_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplegrad') + .desc( + ` +C is i32 or u32 + +fn textureSampleGrad(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: C, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32> + +Parameters: + * t The sampled texture. + * s The sampler type. + * coords The texture coordinates used for sampling. + * array_index The 0-based texture array index to sample. + * ddx The x direction derivative vector used to compute the sampling locations + * ddy The y direction derivative vector used to compute the sampling locations + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('coords', generateCoordBoundaries(3)) + ) + .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleLevel.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleLevel.spec.ts new file mode 100644 index 0000000000..f8073c65d6 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleLevel.spec.ts @@ -0,0 +1,274 @@ +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 { generateCoordBoundaries, generateOffsets } from './utils.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('sampled_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel') + .desc( + ` +fn textureSampleLevel(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, level: f32) -> vec4<f32> +fn textureSampleLevel(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, level: f32, offset: vec2<i32>) -> vec4<f32> + +Parameters: + * t The sampled or depth texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * level + * The mip level, with level 0 containing a full size version of the texture. + * For the functions where level is a f32, fractional values may interpolate between + two levels if the format is filterable according to the Texture Format Capabilities. + * When not specified, mip level 0 is sampled. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(2)) + .combine('offset', generateOffsets(2)) + .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const) + ) + .unimplemented(); + +g.test('sampled_array_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel') + .desc( + ` +C is i32 or u32 + +fn textureSampleLevel(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, level: f32) -> vec4<f32> +fn textureSampleLevel(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, level: f32, offset: vec2<i32>) -> vec4<f32> + +Parameters: + * t The sampled or depth texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * array_index The 0-based texture array index to sample. + * level + * The mip level, with level 0 containing a full size version of the texture. + * For the functions where level is a f32, fractional values may interpolate between + two levels if the format is filterable according to the Texture Format Capabilities. + * When not specified, mip level 0 is sampled. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('coords', generateCoordBoundaries(2)) + .combine('offset', generateOffsets(2)) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const) + ) + .unimplemented(); + +g.test('sampled_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel') + .desc( + ` +fn textureSampleLevel(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, level: f32) -> vec4<f32> +fn textureSampleLevel(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, level: f32, offset: vec3<i32>) -> vec4<f32> +fn textureSampleLevel(t: texture_cube<f32>, s: sampler, coords: vec3<f32>, level: f32) -> vec4<f32> + +Parameters: + * t The sampled or depth texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * level + * The mip level, with level 0 containing a full size version of the texture. + * For the functions where level is a f32, fractional values may interpolate between + two levels if the format is filterable according to the Texture Format Capabilities. + * When not specified, mip level 0 is sampled. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .params(u => + u + .combine('texture_type', ['texture_3d', 'texture_cube'] as const) + .beginSubcases() + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('coords', generateCoordBoundaries(3)) + .combine('offset', generateOffsets(3)) + .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const) + ) + .unimplemented(); + +g.test('sampled_array_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel') + .desc( + ` +C is i32 or u32 + +fn textureSampleLevel(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: C, level: f32) -> vec4<f32> + +Parameters: + * t The sampled or depth texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * array_index The 0-based texture array index to sample. + * level + * The mip level, with level 0 containing a full size version of the texture. + * For the functions where level is a f32, fractional values may interpolate between + two levels if the format is filterable according to the Texture Format Capabilities. + * When not specified, mip level 0 is sampled. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('coords', generateCoordBoundaries(3)) + .combine('offset', generateOffsets(3)) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const) + ) + .unimplemented(); + +g.test('depth_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel') + .desc( + ` +C is i32 or u32 + +fn textureSampleLevel(t: texture_depth_2d, s: sampler, coords: vec2<f32>, level: C) -> f32 +fn textureSampleLevel(t: texture_depth_2d, s: sampler, coords: vec2<f32>, level: C, offset: vec2<i32>) -> f32 + +Parameters: + * t The sampled or depth texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * level + * The mip level, with level 0 containing a full size version of the texture. + * For the functions where level is a f32, fractional values may interpolate between + two levels if the format is filterable according to the Texture Format Capabilities. + * When not specified, mip level 0 is sampled. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('coords', generateCoordBoundaries(2)) + .combine('offset', generateOffsets(2)) + .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const) + ) + .unimplemented(); + +g.test('depth_array_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel') + .desc( + ` +C is i32 or u32 + +fn textureSampleLevel(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C, level: C) -> f32 +fn textureSampleLevel(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C, level: C, offset: vec2<i32>) -> f32 + +Parameters: + * t The sampled or depth texture to sample. + * s The sampler type. + * array_index The 0-based texture array index to sample. + * coords The texture coordinates used for sampling. + * level + * The mip level, with level 0 containing a full size version of the texture. + * For the functions where level is a f32, fractional values may interpolate between + two levels if the format is filterable according to the Texture Format Capabilities. + * When not specified, mip level 0 is sampled. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .paramsSubcasesOnly(u => + u + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('coords', generateCoordBoundaries(2)) + .combine('offset', generateOffsets(2)) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const) + ) + .unimplemented(); + +g.test('depth_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel') + .desc( + ` +C is i32 or u32 + +fn textureSampleLevel(t: texture_depth_cube, s: sampler, coords: vec3<f32>, level: C) -> f32 +fn textureSampleLevel(t: texture_depth_cube_array, s: sampler, coords: vec3<f32>, array_index: C, level: C) -> f32 + +Parameters: + * t The sampled or depth texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * level + * The mip level, with level 0 containing a full size version of the texture. + * For the functions where level is a f32, fractional values may interpolate between + two levels if the format is filterable according to the Texture Format Capabilities. + * When not specified, mip level 0 is sampled. + * offset + * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture. + * This offset is applied before applying any texture wrapping modes. + * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)). + * Each offset component must be at least -8 and at most 7. + Values outside of this range will result in a shader-creation error. +` + ) + .params(u => + u + .combine('texture_type', ['texture_depth_cube', 'texture_depth_cube_array'] as const) + .beginSubcases() + .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + .combine('coords', generateCoordBoundaries(3)) + /* array_index not param'd as out-of-bounds is implementation specific */ + .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const) + ) + .unimplemented(); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureStore.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureStore.spec.ts new file mode 100644 index 0000000000..efef971e24 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureStore.spec.ts @@ -0,0 +1,122 @@ +export const description = ` +Writes a single texel to a texture. + +The channel format T depends on the storage texel format F. +See the texel format table for the mapping of texel format to channel format. + +Note: An out-of-bounds access occurs if: + * any element of coords is outside the range [0, textureDimensions(t)) for the corresponding element, or + * array_index is outside the range of [0, textureNumLayers(t)) + +If an out-of-bounds access occurs, the built-in function may do any of the following: + * not be executed + * store value to some in bounds texel +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; +import { TexelFormats } from '../../../../types.js'; + +import { generateCoordBoundaries } from './utils.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('store_1d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturestore') + .desc( + ` +C is i32 or u32 + +fn textureStore(t: texture_storage_1d<F,write>, coords: C, value: vec4<T>) + +Parameters: + * t The sampled, depth, or external texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * value The new texel value +` + ) + .params(u => + u + .combineWithParams(TexelFormats) + .beginSubcases() + .combine('coords', generateCoordBoundaries(1)) + .combine('C', ['i32', 'u32'] as const) + ) + .unimplemented(); + +g.test('store_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturestore') + .desc( + ` +C is i32 or u32 + +fn textureStore(t: texture_storage_2d<F,write>, coords: vec2<C>, value: vec4<T>) + +Parameters: + * t The sampled, depth, or external texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * value The new texel value +` + ) + .params(u => + u + .combineWithParams(TexelFormats) + .beginSubcases() + .combine('coords', generateCoordBoundaries(2)) + .combine('C', ['i32', 'u32'] as const) + ) + .unimplemented(); + +g.test('store_array_2d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturestore') + .desc( + ` +C is i32 or u32 + +fn textureStore(t: texture_storage_2d_array<F,write>, coords: vec2<C>, array_index: C, value: vec4<T>) + +Parameters: + * t The sampled, depth, or external texture to sample. + * s The sampler type. + * array_index The 0-based texture array index + * coords The texture coordinates used for sampling. + * value The new texel value +` + ) + .params( + u => + u + .combineWithParams(TexelFormats) + .beginSubcases() + .combine('coords', generateCoordBoundaries(2)) + .combine('C', ['i32', 'u32'] as const) + .combine('C_value', [-1, 0, 1, 2, 3, 4] as const) + /* array_index not param'd as out-of-bounds is implementation specific */ + ) + .unimplemented(); + +g.test('store_3d_coords') + .specURL('https://www.w3.org/TR/WGSL/#texturestore') + .desc( + ` +C is i32 or u32 + +fn textureStore(t: texture_storage_3d<F,write>, coords: vec3<C>, value: vec4<T>) + +Parameters: + * t The sampled, depth, or external texture to sample. + * s The sampler type. + * coords The texture coordinates used for sampling. + * value The new texel value +` + ) + .params(u => + u + .combineWithParams(TexelFormats) + .beginSubcases() + .combine('coords', generateCoordBoundaries(3)) + .combine('C', ['i32', 'u32'] as const) + ) + .unimplemented(); 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 new file mode 100644 index 0000000000..6fd4887f35 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts @@ -0,0 +1,158 @@ +export const description = ` +Execution tests for the 'transpose' builtin function + +T is AbstractFloat, 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 { allInputSources, onlyConstInputSource, run } from '../../expression.js'; + +import { abstractBuiltin, builtin } from './builtin.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`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('cols', [2, 3, 4] as const) + .combine('rows', [2, 3, 4] as const) + ) + .fn(async t => { + const cols = t.params.cols; + const rows = t.params.rows; + const cases = await d.get(`abstract_mat${cols}x${rows}`); + await run( + t, + abstractBuiltin('transpose'), + [TypeMat(cols, rows, TypeAbstractFloat)], + TypeMat(rows, cols, TypeAbstractFloat), + t.params, + cases + ); + }); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions') + .desc(`f32 tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('cols', [2, 3, 4] as const) + .combine('rows', [2, 3, 4] as const) + ) + .fn(async t => { + const cols = t.params.cols; + const rows = t.params.rows; + const cases = await d.get( + t.params.inputSource === 'const' + ? `f32_mat${cols}x${rows}_const` + : `f32_mat${cols}x${rows}_non_const` + ); + await run( + t, + builtin('transpose'), + [TypeMat(cols, rows, TypeF32)], + TypeMat(rows, cols, TypeF32), + t.params, + cases + ); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions') + .desc(`f16 tests`) + .params(u => + u + .combine('inputSource', allInputSources) + .combine('cols', [2, 3, 4] as const) + .combine('rows', [2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cols = t.params.cols; + const rows = t.params.rows; + const cases = await d.get( + t.params.inputSource === 'const' + ? `f16_mat${cols}x${rows}_const` + : `f16_mat${cols}x${rows}_non_const` + ); + await run( + t, + builtin('transpose'), + [TypeMat(cols, rows, TypeF16)], + TypeMat(rows, cols, TypeF16), + t.params, + 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 new file mode 100644 index 0000000000..63cd8470f5 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts @@ -0,0 +1,75 @@ +export const description = ` +Execution tests for the 'trunc' builtin function + +S is AbstractFloat, 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. +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 { allInputSources, onlyConstInputSource, run } from '../../expression.js'; + +import { abstractBuiltin, builtin } from './builtin.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`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstract'); + await run(t, abstractBuiltin('trunc'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases); + }); + +g.test('f32') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f32 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('f32'); + await run(t, builtin('trunc'), [TypeF32], TypeF32, t.params, cases); + }); + +g.test('f16') + .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') + .desc(`f16 tests`) + .params(u => + u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase('shader-f16'); + }) + .fn(async t => { + const cases = await d.get('f16'); + await run(t, builtin('trunc'), [TypeF16], TypeF16, t.params, cases); + }); 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 new file mode 100644 index 0000000000..4a0bf075e9 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts @@ -0,0 +1,48 @@ +export const description = ` +Decomposes a 32-bit value into two 16-bit chunks, and reinterpets each chunk as +a floating point value. +Component i of the result is the f32 representation of v, where v is the +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 { allInputSources, run } from '../../expression.js'; + +import { builtin } from './builtin.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( + ` +@const fn unpack2x16float(e: u32) -> vec2<f32> +` + ) + .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); + }); 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 new file mode 100644 index 0000000000..195cfd9a01 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts @@ -0,0 +1,48 @@ +export const description = ` +Decomposes a 32-bit value into two 16-bit chunks, then reinterprets each chunk +as a signed normalized floating point value. +Component i of the result is max(v ÷ 32767, -1), where v is the interpretation +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 { allInputSources, run } from '../../expression.js'; + +import { builtin } from './builtin.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( + ` +@const fn unpack2x16snorm(e: u32) -> vec2<f32> +` + ) + .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); + }); 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 new file mode 100644 index 0000000000..16b4e6397c --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts @@ -0,0 +1,48 @@ +export const description = ` +Decomposes a 32-bit value into two 16-bit chunks, then reinterprets each chunk +as an unsigned normalized floating point value. +Component i of the result is v ÷ 65535, where v is the interpretation of bits +16×i through 16×i+15 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 { allInputSources, run } from '../../expression.js'; + +import { builtin } from './builtin.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( + ` +@const fn unpack2x16unorm(e: u32) -> vec2<f32> +` + ) + .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); + }); 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 new file mode 100644 index 0000000000..7ea8d51918 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts @@ -0,0 +1,48 @@ +export const description = ` +Decomposes a 32-bit value into four 8-bit chunks, then reinterprets each chunk +as a signed normalized floating point value. +Component i of the result is max(v ÷ 127, -1), where v is the interpretation of +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 { allInputSources, run } from '../../expression.js'; + +import { builtin } from './builtin.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( + ` +@const fn unpack4x8snorm(e: u32) -> vec4<f32> +` + ) + .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); + }); 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 new file mode 100644 index 0000000000..bf54d23c12 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts @@ -0,0 +1,48 @@ +export const description = ` +Decomposes a 32-bit value into four 8-bit chunks, then reinterprets each chunk +as an unsigned normalized floating point value. +Component i of the result is v ÷ 255, where v is the interpretation of bits 8×i +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 { allInputSources, run } from '../../expression.js'; + +import { builtin } from './builtin.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( + ` +@const fn unpack4x8unorm(e: u32) -> vec4<f32> +` + ) + .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); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/utils.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/utils.ts new file mode 100644 index 0000000000..9cbee00939 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/utils.ts @@ -0,0 +1,45 @@ +/** + * Generates the boundary entries for the given number of dimensions + * + * @param numDimensions: The number of dimensions to generate for + * @returns an array of generated coord boundaries + */ +export function generateCoordBoundaries(numDimensions: number) { + const ret = ['in-bounds']; + + if (numDimensions < 1 || numDimensions > 3) { + throw new Error(`invalid numDimensions: ${numDimensions}`); + } + + const name = 'xyz'; + for (let i = 0; i < numDimensions; ++i) { + for (const j of ['min', 'max']) { + for (const k of ['wrap', 'boundary']) { + ret.push(`${name[i]}-${j}-${k}`); + } + } + } + + return ret; +} + +/** + * Generates a set of offset values to attempt in the range [-9, 8]. + * + * @param numDimensions: The number of dimensions to generate for + * @return an array of generated offset values + */ +export function generateOffsets(numDimensions: number) { + if (numDimensions < 2 || numDimensions > 3) { + throw new Error(`generateOffsets: invalid numDimensions: ${numDimensions}`); + } + const ret: Array<undefined | Array<number>> = [undefined]; + for (const val of [-9, -8, 0, 1, 7, 8]) { + const v = []; + for (let i = 0; i < numDimensions; ++i) { + v.push(val); + } + ret.push(v); + } + return ret; +} diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupBarrier.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupBarrier.spec.ts new file mode 100644 index 0000000000..74e0f12325 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupBarrier.spec.ts @@ -0,0 +1,38 @@ +export const description = ` +'workgroupBarrier' affects memory and atomic operations in the workgroup address space. + +All synchronization functions execute a control barrier with Acquire/Release memory ordering. +That is, all synchronization functions, and affected memory and atomic operations are ordered +in program order relative to the synchronization function. Additionally, the affected memory +and atomic operations program-ordered before the synchronization function must be visible to +all other threads in the workgroup before any affected memory or atomic operation program-ordered +after the synchronization function is executed by a member of the workgroup. All synchronization +functions use the Workgroup memory scope. All synchronization functions have a Workgroup +execution scope. + +All synchronization functions must only be used in the compute shader stage. +`; + +import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../../gpu_test.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('stage') + .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions') + .desc( + ` +All synchronization functions must only be used in the compute shader stage. +` + ) + .params(u => u.combine('stage', ['vertex', 'fragment', 'compute'] as const)) + .unimplemented(); + +g.test('barrier') + .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions') + .desc( + ` +fn workgroupBarrier() +` + ) + .unimplemented(); |