From de68a79d02318f3f451017917a94e310863176ef Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Tue, 24 Oct 2023 15:34:17 -0400 Subject: [PATCH] wgsl: Add AbstractFloat `mix` execution tests Fixes #2566 --- src/unittests/floating_point.spec.ts | 51 +++++++++- src/webgpu/listing_meta.json | 8 +- .../expression/call/builtin/mix.spec.ts | 92 ++++++++++++++++--- src/webgpu/util/floating_point.ts | 10 +- 4 files changed, 132 insertions(+), 29 deletions(-) diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 3d73d4254577..dba25440ee03 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -5342,10 +5342,9 @@ const kMixImpreciseIntervalCases = { { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9999_8000_0000n), reinterpretU64AsF64(0x3fe9_9999_c000_0000n)] }, // ~0.8 // Showing how precise and imprecise versions diff - // Note that this expectation is 0 only in f32 as 10.0 is much smaller that f32.negative.min, + // Note that this expectation is 0 in f32 as 10.0 is much smaller that f32.negative.min, // So that 10 - f32.negative.min == f32.negative.min even in f64. But for f16, there is not - // a exactly-represenatble f16 value v that make v - f16.negative.min == f16.negative.min - // in f64, in fact that require v being smaller than 2**-37. + // an exactly-represenatble f16 value v that make v - f16.negative.min == f16.negative.min { input: [kValue.f32.negative.min, 10.0, 1.0], expected: 0.0 }, // -10.0 is the same, much smaller than f32.negative.min { input: [kValue.f32.negative.min, -10.0, 1.0], expected: 0.0 }, @@ -5375,12 +5374,37 @@ const kMixImpreciseIntervalCases = { // The result is -65504 + 65472 = -32 or -65504 + 65504 = 0. { input: [kValue.f16.negative.min, -10.0, 1.0], expected: [-32, 0] }, ] as ScalarTripleToIntervalCase[], + abstract: [ + // [0.0, 1.0] cases + { input: [0.0, 1.0, 0.1], expected: 0.1 }, + { input: [0.0, 1.0, 0.9], expected: 0.9 }, + // [1.0, 0.0] cases + { input: [1.0, 0.0, 0.1], expected: 0.9 }, + { input: [1.0, 0.0, 0.9], expected: kMinusNULPFunctions['abstract'](0.1, 2) }, // This not being 0.1 is related to https://github.com/gpuweb/cts/issues/2993 + // [0.0, 10.0] cases + { input: [0.0, 10.0, 0.1], expected: 1 }, + { input: [0.0, 10.0, 0.9], expected: 9 }, + // [2.0, 10.0] cases + { input: [2.0, 10.0, 0.1], expected: 2.8 }, + { input: [2.0, 10.0, 0.9], expected: 9.2 }, + // [-1.0, 1.0] cases + { input: [-1.0, 1.0, 0.1], expected: -0.8 }, + { input: [-1.0, 1.0, 0.9], expected: 0.8 }, + + // Showing how precise and imprecise versions diff + // Note that this expectation is 0 in f64 as 10.0 is much smaller that f63.negative.min, + // So that 10 - f64.negative.min == f64.negative.min even in f64. But for f16, there is not + // an exactly-represenatble f16 value v that make v - f16.negative.min == f16.negative.min + { input: [kValue.f64.negative.min, 10.0, 1.0], expected: 0.0 }, + // -10.0 is the same, much smaller than f64.negative.min + { input: [kValue.f64.negative.min, -10.0, 1.0], expected: 0.0 }, + ] as ScalarTripleToIntervalCase[], } as const; g.test('mixImpreciseInterval') .params(u => u - .combine('trait', ['f32', 'f16'] as const) + .combine('trait', ['f32', 'f16', 'abstract'] as const) .beginSubcases() .expandWithParams(p => { const trait = FP[p.trait]; @@ -5490,12 +5514,29 @@ const kMixPreciseIntervalCases = { { input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_a000_0000_0000n), reinterpretU64AsF64(0xbfe9_9800_0000_0000n)] }, // ~-0.8 { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9800_0000_0000n), reinterpretU64AsF64(0x3fe9_a000_0000_0000n)] }, // ~0.8 ] as ScalarTripleToIntervalCase[], + abstract: [ + // [0.0, 1.0] cases + { input: [0.0, 1.0, 0.1], expected: 0.1 }, + { input: [0.0, 1.0, 0.9], expected: 0.9 }, + // [1.0, 0.0] cases + { input: [1.0, 0.0, 0.1], expected: 0.9 }, + { input: [1.0, 0.0, 0.9], expected: kMinusNULPFunctions['abstract'](0.1, 2) }, // This not being 0.1 is related to https://github.com/gpuweb/cts/issues/2993 + // [0.0, 10.0] cases + { input: [0.0, 10.0, 0.1], expected: 1 }, + { input: [0.0, 10.0, 0.9], expected: 9 }, + // [2.0, 10.0] cases + { input: [2.0, 10.0, 0.1], expected: 2.8 }, + { input: [2.0, 10.0, 0.9], expected: 9.2 }, + // [-1.0, 1.0] cases + { input: [-1.0, 1.0, 0.1], expected: -0.8 }, + { input: [-1.0, 1.0, 0.9], expected: 0.8 }, + ] as ScalarTripleToIntervalCase[], } as const; g.test('mixPreciseInterval') .params(u => u - .combine('trait', ['f32', 'f16'] as const) + .combine('trait', ['f32', 'f16', 'abstract'] as const) .beginSubcases() .expandWithParams(p => { const trait = FP[p.trait]; diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index fa57ff978139..82f8df4745be 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -1277,10 +1277,10 @@ "webgpu:shader,execution,expression,call,builtin,min:f32:*": { "subcaseMS": 298.463 }, "webgpu:shader,execution,expression,call,builtin,min:i32:*": { "subcaseMS": 7.825 }, "webgpu:shader,execution,expression,call,builtin,min:u32:*": { "subcaseMS": 6.932 }, - "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_matching:*": { "subcaseMS": 215.206 }, - "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec2:*": { "subcaseMS": 14.601 }, - "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec3:*": { "subcaseMS": 18.302 }, - "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec4:*": { "subcaseMS": 12.602 }, + "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_matching:*": { "subcaseMS": 30026.025 }, + "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec2:*": { "subcaseMS": 16161.901 }, + "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec3:*": { "subcaseMS": 25039.601 }, + "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec4:*": { "subcaseMS": 11499.101 }, "webgpu:shader,execution,expression,call,builtin,mix:f16_matching:*": { "subcaseMS": 321.700 }, "webgpu:shader,execution,expression,call,builtin,mix:f16_nonmatching_vec2:*": { "subcaseMS": 653.851 }, "webgpu:shader,execution,expression,call,builtin,mix:f16_nonmatching_vec3:*": { "subcaseMS": 832.076 }, diff --git a/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts index 95e9f6b31017..98984e750ef7 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts @@ -16,18 +16,20 @@ 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 { TypeVec, TypeF32, TypeF16, TypeAbstractFloat } from '../../../../../util/conversion.js'; import { FP } from '../../../../../util/floating_point.js'; import { sparseF32Range, sparseF16Range, sparseVectorF32Range, sparseVectorF16Range, + sparseVectorF64Range, + sparseF64Range, } from '../../../../../util/math.js'; import { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run } from '../../expression.js'; +import { allInputSources, onlyConstInputSource, run } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { abstractBuiltin, builtin } from './builtin.js'; export const g = makeTestGroup(GPUTest); @@ -65,6 +67,21 @@ const f16_vec_scalar_cases = ([2, 3, 4] as const) ) .reduce((a, b) => ({ ...a, ...b }), {}); +// Cases: abstract_vecN_scalar +const abstract_vec_scalar_cases = ([2, 3, 4] as const) + .map(n => ({ + [`abstract_vec${n}_scalar`]: () => { + return FP.abstract.generateVectorPairScalarToVectorComponentWiseCase( + sparseVectorF64Range(n), + sparseVectorF64Range(n), + sparseF64Range(), + 'finite', + ...FP.abstract.mixIntervals + ); + }, + })) + .reduce((a, b) => ({ ...a, ...b }), {}); + export const d = makeCaseCache('mix', { f32_const: () => { return FP.f32.generateScalarTripleToIntervalCases( @@ -104,33 +121,84 @@ export const d = makeCaseCache('mix', { ); }, ...f16_vec_scalar_cases, + abstract: () => { + return FP.abstract.generateScalarTripleToIntervalCases( + sparseF64Range(), + sparseF64Range(), + sparseF64Range(), + 'finite', + ...FP.abstract.mixIntervals + ); + }, + ...abstract_vec_scalar_cases, }); g.test('abstract_float_matching') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract_float test with matching third param`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); - + .fn(async t => { + const cases = await d.get('abstract'); + await run( + t, + abstractBuiltin('mix'), + [TypeAbstractFloat, TypeAbstractFloat, TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); g.test('abstract_float_nonmatching_vec2') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract_float tests with two vec2 params and scalar third param`) - .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec2_scalar'); + await run( + t, + abstractBuiltin('mix'), + [TypeVec(2, TypeAbstractFloat), TypeVec(2, TypeAbstractFloat), TypeAbstractFloat], + TypeVec(2, TypeAbstractFloat), + t.params, + cases + ); + }); g.test('abstract_float_nonmatching_vec3') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract_float tests with two vec3 params and scalar third param`) - .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec3_scalar'); + await run( + t, + abstractBuiltin('mix'), + [TypeVec(3, TypeAbstractFloat), TypeVec(3, TypeAbstractFloat), TypeAbstractFloat], + TypeVec(3, TypeAbstractFloat), + t.params, + cases + ); + }); g.test('abstract_float_nonmatching_vec4') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract_float tests with two vec4 params and scalar third param`) - .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract_vec3_scalar'); + await run( + t, + abstractBuiltin('mix'), + [TypeVec(3, TypeAbstractFloat), TypeVec(3, TypeAbstractFloat), TypeAbstractFloat], + TypeVec(3, TypeAbstractFloat), + t.params, + cases + ); + }); g.test('f32_matching') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') diff --git a/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index 20f74483df31..92f1dcf3ec27 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -5018,14 +5018,8 @@ class FPAbstractTraits extends FPTraits { public readonly log2Interval = this.unimplementedScalarToInterval.bind(this, 'log2Interval'); public readonly maxInterval = this.maxIntervalImpl.bind(this); public readonly minInterval = this.minIntervalImpl.bind(this); - public readonly mixImpreciseInterval = this.unimplementedScalarTripleToInterval.bind( - this, - 'mixImpreciseInterval' - ); - public readonly mixPreciseInterval = this.unimplementedScalarTripleToInterval.bind( - this, - 'mixPreciseInterval' - ); + public readonly mixImpreciseInterval = this.mixImpreciseIntervalImpl.bind(this); + public readonly mixPreciseInterval = this.mixPreciseIntervalImpl.bind(this); public readonly mixIntervals = [this.mixImpreciseInterval, this.mixPreciseInterval]; public readonly modfInterval = this.modfIntervalImpl.bind(this); public readonly multiplicationInterval = this.multiplicationIntervalImpl.bind(this);