From 3acbf58bd5c2d28fb84d953e65de7582fad28faa Mon Sep 17 00:00:00 2001 From: jzm-intel Date: Tue, 22 Aug 2023 12:14:04 +0800 Subject: [PATCH 001/166] wgsl: f16 built-in execution test for bitcast (#2897) This PR add execution tests for bitcast built-in from and to f16 types. Issue: #1248, #1609 --- .../expression/call/builtin/bitcast.spec.ts | 973 +++++++++++++++--- .../expression/call/builtin/builtin.ts | 14 +- .../shader/execution/expression/expression.ts | 294 +++--- 3 files changed, 1033 insertions(+), 248 deletions(-) diff --git a/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts index 5d635fbb6a0a..7269d0a1772f 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts @@ -20,34 +20,49 @@ 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 { reinterpretI32AsF32, + reinterpretI32AsU32, reinterpretF32AsI32, reinterpretF32AsU32, reinterpretU32AsF32, reinterpretU32AsI32, + reinterpretU16AsF16, + reinterpretF16AsU16, f32, i32, u32, - Type, + 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 { makeCaseCache } from '../../case_cache.js'; -import { allInputSources, run, CaseList, InputSource, ShaderBuilder } from '../../expression.js'; +import { allInputSources, run, ShaderBuilder } from '../../expression.js'; -import { builtin } from './builtin.js'; +import { builtinWithPredeclaration } from './builtin.js'; export const g = makeTestGroup(GPUTest); @@ -71,44 +86,197 @@ 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.infinity.positive + 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.infinity.negative + 1, 65535, numNaNs).map(v => Math.floor(v)), + kBit.f16.infinity.positive, + kBit.f16.infinity.negative, +]; +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: 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 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 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 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 interesting (zeros, Inf, and NaN) values for testing cases. +// vec2 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 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 +// u32 values for bitcasting to vec2 finite, Inf, and NaN. +const u32RangeForF16Vec2FiniteInfNaN: number[] = [ + ...fullU32Range(), + ...f16Vec2ZerosInU32, + ...f16Vec2InfAndNaNInU32, +]; +// u32 values for bitcasting to finite only vec2, used for constant evaluation. +const u32RangeForF16Vec2Finite: number[] = u32RangeForF16Vec2FiniteInfNaN.filter( + canU32BitcastToFiniteVec2F16 +); +// i32 values for bitcasting to vec2 finite, zeros, Inf, and NaN. +const i32RangeForF16Vec2FiniteInfNaN: number[] = [ + ...fullI32Range(), + ...f16Vec2ZerosInI32, + ...f16Vec2InfAndNaNInI32, +]; +// i32 values for bitcasting to finite only vec2, used for constant evaluation. +const i32RangeForF16Vec2Finite: number[] = i32RangeForF16Vec2FiniteInfNaN.filter(u => + canU32BitcastToFiniteVec2F16(reinterpretI32AsU32(u)) +); +// f32 values with finite/Inf/NaN f32, for bitcasting to vec2 finite, zeros, Inf, and NaN. +const f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN: number[] = [ + ...f32RangeWithInfAndNaN, + ...u32RangeForF16Vec2FiniteInfNaN.map(reinterpretU32AsF32), +]; +// Finite f32 values for bitcasting to finite only vec2, used for constant evaluation. +const f32FiniteRangeForF16Vec2Finite: number[] = f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN + .filter(isFiniteF32) + .filter(u => canU32BitcastToFiniteVec2F16(reinterpretF32AsU32(u))); + +// vec2 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 cases for bitcasting to vec2, 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'); -const i32RangeWithBitsForInfAndNaNAndZeros: number[] = [ +// 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, ]; -const i32RangeWithFiniteF32: number[] = i32RangeWithBitsForInfAndNaNAndZeros.filter(i => - isFinite(reinterpretI32AsF32(i)) +// i32 cases for bitcasting to f32 finite only. +const i32RangeForF32Finite: number[] = i32RangeForF32FiniteInfNaN.filter(i => + isFiniteF32(reinterpretI32AsF32(i)) ); - -const u32RangeWithBitsForInfAndNaNAndZeros: number[] = [ +// u32 cases for bitcasting to f32 finite, zeros, Inf, and NaN. +const u32RangeForF32FiniteInfNaN: number[] = [ ...fullU32Range(), ...f32ZerosInU32, ...f32InfAndNaNInU32, ]; -const u32RangeWithFiniteF32: number[] = u32RangeWithBitsForInfAndNaNAndZeros.filter(u => - isFinite(reinterpretU32AsF32(u)) +// u32 cases for bitcasting to f32 finite only. +const u32RangeForF32Finite: number[] = u32RangeForF32FiniteInfNaN.filter(u => + isFiniteF32(reinterpretU32AsF32(u)) ); -function isFinite(f: number): boolean { - return !(Number.isNaN(f) || f === Number.POSITIVE_INFINITY || f === Number.NEGATIVE_INFINITY); -} - /** * @returns a Comparator for checking if a f32 value is a valid * bitcast conversion from f32. */ function bitcastF32ToF32Comparator(f: number): Comparator { - if (!isFinite(f)) return anyF32; + if (!isFiniteF32(f)) return anyF32; const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])]; return anyOf(...acceptable.map(f32)); } @@ -118,7 +286,7 @@ function bitcastF32ToF32Comparator(f: number): Comparator { * bitcast conversion from f32. */ function bitcastF32ToU32Comparator(f: number): Comparator { - if (!isFinite(f)) return anyU32; + if (!isFiniteF32(f)) return anyU32; const acceptable: number[] = [ reinterpretF32AsU32(f), ...(isSubnormalNumberF32(f) ? f32ZerosInU32 : []), @@ -131,7 +299,7 @@ function bitcastF32ToU32Comparator(f: number): Comparator { * bitcast conversion from f32. */ function bitcastF32ToI32Comparator(f: number): Comparator { - if (!isFinite(f)) return anyI32; + if (!isFiniteF32(f)) return anyI32; const acceptable: number[] = [ reinterpretF32AsI32(f), ...(isSubnormalNumberF32(f) ? f32ZerosInI32 : []), @@ -145,7 +313,7 @@ function bitcastF32ToI32Comparator(f: number): Comparator { */ function bitcastI32ToF32Comparator(i: number): Comparator { const f: number = reinterpretI32AsF32(i); - if (!isFinite(f)) return anyI32; + 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 : [])]; @@ -158,13 +326,320 @@ function bitcastI32ToF32Comparator(i: number): Comparator { */ function bitcastU32ToF32Comparator(u: number): Comparator { const f: number = reinterpretU32AsF32(u); - if (!isFinite(f)) return anyU32; + 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 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 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 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 is a valid + * bitcast conversion from vec2. + */ +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 is a valid + * bitcast conversion from vec2. + */ +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 is a valid + * bitcast conversion from vec2. + */ +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( + (possibleBitsU16x2: 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'); + } + 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'); + } + 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) })), @@ -176,27 +651,34 @@ export const d = makeCaseCache('bitcast', { })), 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: () => - i32RangeWithFiniteF32.map(e => ({ + i32RangeForF32Finite.map(e => ({ input: i32(e), expected: bitcastI32ToF32Comparator(e), })), i32_to_f32_inf_nan: () => - i32RangeWithBitsForInfAndNaNAndZeros.map(e => ({ + i32RangeForF32FiniteInfNaN.map(e => ({ input: i32(e), expected: bitcastI32ToF32Comparator(e), })), u32_to_i32: () => fullU32Range().map(e => ({ input: u32(e), expected: i32(e) })), u32_to_f32: () => - u32RangeWithFiniteF32.map(e => ({ + u32RangeForF32Finite.map(e => ({ input: u32(e), expected: bitcastU32ToF32Comparator(e), })), u32_to_f32_inf_nan: () => - u32RangeWithBitsForInfAndNaNAndZeros.map(e => ({ + u32RangeForF32FiniteInfNaN.map(e => ({ input: u32(e), expected: bitcastU32ToF32Comparator(e), })), @@ -215,6 +697,142 @@ export const d = makeCaseCache('bitcast', { })), f32_to_u32: () => f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToU32Comparator(e) })), + + // i32,u32,f32 to vec2 + 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, vec2, vec2 to vec4 + 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 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 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), + })), }); /** @@ -227,17 +845,10 @@ function bitcastBuilder(canonicalDestType: string, params: TestParams): ShaderBu ? `vec${params.vectorize}<${canonicalDestType}>` : canonicalDestType; - if (params.alias) { - return ( - parameterTypes: Array, - resultType: Type, - cases: CaseList, - inputSource: InputSource - ) => - `alias myalias = ${destType};\n` + - builtin(`bitcast`)(parameterTypes, resultType, cases, inputSource); - } - return builtin(`bitcast<${destType}>`); + return builtinWithPredeclaration( + `bitcast<${destType}>`, + params.alias ? `alias myalias = ${destType};` : '' + ); } // Identity cases @@ -399,140 +1010,264 @@ g.test('f16_to_f16') .combine('vectorize', [undefined, 2, 3, 4] as const) .combine('alias', [false, true]) ) - .unimplemented(); + .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 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('vectorize', [undefined, 2, 3, 4] as const) - .combine('alias', [false, true]) - ) - .unimplemented(); + .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', 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('vectorize', [undefined, 2, 3, 4] as const) - .combine('alias', [false, true]) - ) - .unimplemented(); + .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', 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('vectorize', [undefined, 2, 3, 4] as const) - .combine('alias', [false, true]) - ) - .unimplemented(); + .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', t.params), + [TypeF32], + TypeVec(2, TypeF16), + t.params, + cases + ); + }); // f16: vec2<32-bit scalar numeric> to vec4 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('vectorize', [undefined, 2, 3, 4] as const) - .combine('alias', [false, true]) - ) - .unimplemented(); + .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', 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('vectorize', [undefined, 2, 3, 4] as const) - .combine('alias', [false, true]) - ) - .unimplemented(); + .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', 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('vectorize', [undefined, 2, 3, 4] as const) - .combine('alias', [false, true]) - ) - .unimplemented(); + .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', t.params), + [TypeVec(2, TypeF32)], + TypeVec(4, TypeF16), + t.params, + cases + ); + }); // f16: vec2 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('vectorize', [undefined, 2, 3, 4] as const) - .combine('alias', [false, true]) - ) - .unimplemented(); + .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('vectorize', [undefined, 2, 3, 4] as const) - .combine('alias', [false, true]) - ) - .unimplemented(); + .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('vectorize', [undefined, 2, 3, 4] as const) - .combine('alias', [false, true]) - ) - .unimplemented(); + .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 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('vectorize', [undefined, 2, 3, 4] as const) - .combine('alias', [false, true]) - ) - .unimplemented(); + .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', 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('vectorize', [undefined, 2, 3, 4] as const) - .combine('alias', [false, true]) - ) - .unimplemented(); + .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', 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('vectorize', [undefined, 2, 3, 4] as const) - .combine('alias', [false, true]) - ) - .unimplemented(); + .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', t.params), + [TypeVec(4, TypeF16)], + TypeVec(2, TypeF32), + t.params, + cases + ); + }); diff --git a/src/webgpu/shader/execution/expression/call/builtin/builtin.ts b/src/webgpu/shader/execution/expression/call/builtin/builtin.ts index f25faa30440b..26424dd1ce6e 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/builtin.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/builtin.ts @@ -1,6 +1,18 @@ -import { basicExpressionBuilder, ShaderBuilder } from '../../expression.js'; +import { + 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 and has given predeclaration */ +export function builtinWithPredeclaration(name: string, predeclaration: string): ShaderBuilder { + return basicExpressionWithPredeclarationBuilder( + values => `${name}(${values.join(', ')})`, + predeclaration + ); +} diff --git a/src/webgpu/shader/execution/expression/expression.ts b/src/webgpu/shader/execution/expression/expression.ts index 80fb0fbf8006..e12a3f71392d 100644 --- a/src/webgpu/shader/execution/expression/expression.ts +++ b/src/webgpu/shader/execution/expression/expression.ts @@ -501,107 +501,107 @@ export type ExpressionBuilder = (values: Array) => string; * Returns a ShaderBuilder that builds a basic expression test shader. * @param expressionBuilder the expression builder */ -export function basicExpressionBuilder(expressionBuilder: ExpressionBuilder): ShaderBuilder { - return ( - parameterTypes: Array, - resultType: Type, - cases: CaseList, - inputSource: InputSource - ) => { - if (inputSource === 'const') { - ////////////////////////////////////////////////////////////////////////// - // Constant eval - ////////////////////////////////////////////////////////////////////////// - let body = ''; - if ( - scalarTypeOf(resultType).kind !== 'abstract-float' && - parameterTypes.some(ty => scalarTypeOf(ty).kind === 'abstract-float') - ) { - // Directly assign the expression to the output, to avoid an - // intermediate store, which will concretize the value early - body = cases - .map( - (c, i) => - ` outputs[${i}].value = ${toStorage( - resultType, - expressionBuilder(map(c.input, v => v.wgsl())) - )};` - ) - .join('\n '); - } else if (scalarTypeOf(resultType).kind === 'abstract-float') { - // AbstractFloats are f64s under the hood. WebGPU does not support - // putting f64s in buffers, so the result needs to be split up into u32s - // and rebuilt in the test framework. - // - // This is complicated by the fact that user defined functions cannot - // take/return AbstractFloats, and AbstractFloats cannot be stored in - // variables, so the code cannot just inject a simple utility function - // at the top of the shader, instead this snippet needs to be inlined - // everywhere the test needs to return an AbstractFloat. - // - // select is used below, since ifs are not available during constant - // eval. This has the side effect of short-circuiting doesn't occur, so - // both sides of the select have to evaluate and be valid. - // - // This snippet implements FTZ for subnormals to bypass the need for - // complex subnormal specific logic. - // - // Expressions resulting in subnormals can still be reasonably tested, - // since this snippet will return 0 with the correct sign, which is - // always in the acceptance interval for a subnormal result, since an - // implementation may FTZ. - // - // Document for the snippet is included here in this code block, since - // shader length affects compilation time significantly on some - // backends. - // - // Snippet with documentation: - // const kExponentBias = 1022; - // - // // Detect if the value is zero or subnormal, so that FTZ behaviour - // // can occur - // const subnormal_or_zero : bool = (${expr} <= ${kValue.f64.subnormal.positive.max}) && (${expr} >= ${kValue.f64.subnormal.negative.min}); - // - // // MSB of the upper u32 is 1 if the value is negative, otherwise 0 - // // Extract the sign bit early, so that abs() can be used with - // // frexp() so negative cases do not need to be handled - // const sign_bit : u32 = select(0, 0x80000000, ${expr} < 0); - // - // // Use frexp() to obtain the exponent and fractional parts, and - // // then perform FTZ if needed - // const f = frexp(abs(${expr})); - // const f_fract = select(f.fract, 0, subnormal_or_zero); - // const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); - // - // // Adjust for the exponent bias and shift for storing in bits - // // [20..31] of the upper u32 - // const exponent_bits : u32 = (f_exp + kExponentBias) << 20; - // - // // Extract the portion of the mantissa that appears in upper u32 as - // // a float for later use - // const high_mantissa = ldexp(f_fract, 21); - // - // // Extract the portion of the mantissa that appears in upper u32 as - // // as bits. This value is masked, because normals will explicitly - // // have the implicit leading 1 that should not be in the final - // // result. - // const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; - // - // // Calculate the mantissa stored in the lower u32 as a float - // const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); - // - // // Convert the lower u32 mantissa to bits - // const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); - // - // // Pack the result into 2x u32s for writing out to the testing - // // framework - // outputs[${i}].value.x = low_mantissa_bits; - // outputs[${i}].value.y = sign_bit | exponent_bits | high_mantissa_bits; - body = cases - .map((c, i) => { - const expr = `${expressionBuilder(map(c.input, v => v.wgsl()))}`; - // prettier-ignore - return ` { +function basicExpressionShaderBody( + expressionBuilder: ExpressionBuilder, + parameterTypes: Array, + resultType: Type, + cases: CaseList, + inputSource: InputSource +): string { + if (inputSource === 'const') { + ////////////////////////////////////////////////////////////////////////// + // Constant eval + ////////////////////////////////////////////////////////////////////////// + let body = ''; + if ( + scalarTypeOf(resultType).kind !== 'abstract-float' && + parameterTypes.some(ty => scalarTypeOf(ty).kind === 'abstract-float') + ) { + // Directly assign the expression to the output, to avoid an + // intermediate store, which will concretize the value early + body = cases + .map( + (c, i) => + ` outputs[${i}].value = ${toStorage( + resultType, + expressionBuilder(map(c.input, v => v.wgsl())) + )};` + ) + .join('\n '); + } else if (scalarTypeOf(resultType).kind === 'abstract-float') { + // AbstractFloats are f64s under the hood. WebGPU does not support + // putting f64s in buffers, so the result needs to be split up into u32s + // and rebuilt in the test framework. + // + // This is complicated by the fact that user defined functions cannot + // take/return AbstractFloats, and AbstractFloats cannot be stored in + // variables, so the code cannot just inject a simple utility function + // at the top of the shader, instead this snippet needs to be inlined + // everywhere the test needs to return an AbstractFloat. + // + // select is used below, since ifs are not available during constant + // eval. This has the side effect of short-circuiting doesn't occur, so + // both sides of the select have to evaluate and be valid. + // + // This snippet implements FTZ for subnormals to bypass the need for + // complex subnormal specific logic. + // + // Expressions resulting in subnormals can still be reasonably tested, + // since this snippet will return 0 with the correct sign, which is + // always in the acceptance interval for a subnormal result, since an + // implementation may FTZ. + // + // Document for the snippet is included here in this code block, since + // shader length affects compilation time significantly on some + // backends. + // + // Snippet with documentation: + // const kExponentBias = 1022; + // + // // Detect if the value is zero or subnormal, so that FTZ behaviour + // // can occur + // const subnormal_or_zero : bool = (${expr} <= ${kValue.f64.subnormal.positive.max}) && (${expr} >= ${kValue.f64.subnormal.negative.min}); + // + // // MSB of the upper u32 is 1 if the value is negative, otherwise 0 + // // Extract the sign bit early, so that abs() can be used with + // // frexp() so negative cases do not need to be handled + // const sign_bit : u32 = select(0, 0x80000000, ${expr} < 0); + // + // // Use frexp() to obtain the exponent and fractional parts, and + // // then perform FTZ if needed + // const f = frexp(abs(${expr})); + // const f_fract = select(f.fract, 0, subnormal_or_zero); + // const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); + // + // // Adjust for the exponent bias and shift for storing in bits + // // [20..31] of the upper u32 + // const exponent_bits : u32 = (f_exp + kExponentBias) << 20; + // + // // Extract the portion of the mantissa that appears in upper u32 as + // // a float for later use + // const high_mantissa = ldexp(f_fract, 21); + // + // // Extract the portion of the mantissa that appears in upper u32 as + // // as bits. This value is masked, because normals will explicitly + // // have the implicit leading 1 that should not be in the final + // // result. + // const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; + // + // // Calculate the mantissa stored in the lower u32 as a float + // const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); + // + // // Convert the lower u32 mantissa to bits + // const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); + // + // // Pack the result into 2x u32s for writing out to the testing + // // framework + // outputs[${i}].value.x = low_mantissa_bits; + // outputs[${i}].value.y = sign_bit | exponent_bits | high_mantissa_bits; + body = cases + .map((c, i) => { + const expr = `${expressionBuilder(map(c.input, v => v.wgsl()))}`; + // prettier-ignore + return ` { const kExponentBias = 1022; const subnormal_or_zero : bool = (${expr} <= ${kValue.f64.subnormal.positive.max}) && (${expr} >= ${kValue.f64.subnormal.negative.min}); const sign_bit : u32 = select(0, 0x80000000, ${expr} < 0); @@ -616,25 +616,23 @@ export function basicExpressionBuilder(expressionBuilder: ExpressionBuilder): Sh outputs[${i}].value.x = low_mantissa_bits; outputs[${i}].value.y = sign_bit | exponent_bits | high_mantissa_bits; }`; - }) - .join('\n '); - } else if (globalTestConfig.unrollConstEvalLoops) { - body = cases - .map((_, i) => { - const value = `values[${i}]`; - return ` outputs[${i}].value = ${toStorage(resultType, value)};`; - }) - .join('\n '); - } else { - body = ` + }) + .join('\n '); + } else if (globalTestConfig.unrollConstEvalLoops) { + body = cases + .map((_, i) => { + const value = `values[${i}]`; + return ` outputs[${i}].value = ${toStorage(resultType, value)};`; + }) + .join('\n '); + } else { + body = ` for (var i = 0u; i < ${cases.length}; i++) { outputs[i].value = ${toStorage(resultType, `values[i]`)}; }`; - } - - return ` -${wgslHeader(parameterTypes, resultType)} + } + return ` ${wgslOutputs(resultType, cases.length)} ${wgslValuesArray(parameterTypes, resultType, cases, expressionBuilder)} @@ -643,20 +641,18 @@ ${wgslValuesArray(parameterTypes, resultType, cases, expressionBuilder)} fn main() { ${body} }`; - } else { - ////////////////////////////////////////////////////////////////////////// - // Runtime eval - ////////////////////////////////////////////////////////////////////////// - - // returns the WGSL expression to load the ith parameter of the given type from the input buffer - const paramExpr = (ty: Type, i: number) => fromStorage(ty, `inputs[i].param${i}`); + } else { + ////////////////////////////////////////////////////////////////////////// + // Runtime eval + ////////////////////////////////////////////////////////////////////////// - // resolves to the expression that calls the builtin - const expr = toStorage(resultType, expressionBuilder(parameterTypes.map(paramExpr))); + // returns the WGSL expression to load the ith parameter of the given type from the input buffer + const paramExpr = (ty: Type, i: number) => fromStorage(ty, `inputs[i].param${i}`); - return ` -${wgslHeader(parameterTypes, resultType)} + // resolves to the expression that calls the builtin + const expr = toStorage(resultType, expressionBuilder(parameterTypes.map(paramExpr))); + return ` struct Input { ${parameterTypes .map((ty, i) => ` @size(${valueStride(ty)}) param${i} : ${storageType(ty)},`) @@ -674,7 +670,49 @@ fn main() { } } `; - } + } +} + +/** + * Returns a ShaderBuilder that builds a basic expression test shader. + * @param expressionBuilder the expression builder + */ +export function basicExpressionBuilder(expressionBuilder: ExpressionBuilder): ShaderBuilder { + return ( + parameterTypes: Array, + resultType: Type, + cases: CaseList, + inputSource: InputSource + ) => { + return `\ +${wgslHeader(parameterTypes, resultType)} + +${basicExpressionShaderBody(expressionBuilder, parameterTypes, resultType, cases, inputSource)}`; + }; +} + +/** + * Returns a ShaderBuilder that builds a basic expression test shader with given predeclaration + * string goes after WGSL header (i.e. enable directives) if any but before anything else. + * @param expressionBuilder the expression builder + * @param predeclaration the predeclaration string + */ +export function basicExpressionWithPredeclarationBuilder( + expressionBuilder: ExpressionBuilder, + predeclaration: string +): ShaderBuilder { + return ( + parameterTypes: Array, + resultType: Type, + cases: CaseList, + inputSource: InputSource + ) => { + return `\ +${wgslHeader(parameterTypes, resultType)} + +${predeclaration} + +${basicExpressionShaderBody(expressionBuilder, parameterTypes, resultType, cases, inputSource)}`; }; } From 4fc748388076ebf618edb742505be93892e4057a Mon Sep 17 00:00:00 2001 From: David Neto Date: Tue, 22 Aug 2023 16:37:56 -0400 Subject: [PATCH 002/166] Remove TODO about f32(-0.0) printing as '-0.0f' (#2903) It's not guaranteed that the -0.0 value will survive once it is stored in a Scalar object Issue: #2901 --- src/unittests/conversion.spec.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/unittests/conversion.spec.ts b/src/unittests/conversion.spec.ts index 95da248a3087..589e20fdc652 100644 --- a/src/unittests/conversion.spec.ts +++ b/src/unittests/conversion.spec.ts @@ -193,9 +193,11 @@ g.test('floatBitsToULPFromZero,32').fn(t => { g.test('scalarWGSL').fn(t => { const cases: Array<[Scalar, string]> = [ [f32(0.0), '0.0f'], - // f32(-0.0) should map to '-0.0f' - // Tracked by https://github.com/gpuweb/cts/issues/2901 - [f32(-0.0), '0.0f'], + // The number -0.0 can be remapped to 0.0 when stored in a Scalar + // object. It is not possible to guarantee that '-0.0f' will + // be emitted. So the WGSL scalar value printing does not try + // to handle this case. + [f32(-0.0), '0.0f'], // -0.0 can be remapped to 0.0 [f32(1.0), '1.0f'], [f32(-1.0), '-1.0f'], [f32Bits(0x70000000), '1.5845632502852868e+29f'], From 2ee990a4cb91b41491f83b52c9520476b18a9fd8 Mon Sep 17 00:00:00 2001 From: Greggman Date: Tue, 22 Aug 2023 15:45:19 -0700 Subject: [PATCH 003/166] Prevent details toggle if user is selecting text (#2906) * Prevent details toggle if user is selecting text * select all on click --- src/common/runtime/standalone.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/common/runtime/standalone.ts b/src/common/runtime/standalone.ts index da647de29a01..046265db8c29 100644 --- a/src/common/runtime/standalone.ts +++ b/src/common/runtime/standalone.ts @@ -390,6 +390,19 @@ function makeTreeNodeHeaderHTML( const div = $('
').addClass('nodeheader'); const header = $('').appendTo(div); + // prevent toggling if user is selecting text from an input element + { + let lastNodeName = ''; + div.on('pointerdown', event => { + lastNodeName = event.target.nodeName; + }); + div.on('click', event => { + if (lastNodeName === 'INPUT') { + event.preventDefault(); + } + }); + } + const setChecked = () => { div.prop('open', true); // (does not fire onChange) onChange(true); @@ -445,6 +458,9 @@ function makeTreeNodeHeaderHTML( .attr('type', 'text') .prop('readonly', true) .addClass('nodequery') + .on('click', event => { + (event.target as HTMLInputElement).select(); + }) .val(n.query.toString()) .appendTo(nodecolumns); if (n.subtreeCounts) { From 5dfa3b88f0863c3949f713804e0461c9cc34a030 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Wed, 23 Aug 2023 16:42:08 -0700 Subject: [PATCH 004/166] Fix handling of batches in case filtering (#2908) --- src/common/internal/test_group.ts | 60 +++++++++++++++++++------ src/common/internal/tree.ts | 6 +-- src/unittests/loaders_and_trees.spec.ts | 51 ++++++++++++++++++--- 3 files changed, 95 insertions(+), 22 deletions(-) diff --git a/src/common/internal/test_group.ts b/src/common/internal/test_group.ts index 76b110af68cd..bf5c67e4abdf 100644 --- a/src/common/internal/test_group.ts +++ b/src/common/internal/test_group.ts @@ -74,6 +74,9 @@ export function makeTestGroupForUnitTesting( return new TestGroup(fixture); } +/** Parameter name for batch number (see also TestBuilder.batch). */ +const kBatchParamName = 'batch__'; + type TestFn = (t: F & { params: P }) => Promise | void; type BeforeAllSubcasesFn = ( s: S & { params: P } @@ -283,7 +286,7 @@ class TestBuilder { for (const [caseParams, subcases] of builderIterateCasesWithSubcases(this.testCases, null)) { for (const subcaseParams of subcases ?? [{}]) { const params = mergeParams(caseParams, subcaseParams); - assert(this.batchSize === 0 || !('batch__' in params)); + assert(this.batchSize === 0 || !(kBatchParamName in params)); // stringifyPublicParams also checks for invalid params values let testcaseString; @@ -348,24 +351,53 @@ class TestBuilder { *iterate(caseFilter: TestParams | null): IterableIterator { this.testCases ??= kUnitCaseParamsBuilder; + + // Remove the batch__ from the caseFilter because the params builder doesn't + // know about it (we don't add it until later in this function). + let filterToBatch: number | undefined; + const caseFilterWithoutBatch = caseFilter ? { ...caseFilter } : null; + if (caseFilterWithoutBatch && kBatchParamName in caseFilterWithoutBatch) { + const batchParam = caseFilterWithoutBatch[kBatchParamName]; + assert(typeof batchParam === 'number'); + filterToBatch = batchParam; + delete caseFilterWithoutBatch[kBatchParamName]; + } + for (const [caseParams, subcases] of builderIterateCasesWithSubcases( this.testCases, - caseFilter + caseFilterWithoutBatch )) { + // If batches are not used, yield just one case. if (this.batchSize === 0 || subcases === undefined) { yield this.makeCaseSpecific(caseParams, subcases); - } else { - const subcaseArray = Array.from(subcases); - if (subcaseArray.length <= this.batchSize) { - yield this.makeCaseSpecific(caseParams, subcaseArray); - } else { - for (let i = 0; i < subcaseArray.length; i = i + this.batchSize) { - yield this.makeCaseSpecific( - { ...caseParams, batch__: i / this.batchSize }, - subcaseArray.slice(i, Math.min(subcaseArray.length, i + this.batchSize)) - ); - } - } + continue; + } + + // Same if there ends up being only one batch. + const subcaseArray = Array.from(subcases); + if (subcaseArray.length <= this.batchSize) { + yield this.makeCaseSpecific(caseParams, subcaseArray); + continue; + } + + // There are multiple batches. Helper function for this case: + const makeCaseForBatch = (batch: number) => { + const sliceStart = batch * this.batchSize; + return this.makeCaseSpecific( + { ...caseParams, [kBatchParamName]: batch }, + subcaseArray.slice(sliceStart, Math.min(subcaseArray.length, sliceStart + this.batchSize)) + ); + }; + + // If we filter to just one batch, yield it. + if (filterToBatch !== undefined) { + yield makeCaseForBatch(filterToBatch); + continue; + } + + // Finally, if not, yield all of the batches. + for (let batch = 0; batch * this.batchSize < subcaseArray.length; ++batch) { + yield makeCaseForBatch(batch); } } } diff --git a/src/common/internal/tree.ts b/src/common/internal/tree.ts index 812b88c59e91..6cdce2d39a60 100644 --- a/src/common/internal/tree.ts +++ b/src/common/internal/tree.ts @@ -350,14 +350,14 @@ export async function loadTreeForQuery( subtreeL2.subtreeCounts ??= { tests: 1, nodesWithTODO: 0 }; if (t.description) setSubtreeDescriptionAndCountTODOs(subtreeL2, t.description); - let paramsFilter = null; + let caseFilter = null; if ('params' in queryToLoad) { - paramsFilter = queryToLoad.params; + caseFilter = queryToLoad.params; } // MAINTENANCE_TODO: If tree generation gets too slow, avoid actually iterating the cases in a // file if there's no need to (based on the subqueriesToExpand). - for (const c of t.iterate(paramsFilter)) { + for (const c of t.iterate(caseFilter)) { // iterate() guarantees c's query is equal to or a subset of queryToLoad. if (queryToLoad instanceof TestQuerySingleCase) { diff --git a/src/unittests/loaders_and_trees.spec.ts b/src/unittests/loaders_and_trees.spec.ts index 238c89af147c..080fa762a5fd 100644 --- a/src/unittests/loaders_and_trees.spec.ts +++ b/src/unittests/loaders_and_trees.spec.ts @@ -74,6 +74,11 @@ const specsData: { [k: string]: SpecFile } = { { b: 3, a: 1, _c: 0 }, ]) .fn(() => {}); + g.test('batched') + // creates two cases: one for subcases 1,2 and one for subcase 3 + .paramsSubcasesOnly(u => u.combine('x', [1, 2, 3])) + .batch(2) + .fn(() => {}); return g; })(), }, @@ -141,7 +146,7 @@ g.test('suite').fn(t => { g.test('group').fn(async t => { t.collectEvents(); - t.expect((await t.load('suite1:*')).length === 8); + t.expect((await t.load('suite1:*')).length === 10); t.expect( objectEquals(t.events, [ 'suite1/foo.spec.js', @@ -187,7 +192,7 @@ g.test('test').fn(async t => { t.expect((await t.load('suite1:foo:*')).length === 3); t.expect((await t.load('suite1:bar,buzz,buzz:*')).length === 1); - t.expect((await t.load('suite1:baz:*')).length === 4); + t.expect((await t.load('suite1:baz:*')).length === 6); t.expect((await t.load('suite2:foof:bluh,*')).length === 1); t.expect((await t.load('suite2:foof:bluh,a,*')).length === 1); @@ -239,6 +244,13 @@ g.test('case').fn(async t => { } }); +g.test('batching').fn(async t => { + t.expect((await t.load('suite1:baz:batched,*')).length === 2); + t.expect((await t.load('suite1:baz:batched:*')).length === 2); + t.expect((await t.load('suite1:baz:batched:batch__=1;*')).length === 1); + t.expect((await t.load('suite1:baz:batched:batch__=1')).length === 1); +}); + async function runTestcase( t: Fixture, log: Logger, @@ -738,6 +750,7 @@ g.test('iterateCollapsed').fn(async t => { ['suite1:bar,buzz,buzz:zap:*', 0], ['suite1:baz:wye:*', 0], ['suite1:baz:zed:*', 0], + ['suite1:baz:batched:*', 0], ] ); await testIterateCollapsed( @@ -753,6 +766,8 @@ g.test('iterateCollapsed').fn(async t => { ['suite1:baz:wye:x=1', undefined], ['suite1:baz:zed:a=1;b=2', undefined], ['suite1:baz:zed:b=3;a=1', undefined], + ['suite1:baz:batched:batch__=0', undefined], + ['suite1:baz:batched:batch__=1', undefined], ] ); @@ -780,6 +795,7 @@ g.test('iterateCollapsed').fn(async t => { 'suite1:bar,buzz,buzz:zap:*', 'suite1:baz:wye:*', 'suite1:baz:zed:*', + 'suite1:baz:batched:*', ] ); // Test with includeEmptySubtrees=true @@ -808,6 +824,7 @@ g.test('iterateCollapsed').fn(async t => { 'suite1:bar,buzz,buzz:zap:*', 'suite1:baz:wye:*', 'suite1:baz:zed:*', + 'suite1:baz:batched:*', 'suite1:empty,*', ], true @@ -818,19 +835,37 @@ g.test('iterateCollapsed').fn(async t => { t, 1, ['suite1:baz:wye:*'], - ['suite1:foo:*', 'suite1:bar,buzz,buzz:*', 'suite1:baz:wye:*', 'suite1:baz:zed,*'] + [ + 'suite1:foo:*', + 'suite1:bar,buzz,buzz:*', + 'suite1:baz:wye:*', + 'suite1:baz:zed,*', + 'suite1:baz:batched,*', + ] ); await testIterateCollapsed( t, 1, ['suite1:baz:zed:*'], - ['suite1:foo:*', 'suite1:bar,buzz,buzz:*', 'suite1:baz:wye,*', 'suite1:baz:zed:*'] + [ + 'suite1:foo:*', + 'suite1:bar,buzz,buzz:*', + 'suite1:baz:wye,*', + 'suite1:baz:zed:*', + 'suite1:baz:batched,*', + ] ); await testIterateCollapsed( t, 1, ['suite1:baz:wye:*', 'suite1:baz:zed:*'], - ['suite1:foo:*', 'suite1:bar,buzz,buzz:*', 'suite1:baz:wye:*', 'suite1:baz:zed:*'] + [ + 'suite1:foo:*', + 'suite1:bar,buzz,buzz:*', + 'suite1:baz:wye:*', + 'suite1:baz:zed:*', + 'suite1:baz:batched,*', + ] ); await testIterateCollapsed( t, @@ -842,6 +877,7 @@ g.test('iterateCollapsed').fn(async t => { 'suite1:baz:wye:', 'suite1:baz:wye:x=1;*', 'suite1:baz:zed,*', + 'suite1:baz:batched,*', ] ); await testIterateCollapsed( @@ -854,6 +890,7 @@ g.test('iterateCollapsed').fn(async t => { 'suite1:baz:wye:', 'suite1:baz:wye:x=1', 'suite1:baz:zed,*', + 'suite1:baz:batched,*', ] ); await testIterateCollapsed( @@ -866,6 +903,7 @@ g.test('iterateCollapsed').fn(async t => { 'suite1:baz:wye:', 'suite1:baz:wye:x=1;*', 'suite1:baz:zed,*', + 'suite1:baz:batched,*', ] ); await testIterateCollapsed( @@ -880,6 +918,7 @@ g.test('iterateCollapsed').fn(async t => { 'suite1:baz:wye:', 'suite1:baz:wye:x=1;*', 'suite1:baz:zed:*', + 'suite1:baz:batched:*', ] ); await testIterateCollapsed( @@ -894,6 +933,7 @@ g.test('iterateCollapsed').fn(async t => { 'suite1:baz:wye:', 'suite1:baz:wye:x=1', 'suite1:baz:zed:*', + 'suite1:baz:batched:*', ] ); await testIterateCollapsed( @@ -908,6 +948,7 @@ g.test('iterateCollapsed').fn(async t => { 'suite1:baz:wye:', 'suite1:baz:wye:x=1;*', 'suite1:baz:zed:*', + 'suite1:baz:batched:*', ] ); From 75c54607fe0072e4736b740d68d5826d4be75a03 Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Mon, 28 Aug 2023 15:02:48 -0400 Subject: [PATCH 005/166] wgsl: Add execution tests for AF negation (#2909) This refactors the existing code to have a clearer separation from the non-AF test running code, and sets up for implementing vector support. Issue #1626 --- src/unittests/floating_point.spec.ts | 104 ++++-- .../shader/execution/expression/expression.ts | 327 ++++++++++++------ .../expression/unary/af_arithmetic.spec.ts | 43 +++ .../expression/unary/af_assignment.spec.ts | 28 +- .../execution/expression/unary/unary.ts | 11 +- src/webgpu/util/floating_point.ts | 6 +- 6 files changed, 370 insertions(+), 149 deletions(-) create mode 100644 src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 3bab3682872b..9ef92528a342 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -13,6 +13,7 @@ import { map2DArray, oneULPF32, oneULPF16, + oneULPF64, } from '../webgpu/util/math.js'; import { UnitTest } from './unit_test.js'; @@ -37,6 +38,9 @@ const kPlusNULPFunctions = { f16: (x: number, n: number) => { return x + n * oneULPF16(x); }, + abstract: (x: number, n: number) => { + return x + n * oneULPF64(x); + }, }; /** @returns a number one ULP greater than the provided number */ @@ -47,6 +51,9 @@ const kPlusOneULPFunctions = { f16: (x: number): number => { return kPlusNULPFunctions['f16'](x, 1); }, + abstract: (x: number): number => { + return kPlusNULPFunctions['abstract'](x, 1); + }, }; /** @returns a number N * ULP less than the provided number */ @@ -57,6 +64,9 @@ const kMinusNULPFunctions = { f16: (x: number, n: number) => { return x - n * oneULPF16(x); }, + abstract: (x: number, n: number) => { + return x - n * oneULPF64(x); + }, }; /** @returns a number one ULP less than the provided number */ @@ -67,6 +77,9 @@ const kMinusOneULPFunctions = { f16: (x: number): number => { return kMinusNULPFunctions['f16'](x, 1); }, + abstract: (x: number): number => { + return kMinusNULPFunctions['abstract'](x, 1); + }, }; /** @returns the expected IntervalBounds adjusted by the given error function @@ -3074,39 +3087,74 @@ g.test('log2Interval') ); }); -g.test('negationInterval_f32') - .paramsSubcasesOnly( - // prettier-ignore - [ - { input: 0, expected: 0 }, - { input: 0.1, expected: [reinterpretU32AsF32(0xbdcccccd), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0xbdcccccd))] }, // ~-0.1 - { input: 1.0, expected: -1.0 }, - { input: 1.9, expected: [reinterpretU32AsF32(0xbff33334), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0xbff33334))] }, // ~-1.9 - { input: -0.1, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x3dcccccd)), reinterpretU32AsF32(0x3dcccccd)] }, // ~0.1 - { input: -1.0, expected: 1 }, - { input: -1.9, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x3ff33334)), reinterpretU32AsF32(0x3ff33334)] }, // ~1.9 - - // Edge cases - { input: kValue.f32.infinity.positive, expected: kUnboundedBounds }, - { input: kValue.f32.infinity.negative, expected: kUnboundedBounds }, - { input: kValue.f32.positive.max, expected: kValue.f32.negative.min }, - { input: kValue.f32.positive.min, expected: kValue.f32.negative.max }, - { input: kValue.f32.negative.min, expected: kValue.f32.positive.max }, - { input: kValue.f32.negative.max, expected: kValue.f32.positive.min }, +// prettier-ignore +const kNegationIntervalCases = { + f32: [ + // Edge cases + { input: kValue.f32.infinity.positive, expected: kUnboundedBounds }, + { input: kValue.f32.infinity.negative, expected: kUnboundedBounds }, + { input: kValue.f32.positive.max, expected: kValue.f32.negative.min }, + { input: kValue.f32.positive.min, expected: kValue.f32.negative.max }, + { input: kValue.f32.negative.min, expected: kValue.f32.positive.max }, + { input: kValue.f32.negative.max, expected: kValue.f32.positive.min }, + + // Normals + { input: 0.1, expected: [reinterpretU32AsF32(0xbdcccccd), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0xbdcccccd))] }, // ~-0.1 + { input: 1.9, expected: [reinterpretU32AsF32(0xbff33334), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0xbff33334))] }, // ~-1.9 + { input: -0.1, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x3dcccccd)), reinterpretU32AsF32(0x3dcccccd)] }, // ~0.1 + { input: -1.9, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x3ff33334)), reinterpretU32AsF32(0x3ff33334)] }, // ~1.9 + + // Subnormals + { input: kValue.f32.subnormal.positive.max, expected: [kValue.f32.subnormal.negative.min, 0] }, + { input: kValue.f32.subnormal.positive.min, expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: kValue.f32.subnormal.negative.min, expected: [0, kValue.f32.subnormal.positive.max] }, + { input: kValue.f32.subnormal.negative.max, expected: [0, kValue.f32.subnormal.positive.min] }, + ] as ScalarToIntervalCase[], + abstract: [ + // Edge cases + { input: kValue.f64.infinity.positive, expected: kUnboundedBounds }, + { input: kValue.f64.infinity.negative, expected: kUnboundedBounds }, + { input: kValue.f64.positive.max, expected: kValue.f64.negative.min }, + { input: kValue.f64.positive.min, expected: kValue.f64.negative.max }, + { input: kValue.f64.negative.min, expected: kValue.f64.positive.max }, + { input: kValue.f64.negative.max, expected: kValue.f64.positive.min }, + + // Normals + { input: 0.1, expected: -0.1 }, + { input: 1.9, expected: -1.9 }, + { input: -0.1, expected: 0.1 }, + { input: -1.9, expected: 1.9 }, + + // Subnormals + { input: kValue.f64.subnormal.positive.max, expected: [kValue.f64.subnormal.negative.min, 0] }, + { input: kValue.f64.subnormal.positive.min, expected: [kValue.f64.subnormal.negative.max, 0] }, + { input: kValue.f64.subnormal.negative.min, expected: [0, kValue.f64.subnormal.positive.max] }, + { input: kValue.f64.subnormal.negative.max, expected: [0, kValue.f64.subnormal.positive.min] }, + ] as ScalarToIntervalCase[], +} as const; - // 32-bit subnormals - { input: kValue.f32.subnormal.positive.max, expected: [kValue.f32.subnormal.negative.min, 0] }, - { input: kValue.f32.subnormal.positive.min, expected: [kValue.f32.subnormal.negative.max, 0] }, - { input: kValue.f32.subnormal.negative.min, expected: [0, kValue.f32.subnormal.positive.max] }, - { input: kValue.f32.subnormal.negative.max, expected: [0, kValue.f32.subnormal.positive.min] }, - ] +g.test('negationInterval') + .params(u => + u + .combine('trait', ['f32', 'abstract'] as const) + .beginSubcases() + .expandWithParams(p => { + // prettier-ignore + return [ + { input: 0, expected: 0 }, + { input: 1.0, expected: -1.0 }, + { input: -1.0, expected: 1 }, + ...kNegationIntervalCases[p.trait], + ]; + }) ) .fn(t => { - const expected = FP.f32.toInterval(t.params.expected); - const got = FP.f32.negationInterval(t.params.input); + const trait = FP[t.params.trait]; + const expected = trait.toInterval(t.params.expected); + const got = trait.negationInterval(t.params.input); t.expect( objectEquals(expected, got), - `f32.negationInterval(${t.params.input}) returned ${got}. Expected ${expected}` + `${t.params.trait}.negationInterval(${t.params.input}) returned ${got}. Expected ${expected}` ); }); diff --git a/src/webgpu/shader/execution/expression/expression.ts b/src/webgpu/shader/execution/expression/expression.ts index e12a3f71392d..8ff28c217fc4 100644 --- a/src/webgpu/shader/execution/expression/expression.ts +++ b/src/webgpu/shader/execution/expression/expression.ts @@ -70,6 +70,9 @@ export type InputSource = /** All possible input sources */ export const allInputSources: InputSource[] = ['const', 'uniform', 'storage_r', 'storage_rw']; +/** Just constant input source */ +export const onlyConstInputSource: InputSource[] = ['const']; + /** Configuration for running a expression test */ export type Config = { // Where the input values are read from @@ -85,6 +88,22 @@ export type Config = { // Helper for returning the stride for a given Type function valueStride(ty: Type): number { + // AbstractFloats are passed out of the shader via a struct of 2x u32s and + // unpacking containers as arrays + if (scalarTypeOf(ty).kind === 'abstract-float') { + if (ty instanceof ScalarType) { + return 16; + } + if (ty instanceof VectorType) { + if (ty.width === 2) { + return 16; + } + // vec3s have padding to make them the same size as vec4s + return 32; + } + unreachable('Matrices of AbstractFloats have not yet been implemented'); + } + if (ty instanceof MatrixType) { switch (ty.cols) { case 2: @@ -135,10 +154,11 @@ function valueStrides(tys: Type[]): number { // Helper for returning the WGSL storage type for the given Type. function storageType(ty: Type): Type { if (ty instanceof ScalarType) { - assert(ty.kind !== 'f64', `'No storage type defined for 'f64' values`); - if (ty.kind === 'abstract-float') { - return TypeVec(2, TypeU32); - } + assert(ty.kind !== 'f64', `No storage type defined for 'f64' values`); + assert( + ty.kind !== 'abstract-float', + `Custom handling is implemented for 'abstract-float' values` + ); if (ty.kind === 'bool') { return TypeU32; } @@ -161,7 +181,7 @@ function fromStorage(ty: Type, expr: string): string { if (ty instanceof VectorType) { assert( ty.elementType.kind !== 'abstract-float', - `AbstractFloat values should not be in input storage` + `AbstractFloat values cannot appear in input storage` ); assert(ty.elementType.kind !== 'f64', `'No storage type defined for 'f64' values`); if (ty.elementType.kind === 'bool') { @@ -176,7 +196,7 @@ function toStorage(ty: Type, expr: string): string { if (ty instanceof ScalarType) { assert( ty.kind !== 'abstract-float', - `AbstractFloat values have custom code writing to input storage` + `AbstractFloat values have custom code for writing to storage` ); assert(ty.kind !== 'f64', `No storage type defined for 'f64' values`); if (ty.kind === 'bool') { @@ -186,7 +206,7 @@ function toStorage(ty: Type, expr: string): string { if (ty instanceof VectorType) { assert( ty.elementType.kind !== 'abstract-float', - `AbstractFloat values have custom code writing to input storage` + `AbstractFloat values have custom code for writing to storage` ); assert(ty.elementType.kind !== 'f64', `'No storage type defined for 'f64' values`); if (ty.elementType.kind === 'bool') { @@ -438,11 +458,40 @@ export type ShaderBuilder = ( * Helper that returns the WGSL to declare the output storage buffer for a shader */ function wgslOutputs(resultType: Type, count: number): string { - return ` + let output_struct = undefined; + if (scalarTypeOf(resultType).kind !== 'abstract-float') { + output_struct = ` struct Output { @size(${valueStride(resultType)}) value : ${storageType(resultType)} +};`; + } else { + if (resultType instanceof ScalarType) { + output_struct = `struct AF { + low: u32, + high: u32, +}; + +struct Output { + @size(${valueStride(resultType)}) value: AF, +};`; + } + if (resultType instanceof VectorType) { + const dim = resultType.width; + output_struct = `struct AF { + low: u32, + high: u32, }; -@group(0) @binding(0) var outputs : array;`; + +struct Output { + @size(${valueStride(resultType)}) value: array, +};`; + } + // TBD: Implement Matrix result support + } + assert(output_struct !== undefined, `No implementation for result type '${resultType}'`); + return `${output_struct} +@group(0) @binding(0) var outputs : array; +`; } /** @@ -454,10 +503,6 @@ function wgslValuesArray( cases: CaseList, expressionBuilder: ExpressionBuilder ): string { - // AbstractFloat values cannot be stored in an array - if (parameterTypes.some(ty => scalarTypeOf(ty).kind === 'abstract-float')) { - return ''; - } return ` const values = array( ${cases.map(c => expressionBuilder(map(c.input, v => v.wgsl()))).join(',\n ')} @@ -508,15 +553,16 @@ function basicExpressionShaderBody( cases: CaseList, inputSource: InputSource ): string { + assert( + scalarTypeOf(resultType).kind !== 'abstract-float', + `abstractFloatShaderBuilder should be used when result type is 'abstract-float` + ); if (inputSource === 'const') { ////////////////////////////////////////////////////////////////////////// // Constant eval ////////////////////////////////////////////////////////////////////////// let body = ''; - if ( - scalarTypeOf(resultType).kind !== 'abstract-float' && - parameterTypes.some(ty => scalarTypeOf(ty).kind === 'abstract-float') - ) { + if (parameterTypes.some(ty => scalarTypeOf(ty).kind === 'abstract-float')) { // Directly assign the expression to the output, to avoid an // intermediate store, which will concretize the value early body = cases @@ -528,96 +574,6 @@ function basicExpressionShaderBody( )};` ) .join('\n '); - } else if (scalarTypeOf(resultType).kind === 'abstract-float') { - // AbstractFloats are f64s under the hood. WebGPU does not support - // putting f64s in buffers, so the result needs to be split up into u32s - // and rebuilt in the test framework. - // - // This is complicated by the fact that user defined functions cannot - // take/return AbstractFloats, and AbstractFloats cannot be stored in - // variables, so the code cannot just inject a simple utility function - // at the top of the shader, instead this snippet needs to be inlined - // everywhere the test needs to return an AbstractFloat. - // - // select is used below, since ifs are not available during constant - // eval. This has the side effect of short-circuiting doesn't occur, so - // both sides of the select have to evaluate and be valid. - // - // This snippet implements FTZ for subnormals to bypass the need for - // complex subnormal specific logic. - // - // Expressions resulting in subnormals can still be reasonably tested, - // since this snippet will return 0 with the correct sign, which is - // always in the acceptance interval for a subnormal result, since an - // implementation may FTZ. - // - // Document for the snippet is included here in this code block, since - // shader length affects compilation time significantly on some - // backends. - // - // Snippet with documentation: - // const kExponentBias = 1022; - // - // // Detect if the value is zero or subnormal, so that FTZ behaviour - // // can occur - // const subnormal_or_zero : bool = (${expr} <= ${kValue.f64.subnormal.positive.max}) && (${expr} >= ${kValue.f64.subnormal.negative.min}); - // - // // MSB of the upper u32 is 1 if the value is negative, otherwise 0 - // // Extract the sign bit early, so that abs() can be used with - // // frexp() so negative cases do not need to be handled - // const sign_bit : u32 = select(0, 0x80000000, ${expr} < 0); - // - // // Use frexp() to obtain the exponent and fractional parts, and - // // then perform FTZ if needed - // const f = frexp(abs(${expr})); - // const f_fract = select(f.fract, 0, subnormal_or_zero); - // const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); - // - // // Adjust for the exponent bias and shift for storing in bits - // // [20..31] of the upper u32 - // const exponent_bits : u32 = (f_exp + kExponentBias) << 20; - // - // // Extract the portion of the mantissa that appears in upper u32 as - // // a float for later use - // const high_mantissa = ldexp(f_fract, 21); - // - // // Extract the portion of the mantissa that appears in upper u32 as - // // as bits. This value is masked, because normals will explicitly - // // have the implicit leading 1 that should not be in the final - // // result. - // const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; - // - // // Calculate the mantissa stored in the lower u32 as a float - // const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); - // - // // Convert the lower u32 mantissa to bits - // const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); - // - // // Pack the result into 2x u32s for writing out to the testing - // // framework - // outputs[${i}].value.x = low_mantissa_bits; - // outputs[${i}].value.y = sign_bit | exponent_bits | high_mantissa_bits; - body = cases - .map((c, i) => { - const expr = `${expressionBuilder(map(c.input, v => v.wgsl()))}`; - // prettier-ignore - return ` { - const kExponentBias = 1022; - const subnormal_or_zero : bool = (${expr} <= ${kValue.f64.subnormal.positive.max}) && (${expr} >= ${kValue.f64.subnormal.negative.min}); - const sign_bit : u32 = select(0, 0x80000000, ${expr} < 0); - const f = frexp(abs(${expr})); - const f_fract = select(f.fract, 0, subnormal_or_zero); - const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); - const exponent_bits : u32 = (f_exp + kExponentBias) << 20; - const high_mantissa = ldexp(f_fract, 21); - const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; - const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); - const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); - outputs[${i}].value.x = low_mantissa_bits; - outputs[${i}].value.y = sign_bit | exponent_bits | high_mantissa_bits; - }`; - }) - .join('\n '); } else if (globalTestConfig.unrollConstEvalLoops) { body = cases .map((_, i) => { @@ -808,6 +764,163 @@ fn main() { }; } +/** + * @returns a string that extracts the value of an AbstractFloat into an output + * destination + * @param expr expression for an AbstractFloat value, if working with vectors or + * matrices, this string needs to include indexing into the + * container. + * @param case_idx index in the case output array to assign the result + * @param accessor string representing how access the AF that needs to be extracted. + * For scalars this should be left as ''. + * For vectors and matrices this will be an indexing operation, + * i.e. '[i]' + * */ +function abstractFloatSnippet(expr: string, case_idx: number, accessor: string = ''): string { + // AbstractFloats are f64s under the hood. WebGPU does not support + // putting f64s in buffers, so the result needs to be split up into u32s + // and rebuilt in the test framework. + // + // Since there is no 64-bit data type that can be used as an element for a + // vector or a matrix in WGSL, the testing framework needs to pass the u32s + // via a struct with two u32s, and deconstruct vectors and matrices into + // arrays. + // + // This is complicated by the fact that user defined functions cannot + // take/return AbstractFloats, and AbstractFloats cannot be stored in + // variables, so the code cannot just inject a simple utility function + // at the top of the shader, instead this snippet needs to be inlined + // everywhere the test needs to return an AbstractFloat. + // + // select is used below, since ifs are not available during constant + // eval. This has the side effect of short-circuiting doesn't occur, so + // both sides of the select have to evaluate and be valid. + // + // This snippet implements FTZ for subnormals to bypass the need for + // complex subnormal specific logic. + // + // Expressions resulting in subnormals can still be reasonably tested, + // since this snippet will return 0 with the correct sign, which is + // always in the acceptance interval for a subnormal result, since an + // implementation may FTZ. + // + // Documentation for the snippet working with scalar results is included here + // in this code block, since shader length affects compilation time + // significantly on some backends. The code for vectors and matrices basically + // the same thing, with extra indexing operations. + // + // Snippet with documentation: + // const kExponentBias = 1022; + // + // // Detect if the value is zero or subnormal, so that FTZ behaviour + // // can occur + // const subnormal_or_zero : bool = (${expr} <= ${kValue.f64.subnormal.positive.max}) && (${expr} >= ${kValue.f64.subnormal.negative.min}); + // + // // MSB of the upper u32 is 1 if the value is negative, otherwise 0 + // // Extract the sign bit early, so that abs() can be used with + // // frexp() so negative cases do not need to be handled + // const sign_bit : u32 = select(0, 0x80000000, ${expr} < 0); + // + // // Use frexp() to obtain the exponent and fractional parts, and + // // then perform FTZ if needed + // const f = frexp(abs(${expr})); + // const f_fract = select(f.fract, 0, subnormal_or_zero); + // const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); + // + // // Adjust for the exponent bias and shift for storing in bits + // // [20..31] of the upper u32 + // const exponent_bits : u32 = (f_exp + kExponentBias) << 20; + // + // // Extract the portion of the mantissa that appears in upper u32 as + // // a float for later use + // const high_mantissa = ldexp(f_fract, 21); + // + // // Extract the portion of the mantissa that appears in upper u32 as + // // as bits. This value is masked, because normals will explicitly + // // have the implicit leading 1 that should not be in the final + // // result. + // const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; + // + // // Calculate the mantissa stored in the lower u32 as a float + // const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); + // + // // Convert the lower u32 mantissa to bits + // const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); + // + // outputs[${i}].value.high = sign_bit | exponent_bits | high_mantissa_bits; + // outputs[${i}].value.low = low_mantissa_bits; + // prettier-ignore + return ` { + const kExponentBias = 1022; + const subnormal_or_zero : bool = (${expr}${accessor} <= ${kValue.f64.subnormal.positive.max}) && (${expr}${accessor} >= ${kValue.f64.subnormal.negative.min}); + const sign_bit : u32 = select(0, 0x80000000, ${expr}${accessor} < 0); + const f = frexp(abs(${expr}${accessor})); + const f_fract = select(f.fract, 0, subnormal_or_zero); + const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); + const exponent_bits : u32 = (f_exp + kExponentBias) << 20; + const high_mantissa = ldexp(f_fract, 21); + const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; + const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); + const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); + outputs[${case_idx}].value${accessor}.high = sign_bit | exponent_bits | high_mantissa_bits; + outputs[${case_idx}].value${accessor}.low = low_mantissa_bits; + }`; +} + +/** @returns a string for a specific case that has a AbstractFloat result */ +function abstractFloatCaseBody(expr: string, resultType: Type, i: number): string { + if (resultType instanceof ScalarType) { + return abstractFloatSnippet(expr, i); + } + + if (resultType instanceof VectorType) { + return [...Array(resultType.width).keys()] + .map(dim_idx => abstractFloatSnippet(expr, i, `[${dim_idx}]`)) + .join(' \n'); + } + // TDB implement matrix support + + unreachable(`Results of type '${resultType}' not yet implemented`); +} + +/** + * @returns a ShaderBuilder that builds a test shader hands AbstractFloat results. + * @param expressionBuilder an expression builder that will return AbstractFloats + */ +export function abstractFloatShaderBuilder(expressionBuilder: ExpressionBuilder): ShaderBuilder { + return ( + parameterTypes: Array, + resultType: Type, + cases: CaseList, + inputSource: InputSource + ) => { + assert(inputSource === 'const', 'AbstractFloat results are only defined for const-eval'); + assert( + scalarTypeOf(resultType).kind === 'abstract-float', + `Expected resultType of 'abstract-float', received '${scalarTypeOf(resultType).kind}' instead` + ); + + const body = cases + .map((c, i) => { + const expr = `${expressionBuilder(map(c.input, v => v.wgsl()))}`; + return abstractFloatCaseBody(expr, resultType, i); + }) + .join('\n '); + + return ` +${wgslHeader(parameterTypes, resultType)} + +${wgslOutputs(resultType, cases.length)} + +${wgslValuesArray(parameterTypes, resultType, cases, expressionBuilder)} + +@compute @workgroup_size(1) +fn main() { +${body} +}`; + }; +} + /** * Constructs and returns a GPUComputePipeline and GPUBindGroup for running a * batch of test cases. If a pre-created pipeline can be found in diff --git a/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts b/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts new file mode 100644 index 000000000000..d7939ad2b17f --- /dev/null +++ b/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts @@ -0,0 +1,43 @@ +export const description = ` +Execution Tests for AbstractFloat arithmetic unary expression operations +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../gpu_test.js'; +import { TypeAbstractFloat } from '../../../../util/conversion.js'; +import { FP } from '../../../../util/floating_point.js'; +import { fullF64Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; +import { onlyConstInputSource, run } from '../expression.js'; + +import { abstract_unary } from './unary.js'; + +export const g = makeTestGroup(GPUTest); + +export const d = makeCaseCache('unary/f32_arithmetic', { + negation: () => { + return FP.abstract.generateScalarToIntervalCases( + fullF64Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }), + 'unfiltered', + FP.abstract.negationInterval + ); + }, +}); + +g.test('negation') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: -x +Accuracy: Correctly rounded +` + ) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('negation'); + await run(t, abstract_unary('-'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1); + }); diff --git a/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts b/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts index eecc43a9b2a7..372051c949a3 100644 --- a/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts +++ b/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts @@ -14,9 +14,21 @@ import { reinterpretU64AsF64, } from '../../../../util/math.js'; import { makeCaseCache } from '../case_cache.js'; -import { allInputSources, run } from '../expression.js'; +import { + abstractFloatShaderBuilder, + basicExpressionBuilder, + onlyConstInputSource, + run, + ShaderBuilder, +} from '../expression.js'; + +function concrete_assignment(): ShaderBuilder { + return basicExpressionBuilder(value => `${value}`); +} -import { assignment } from './unary.js'; +function abstract_assignment(): ShaderBuilder { + return abstractFloatShaderBuilder(value => `${value}`); +} export const g = makeTestGroup(GPUTest); @@ -68,10 +80,10 @@ g.test('abstract') testing that extracting abstract floats works ` ) - .params(u => u.combine('inputSource', [allInputSources[0]])) // Only defined for const-eval + .params(u => u.combine('inputSource', onlyConstInputSource)) .fn(async t => { const cases = await d.get('abstract'); - await run(t, assignment(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1); + await run(t, abstract_assignment(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1); }); g.test('f32') @@ -81,10 +93,10 @@ g.test('f32') concretizing to f32 ` ) - .params(u => u.combine('inputSource', [allInputSources[0]])) // Only defined for const-eval + .params(u => u.combine('inputSource', onlyConstInputSource)) .fn(async t => { const cases = await d.get('f32'); - await run(t, assignment(), [TypeAbstractFloat], TypeF32, t.params, cases); + await run(t, concrete_assignment(), [TypeAbstractFloat], TypeF32, t.params, cases); }); g.test('f16') @@ -97,8 +109,8 @@ concretizing to f16 .beforeAllSubcases(t => { t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); }) - .params(u => u.combine('inputSource', [allInputSources[0]])) // Only defined for const-eval + .params(u => u.combine('inputSource', onlyConstInputSource)) .fn(async t => { const cases = await d.get('f16'); - await run(t, assignment(), [TypeAbstractFloat], TypeF16, t.params, cases); + await run(t, concrete_assignment(), [TypeAbstractFloat], TypeF16, t.params, cases); }); diff --git a/src/webgpu/shader/execution/expression/unary/unary.ts b/src/webgpu/shader/execution/expression/unary/unary.ts index ce9d6b814747..995ca3ea172d 100644 --- a/src/webgpu/shader/execution/expression/unary/unary.ts +++ b/src/webgpu/shader/execution/expression/unary/unary.ts @@ -1,10 +1,15 @@ -import { basicExpressionBuilder, ShaderBuilder } from '../expression.js'; +import { + abstractFloatShaderBuilder, + basicExpressionBuilder, + ShaderBuilder, +} from '../expression.js'; /* @returns a ShaderBuilder that evaluates a prefix unary operation */ export function unary(op: string): ShaderBuilder { return basicExpressionBuilder(value => `${op}(${value})`); } -export function assignment(): ShaderBuilder { - return basicExpressionBuilder(value => `${value}`); +/* @returns a ShaderBuilder that evaluates a prefix unary operation that returns AbstractFloats */ +export function abstract_unary(op: string): ShaderBuilder { + return abstractFloatShaderBuilder(value => `${op}(${value})`); } diff --git a/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index a1b459359c9e..29ca13c39ff8 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -5,9 +5,9 @@ import { Case, IntervalFilter } from '../shader/execution/expression/expression. import { anyOf } from './compare.js'; import { kValue } from './constants.js'; import { + abstractFloat, f16, f32, - f64, isFloatType, reinterpretF16AsU16, reinterpretF32AsU32, @@ -4820,7 +4820,7 @@ class FPAbstractTraits extends FPTraits { public readonly isSubnormal = isSubnormalNumberF64; public readonly flushSubnormal = flushSubnormalNumberF64; public readonly oneULP = oneULPF64; - public readonly scalarBuilder = f64; + public readonly scalarBuilder = abstractFloat; // Framework - Fundamental Error Intervals - Overrides public readonly absoluteErrorInterval = this.unboundedAbsoluteErrorInterval.bind(this); @@ -4886,7 +4886,7 @@ class FPAbstractTraits extends FPTraits { public readonly multiplicationVectorMatrixInterval = this.unimplementedVectorMatrixToVector.bind( this ); - public readonly negationInterval = this.unimplementedScalarToInterval.bind(this); + public readonly negationInterval = this.negationIntervalImpl.bind(this); public readonly normalizeInterval = this.unimplementedVectorToVector.bind(this); public readonly powInterval = this.unimplementedScalarPairToInterval.bind(this); public readonly quantizeToF16Interval = this.unimplementedScalarToInterval.bind(this); From a0dcafc9407e51555921b07b2c91ca5ea2faee35 Mon Sep 17 00:00:00 2001 From: Austin Eng Date: Tue, 29 Aug 2023 09:57:57 -0700 Subject: [PATCH 006/166] Revert "wgsl: Add execution tests for AF negation (#2909)" (#2912) This reverts commit 75c54607fe0072e4736b740d68d5826d4be75a03. --- src/unittests/floating_point.spec.ts | 104 ++---- .../shader/execution/expression/expression.ts | 327 ++++++------------ .../expression/unary/af_arithmetic.spec.ts | 43 --- .../expression/unary/af_assignment.spec.ts | 28 +- .../execution/expression/unary/unary.ts | 11 +- src/webgpu/util/floating_point.ts | 6 +- 6 files changed, 149 insertions(+), 370 deletions(-) delete mode 100644 src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 9ef92528a342..3bab3682872b 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -13,7 +13,6 @@ import { map2DArray, oneULPF32, oneULPF16, - oneULPF64, } from '../webgpu/util/math.js'; import { UnitTest } from './unit_test.js'; @@ -38,9 +37,6 @@ const kPlusNULPFunctions = { f16: (x: number, n: number) => { return x + n * oneULPF16(x); }, - abstract: (x: number, n: number) => { - return x + n * oneULPF64(x); - }, }; /** @returns a number one ULP greater than the provided number */ @@ -51,9 +47,6 @@ const kPlusOneULPFunctions = { f16: (x: number): number => { return kPlusNULPFunctions['f16'](x, 1); }, - abstract: (x: number): number => { - return kPlusNULPFunctions['abstract'](x, 1); - }, }; /** @returns a number N * ULP less than the provided number */ @@ -64,9 +57,6 @@ const kMinusNULPFunctions = { f16: (x: number, n: number) => { return x - n * oneULPF16(x); }, - abstract: (x: number, n: number) => { - return x - n * oneULPF64(x); - }, }; /** @returns a number one ULP less than the provided number */ @@ -77,9 +67,6 @@ const kMinusOneULPFunctions = { f16: (x: number): number => { return kMinusNULPFunctions['f16'](x, 1); }, - abstract: (x: number): number => { - return kMinusNULPFunctions['abstract'](x, 1); - }, }; /** @returns the expected IntervalBounds adjusted by the given error function @@ -3087,74 +3074,39 @@ g.test('log2Interval') ); }); -// prettier-ignore -const kNegationIntervalCases = { - f32: [ - // Edge cases - { input: kValue.f32.infinity.positive, expected: kUnboundedBounds }, - { input: kValue.f32.infinity.negative, expected: kUnboundedBounds }, - { input: kValue.f32.positive.max, expected: kValue.f32.negative.min }, - { input: kValue.f32.positive.min, expected: kValue.f32.negative.max }, - { input: kValue.f32.negative.min, expected: kValue.f32.positive.max }, - { input: kValue.f32.negative.max, expected: kValue.f32.positive.min }, - - // Normals - { input: 0.1, expected: [reinterpretU32AsF32(0xbdcccccd), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0xbdcccccd))] }, // ~-0.1 - { input: 1.9, expected: [reinterpretU32AsF32(0xbff33334), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0xbff33334))] }, // ~-1.9 - { input: -0.1, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x3dcccccd)), reinterpretU32AsF32(0x3dcccccd)] }, // ~0.1 - { input: -1.9, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x3ff33334)), reinterpretU32AsF32(0x3ff33334)] }, // ~1.9 - - // Subnormals - { input: kValue.f32.subnormal.positive.max, expected: [kValue.f32.subnormal.negative.min, 0] }, - { input: kValue.f32.subnormal.positive.min, expected: [kValue.f32.subnormal.negative.max, 0] }, - { input: kValue.f32.subnormal.negative.min, expected: [0, kValue.f32.subnormal.positive.max] }, - { input: kValue.f32.subnormal.negative.max, expected: [0, kValue.f32.subnormal.positive.min] }, - ] as ScalarToIntervalCase[], - abstract: [ - // Edge cases - { input: kValue.f64.infinity.positive, expected: kUnboundedBounds }, - { input: kValue.f64.infinity.negative, expected: kUnboundedBounds }, - { input: kValue.f64.positive.max, expected: kValue.f64.negative.min }, - { input: kValue.f64.positive.min, expected: kValue.f64.negative.max }, - { input: kValue.f64.negative.min, expected: kValue.f64.positive.max }, - { input: kValue.f64.negative.max, expected: kValue.f64.positive.min }, - - // Normals - { input: 0.1, expected: -0.1 }, - { input: 1.9, expected: -1.9 }, - { input: -0.1, expected: 0.1 }, - { input: -1.9, expected: 1.9 }, - - // Subnormals - { input: kValue.f64.subnormal.positive.max, expected: [kValue.f64.subnormal.negative.min, 0] }, - { input: kValue.f64.subnormal.positive.min, expected: [kValue.f64.subnormal.negative.max, 0] }, - { input: kValue.f64.subnormal.negative.min, expected: [0, kValue.f64.subnormal.positive.max] }, - { input: kValue.f64.subnormal.negative.max, expected: [0, kValue.f64.subnormal.positive.min] }, - ] as ScalarToIntervalCase[], -} as const; +g.test('negationInterval_f32') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: 0, expected: 0 }, + { input: 0.1, expected: [reinterpretU32AsF32(0xbdcccccd), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0xbdcccccd))] }, // ~-0.1 + { input: 1.0, expected: -1.0 }, + { input: 1.9, expected: [reinterpretU32AsF32(0xbff33334), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0xbff33334))] }, // ~-1.9 + { input: -0.1, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x3dcccccd)), reinterpretU32AsF32(0x3dcccccd)] }, // ~0.1 + { input: -1.0, expected: 1 }, + { input: -1.9, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x3ff33334)), reinterpretU32AsF32(0x3ff33334)] }, // ~1.9 -g.test('negationInterval') - .params(u => - u - .combine('trait', ['f32', 'abstract'] as const) - .beginSubcases() - .expandWithParams(p => { - // prettier-ignore - return [ - { input: 0, expected: 0 }, - { input: 1.0, expected: -1.0 }, - { input: -1.0, expected: 1 }, - ...kNegationIntervalCases[p.trait], - ]; - }) + // Edge cases + { input: kValue.f32.infinity.positive, expected: kUnboundedBounds }, + { input: kValue.f32.infinity.negative, expected: kUnboundedBounds }, + { input: kValue.f32.positive.max, expected: kValue.f32.negative.min }, + { input: kValue.f32.positive.min, expected: kValue.f32.negative.max }, + { input: kValue.f32.negative.min, expected: kValue.f32.positive.max }, + { input: kValue.f32.negative.max, expected: kValue.f32.positive.min }, + + // 32-bit subnormals + { input: kValue.f32.subnormal.positive.max, expected: [kValue.f32.subnormal.negative.min, 0] }, + { input: kValue.f32.subnormal.positive.min, expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: kValue.f32.subnormal.negative.min, expected: [0, kValue.f32.subnormal.positive.max] }, + { input: kValue.f32.subnormal.negative.max, expected: [0, kValue.f32.subnormal.positive.min] }, + ] ) .fn(t => { - const trait = FP[t.params.trait]; - const expected = trait.toInterval(t.params.expected); - const got = trait.negationInterval(t.params.input); + const expected = FP.f32.toInterval(t.params.expected); + const got = FP.f32.negationInterval(t.params.input); t.expect( objectEquals(expected, got), - `${t.params.trait}.negationInterval(${t.params.input}) returned ${got}. Expected ${expected}` + `f32.negationInterval(${t.params.input}) returned ${got}. Expected ${expected}` ); }); diff --git a/src/webgpu/shader/execution/expression/expression.ts b/src/webgpu/shader/execution/expression/expression.ts index 8ff28c217fc4..e12a3f71392d 100644 --- a/src/webgpu/shader/execution/expression/expression.ts +++ b/src/webgpu/shader/execution/expression/expression.ts @@ -70,9 +70,6 @@ export type InputSource = /** All possible input sources */ export const allInputSources: InputSource[] = ['const', 'uniform', 'storage_r', 'storage_rw']; -/** Just constant input source */ -export const onlyConstInputSource: InputSource[] = ['const']; - /** Configuration for running a expression test */ export type Config = { // Where the input values are read from @@ -88,22 +85,6 @@ export type Config = { // Helper for returning the stride for a given Type function valueStride(ty: Type): number { - // AbstractFloats are passed out of the shader via a struct of 2x u32s and - // unpacking containers as arrays - if (scalarTypeOf(ty).kind === 'abstract-float') { - if (ty instanceof ScalarType) { - return 16; - } - if (ty instanceof VectorType) { - if (ty.width === 2) { - return 16; - } - // vec3s have padding to make them the same size as vec4s - return 32; - } - unreachable('Matrices of AbstractFloats have not yet been implemented'); - } - if (ty instanceof MatrixType) { switch (ty.cols) { case 2: @@ -154,11 +135,10 @@ function valueStrides(tys: Type[]): number { // Helper for returning the WGSL storage type for the given Type. function storageType(ty: Type): Type { if (ty instanceof ScalarType) { - assert(ty.kind !== 'f64', `No storage type defined for 'f64' values`); - assert( - ty.kind !== 'abstract-float', - `Custom handling is implemented for 'abstract-float' values` - ); + assert(ty.kind !== 'f64', `'No storage type defined for 'f64' values`); + if (ty.kind === 'abstract-float') { + return TypeVec(2, TypeU32); + } if (ty.kind === 'bool') { return TypeU32; } @@ -181,7 +161,7 @@ function fromStorage(ty: Type, expr: string): string { if (ty instanceof VectorType) { assert( ty.elementType.kind !== 'abstract-float', - `AbstractFloat values cannot appear in input storage` + `AbstractFloat values should not be in input storage` ); assert(ty.elementType.kind !== 'f64', `'No storage type defined for 'f64' values`); if (ty.elementType.kind === 'bool') { @@ -196,7 +176,7 @@ function toStorage(ty: Type, expr: string): string { if (ty instanceof ScalarType) { assert( ty.kind !== 'abstract-float', - `AbstractFloat values have custom code for writing to storage` + `AbstractFloat values have custom code writing to input storage` ); assert(ty.kind !== 'f64', `No storage type defined for 'f64' values`); if (ty.kind === 'bool') { @@ -206,7 +186,7 @@ function toStorage(ty: Type, expr: string): string { if (ty instanceof VectorType) { assert( ty.elementType.kind !== 'abstract-float', - `AbstractFloat values have custom code for writing to storage` + `AbstractFloat values have custom code writing to input storage` ); assert(ty.elementType.kind !== 'f64', `'No storage type defined for 'f64' values`); if (ty.elementType.kind === 'bool') { @@ -458,40 +438,11 @@ export type ShaderBuilder = ( * Helper that returns the WGSL to declare the output storage buffer for a shader */ function wgslOutputs(resultType: Type, count: number): string { - let output_struct = undefined; - if (scalarTypeOf(resultType).kind !== 'abstract-float') { - output_struct = ` + return ` struct Output { @size(${valueStride(resultType)}) value : ${storageType(resultType)} -};`; - } else { - if (resultType instanceof ScalarType) { - output_struct = `struct AF { - low: u32, - high: u32, -}; - -struct Output { - @size(${valueStride(resultType)}) value: AF, -};`; - } - if (resultType instanceof VectorType) { - const dim = resultType.width; - output_struct = `struct AF { - low: u32, - high: u32, }; - -struct Output { - @size(${valueStride(resultType)}) value: array, -};`; - } - // TBD: Implement Matrix result support - } - assert(output_struct !== undefined, `No implementation for result type '${resultType}'`); - return `${output_struct} -@group(0) @binding(0) var outputs : array; -`; +@group(0) @binding(0) var outputs : array;`; } /** @@ -503,6 +454,10 @@ function wgslValuesArray( cases: CaseList, expressionBuilder: ExpressionBuilder ): string { + // AbstractFloat values cannot be stored in an array + if (parameterTypes.some(ty => scalarTypeOf(ty).kind === 'abstract-float')) { + return ''; + } return ` const values = array( ${cases.map(c => expressionBuilder(map(c.input, v => v.wgsl()))).join(',\n ')} @@ -553,16 +508,15 @@ function basicExpressionShaderBody( cases: CaseList, inputSource: InputSource ): string { - assert( - scalarTypeOf(resultType).kind !== 'abstract-float', - `abstractFloatShaderBuilder should be used when result type is 'abstract-float` - ); if (inputSource === 'const') { ////////////////////////////////////////////////////////////////////////// // Constant eval ////////////////////////////////////////////////////////////////////////// let body = ''; - if (parameterTypes.some(ty => scalarTypeOf(ty).kind === 'abstract-float')) { + if ( + scalarTypeOf(resultType).kind !== 'abstract-float' && + parameterTypes.some(ty => scalarTypeOf(ty).kind === 'abstract-float') + ) { // Directly assign the expression to the output, to avoid an // intermediate store, which will concretize the value early body = cases @@ -574,6 +528,96 @@ function basicExpressionShaderBody( )};` ) .join('\n '); + } else if (scalarTypeOf(resultType).kind === 'abstract-float') { + // AbstractFloats are f64s under the hood. WebGPU does not support + // putting f64s in buffers, so the result needs to be split up into u32s + // and rebuilt in the test framework. + // + // This is complicated by the fact that user defined functions cannot + // take/return AbstractFloats, and AbstractFloats cannot be stored in + // variables, so the code cannot just inject a simple utility function + // at the top of the shader, instead this snippet needs to be inlined + // everywhere the test needs to return an AbstractFloat. + // + // select is used below, since ifs are not available during constant + // eval. This has the side effect of short-circuiting doesn't occur, so + // both sides of the select have to evaluate and be valid. + // + // This snippet implements FTZ for subnormals to bypass the need for + // complex subnormal specific logic. + // + // Expressions resulting in subnormals can still be reasonably tested, + // since this snippet will return 0 with the correct sign, which is + // always in the acceptance interval for a subnormal result, since an + // implementation may FTZ. + // + // Document for the snippet is included here in this code block, since + // shader length affects compilation time significantly on some + // backends. + // + // Snippet with documentation: + // const kExponentBias = 1022; + // + // // Detect if the value is zero or subnormal, so that FTZ behaviour + // // can occur + // const subnormal_or_zero : bool = (${expr} <= ${kValue.f64.subnormal.positive.max}) && (${expr} >= ${kValue.f64.subnormal.negative.min}); + // + // // MSB of the upper u32 is 1 if the value is negative, otherwise 0 + // // Extract the sign bit early, so that abs() can be used with + // // frexp() so negative cases do not need to be handled + // const sign_bit : u32 = select(0, 0x80000000, ${expr} < 0); + // + // // Use frexp() to obtain the exponent and fractional parts, and + // // then perform FTZ if needed + // const f = frexp(abs(${expr})); + // const f_fract = select(f.fract, 0, subnormal_or_zero); + // const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); + // + // // Adjust for the exponent bias and shift for storing in bits + // // [20..31] of the upper u32 + // const exponent_bits : u32 = (f_exp + kExponentBias) << 20; + // + // // Extract the portion of the mantissa that appears in upper u32 as + // // a float for later use + // const high_mantissa = ldexp(f_fract, 21); + // + // // Extract the portion of the mantissa that appears in upper u32 as + // // as bits. This value is masked, because normals will explicitly + // // have the implicit leading 1 that should not be in the final + // // result. + // const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; + // + // // Calculate the mantissa stored in the lower u32 as a float + // const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); + // + // // Convert the lower u32 mantissa to bits + // const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); + // + // // Pack the result into 2x u32s for writing out to the testing + // // framework + // outputs[${i}].value.x = low_mantissa_bits; + // outputs[${i}].value.y = sign_bit | exponent_bits | high_mantissa_bits; + body = cases + .map((c, i) => { + const expr = `${expressionBuilder(map(c.input, v => v.wgsl()))}`; + // prettier-ignore + return ` { + const kExponentBias = 1022; + const subnormal_or_zero : bool = (${expr} <= ${kValue.f64.subnormal.positive.max}) && (${expr} >= ${kValue.f64.subnormal.negative.min}); + const sign_bit : u32 = select(0, 0x80000000, ${expr} < 0); + const f = frexp(abs(${expr})); + const f_fract = select(f.fract, 0, subnormal_or_zero); + const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); + const exponent_bits : u32 = (f_exp + kExponentBias) << 20; + const high_mantissa = ldexp(f_fract, 21); + const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; + const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); + const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); + outputs[${i}].value.x = low_mantissa_bits; + outputs[${i}].value.y = sign_bit | exponent_bits | high_mantissa_bits; + }`; + }) + .join('\n '); } else if (globalTestConfig.unrollConstEvalLoops) { body = cases .map((_, i) => { @@ -764,163 +808,6 @@ fn main() { }; } -/** - * @returns a string that extracts the value of an AbstractFloat into an output - * destination - * @param expr expression for an AbstractFloat value, if working with vectors or - * matrices, this string needs to include indexing into the - * container. - * @param case_idx index in the case output array to assign the result - * @param accessor string representing how access the AF that needs to be extracted. - * For scalars this should be left as ''. - * For vectors and matrices this will be an indexing operation, - * i.e. '[i]' - * */ -function abstractFloatSnippet(expr: string, case_idx: number, accessor: string = ''): string { - // AbstractFloats are f64s under the hood. WebGPU does not support - // putting f64s in buffers, so the result needs to be split up into u32s - // and rebuilt in the test framework. - // - // Since there is no 64-bit data type that can be used as an element for a - // vector or a matrix in WGSL, the testing framework needs to pass the u32s - // via a struct with two u32s, and deconstruct vectors and matrices into - // arrays. - // - // This is complicated by the fact that user defined functions cannot - // take/return AbstractFloats, and AbstractFloats cannot be stored in - // variables, so the code cannot just inject a simple utility function - // at the top of the shader, instead this snippet needs to be inlined - // everywhere the test needs to return an AbstractFloat. - // - // select is used below, since ifs are not available during constant - // eval. This has the side effect of short-circuiting doesn't occur, so - // both sides of the select have to evaluate and be valid. - // - // This snippet implements FTZ for subnormals to bypass the need for - // complex subnormal specific logic. - // - // Expressions resulting in subnormals can still be reasonably tested, - // since this snippet will return 0 with the correct sign, which is - // always in the acceptance interval for a subnormal result, since an - // implementation may FTZ. - // - // Documentation for the snippet working with scalar results is included here - // in this code block, since shader length affects compilation time - // significantly on some backends. The code for vectors and matrices basically - // the same thing, with extra indexing operations. - // - // Snippet with documentation: - // const kExponentBias = 1022; - // - // // Detect if the value is zero or subnormal, so that FTZ behaviour - // // can occur - // const subnormal_or_zero : bool = (${expr} <= ${kValue.f64.subnormal.positive.max}) && (${expr} >= ${kValue.f64.subnormal.negative.min}); - // - // // MSB of the upper u32 is 1 if the value is negative, otherwise 0 - // // Extract the sign bit early, so that abs() can be used with - // // frexp() so negative cases do not need to be handled - // const sign_bit : u32 = select(0, 0x80000000, ${expr} < 0); - // - // // Use frexp() to obtain the exponent and fractional parts, and - // // then perform FTZ if needed - // const f = frexp(abs(${expr})); - // const f_fract = select(f.fract, 0, subnormal_or_zero); - // const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); - // - // // Adjust for the exponent bias and shift for storing in bits - // // [20..31] of the upper u32 - // const exponent_bits : u32 = (f_exp + kExponentBias) << 20; - // - // // Extract the portion of the mantissa that appears in upper u32 as - // // a float for later use - // const high_mantissa = ldexp(f_fract, 21); - // - // // Extract the portion of the mantissa that appears in upper u32 as - // // as bits. This value is masked, because normals will explicitly - // // have the implicit leading 1 that should not be in the final - // // result. - // const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; - // - // // Calculate the mantissa stored in the lower u32 as a float - // const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); - // - // // Convert the lower u32 mantissa to bits - // const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); - // - // outputs[${i}].value.high = sign_bit | exponent_bits | high_mantissa_bits; - // outputs[${i}].value.low = low_mantissa_bits; - // prettier-ignore - return ` { - const kExponentBias = 1022; - const subnormal_or_zero : bool = (${expr}${accessor} <= ${kValue.f64.subnormal.positive.max}) && (${expr}${accessor} >= ${kValue.f64.subnormal.negative.min}); - const sign_bit : u32 = select(0, 0x80000000, ${expr}${accessor} < 0); - const f = frexp(abs(${expr}${accessor})); - const f_fract = select(f.fract, 0, subnormal_or_zero); - const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); - const exponent_bits : u32 = (f_exp + kExponentBias) << 20; - const high_mantissa = ldexp(f_fract, 21); - const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; - const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); - const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); - outputs[${case_idx}].value${accessor}.high = sign_bit | exponent_bits | high_mantissa_bits; - outputs[${case_idx}].value${accessor}.low = low_mantissa_bits; - }`; -} - -/** @returns a string for a specific case that has a AbstractFloat result */ -function abstractFloatCaseBody(expr: string, resultType: Type, i: number): string { - if (resultType instanceof ScalarType) { - return abstractFloatSnippet(expr, i); - } - - if (resultType instanceof VectorType) { - return [...Array(resultType.width).keys()] - .map(dim_idx => abstractFloatSnippet(expr, i, `[${dim_idx}]`)) - .join(' \n'); - } - // TDB implement matrix support - - unreachable(`Results of type '${resultType}' not yet implemented`); -} - -/** - * @returns a ShaderBuilder that builds a test shader hands AbstractFloat results. - * @param expressionBuilder an expression builder that will return AbstractFloats - */ -export function abstractFloatShaderBuilder(expressionBuilder: ExpressionBuilder): ShaderBuilder { - return ( - parameterTypes: Array, - resultType: Type, - cases: CaseList, - inputSource: InputSource - ) => { - assert(inputSource === 'const', 'AbstractFloat results are only defined for const-eval'); - assert( - scalarTypeOf(resultType).kind === 'abstract-float', - `Expected resultType of 'abstract-float', received '${scalarTypeOf(resultType).kind}' instead` - ); - - const body = cases - .map((c, i) => { - const expr = `${expressionBuilder(map(c.input, v => v.wgsl()))}`; - return abstractFloatCaseBody(expr, resultType, i); - }) - .join('\n '); - - return ` -${wgslHeader(parameterTypes, resultType)} - -${wgslOutputs(resultType, cases.length)} - -${wgslValuesArray(parameterTypes, resultType, cases, expressionBuilder)} - -@compute @workgroup_size(1) -fn main() { -${body} -}`; - }; -} - /** * Constructs and returns a GPUComputePipeline and GPUBindGroup for running a * batch of test cases. If a pre-created pipeline can be found in diff --git a/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts b/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts deleted file mode 100644 index d7939ad2b17f..000000000000 --- a/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -export const description = ` -Execution Tests for AbstractFloat arithmetic unary expression operations -`; - -import { makeTestGroup } from '../../../../../common/framework/test_group.js'; -import { GPUTest } from '../../../../gpu_test.js'; -import { TypeAbstractFloat } from '../../../../util/conversion.js'; -import { FP } from '../../../../util/floating_point.js'; -import { fullF64Range } from '../../../../util/math.js'; -import { makeCaseCache } from '../case_cache.js'; -import { onlyConstInputSource, run } from '../expression.js'; - -import { abstract_unary } from './unary.js'; - -export const g = makeTestGroup(GPUTest); - -export const d = makeCaseCache('unary/f32_arithmetic', { - negation: () => { - return FP.abstract.generateScalarToIntervalCases( - fullF64Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }), - 'unfiltered', - FP.abstract.negationInterval - ); - }, -}); - -g.test('negation') - .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') - .desc( - ` -Expression: -x -Accuracy: Correctly rounded -` - ) - .params(u => - u - .combine('inputSource', onlyConstInputSource) - .combine('vectorize', [undefined, 2, 3, 4] as const) - ) - .fn(async t => { - const cases = await d.get('negation'); - await run(t, abstract_unary('-'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1); - }); diff --git a/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts b/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts index 372051c949a3..eecc43a9b2a7 100644 --- a/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts +++ b/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts @@ -14,21 +14,9 @@ import { reinterpretU64AsF64, } from '../../../../util/math.js'; import { makeCaseCache } from '../case_cache.js'; -import { - abstractFloatShaderBuilder, - basicExpressionBuilder, - onlyConstInputSource, - run, - ShaderBuilder, -} from '../expression.js'; - -function concrete_assignment(): ShaderBuilder { - return basicExpressionBuilder(value => `${value}`); -} +import { allInputSources, run } from '../expression.js'; -function abstract_assignment(): ShaderBuilder { - return abstractFloatShaderBuilder(value => `${value}`); -} +import { assignment } from './unary.js'; export const g = makeTestGroup(GPUTest); @@ -80,10 +68,10 @@ g.test('abstract') testing that extracting abstract floats works ` ) - .params(u => u.combine('inputSource', onlyConstInputSource)) + .params(u => u.combine('inputSource', [allInputSources[0]])) // Only defined for const-eval .fn(async t => { const cases = await d.get('abstract'); - await run(t, abstract_assignment(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1); + await run(t, assignment(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1); }); g.test('f32') @@ -93,10 +81,10 @@ g.test('f32') concretizing to f32 ` ) - .params(u => u.combine('inputSource', onlyConstInputSource)) + .params(u => u.combine('inputSource', [allInputSources[0]])) // Only defined for const-eval .fn(async t => { const cases = await d.get('f32'); - await run(t, concrete_assignment(), [TypeAbstractFloat], TypeF32, t.params, cases); + await run(t, assignment(), [TypeAbstractFloat], TypeF32, t.params, cases); }); g.test('f16') @@ -109,8 +97,8 @@ concretizing to f16 .beforeAllSubcases(t => { t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); }) - .params(u => u.combine('inputSource', onlyConstInputSource)) + .params(u => u.combine('inputSource', [allInputSources[0]])) // Only defined for const-eval .fn(async t => { const cases = await d.get('f16'); - await run(t, concrete_assignment(), [TypeAbstractFloat], TypeF16, t.params, cases); + await run(t, assignment(), [TypeAbstractFloat], TypeF16, t.params, cases); }); diff --git a/src/webgpu/shader/execution/expression/unary/unary.ts b/src/webgpu/shader/execution/expression/unary/unary.ts index 995ca3ea172d..ce9d6b814747 100644 --- a/src/webgpu/shader/execution/expression/unary/unary.ts +++ b/src/webgpu/shader/execution/expression/unary/unary.ts @@ -1,15 +1,10 @@ -import { - abstractFloatShaderBuilder, - basicExpressionBuilder, - ShaderBuilder, -} from '../expression.js'; +import { basicExpressionBuilder, ShaderBuilder } from '../expression.js'; /* @returns a ShaderBuilder that evaluates a prefix unary operation */ export function unary(op: string): ShaderBuilder { return basicExpressionBuilder(value => `${op}(${value})`); } -/* @returns a ShaderBuilder that evaluates a prefix unary operation that returns AbstractFloats */ -export function abstract_unary(op: string): ShaderBuilder { - return abstractFloatShaderBuilder(value => `${op}(${value})`); +export function assignment(): ShaderBuilder { + return basicExpressionBuilder(value => `${value}`); } diff --git a/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index 29ca13c39ff8..a1b459359c9e 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -5,9 +5,9 @@ import { Case, IntervalFilter } from '../shader/execution/expression/expression. import { anyOf } from './compare.js'; import { kValue } from './constants.js'; import { - abstractFloat, f16, f32, + f64, isFloatType, reinterpretF16AsU16, reinterpretF32AsU32, @@ -4820,7 +4820,7 @@ class FPAbstractTraits extends FPTraits { public readonly isSubnormal = isSubnormalNumberF64; public readonly flushSubnormal = flushSubnormalNumberF64; public readonly oneULP = oneULPF64; - public readonly scalarBuilder = abstractFloat; + public readonly scalarBuilder = f64; // Framework - Fundamental Error Intervals - Overrides public readonly absoluteErrorInterval = this.unboundedAbsoluteErrorInterval.bind(this); @@ -4886,7 +4886,7 @@ class FPAbstractTraits extends FPTraits { public readonly multiplicationVectorMatrixInterval = this.unimplementedVectorMatrixToVector.bind( this ); - public readonly negationInterval = this.negationIntervalImpl.bind(this); + public readonly negationInterval = this.unimplementedScalarToInterval.bind(this); public readonly normalizeInterval = this.unimplementedVectorToVector.bind(this); public readonly powInterval = this.unimplementedScalarPairToInterval.bind(this); public readonly quantizeToF16Interval = this.unimplementedScalarToInterval.bind(this); From 7f4eced1cd152835addd5a88b36bea4c68393adc Mon Sep 17 00:00:00 2001 From: Greggman Date: Tue, 29 Aug 2023 10:22:56 -0700 Subject: [PATCH 007/166] Don't color tests which have not been run as skipped (#2910) --- src/common/runtime/standalone.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/runtime/standalone.ts b/src/common/runtime/standalone.ts index 046265db8c29..87689ebac733 100644 --- a/src/common/runtime/standalone.ts +++ b/src/common/runtime/standalone.ts @@ -321,7 +321,7 @@ function makeSubtreeHTML(n: TestSubtree, parentLevel: TestQueryLevel): Visualize if (subtreeResult.fail > 0) { status += 'fail'; } - if (subtreeResult.skip === subtreeResult.total) { + if (subtreeResult.skip === subtreeResult.total && subtreeResult.total > 0) { status += 'skip'; } div.setAttribute('data-status', status); From 03819a515332bd0ac6daf11ab13839bdd75eae7f Mon Sep 17 00:00:00 2001 From: Brandon Jones Date: Tue, 29 Aug 2023 16:49:49 -0700 Subject: [PATCH 008/166] Remove orientation from several texture copy tests (#2914) * Remove orientation from several texture copy tests The orientation argument for these tests was copied from tests which use an ImageBitmap source. Because CreateImageBitmap has an imageOrientation option it made sense to test against all variants of it. However, other tests, such as those that consume ImageData directly, don't have any native mechanism for Y-flipping the source. The data could be generated Y-flipped, but that doesn't increase coverage of any implementation features. Despite not flipping the source orientation, however, the test was still expected the data to be flipped. By removing this argument and no longer testing for flipped source data, these tests all begin passing with no loss of platform coverage. Bug: dawn:2017 * Lint fix --- .../copyToTexture/ImageData.spec.ts | 17 ++++------------- .../web_platform/copyToTexture/image.spec.ts | 17 ++++------------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/src/webgpu/web_platform/copyToTexture/ImageData.spec.ts b/src/webgpu/web_platform/copyToTexture/ImageData.spec.ts index 6bb549326a2d..38876f041928 100644 --- a/src/webgpu/web_platform/copyToTexture/ImageData.spec.ts +++ b/src/webgpu/web_platform/copyToTexture/ImageData.spec.ts @@ -39,7 +39,6 @@ g.test('from_ImageData') ) .params(u => u - .combine('orientation', ['none', 'flipY'] as const) .combine('srcDoFlipYDuringCopy', [true, false]) .combine('dstColorFormat', kValidTextureFormatsForCopyE2T) .combine('dstPremultiplied', [true, false]) @@ -51,14 +50,7 @@ g.test('from_ImageData') t.skipIfTextureFormatNotSupported(t.params.dstColorFormat); }) .fn(t => { - const { - width, - height, - orientation, - dstColorFormat, - dstPremultiplied, - srcDoFlipYDuringCopy, - } = t.params; + const { width, height, dstColorFormat, dstPremultiplied, srcDoFlipYDuringCopy } = t.params; const testColors = kTestColorsAll; @@ -87,7 +79,7 @@ g.test('from_ImageData') }); const expFormat = kTextureFormatInfo[dstColorFormat].baseFormat ?? dstColorFormat; - const flipSrcBeforeCopy = orientation === 'flipY'; + const flipSrcBeforeCopy = false; const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({ srcPixels: imageData.data, srcOrigin: [0, 0], @@ -155,14 +147,13 @@ g.test('copy_subrect_from_ImageData') ) .params(u => u - .combine('orientation', ['none', 'flipY'] as const) .combine('srcDoFlipYDuringCopy', [true, false]) .combine('dstPremultiplied', [true, false]) .beginSubcases() .combine('copySubRectInfo', kCopySubrectInfo) ) .fn(t => { - const { copySubRectInfo, orientation, dstPremultiplied, srcDoFlipYDuringCopy } = t.params; + const { copySubRectInfo, dstPremultiplied, srcDoFlipYDuringCopy } = t.params; const testColors = kTestColorsAll; const { srcOrigin, dstOrigin, srcSize, dstSize, copyExtent } = copySubRectInfo; @@ -192,7 +183,7 @@ g.test('copy_subrect_from_ImageData') GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, }); - const flipSrcBeforeCopy = orientation === 'flipY'; + const flipSrcBeforeCopy = false; const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({ srcPixels: imageData.data, srcOrigin, diff --git a/src/webgpu/web_platform/copyToTexture/image.spec.ts b/src/webgpu/web_platform/copyToTexture/image.spec.ts index b25ade90ab10..e19f986c0f85 100644 --- a/src/webgpu/web_platform/copyToTexture/image.spec.ts +++ b/src/webgpu/web_platform/copyToTexture/image.spec.ts @@ -53,7 +53,6 @@ g.test('from_image') ) .params(u => u - .combine('orientation', ['none', 'flipY'] as const) .combine('srcDoFlipYDuringCopy', [true, false]) .combine('dstColorFormat', kValidTextureFormatsForCopyE2T) .combine('dstPremultiplied', [true, false]) @@ -66,14 +65,7 @@ g.test('from_image') if (typeof HTMLImageElement === 'undefined') t.skip('HTMLImageElement not available'); }) .fn(async t => { - const { - width, - height, - orientation, - dstColorFormat, - dstPremultiplied, - srcDoFlipYDuringCopy, - } = t.params; + const { width, height, dstColorFormat, dstPremultiplied, srcDoFlipYDuringCopy } = t.params; const imageCanvas = document.createElement('canvas'); imageCanvas.width = width; @@ -117,7 +109,7 @@ g.test('from_image') }); const expFormat = kTextureFormatInfo[dstColorFormat].baseFormat ?? dstColorFormat; - const flipSrcBeforeCopy = orientation === 'flipY'; + const flipSrcBeforeCopy = false; const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({ srcPixels: imageData.data, srcOrigin: [0, 0], @@ -186,7 +178,6 @@ g.test('copy_subrect_from_2D_Canvas') ) .params(u => u - .combine('orientation', ['none', 'flipY'] as const) .combine('srcDoFlipYDuringCopy', [true, false]) .combine('dstPremultiplied', [true, false]) .beginSubcases() @@ -196,7 +187,7 @@ g.test('copy_subrect_from_2D_Canvas') if (typeof HTMLImageElement === 'undefined') t.skip('HTMLImageElement not available'); }) .fn(async t => { - const { copySubRectInfo, orientation, dstPremultiplied, srcDoFlipYDuringCopy } = t.params; + const { copySubRectInfo, dstPremultiplied, srcDoFlipYDuringCopy } = t.params; const { srcOrigin, dstOrigin, srcSize, dstSize, copyExtent } = copySubRectInfo; const kColorFormat = 'rgba8unorm'; @@ -242,7 +233,7 @@ g.test('copy_subrect_from_2D_Canvas') GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, }); - const flipSrcBeforeCopy = orientation === 'flipY'; + const flipSrcBeforeCopy = false; const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({ srcPixels: imageData.data, srcOrigin, From 4c8d2f67ecd03fbd3cb4c517ef8461427b6d635d Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Tue, 5 Sep 2023 10:52:37 +0100 Subject: [PATCH 009/166] Add `generate-cache` step to `grunt pre` This is run by `npm test`, and should catch cache file collision issues which have caused reverts in the past. --- Gruntfile.js | 3 ++- src/common/tools/gen_cache.ts | 25 ++++++++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 03f5d1cfcc38..05d70074afc3 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -32,7 +32,7 @@ module.exports = function (grunt) { }, 'generate-cache': { cmd: 'node', - args: ['tools/gen_cache', 'out/data', 'src/webgpu'], + args: ['tools/gen_cache', 'out', 'src/webgpu'], }, unittest: { cmd: 'node', @@ -193,6 +193,7 @@ module.exports = function (grunt) { 'run:generate-listings', 'build-wpt', 'run:build-out-node', + 'run:generate-cache', 'build-done-message', 'ts:check', 'run:presubmit', diff --git a/src/common/tools/gen_cache.ts b/src/common/tools/gen_cache.ts index e7e6d8514f1a..f4674d14db3a 100644 --- a/src/common/tools/gen_cache.ts +++ b/src/common/tools/gen_cache.ts @@ -14,22 +14,30 @@ DataCache will load this instead of building the expensive data at CTS runtime. Options: --help Print this message and exit. --list Print the list of output files without writing them. + --verbose Print each action taken. `); process.exit(rc); } let mode: 'emit' | 'list' = 'emit'; +let verbose = false; const nonFlagsArgs: string[] = []; for (const a of process.argv) { if (a.startsWith('-')) { - if (a === '--list') { - mode = 'list'; - } else if (a === '--help') { - usage(0); - } else { - console.log('unrecognized flag: ', a); - usage(1); + switch (a) { + case '--list': + mode = 'list'; + break; + case '--help': + usage(0); + break; + case '--verbose': + verbose = true; + break; + default: + console.log('unrecognized flag: ', a); + usage(1); } } else { nonFlagsArgs.push(a); @@ -127,6 +135,9 @@ and switch (mode) { case 'emit': { + if (verbose) { + console.log(`building '${outPath}'`); + } const data = await cacheable.build(); const serialized = cacheable.serialize(data); fs.mkdirSync(path.dirname(outPath), { recursive: true }); From d2b2badc17f2894a78b7da92f4b38c47590bdfac Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Mon, 4 Sep 2023 13:47:12 +0100 Subject: [PATCH 010/166] Update fp_primer.md --- docs/fp_primer.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/fp_primer.md b/docs/fp_primer.md index 6d0294b4d1e5..4d08d588f5e0 100644 --- a/docs/fp_primer.md +++ b/docs/fp_primer.md @@ -69,7 +69,7 @@ reference, see [binary64 on Wikipedia](https://en.wikipedia.org/wiki/Double-precision_floating-point_format), [binary32 on Wikipedia](https://en.wikipedia.org/wiki/Single-precision_floating-point_format), and -[binar16 on Wikipedia](https://en.wikipedia.org/wiki/Half-precision_floating-point_format). +[binary16 on Wikipedia](https://en.wikipedia.org/wiki/Half-precision_floating-point_format). In the floating points formats described above, there are two possible zero values, one with all bits being 0, called positive zero, and one all the same @@ -144,7 +144,7 @@ This concept of near-overflow vs far-overflow divides the real number line into | -∞ < `x` <= `-(2 ** (exp_max + 1))` | must round to -∞ | | `-(2 ** (exp_max + 1))` < `x` <= min fp value | must round to -∞ or min value | | min fp value < `x` < max fp value | round as discussed below | -| min fp value <= `x` < `2 ** (exp_max + 1)` | must round to max value or ∞ | +| max fp value <= `x` < `2 ** (exp_max + 1)` | must round to max value or ∞ | | `2 ** (exp_max + 1))` < `x` | implementations must round to ∞ | @@ -184,7 +184,7 @@ operations. Operations, which can be thought of as mathematical functions, are mappings from a set of inputs to a set of outputs. -Denoted `f(x, y) = X`, where f is a placeholder or the name of the operation, +Denoted `f(x, y) = X`, where `f` is a placeholder or the name of the operation, lower case variables are the inputs to the function, and uppercase variables are the outputs of the function. @@ -208,7 +208,7 @@ Some examples of different types of operations: `multiplication(x, y) = X`, which represents the WGSL expression `x * y`, takes in floating point values, `x` and `y`, and produces a floating point value `X`. -`lessThen(x, y) = X`, which represents the WGSL expression `x < y`, again takes +`lessThan(x, y) = X`, which represents the WGSL expression `x < y`, again takes in floating point values, but in this case returns a boolean value. `ldexp(x, y) = X`, which builds a floating point value, takes in a floating @@ -406,9 +406,9 @@ In more precise terms: X = [min(f(x)), max(f(x))] X = [min(f([a, b])), max(f([a, b]))] - X = [f(m), f(M)] + X = [f(m), f(n)] ``` -where m and M are in `[a, b]`, `m <= M`, and produce the min and max results +where `m` and `n` are in `[a, b]`, `m <= n`, and produce the min and max results for `f` on the interval, respectively. So how do we find the minima and maxima for our operation in the domain? @@ -499,15 +499,15 @@ literally pages of expanded intervals. sin(π/2) => [sin(π/2) - 2 ** -11, sin(π/2) + 2 ** -11] => [0 - 2 ** -11, 0 + 2 ** -11] - => [-0.000488.., 0.000488...] + => [-0.000488…, 0.000488…] cos(π/2) => [cos(π/2) - 2 ** -11, cos(π/2) + 2 ** -11] - => [-0.500488, -0.499511...] + => [-0.500488…, -0.499511…] tan(π/2) => sin(π/2)/cos(π/2) - => [-0.000488.., 0.000488...]/[-0.500488..., -0.499511...] - => [min({-0.000488.../-0.500488..., -0.000488.../-0.499511..., ...}), - max(min({-0.000488.../-0.500488..., -0.000488.../-0.499511..., ...}) ] - => [0.000488.../-0.499511..., 0.000488.../0.499511...] + => [-0.000488…, 0.000488…]/[-0.500488…, -0.499511…] + => [min(-0.000488…/-0.500488…, -0.000488…/-0.499511…, 0.000488…/-0.500488…, 0.000488…/-0.499511…), + max(-0.000488…/-0.500488…, -0.000488…/-0.499511…, 0.000488…/-0.500488…, 0.000488…/-0.499511…)] + => [0.000488…/-0.499511…, 0.000488…/0.499511…] => [-0.0009775171, 0.0009775171] ``` @@ -553,10 +553,10 @@ These are compile vs run time, and CPU vs GPU. Broadly speaking compile time execution happens on the host CPU, and run time evaluation occurs on a dedicated GPU. -(SwiftShader technically breaks this by being a software emulation of a GPU that -runs on the CPU, but conceptually one can think of SwiftShader has being a type -of GPU in this context, since it has similar constraints when it comes to -precision, etc.) +(Software graphics implementations like WARP and SwiftShader technically break this by +being a software emulation of a GPU that runs on the CPU, but conceptually one can +think of these implementations being a type of GPU in this context, since it has +similar constraints when it comes to precision, etc.) Compile time evaluation is execution that occurs when setting up a shader module, i.e. when compiling WGSL to a platform specific shading language. It is @@ -588,18 +588,18 @@ let c: f32 = a + b and ``` // compile time -const c: f32 = 1 + 2 +const c: f32 = 1.0f + 2.0f ``` -should produce the same result of `3` in the variable `c`, assuming `1` and `2` -were passed in as `a` & `b`. +should produce the same result of `3.0` in the variable `c`, assuming `1.0` and `2.0` +were passed in as `a` and `b`. The only difference, is when/where the execution occurs. The difference in behaviour between these two occur when the result of the operation is not finite for the underlying floating point type. -If instead of `1` and `2`, we had `10` and `f32.max`, so the true result is -`f32.max + 10`, the user will see a difference. Specifically the runtime +If instead of `1.0` and `2.0`, we had `10.0` and `f32.max`, so the true result is +`f32.max + 10.0`, the behaviours differ. Specifically the runtime evaluated version will still run, but the result in `c` will be an indeterminate value, which is any finite f32 value. For the compile time example instead, compiling the shader will fail validation. @@ -611,7 +611,7 @@ execution. Unfortunately we are dealing with intervals of results and not precise results. So this leads to more even conceptual complexity. For runtime evaluation, this -isn't too bad, because the rule becomes if any part of the interval is +isn't too bad, because the rule becomes: if any part of the interval is non-finite then an indeterminate value can be a result, and the interval for an indeterminate result `[fp min, fp max]`, will include any finite portions of the interval. From d8c45b27fcd54d7f79ea12009f8d947b137f6619 Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Tue, 5 Sep 2023 13:49:33 -0400 Subject: [PATCH 011/166] Reland AF negation tests: (#2920) wgsl: Add execution tests for AF negation (#2909) This refactors the existing code to have a clearer separation from the non-AF test running code, and sets up for implementing vector support. Issue #1626 This PR fixes the issues with cache collisions that caused the initial revert --- src/unittests/floating_point.spec.ts | 104 ++++-- .../shader/execution/expression/expression.ts | 327 ++++++++++++------ .../expression/unary/af_arithmetic.spec.ts | 43 +++ .../expression/unary/af_assignment.spec.ts | 28 +- .../execution/expression/unary/unary.ts | 11 +- src/webgpu/util/floating_point.ts | 6 +- 6 files changed, 370 insertions(+), 149 deletions(-) create mode 100644 src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 3bab3682872b..9ef92528a342 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -13,6 +13,7 @@ import { map2DArray, oneULPF32, oneULPF16, + oneULPF64, } from '../webgpu/util/math.js'; import { UnitTest } from './unit_test.js'; @@ -37,6 +38,9 @@ const kPlusNULPFunctions = { f16: (x: number, n: number) => { return x + n * oneULPF16(x); }, + abstract: (x: number, n: number) => { + return x + n * oneULPF64(x); + }, }; /** @returns a number one ULP greater than the provided number */ @@ -47,6 +51,9 @@ const kPlusOneULPFunctions = { f16: (x: number): number => { return kPlusNULPFunctions['f16'](x, 1); }, + abstract: (x: number): number => { + return kPlusNULPFunctions['abstract'](x, 1); + }, }; /** @returns a number N * ULP less than the provided number */ @@ -57,6 +64,9 @@ const kMinusNULPFunctions = { f16: (x: number, n: number) => { return x - n * oneULPF16(x); }, + abstract: (x: number, n: number) => { + return x - n * oneULPF64(x); + }, }; /** @returns a number one ULP less than the provided number */ @@ -67,6 +77,9 @@ const kMinusOneULPFunctions = { f16: (x: number): number => { return kMinusNULPFunctions['f16'](x, 1); }, + abstract: (x: number): number => { + return kMinusNULPFunctions['abstract'](x, 1); + }, }; /** @returns the expected IntervalBounds adjusted by the given error function @@ -3074,39 +3087,74 @@ g.test('log2Interval') ); }); -g.test('negationInterval_f32') - .paramsSubcasesOnly( - // prettier-ignore - [ - { input: 0, expected: 0 }, - { input: 0.1, expected: [reinterpretU32AsF32(0xbdcccccd), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0xbdcccccd))] }, // ~-0.1 - { input: 1.0, expected: -1.0 }, - { input: 1.9, expected: [reinterpretU32AsF32(0xbff33334), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0xbff33334))] }, // ~-1.9 - { input: -0.1, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x3dcccccd)), reinterpretU32AsF32(0x3dcccccd)] }, // ~0.1 - { input: -1.0, expected: 1 }, - { input: -1.9, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x3ff33334)), reinterpretU32AsF32(0x3ff33334)] }, // ~1.9 - - // Edge cases - { input: kValue.f32.infinity.positive, expected: kUnboundedBounds }, - { input: kValue.f32.infinity.negative, expected: kUnboundedBounds }, - { input: kValue.f32.positive.max, expected: kValue.f32.negative.min }, - { input: kValue.f32.positive.min, expected: kValue.f32.negative.max }, - { input: kValue.f32.negative.min, expected: kValue.f32.positive.max }, - { input: kValue.f32.negative.max, expected: kValue.f32.positive.min }, +// prettier-ignore +const kNegationIntervalCases = { + f32: [ + // Edge cases + { input: kValue.f32.infinity.positive, expected: kUnboundedBounds }, + { input: kValue.f32.infinity.negative, expected: kUnboundedBounds }, + { input: kValue.f32.positive.max, expected: kValue.f32.negative.min }, + { input: kValue.f32.positive.min, expected: kValue.f32.negative.max }, + { input: kValue.f32.negative.min, expected: kValue.f32.positive.max }, + { input: kValue.f32.negative.max, expected: kValue.f32.positive.min }, + + // Normals + { input: 0.1, expected: [reinterpretU32AsF32(0xbdcccccd), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0xbdcccccd))] }, // ~-0.1 + { input: 1.9, expected: [reinterpretU32AsF32(0xbff33334), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0xbff33334))] }, // ~-1.9 + { input: -0.1, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x3dcccccd)), reinterpretU32AsF32(0x3dcccccd)] }, // ~0.1 + { input: -1.9, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x3ff33334)), reinterpretU32AsF32(0x3ff33334)] }, // ~1.9 + + // Subnormals + { input: kValue.f32.subnormal.positive.max, expected: [kValue.f32.subnormal.negative.min, 0] }, + { input: kValue.f32.subnormal.positive.min, expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: kValue.f32.subnormal.negative.min, expected: [0, kValue.f32.subnormal.positive.max] }, + { input: kValue.f32.subnormal.negative.max, expected: [0, kValue.f32.subnormal.positive.min] }, + ] as ScalarToIntervalCase[], + abstract: [ + // Edge cases + { input: kValue.f64.infinity.positive, expected: kUnboundedBounds }, + { input: kValue.f64.infinity.negative, expected: kUnboundedBounds }, + { input: kValue.f64.positive.max, expected: kValue.f64.negative.min }, + { input: kValue.f64.positive.min, expected: kValue.f64.negative.max }, + { input: kValue.f64.negative.min, expected: kValue.f64.positive.max }, + { input: kValue.f64.negative.max, expected: kValue.f64.positive.min }, + + // Normals + { input: 0.1, expected: -0.1 }, + { input: 1.9, expected: -1.9 }, + { input: -0.1, expected: 0.1 }, + { input: -1.9, expected: 1.9 }, + + // Subnormals + { input: kValue.f64.subnormal.positive.max, expected: [kValue.f64.subnormal.negative.min, 0] }, + { input: kValue.f64.subnormal.positive.min, expected: [kValue.f64.subnormal.negative.max, 0] }, + { input: kValue.f64.subnormal.negative.min, expected: [0, kValue.f64.subnormal.positive.max] }, + { input: kValue.f64.subnormal.negative.max, expected: [0, kValue.f64.subnormal.positive.min] }, + ] as ScalarToIntervalCase[], +} as const; - // 32-bit subnormals - { input: kValue.f32.subnormal.positive.max, expected: [kValue.f32.subnormal.negative.min, 0] }, - { input: kValue.f32.subnormal.positive.min, expected: [kValue.f32.subnormal.negative.max, 0] }, - { input: kValue.f32.subnormal.negative.min, expected: [0, kValue.f32.subnormal.positive.max] }, - { input: kValue.f32.subnormal.negative.max, expected: [0, kValue.f32.subnormal.positive.min] }, - ] +g.test('negationInterval') + .params(u => + u + .combine('trait', ['f32', 'abstract'] as const) + .beginSubcases() + .expandWithParams(p => { + // prettier-ignore + return [ + { input: 0, expected: 0 }, + { input: 1.0, expected: -1.0 }, + { input: -1.0, expected: 1 }, + ...kNegationIntervalCases[p.trait], + ]; + }) ) .fn(t => { - const expected = FP.f32.toInterval(t.params.expected); - const got = FP.f32.negationInterval(t.params.input); + const trait = FP[t.params.trait]; + const expected = trait.toInterval(t.params.expected); + const got = trait.negationInterval(t.params.input); t.expect( objectEquals(expected, got), - `f32.negationInterval(${t.params.input}) returned ${got}. Expected ${expected}` + `${t.params.trait}.negationInterval(${t.params.input}) returned ${got}. Expected ${expected}` ); }); diff --git a/src/webgpu/shader/execution/expression/expression.ts b/src/webgpu/shader/execution/expression/expression.ts index e12a3f71392d..8ff28c217fc4 100644 --- a/src/webgpu/shader/execution/expression/expression.ts +++ b/src/webgpu/shader/execution/expression/expression.ts @@ -70,6 +70,9 @@ export type InputSource = /** All possible input sources */ export const allInputSources: InputSource[] = ['const', 'uniform', 'storage_r', 'storage_rw']; +/** Just constant input source */ +export const onlyConstInputSource: InputSource[] = ['const']; + /** Configuration for running a expression test */ export type Config = { // Where the input values are read from @@ -85,6 +88,22 @@ export type Config = { // Helper for returning the stride for a given Type function valueStride(ty: Type): number { + // AbstractFloats are passed out of the shader via a struct of 2x u32s and + // unpacking containers as arrays + if (scalarTypeOf(ty).kind === 'abstract-float') { + if (ty instanceof ScalarType) { + return 16; + } + if (ty instanceof VectorType) { + if (ty.width === 2) { + return 16; + } + // vec3s have padding to make them the same size as vec4s + return 32; + } + unreachable('Matrices of AbstractFloats have not yet been implemented'); + } + if (ty instanceof MatrixType) { switch (ty.cols) { case 2: @@ -135,10 +154,11 @@ function valueStrides(tys: Type[]): number { // Helper for returning the WGSL storage type for the given Type. function storageType(ty: Type): Type { if (ty instanceof ScalarType) { - assert(ty.kind !== 'f64', `'No storage type defined for 'f64' values`); - if (ty.kind === 'abstract-float') { - return TypeVec(2, TypeU32); - } + assert(ty.kind !== 'f64', `No storage type defined for 'f64' values`); + assert( + ty.kind !== 'abstract-float', + `Custom handling is implemented for 'abstract-float' values` + ); if (ty.kind === 'bool') { return TypeU32; } @@ -161,7 +181,7 @@ function fromStorage(ty: Type, expr: string): string { if (ty instanceof VectorType) { assert( ty.elementType.kind !== 'abstract-float', - `AbstractFloat values should not be in input storage` + `AbstractFloat values cannot appear in input storage` ); assert(ty.elementType.kind !== 'f64', `'No storage type defined for 'f64' values`); if (ty.elementType.kind === 'bool') { @@ -176,7 +196,7 @@ function toStorage(ty: Type, expr: string): string { if (ty instanceof ScalarType) { assert( ty.kind !== 'abstract-float', - `AbstractFloat values have custom code writing to input storage` + `AbstractFloat values have custom code for writing to storage` ); assert(ty.kind !== 'f64', `No storage type defined for 'f64' values`); if (ty.kind === 'bool') { @@ -186,7 +206,7 @@ function toStorage(ty: Type, expr: string): string { if (ty instanceof VectorType) { assert( ty.elementType.kind !== 'abstract-float', - `AbstractFloat values have custom code writing to input storage` + `AbstractFloat values have custom code for writing to storage` ); assert(ty.elementType.kind !== 'f64', `'No storage type defined for 'f64' values`); if (ty.elementType.kind === 'bool') { @@ -438,11 +458,40 @@ export type ShaderBuilder = ( * Helper that returns the WGSL to declare the output storage buffer for a shader */ function wgslOutputs(resultType: Type, count: number): string { - return ` + let output_struct = undefined; + if (scalarTypeOf(resultType).kind !== 'abstract-float') { + output_struct = ` struct Output { @size(${valueStride(resultType)}) value : ${storageType(resultType)} +};`; + } else { + if (resultType instanceof ScalarType) { + output_struct = `struct AF { + low: u32, + high: u32, +}; + +struct Output { + @size(${valueStride(resultType)}) value: AF, +};`; + } + if (resultType instanceof VectorType) { + const dim = resultType.width; + output_struct = `struct AF { + low: u32, + high: u32, }; -@group(0) @binding(0) var outputs : array;`; + +struct Output { + @size(${valueStride(resultType)}) value: array, +};`; + } + // TBD: Implement Matrix result support + } + assert(output_struct !== undefined, `No implementation for result type '${resultType}'`); + return `${output_struct} +@group(0) @binding(0) var outputs : array; +`; } /** @@ -454,10 +503,6 @@ function wgslValuesArray( cases: CaseList, expressionBuilder: ExpressionBuilder ): string { - // AbstractFloat values cannot be stored in an array - if (parameterTypes.some(ty => scalarTypeOf(ty).kind === 'abstract-float')) { - return ''; - } return ` const values = array( ${cases.map(c => expressionBuilder(map(c.input, v => v.wgsl()))).join(',\n ')} @@ -508,15 +553,16 @@ function basicExpressionShaderBody( cases: CaseList, inputSource: InputSource ): string { + assert( + scalarTypeOf(resultType).kind !== 'abstract-float', + `abstractFloatShaderBuilder should be used when result type is 'abstract-float` + ); if (inputSource === 'const') { ////////////////////////////////////////////////////////////////////////// // Constant eval ////////////////////////////////////////////////////////////////////////// let body = ''; - if ( - scalarTypeOf(resultType).kind !== 'abstract-float' && - parameterTypes.some(ty => scalarTypeOf(ty).kind === 'abstract-float') - ) { + if (parameterTypes.some(ty => scalarTypeOf(ty).kind === 'abstract-float')) { // Directly assign the expression to the output, to avoid an // intermediate store, which will concretize the value early body = cases @@ -528,96 +574,6 @@ function basicExpressionShaderBody( )};` ) .join('\n '); - } else if (scalarTypeOf(resultType).kind === 'abstract-float') { - // AbstractFloats are f64s under the hood. WebGPU does not support - // putting f64s in buffers, so the result needs to be split up into u32s - // and rebuilt in the test framework. - // - // This is complicated by the fact that user defined functions cannot - // take/return AbstractFloats, and AbstractFloats cannot be stored in - // variables, so the code cannot just inject a simple utility function - // at the top of the shader, instead this snippet needs to be inlined - // everywhere the test needs to return an AbstractFloat. - // - // select is used below, since ifs are not available during constant - // eval. This has the side effect of short-circuiting doesn't occur, so - // both sides of the select have to evaluate and be valid. - // - // This snippet implements FTZ for subnormals to bypass the need for - // complex subnormal specific logic. - // - // Expressions resulting in subnormals can still be reasonably tested, - // since this snippet will return 0 with the correct sign, which is - // always in the acceptance interval for a subnormal result, since an - // implementation may FTZ. - // - // Document for the snippet is included here in this code block, since - // shader length affects compilation time significantly on some - // backends. - // - // Snippet with documentation: - // const kExponentBias = 1022; - // - // // Detect if the value is zero or subnormal, so that FTZ behaviour - // // can occur - // const subnormal_or_zero : bool = (${expr} <= ${kValue.f64.subnormal.positive.max}) && (${expr} >= ${kValue.f64.subnormal.negative.min}); - // - // // MSB of the upper u32 is 1 if the value is negative, otherwise 0 - // // Extract the sign bit early, so that abs() can be used with - // // frexp() so negative cases do not need to be handled - // const sign_bit : u32 = select(0, 0x80000000, ${expr} < 0); - // - // // Use frexp() to obtain the exponent and fractional parts, and - // // then perform FTZ if needed - // const f = frexp(abs(${expr})); - // const f_fract = select(f.fract, 0, subnormal_or_zero); - // const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); - // - // // Adjust for the exponent bias and shift for storing in bits - // // [20..31] of the upper u32 - // const exponent_bits : u32 = (f_exp + kExponentBias) << 20; - // - // // Extract the portion of the mantissa that appears in upper u32 as - // // a float for later use - // const high_mantissa = ldexp(f_fract, 21); - // - // // Extract the portion of the mantissa that appears in upper u32 as - // // as bits. This value is masked, because normals will explicitly - // // have the implicit leading 1 that should not be in the final - // // result. - // const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; - // - // // Calculate the mantissa stored in the lower u32 as a float - // const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); - // - // // Convert the lower u32 mantissa to bits - // const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); - // - // // Pack the result into 2x u32s for writing out to the testing - // // framework - // outputs[${i}].value.x = low_mantissa_bits; - // outputs[${i}].value.y = sign_bit | exponent_bits | high_mantissa_bits; - body = cases - .map((c, i) => { - const expr = `${expressionBuilder(map(c.input, v => v.wgsl()))}`; - // prettier-ignore - return ` { - const kExponentBias = 1022; - const subnormal_or_zero : bool = (${expr} <= ${kValue.f64.subnormal.positive.max}) && (${expr} >= ${kValue.f64.subnormal.negative.min}); - const sign_bit : u32 = select(0, 0x80000000, ${expr} < 0); - const f = frexp(abs(${expr})); - const f_fract = select(f.fract, 0, subnormal_or_zero); - const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); - const exponent_bits : u32 = (f_exp + kExponentBias) << 20; - const high_mantissa = ldexp(f_fract, 21); - const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; - const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); - const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); - outputs[${i}].value.x = low_mantissa_bits; - outputs[${i}].value.y = sign_bit | exponent_bits | high_mantissa_bits; - }`; - }) - .join('\n '); } else if (globalTestConfig.unrollConstEvalLoops) { body = cases .map((_, i) => { @@ -808,6 +764,163 @@ fn main() { }; } +/** + * @returns a string that extracts the value of an AbstractFloat into an output + * destination + * @param expr expression for an AbstractFloat value, if working with vectors or + * matrices, this string needs to include indexing into the + * container. + * @param case_idx index in the case output array to assign the result + * @param accessor string representing how access the AF that needs to be extracted. + * For scalars this should be left as ''. + * For vectors and matrices this will be an indexing operation, + * i.e. '[i]' + * */ +function abstractFloatSnippet(expr: string, case_idx: number, accessor: string = ''): string { + // AbstractFloats are f64s under the hood. WebGPU does not support + // putting f64s in buffers, so the result needs to be split up into u32s + // and rebuilt in the test framework. + // + // Since there is no 64-bit data type that can be used as an element for a + // vector or a matrix in WGSL, the testing framework needs to pass the u32s + // via a struct with two u32s, and deconstruct vectors and matrices into + // arrays. + // + // This is complicated by the fact that user defined functions cannot + // take/return AbstractFloats, and AbstractFloats cannot be stored in + // variables, so the code cannot just inject a simple utility function + // at the top of the shader, instead this snippet needs to be inlined + // everywhere the test needs to return an AbstractFloat. + // + // select is used below, since ifs are not available during constant + // eval. This has the side effect of short-circuiting doesn't occur, so + // both sides of the select have to evaluate and be valid. + // + // This snippet implements FTZ for subnormals to bypass the need for + // complex subnormal specific logic. + // + // Expressions resulting in subnormals can still be reasonably tested, + // since this snippet will return 0 with the correct sign, which is + // always in the acceptance interval for a subnormal result, since an + // implementation may FTZ. + // + // Documentation for the snippet working with scalar results is included here + // in this code block, since shader length affects compilation time + // significantly on some backends. The code for vectors and matrices basically + // the same thing, with extra indexing operations. + // + // Snippet with documentation: + // const kExponentBias = 1022; + // + // // Detect if the value is zero or subnormal, so that FTZ behaviour + // // can occur + // const subnormal_or_zero : bool = (${expr} <= ${kValue.f64.subnormal.positive.max}) && (${expr} >= ${kValue.f64.subnormal.negative.min}); + // + // // MSB of the upper u32 is 1 if the value is negative, otherwise 0 + // // Extract the sign bit early, so that abs() can be used with + // // frexp() so negative cases do not need to be handled + // const sign_bit : u32 = select(0, 0x80000000, ${expr} < 0); + // + // // Use frexp() to obtain the exponent and fractional parts, and + // // then perform FTZ if needed + // const f = frexp(abs(${expr})); + // const f_fract = select(f.fract, 0, subnormal_or_zero); + // const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); + // + // // Adjust for the exponent bias and shift for storing in bits + // // [20..31] of the upper u32 + // const exponent_bits : u32 = (f_exp + kExponentBias) << 20; + // + // // Extract the portion of the mantissa that appears in upper u32 as + // // a float for later use + // const high_mantissa = ldexp(f_fract, 21); + // + // // Extract the portion of the mantissa that appears in upper u32 as + // // as bits. This value is masked, because normals will explicitly + // // have the implicit leading 1 that should not be in the final + // // result. + // const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; + // + // // Calculate the mantissa stored in the lower u32 as a float + // const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); + // + // // Convert the lower u32 mantissa to bits + // const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); + // + // outputs[${i}].value.high = sign_bit | exponent_bits | high_mantissa_bits; + // outputs[${i}].value.low = low_mantissa_bits; + // prettier-ignore + return ` { + const kExponentBias = 1022; + const subnormal_or_zero : bool = (${expr}${accessor} <= ${kValue.f64.subnormal.positive.max}) && (${expr}${accessor} >= ${kValue.f64.subnormal.negative.min}); + const sign_bit : u32 = select(0, 0x80000000, ${expr}${accessor} < 0); + const f = frexp(abs(${expr}${accessor})); + const f_fract = select(f.fract, 0, subnormal_or_zero); + const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero); + const exponent_bits : u32 = (f_exp + kExponentBias) << 20; + const high_mantissa = ldexp(f_fract, 21); + const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff; + const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21); + const low_mantissa_bits = u32(ldexp(low_mantissa, 53)); + outputs[${case_idx}].value${accessor}.high = sign_bit | exponent_bits | high_mantissa_bits; + outputs[${case_idx}].value${accessor}.low = low_mantissa_bits; + }`; +} + +/** @returns a string for a specific case that has a AbstractFloat result */ +function abstractFloatCaseBody(expr: string, resultType: Type, i: number): string { + if (resultType instanceof ScalarType) { + return abstractFloatSnippet(expr, i); + } + + if (resultType instanceof VectorType) { + return [...Array(resultType.width).keys()] + .map(dim_idx => abstractFloatSnippet(expr, i, `[${dim_idx}]`)) + .join(' \n'); + } + // TDB implement matrix support + + unreachable(`Results of type '${resultType}' not yet implemented`); +} + +/** + * @returns a ShaderBuilder that builds a test shader hands AbstractFloat results. + * @param expressionBuilder an expression builder that will return AbstractFloats + */ +export function abstractFloatShaderBuilder(expressionBuilder: ExpressionBuilder): ShaderBuilder { + return ( + parameterTypes: Array, + resultType: Type, + cases: CaseList, + inputSource: InputSource + ) => { + assert(inputSource === 'const', 'AbstractFloat results are only defined for const-eval'); + assert( + scalarTypeOf(resultType).kind === 'abstract-float', + `Expected resultType of 'abstract-float', received '${scalarTypeOf(resultType).kind}' instead` + ); + + const body = cases + .map((c, i) => { + const expr = `${expressionBuilder(map(c.input, v => v.wgsl()))}`; + return abstractFloatCaseBody(expr, resultType, i); + }) + .join('\n '); + + return ` +${wgslHeader(parameterTypes, resultType)} + +${wgslOutputs(resultType, cases.length)} + +${wgslValuesArray(parameterTypes, resultType, cases, expressionBuilder)} + +@compute @workgroup_size(1) +fn main() { +${body} +}`; + }; +} + /** * Constructs and returns a GPUComputePipeline and GPUBindGroup for running a * batch of test cases. If a pre-created pipeline can be found in diff --git a/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts b/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts new file mode 100644 index 000000000000..67b2390bfa88 --- /dev/null +++ b/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts @@ -0,0 +1,43 @@ +export const description = ` +Execution Tests for AbstractFloat arithmetic unary expression operations +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../gpu_test.js'; +import { TypeAbstractFloat } from '../../../../util/conversion.js'; +import { FP } from '../../../../util/floating_point.js'; +import { fullF64Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; +import { onlyConstInputSource, run } from '../expression.js'; + +import { abstract_unary } from './unary.js'; + +export const g = makeTestGroup(GPUTest); + +export const d = makeCaseCache('unary/af_arithmetic', { + negation: () => { + return FP.abstract.generateScalarToIntervalCases( + fullF64Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }), + 'unfiltered', + FP.abstract.negationInterval + ); + }, +}); + +g.test('negation') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: -x +Accuracy: Correctly rounded +` + ) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('negation'); + await run(t, abstract_unary('-'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1); + }); diff --git a/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts b/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts index eecc43a9b2a7..372051c949a3 100644 --- a/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts +++ b/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts @@ -14,9 +14,21 @@ import { reinterpretU64AsF64, } from '../../../../util/math.js'; import { makeCaseCache } from '../case_cache.js'; -import { allInputSources, run } from '../expression.js'; +import { + abstractFloatShaderBuilder, + basicExpressionBuilder, + onlyConstInputSource, + run, + ShaderBuilder, +} from '../expression.js'; + +function concrete_assignment(): ShaderBuilder { + return basicExpressionBuilder(value => `${value}`); +} -import { assignment } from './unary.js'; +function abstract_assignment(): ShaderBuilder { + return abstractFloatShaderBuilder(value => `${value}`); +} export const g = makeTestGroup(GPUTest); @@ -68,10 +80,10 @@ g.test('abstract') testing that extracting abstract floats works ` ) - .params(u => u.combine('inputSource', [allInputSources[0]])) // Only defined for const-eval + .params(u => u.combine('inputSource', onlyConstInputSource)) .fn(async t => { const cases = await d.get('abstract'); - await run(t, assignment(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1); + await run(t, abstract_assignment(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1); }); g.test('f32') @@ -81,10 +93,10 @@ g.test('f32') concretizing to f32 ` ) - .params(u => u.combine('inputSource', [allInputSources[0]])) // Only defined for const-eval + .params(u => u.combine('inputSource', onlyConstInputSource)) .fn(async t => { const cases = await d.get('f32'); - await run(t, assignment(), [TypeAbstractFloat], TypeF32, t.params, cases); + await run(t, concrete_assignment(), [TypeAbstractFloat], TypeF32, t.params, cases); }); g.test('f16') @@ -97,8 +109,8 @@ concretizing to f16 .beforeAllSubcases(t => { t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); }) - .params(u => u.combine('inputSource', [allInputSources[0]])) // Only defined for const-eval + .params(u => u.combine('inputSource', onlyConstInputSource)) .fn(async t => { const cases = await d.get('f16'); - await run(t, assignment(), [TypeAbstractFloat], TypeF16, t.params, cases); + await run(t, concrete_assignment(), [TypeAbstractFloat], TypeF16, t.params, cases); }); diff --git a/src/webgpu/shader/execution/expression/unary/unary.ts b/src/webgpu/shader/execution/expression/unary/unary.ts index ce9d6b814747..995ca3ea172d 100644 --- a/src/webgpu/shader/execution/expression/unary/unary.ts +++ b/src/webgpu/shader/execution/expression/unary/unary.ts @@ -1,10 +1,15 @@ -import { basicExpressionBuilder, ShaderBuilder } from '../expression.js'; +import { + abstractFloatShaderBuilder, + basicExpressionBuilder, + ShaderBuilder, +} from '../expression.js'; /* @returns a ShaderBuilder that evaluates a prefix unary operation */ export function unary(op: string): ShaderBuilder { return basicExpressionBuilder(value => `${op}(${value})`); } -export function assignment(): ShaderBuilder { - return basicExpressionBuilder(value => `${value}`); +/* @returns a ShaderBuilder that evaluates a prefix unary operation that returns AbstractFloats */ +export function abstract_unary(op: string): ShaderBuilder { + return abstractFloatShaderBuilder(value => `${op}(${value})`); } diff --git a/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index a1b459359c9e..29ca13c39ff8 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -5,9 +5,9 @@ import { Case, IntervalFilter } from '../shader/execution/expression/expression. import { anyOf } from './compare.js'; import { kValue } from './constants.js'; import { + abstractFloat, f16, f32, - f64, isFloatType, reinterpretF16AsU16, reinterpretF32AsU32, @@ -4820,7 +4820,7 @@ class FPAbstractTraits extends FPTraits { public readonly isSubnormal = isSubnormalNumberF64; public readonly flushSubnormal = flushSubnormalNumberF64; public readonly oneULP = oneULPF64; - public readonly scalarBuilder = f64; + public readonly scalarBuilder = abstractFloat; // Framework - Fundamental Error Intervals - Overrides public readonly absoluteErrorInterval = this.unboundedAbsoluteErrorInterval.bind(this); @@ -4886,7 +4886,7 @@ class FPAbstractTraits extends FPTraits { public readonly multiplicationVectorMatrixInterval = this.unimplementedVectorMatrixToVector.bind( this ); - public readonly negationInterval = this.unimplementedScalarToInterval.bind(this); + public readonly negationInterval = this.negationIntervalImpl.bind(this); public readonly normalizeInterval = this.unimplementedVectorToVector.bind(this); public readonly powInterval = this.unimplementedScalarPairToInterval.bind(this); public readonly quantizeToF16Interval = this.unimplementedScalarToInterval.bind(this); From 4f3574d1833552d60cf54d63078eaa99f5291328 Mon Sep 17 00:00:00 2001 From: Greggman Date: Tue, 5 Sep 2023 15:31:23 -0700 Subject: [PATCH 012/166] Compat: Skip if copyTextureToTexture not supported (#2923) This may be a temporary change if copying compressed textures get removed from core and moved to a feature. --- .../command_buffer/copyTextureToTexture.spec.ts | 3 ++- src/webgpu/gpu_test.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts b/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts index 05ffcef0decb..4c55b5162f9b 100644 --- a/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts +++ b/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts @@ -904,6 +904,7 @@ g.test('color_textures,compressed,non_array') ) .beforeAllSubcases(t => { const { srcFormat, dstFormat } = t.params; + t.skipIfCopyTextureToTextureNotSupportedForFormat(srcFormat, dstFormat); t.selectDeviceOrSkipTestCase([ kTextureFormatInfo[srcFormat].feature, kTextureFormatInfo[dstFormat].feature, @@ -1058,7 +1059,7 @@ g.test('color_textures,compressed,array') ) .beforeAllSubcases(t => { const { srcFormat, dstFormat } = t.params; - + t.skipIfCopyTextureToTextureNotSupportedForFormat(srcFormat, dstFormat); t.selectDeviceOrSkipTestCase([ kTextureFormatInfo[srcFormat].feature, kTextureFormatInfo[dstFormat].feature, diff --git a/src/webgpu/gpu_test.ts b/src/webgpu/gpu_test.ts index 8a8eff5e0404..68683b63fe0c 100644 --- a/src/webgpu/gpu_test.ts +++ b/src/webgpu/gpu_test.ts @@ -213,6 +213,16 @@ export class GPUTestSubcaseBatchState extends SubcaseBatchState { } } + skipIfCopyTextureToTextureNotSupportedForFormat(...formats: (GPUTextureFormat | undefined)[]) { + if (this.isCompatibility) { + for (const format of formats) { + if (format && isCompressedTextureFormat(format)) { + this.skip(`copyTextureToTexture with ${format} is not supported`); + } + } + } + } + skipIfTextureViewDimensionNotSupported(...dimensions: (GPUTextureViewDimension | undefined)[]) { if (this.isCompatibility) { for (const dimension of dimensions) { From 8ca48c83c4e95a77d74b234cbd144a0adde19cea Mon Sep 17 00:00:00 2001 From: jzm-intel Date: Wed, 6 Sep 2023 10:40:12 +0800 Subject: [PATCH 013/166] wgsl: f16 built-in execution test for clamp (#2918) This PR add execution tests for f16 built-in clamp. Issue: #1248 --- src/unittests/floating_point.spec.ts | 176 ++++++++++-------- .../expression/call/builtin/clamp.spec.ts | 104 ++++++----- src/webgpu/util/floating_point.ts | 4 +- 3 files changed, 157 insertions(+), 127 deletions(-) diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 9ef92528a342..40e2534f38d9 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -4589,103 +4589,119 @@ interface ScalarTripleToIntervalCase { expected: number | IntervalBounds; } -g.test('clampMedianInterval_f32') - .paramsSubcasesOnly( - // prettier-ignore - [ - // Normals - { input: [0, 0, 0], expected: 0 }, - { input: [1, 0, 0], expected: 0 }, - { input: [0, 1, 0], expected: 0 }, - { input: [0, 0, 1], expected: 0 }, - { input: [1, 0, 1], expected: 1 }, - { input: [1, 1, 0], expected: 1 }, - { input: [0, 1, 1], expected: 1 }, - { input: [1, 1, 1], expected: 1 }, - { input: [1, 10, 100], expected: 10 }, - { input: [10, 1, 100], expected: 10 }, - { input: [100, 1, 10], expected: 10 }, - { input: [-10, 1, 100], expected: 1 }, - { input: [10, 1, -100], expected: 1 }, - { input: [-10, 1, -100], expected: -10 }, - { input: [-10, -10, -10], expected: -10 }, +g.test('clampMedianInterval') + .params(u => + u + .combine('trait', ['f32', 'f16'] as const) + .beginSubcases() + .expandWithParams(p => { + const trait = FP[p.trait]; + const constants = trait.constants(); + // prettier-ignore + return [ + // Normals + { input: [0, 0, 0], expected: 0 }, + { input: [1, 0, 0], expected: 0 }, + { input: [0, 1, 0], expected: 0 }, + { input: [0, 0, 1], expected: 0 }, + { input: [1, 0, 1], expected: 1 }, + { input: [1, 1, 0], expected: 1 }, + { input: [0, 1, 1], expected: 1 }, + { input: [1, 1, 1], expected: 1 }, + { input: [1, 10, 100], expected: 10 }, + { input: [10, 1, 100], expected: 10 }, + { input: [100, 1, 10], expected: 10 }, + { input: [-10, 1, 100], expected: 1 }, + { input: [10, 1, -100], expected: 1 }, + { input: [-10, 1, -100], expected: -10 }, + { input: [-10, -10, -10], expected: -10 }, - // Subnormals - { input: [kValue.f32.subnormal.positive.max, 0, 0], expected: 0 }, - { input: [0, kValue.f32.subnormal.positive.max, 0], expected: 0 }, - { input: [0, 0, kValue.f32.subnormal.positive.max], expected: 0 }, - { input: [kValue.f32.subnormal.positive.max, 0, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, - { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max, 0], expected: [0, kValue.f32.subnormal.positive.max] }, - { input: [0, kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, - { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, - { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.min, kValue.f32.subnormal.negative.max], expected: [0, kValue.f32.subnormal.positive.min] }, - { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.negative.min, kValue.f32.subnormal.negative.max], expected: [kValue.f32.subnormal.negative.max, 0] }, - { input: [kValue.f32.positive.max, kValue.f32.positive.max, kValue.f32.subnormal.positive.min], expected: kValue.f32.positive.max }, + // Subnormals + { input: [constants.positive.subnormal.max, 0, 0], expected: 0 }, + { input: [0, constants.positive.subnormal.max, 0], expected: 0 }, + { input: [0, 0, constants.positive.subnormal.max], expected: 0 }, + { input: [constants.positive.subnormal.max, 0, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] }, + { input: [constants.positive.subnormal.max, constants.positive.subnormal.max, 0], expected: [0, constants.positive.subnormal.max] }, + { input: [0, constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] }, + { input: [constants.positive.subnormal.max, constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] }, + { input: [constants.positive.subnormal.max, constants.positive.subnormal.min, constants.negative.subnormal.max], expected: [0, constants.positive.subnormal.min] }, + { input: [constants.positive.subnormal.max, constants.negative.subnormal.min, constants.negative.subnormal.max], expected: [constants.negative.subnormal.max, 0] }, + { input: [constants.positive.max, constants.positive.max, constants.positive.subnormal.min], expected: constants.positive.max }, - // Infinities - { input: [0, 1, kValue.f32.infinity.positive], expected: kUnboundedBounds }, - { input: [0, kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kUnboundedBounds }, - { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kUnboundedBounds }, - { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kUnboundedBounds }, - ] + // Infinities + { input: [0, 1, constants.positive.infinity], expected: kUnboundedBounds }, + { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds }, + { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds }, + { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds }, + ]; + }) ) .fn(t => { const [x, y, z] = t.params.input; - const expected = FP.f32.toInterval(t.params.expected); - const got = FP.f32.clampMedianInterval(x, y, z); + const trait = FP[t.params.trait]; + const expected = trait.toInterval(t.params.expected); + const got = trait.clampMedianInterval(x, y, z); t.expect( objectEquals(expected, got), - `f32.clampMedianInterval(${x}, ${y}, ${z}) returned ${got}. Expected ${expected}` + `${t.params.trait}.clampMedianInterval(${x}, ${y}, ${z}) returned ${got}. Expected ${expected}` ); }); -g.test('clampMinMaxInterval_f32') - .paramsSubcasesOnly( - // prettier-ignore - [ - // Normals - { input: [0, 0, 0], expected: 0 }, - { input: [1, 0, 0], expected: 0 }, - { input: [0, 1, 0], expected: 0 }, - { input: [0, 0, 1], expected: 0 }, - { input: [1, 0, 1], expected: 1 }, - { input: [1, 1, 0], expected: 0 }, - { input: [0, 1, 1], expected: 1 }, - { input: [1, 1, 1], expected: 1 }, - { input: [1, 10, 100], expected: 10 }, - { input: [10, 1, 100], expected: 10 }, - { input: [100, 1, 10], expected: 10 }, - { input: [-10, 1, 100], expected: 1 }, - { input: [10, 1, -100], expected: -100 }, - { input: [-10, 1, -100], expected: -100 }, - { input: [-10, -10, -10], expected: -10 }, +g.test('clampMinMaxInterval') + .params(u => + u + .combine('trait', ['f32', 'f16'] as const) + .beginSubcases() + .expandWithParams(p => { + const trait = FP[p.trait]; + const constants = trait.constants(); + // prettier-ignore + return [ + // Normals + { input: [0, 0, 0], expected: 0 }, + { input: [1, 0, 0], expected: 0 }, + { input: [0, 1, 0], expected: 0 }, + { input: [0, 0, 1], expected: 0 }, + { input: [1, 0, 1], expected: 1 }, + { input: [1, 1, 0], expected: 0 }, + { input: [0, 1, 1], expected: 1 }, + { input: [1, 1, 1], expected: 1 }, + { input: [1, 10, 100], expected: 10 }, + { input: [10, 1, 100], expected: 10 }, + { input: [100, 1, 10], expected: 10 }, + { input: [-10, 1, 100], expected: 1 }, + { input: [10, 1, -100], expected: -100 }, + { input: [-10, 1, -100], expected: -100 }, + { input: [-10, -10, -10], expected: -10 }, - // Subnormals - { input: [kValue.f32.subnormal.positive.max, 0, 0], expected: [0, kValue.f32.subnormal.positive.max] }, - { input: [0, kValue.f32.subnormal.positive.max, 0], expected: [0, kValue.f32.subnormal.positive.max] }, - { input: [0, 0, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, - { input: [kValue.f32.subnormal.positive.max, 0, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, - { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max, 0], expected: [0, kValue.f32.subnormal.positive.max] }, - { input: [0, kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, - { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, - { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.min, kValue.f32.subnormal.negative.max], expected: [kValue.f32.subnormal.negative.max, kValue.f32.subnormal.positive.max] }, - { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.negative.min, kValue.f32.subnormal.negative.max], expected: [kValue.f32.subnormal.negative.min, kValue.f32.subnormal.positive.max] }, - { input: [kValue.f32.positive.max, kValue.f32.positive.max, kValue.f32.subnormal.positive.min], expected: [0, kValue.f32.subnormal.positive.min] }, + // Subnormals + { input: [constants.positive.subnormal.max, 0, 0], expected: [0, constants.positive.subnormal.max] }, + { input: [0, constants.positive.subnormal.max, 0], expected: [0, constants.positive.subnormal.max] }, + { input: [0, 0, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] }, + { input: [constants.positive.subnormal.max, 0, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] }, + { input: [constants.positive.subnormal.max, constants.positive.subnormal.max, 0], expected: [0, constants.positive.subnormal.max] }, + { input: [0, constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] }, + { input: [constants.positive.subnormal.max, constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] }, + { input: [constants.positive.subnormal.max, constants.positive.subnormal.min, constants.negative.subnormal.max], expected: [constants.negative.subnormal.max, constants.positive.subnormal.max] }, + { input: [constants.positive.subnormal.max, constants.negative.subnormal.min, constants.negative.subnormal.max], expected: [constants.negative.subnormal.min, constants.positive.subnormal.max] }, + { input: [constants.positive.max, constants.positive.max, constants.positive.subnormal.min], expected: [0, constants.positive.subnormal.min] }, - // Infinities - { input: [0, 1, kValue.f32.infinity.positive], expected: kUnboundedBounds }, - { input: [0, kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kUnboundedBounds }, - { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kUnboundedBounds }, - { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kUnboundedBounds }, - ] + // Infinities + { input: [0, 1, constants.positive.infinity], expected: kUnboundedBounds }, + { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds }, + { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds }, + { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds }, + ]; + }) ) .fn(t => { const [x, y, z] = t.params.input; - const expected = FP.f32.toInterval(t.params.expected); - const got = FP.f32.clampMinMaxInterval(x, y, z); + const trait = FP[t.params.trait]; + const expected = trait.toInterval(t.params.expected); + const got = trait.clampMinMaxInterval(x, y, z); t.expect( objectEquals(expected, got), - `f32.clampMinMaxInterval(${x}, ${y}, ${z}) returned ${got}. Expected ${expected}` + `${t.params.trait}.clampMinMaxInterval(${x}, ${y}, ${z}) returned ${got}. Expected ${expected}` ); }); diff --git a/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts index 308fb852fbbc..a7fb74d56e89 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts @@ -16,9 +16,9 @@ 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, TypeI32, TypeU32 } from '../../../../../util/conversion.js'; +import { ScalarType, TypeF32, TypeF16, TypeI32, TypeU32 } from '../../../../../util/conversion.js'; import { FP } from '../../../../../util/floating_point.js'; -import { sparseF32Range } from '../../../../../util/math.js'; +import { sparseF32Range, sparseF16Range } from '../../../../../util/math.js'; import { makeCaseCache } from '../../case_cache.js'; import { allInputSources, Case, run } from '../../expression.js'; @@ -26,9 +26,20 @@ import { builtin } from './builtin.js'; export const g = makeTestGroup(GPUTest); -const u32Values = [kValue.u32.min, 1, 2, 0x70000000, 0x80000000, kValue.u32.max]; - -const i32Values = [kValue.i32.negative.min, -2, -1, 0, 1, 2, 0x70000000, kValue.i32.positive.max]; +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: () => { @@ -44,10 +55,16 @@ export const d = makeCaseCache('clamp', { return generateIntegerTestCases(i32Values, TypeI32, 'const'); }, f32_const: () => { - return generateF32TestCases(sparseF32Range(), 'const'); + return generateFloatTestCases(sparseF32Range(), 'f32', 'const'); }, f32_non_const: () => { - return generateF32TestCases(sparseF32Range(), 'non-const'); + return generateFloatTestCases(sparseF32Range(), 'f32', 'non-const'); + }, + f16_const: () => { + return generateFloatTestCases(sparseF16Range(), 'f16', 'const'); + }, + f16_non_const: () => { + return generateFloatTestCases(sparseF16Range(), 'f16', 'non-const'); }, }); @@ -57,48 +74,39 @@ function generateIntegerTestCases( type: ScalarType, stage: 'const' | 'non-const' ): Array { - const cases = new Array(); - for (const e of test_values) { - for (const low of test_values) { - for (const high of test_values) { - if (stage === 'const' && low > high) { - continue; // This would result in a shader compilation error - } - cases.push({ - input: [type.create(e), type.create(low), type.create(high)], - expected: type.create(Math.min(Math.max(e, low), high)), - }); - } - } - } - return cases; + 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 generateF32TestCases( +function generateFloatTestCases( test_values: Array, + trait: 'f32' | 'f16', stage: 'const' | 'non-const' ): Array { - const cases = new Array(); - for (const e of test_values) { - for (const low of test_values) { - for (const high of test_values) { - if (stage === 'const' && low > high) { - continue; // This would result in a shader compilation error - } - const c = FP.f32.makeScalarTripleToIntervalCase( - e, - low, - high, - stage === 'const' ? 'finite' : 'unfiltered', - ...FP.f32.clampIntervals - ); - if (c !== undefined) { - cases.push(c); - } - } - } - } - return cases; + 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') @@ -156,4 +164,10 @@ g.test('f16') .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .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/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index 29ca13c39ff8..2197ecc60f4e 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -5127,8 +5127,8 @@ class F16Traits extends FPTraits { public readonly atan2Interval = this.atan2IntervalImpl.bind(this); public readonly atanhInterval = this.unimplementedScalarToInterval.bind(this); public readonly ceilInterval = this.ceilIntervalImpl.bind(this); - public readonly clampMedianInterval = this.unimplementedScalarTripleToInterval.bind(this); - public readonly clampMinMaxInterval = this.unimplementedScalarTripleToInterval.bind(this); + public readonly clampMedianInterval = this.clampMedianIntervalImpl.bind(this); + public readonly clampMinMaxInterval = this.clampMinMaxIntervalImpl.bind(this); public readonly clampIntervals = [this.clampMedianInterval, this.clampMinMaxInterval]; public readonly cosInterval = this.cosIntervalImpl.bind(this); public readonly coshInterval = this.unimplementedScalarToInterval.bind(this); From ef82e7bde92132c7981574bb8c976a6ff91d9357 Mon Sep 17 00:00:00 2001 From: jzm-intel Date: Wed, 6 Sep 2023 10:51:18 +0800 Subject: [PATCH 014/166] wgsl: f16 built-in execution test for sign and step (#2911) This PR add execution tests for f16 built-in sign and step. Issue: #1248, #2583, #2529 --- src/unittests/floating_point.spec.ts | 252 +++++++++--------- .../expression/call/builtin/sign.spec.ts | 15 +- .../expression/call/builtin/step.spec.ts | 70 ++--- src/webgpu/util/floating_point.ts | 4 +- 4 files changed, 183 insertions(+), 158 deletions(-) diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 40e2534f38d9..9a423e914e02 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -3246,37 +3246,37 @@ g.test('roundInterval') const constants = FP[p.trait].constants(); // prettier-ignore return [ - { input: 0, expected: 0 }, - { input: 0.1, expected: 0 }, - { input: 0.5, expected: 0 }, // Testing tie breaking - { input: 0.9, expected: 1 }, - { input: 1.0, expected: 1 }, - { input: 1.1, expected: 1 }, - { input: 1.5, expected: 2 }, // Testing tie breaking - { input: 1.9, expected: 2 }, - { input: -0.1, expected: 0 }, - { input: -0.5, expected: 0 }, // Testing tie breaking - { input: -0.9, expected: -1 }, - { input: -1.0, expected: -1 }, - { input: -1.1, expected: -1 }, - { input: -1.5, expected: -2 }, // Testing tie breaking - { input: -1.9, expected: -2 }, + { input: 0, expected: 0 }, + { input: 0.1, expected: 0 }, + { input: 0.5, expected: 0 }, // Testing tie breaking + { input: 0.9, expected: 1 }, + { input: 1.0, expected: 1 }, + { input: 1.1, expected: 1 }, + { input: 1.5, expected: 2 }, // Testing tie breaking + { input: 1.9, expected: 2 }, + { input: -0.1, expected: 0 }, + { input: -0.5, expected: 0 }, // Testing tie breaking + { input: -0.9, expected: -1 }, + { input: -1.0, expected: -1 }, + { input: -1.1, expected: -1 }, + { input: -1.5, expected: -2 }, // Testing tie breaking + { input: -1.9, expected: -2 }, - // Edge cases - { input: constants.positive.infinity, expected: kUnboundedBounds }, - { input: constants.negative.infinity, expected: kUnboundedBounds }, - { input: constants.positive.max, expected: constants.positive.max }, - { input: constants.positive.min, expected: 0 }, - { input: constants.negative.min, expected: constants.negative.min }, - { input: constants.negative.max, expected: 0 }, - ...kRoundIntervalCases[p.trait], + // Edge cases + { input: constants.positive.infinity, expected: kUnboundedBounds }, + { input: constants.negative.infinity, expected: kUnboundedBounds }, + { input: constants.positive.max, expected: constants.positive.max }, + { input: constants.positive.min, expected: 0 }, + { input: constants.negative.min, expected: constants.negative.min }, + { input: constants.negative.max, expected: 0 }, + ...kRoundIntervalCases[p.trait], - // 32-bit subnormals - { input: constants.positive.subnormal.max, expected: 0 }, - { input: constants.positive.subnormal.min, expected: 0 }, - { input: constants.negative.subnormal.min, expected: 0 }, - { input: constants.negative.subnormal.max, expected: 0 }, - ]; + // 32-bit subnormals + { input: constants.positive.subnormal.max, expected: 0 }, + { input: constants.positive.subnormal.min, expected: 0 }, + { input: constants.negative.subnormal.min, expected: 0 }, + { input: constants.negative.subnormal.max, expected: 0 }, + ]; }) ) .fn(t => { @@ -3327,35 +3327,42 @@ g.test('saturateInterval_f32') ); }); -g.test('signInterval_f32') - .paramsSubcasesOnly( - // prettier-ignore - [ - { input: kValue.f32.infinity.negative, expected: kUnboundedBounds }, - { input: kValue.f32.negative.min, expected: -1 }, - { input: -10, expected: -1 }, - { input: -1, expected: -1 }, - { input: -0.1, expected: -1 }, - { input: kValue.f32.negative.max, expected: -1 }, - { input: kValue.f32.subnormal.negative.min, expected: [-1, 0] }, - { input: kValue.f32.subnormal.negative.max, expected: [-1, 0] }, - { input: 0, expected: 0 }, - { input: kValue.f32.subnormal.positive.max, expected: [0, 1] }, - { input: kValue.f32.subnormal.positive.min, expected: [0, 1] }, - { input: kValue.f32.positive.min, expected: 1 }, - { input: 0.1, expected: 1 }, - { input: 1, expected: 1 }, - { input: 10, expected: 1 }, - { input: kValue.f32.positive.max, expected: 1 }, - { input: kValue.f32.infinity.positive, expected: kUnboundedBounds }, - ] +g.test('signInterval') + .params(u => + u + .combine('trait', ['f32', 'f16'] as const) + .beginSubcases() + .expandWithParams(p => { + const constants = FP[p.trait].constants(); + // prettier-ignore + return [ + { input: constants.negative.infinity, expected: kUnboundedBounds }, + { input: constants.negative.min, expected: -1 }, + { input: -10, expected: -1 }, + { input: -1, expected: -1 }, + { input: -0.1, expected: -1 }, + { input: constants.negative.max, expected: -1 }, + { input: constants.negative.subnormal.min, expected: [-1, 0] }, + { input: constants.negative.subnormal.max, expected: [-1, 0] }, + { input: 0, expected: 0 }, + { input: constants.positive.subnormal.max, expected: [0, 1] }, + { input: constants.positive.subnormal.min, expected: [0, 1] }, + { input: constants.positive.min, expected: 1 }, + { input: 0.1, expected: 1 }, + { input: 1, expected: 1 }, + { input: 10, expected: 1 }, + { input: constants.positive.max, expected: 1 }, + { input: constants.positive.infinity, expected: kUnboundedBounds }, + ]; + }) ) .fn(t => { - const expected = FP.f32.toInterval(t.params.expected); - const got = FP.f32.signInterval(t.params.input); + const trait = FP[t.params.trait]; + const expected = trait.toInterval(t.params.expected); + const got = trait.signInterval(t.params.input); t.expect( objectEquals(expected, got), - `f32.signInterval(${t.params.input}) returned ${got}. Expected ${expected}` + `${t.params.trait}.signInterval(${t.params.input}) returned ${got}. Expected ${expected}` ); }); @@ -4415,79 +4422,86 @@ g.test('remainderInterval_f32') ); }); -g.test('stepInterval_f32') - .paramsSubcasesOnly( - // prettier-ignore - [ - // 32-bit normals - { input: [0, 0], expected: 1 }, - { input: [1, 1], expected: 1 }, - { input: [0, 1], expected: 1 }, - { input: [1, 0], expected: 0 }, - { input: [-1, -1], expected: 1 }, - { input: [0, -1], expected: 0 }, - { input: [-1, 0], expected: 1 }, - { input: [-1, 1], expected: 1 }, - { input: [1, -1], expected: 0 }, +g.test('stepInterval') + .params(u => + u + .combine('trait', ['f32', 'f16'] as const) + .beginSubcases() + .expandWithParams(p => { + const constants = FP[p.trait].constants(); + // prettier-ignore + return [ + // 32-bit normals + { input: [0, 0], expected: 1 }, + { input: [1, 1], expected: 1 }, + { input: [0, 1], expected: 1 }, + { input: [1, 0], expected: 0 }, + { input: [-1, -1], expected: 1 }, + { input: [0, -1], expected: 0 }, + { input: [-1, 0], expected: 1 }, + { input: [-1, 1], expected: 1 }, + { input: [1, -1], expected: 0 }, - // 64-bit normals - { input: [0.1, 0.1], expected: [0, 1] }, - { input: [0, 0.1], expected: 1 }, - { input: [0.1, 0], expected: 0 }, - { input: [0.1, 1], expected: 1 }, - { input: [1, 0.1], expected: 0 }, - { input: [-0.1, -0.1], expected: [0, 1] }, - { input: [0, -0.1], expected: 0 }, - { input: [-0.1, 0], expected: 1 }, - { input: [-0.1, -1], expected: 0 }, - { input: [-1, -0.1], expected: 1 }, + // 64-bit normals + { input: [0.1, 0.1], expected: [0, 1] }, + { input: [0, 0.1], expected: 1 }, + { input: [0.1, 0], expected: 0 }, + { input: [0.1, 1], expected: 1 }, + { input: [1, 0.1], expected: 0 }, + { input: [-0.1, -0.1], expected: [0, 1] }, + { input: [0, -0.1], expected: 0 }, + { input: [-0.1, 0], expected: 1 }, + { input: [-0.1, -1], expected: 0 }, + { input: [-1, -0.1], expected: 1 }, - // Subnormals - { input: [0, kValue.f32.subnormal.positive.max], expected: 1 }, - { input: [0, kValue.f32.subnormal.positive.min], expected: 1 }, - { input: [0, kValue.f32.subnormal.negative.max], expected: [0, 1] }, - { input: [0, kValue.f32.subnormal.negative.min], expected: [0, 1] }, - { input: [1, kValue.f32.subnormal.positive.max], expected: 0 }, - { input: [1, kValue.f32.subnormal.positive.min], expected: 0 }, - { input: [1, kValue.f32.subnormal.negative.max], expected: 0 }, - { input: [1, kValue.f32.subnormal.negative.min], expected: 0 }, - { input: [-1, kValue.f32.subnormal.positive.max], expected: 1 }, - { input: [-1, kValue.f32.subnormal.positive.min], expected: 1 }, - { input: [-1, kValue.f32.subnormal.negative.max], expected: 1 }, - { input: [-1, kValue.f32.subnormal.negative.min], expected: 1 }, - { input: [kValue.f32.subnormal.positive.max, 0], expected: [0, 1] }, - { input: [kValue.f32.subnormal.positive.min, 0], expected: [0, 1] }, - { input: [kValue.f32.subnormal.negative.max, 0], expected: 1 }, - { input: [kValue.f32.subnormal.negative.min, 0], expected: 1 }, - { input: [kValue.f32.subnormal.positive.max, 1], expected: 1 }, - { input: [kValue.f32.subnormal.positive.min, 1], expected: 1 }, - { input: [kValue.f32.subnormal.negative.max, 1], expected: 1 }, - { input: [kValue.f32.subnormal.negative.min, 1], expected: 1 }, - { input: [kValue.f32.subnormal.positive.max, -1], expected: 0 }, - { input: [kValue.f32.subnormal.positive.min, -1], expected: 0 }, - { input: [kValue.f32.subnormal.negative.max, -1], expected: 0 }, - { input: [kValue.f32.subnormal.negative.min, -1], expected: 0 }, - { input: [kValue.f32.subnormal.negative.min, kValue.f32.subnormal.positive.max], expected: 1 }, - { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.negative.min], expected: [0, 1] }, + // Subnormals + { input: [0, constants.positive.subnormal.max], expected: 1 }, + { input: [0, constants.positive.subnormal.min], expected: 1 }, + { input: [0, constants.negative.subnormal.max], expected: [0, 1] }, + { input: [0, constants.negative.subnormal.min], expected: [0, 1] }, + { input: [1, constants.positive.subnormal.max], expected: 0 }, + { input: [1, constants.positive.subnormal.min], expected: 0 }, + { input: [1, constants.negative.subnormal.max], expected: 0 }, + { input: [1, constants.negative.subnormal.min], expected: 0 }, + { input: [-1, constants.positive.subnormal.max], expected: 1 }, + { input: [-1, constants.positive.subnormal.min], expected: 1 }, + { input: [-1, constants.negative.subnormal.max], expected: 1 }, + { input: [-1, constants.negative.subnormal.min], expected: 1 }, + { input: [constants.positive.subnormal.max, 0], expected: [0, 1] }, + { input: [constants.positive.subnormal.min, 0], expected: [0, 1] }, + { input: [constants.negative.subnormal.max, 0], expected: 1 }, + { input: [constants.negative.subnormal.min, 0], expected: 1 }, + { input: [constants.positive.subnormal.max, 1], expected: 1 }, + { input: [constants.positive.subnormal.min, 1], expected: 1 }, + { input: [constants.negative.subnormal.max, 1], expected: 1 }, + { input: [constants.negative.subnormal.min, 1], expected: 1 }, + { input: [constants.positive.subnormal.max, -1], expected: 0 }, + { input: [constants.positive.subnormal.min, -1], expected: 0 }, + { input: [constants.negative.subnormal.max, -1], expected: 0 }, + { input: [constants.negative.subnormal.min, -1], expected: 0 }, + { input: [constants.negative.subnormal.min, constants.positive.subnormal.max], expected: 1 }, + { input: [constants.positive.subnormal.max, constants.negative.subnormal.min], expected: [0, 1] }, - // Infinities - { input: [0, kValue.f32.infinity.positive], expected: kUnboundedBounds }, - { input: [kValue.f32.infinity.positive, 0], expected: kUnboundedBounds }, - { input: [kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kUnboundedBounds }, - { input: [0, kValue.f32.infinity.negative], expected: kUnboundedBounds }, - { input: [kValue.f32.infinity.negative, 0], expected: kUnboundedBounds }, - { input: [kValue.f32.infinity.negative, kValue.f32.infinity.negative], expected: kUnboundedBounds }, - { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: kUnboundedBounds }, - { input: [kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kUnboundedBounds }, - ] + // Infinities + { input: [0, constants.positive.infinity], expected: kUnboundedBounds }, + { input: [constants.positive.infinity, 0], expected: kUnboundedBounds }, + { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds }, + { input: [0, constants.negative.infinity], expected: kUnboundedBounds }, + { input: [constants.negative.infinity, 0], expected: kUnboundedBounds }, + { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedBounds }, + { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedBounds }, + { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds }, + ]; + }) ) .fn(t => { + const trait = FP[t.params.trait]; const [edge, x] = t.params.input; - const expected = FP.f32.toInterval(t.params.expected); - const got = FP.f32.stepInterval(edge, x); + const expected = trait.toInterval(t.params.expected); + const got = trait.stepInterval(edge, x); t.expect( objectEquals(expected, got), - `f32.stepInterval(${edge}, ${x}) returned ${got}. Expected ${expected}` + `${t.params.trait}.stepInterval(${edge}, ${x}) returned ${got}. Expected ${expected}` ); }); diff --git a/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts index 70a4c45fb716..454ff48e6edc 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts @@ -9,9 +9,9 @@ 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, TypeI32 } from '../../../../../util/conversion.js'; +import { i32, TypeF32, TypeF16, TypeI32 } from '../../../../../util/conversion.js'; import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullI32Range } from '../../../../../util/math.js'; +import { fullF32Range, fullF16Range, fullI32Range } from '../../../../../util/math.js'; import { makeCaseCache } from '../../case_cache.js'; import { allInputSources, run } from '../../expression.js'; @@ -23,6 +23,9 @@ 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); + }, i32: () => fullI32Range().map(i => { const signFunc = (i: number): number => (i < 0 ? -1 : i > 0 ? 1 : 0); @@ -74,4 +77,10 @@ g.test('f16') .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .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/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts index f0892b6c4a71..752e2676e651 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts @@ -10,9 +10,9 @@ 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 } from '../../../../../util/conversion.js'; +import { TypeF32, TypeF16 } from '../../../../../util/conversion.js'; import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range } from '../../../../../util/math.js'; +import { fullF32Range, fullF16Range } from '../../../../../util/math.js'; import { makeCaseCache } from '../../case_cache.js'; import { allInputSources, Case, run } from '../../expression.js'; @@ -20,40 +20,36 @@ import { builtin } from './builtin.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('step', { - f32: () => { - const zeroInterval = FP.f32.toInterval(0); - const oneInterval = FP.f32.toInterval(1); - - // stepInterval's return value isn't always interpreted as an acceptance - // interval, so makeBinaryToF32IntervalCase cannot be used here. - // See the comment block on stepInterval for more details - const makeCase = (edge: number, x: number): Case => { - edge = FP.f32.quantize(edge); - x = FP.f32.quantize(x); - const expected = FP.f32.stepInterval(edge, x); +// 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: [FP.f32.scalarBuilder(edge), FP.f32.scalarBuilder(x)], expected }; - } + // [0, 0], [1, 1], or [-∞, +∞] cases + if (expected.isPoint() || !expected.isFinite()) { + return { input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)], expected }; + } - // [0, 1] case - return { - input: [FP.f32.scalarBuilder(edge), FP.f32.scalarBuilder(x)], - expected: anyOf(zeroInterval, oneInterval), - }; - }; + // [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), + }; +}; - const range = fullF32Range(); - const cases: Array = []; - range.forEach(edge => { - range.forEach(x => { - cases.push(makeCase(edge, x)); - }); - }); - - return cases; +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))); }, }); @@ -82,4 +78,10 @@ g.test('f16') .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .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/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index 2197ecc60f4e..746efe587fc8 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -5181,12 +5181,12 @@ class F16Traits extends FPTraits { public readonly remainderInterval = this.unimplementedScalarPairToInterval.bind(this); public readonly roundInterval = this.roundIntervalImpl.bind(this); public readonly saturateInterval = this.unimplementedScalarToInterval.bind(this); - public readonly signInterval = this.unimplementedScalarToInterval.bind(this); + public readonly signInterval = this.signIntervalImpl.bind(this); public readonly sinInterval = this.sinIntervalImpl.bind(this); public readonly sinhInterval = this.unimplementedScalarToInterval.bind(this); public readonly smoothStepInterval = this.unimplementedScalarTripleToInterval.bind(this); public readonly sqrtInterval = this.sqrtIntervalImpl.bind(this); - public readonly stepInterval = this.unimplementedScalarPairToInterval.bind(this); + public readonly stepInterval = this.stepIntervalImpl.bind(this); public readonly subtractionInterval = this.subtractionIntervalImpl.bind(this); public readonly subtractionMatrixMatrixInterval = this.unimplementedMatrixPairToMatrix.bind(this); public readonly tanInterval = this.unimplementedScalarToInterval.bind(this); From 23cdd20d853a1fde201af27a90eea546167c4dea Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Wed, 6 Sep 2023 10:48:35 -0400 Subject: [PATCH 015/166] wgsl: Implement scalar/vector AbstractFloat addition execution tests (#2922) Matrix addition will be covered in future PRs. Issue #1626 Co-authored-by: jzm-intel --- src/unittests/floating_point.spec.ts | 17 +- .../expression/binary/af_addition.spec.ts | 151 ++++++++++++++++++ .../execution/expression/binary/binary.ts | 12 +- .../expression/binary/f32_addition.spec.ts | 8 - .../expression/unary/af_arithmetic.spec.ts | 4 +- .../execution/expression/unary/unary.ts | 2 +- src/webgpu/util/floating_point.ts | 2 +- src/webgpu/util/math.ts | 32 ++++ 8 files changed, 214 insertions(+), 14 deletions(-) create mode 100644 src/webgpu/shader/execution/expression/binary/af_addition.spec.ts diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 9a423e914e02..8b1e588be858 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -3670,12 +3670,27 @@ const kAdditionInterval64BitsNormalCases = { // -0.1+0.1 expect f16 interval [0xAE67+0x2E66, 0xAE66+0x2E67] { input: [-0.1, 0.1], expected: [reinterpretU16AsF16(0xae67)+reinterpretU16AsF16(0x2e66), reinterpretU16AsF16(0xae66)+reinterpretU16AsF16(0x2e67)] }, // ~0.0 ] as ScalarPairToIntervalCase[], + abstract: [ + // 0.1 isn't exactly representable in f64, but will be quantized to an + // exact value when storing to a 'number' (0x3FB999999999999A). + // This is why below the expectations are not intervals. + { input: [0.1, 0], expected: [0.1] }, + { input: [0, 0.1], expected: [0.1] }, + { input: [-0.1, 0], expected: [-0.1] }, + { input: [0, -0.1], expected: [-0.1] }, + // f64 0x3FB999999999999A+0x3FB999999999999A = 0x3FC999999999999A + { input: [0.1, 0.1], expected: [reinterpretU64AsF64(0x3FC999999999999An)] }, // ~0.2 + // f64 0xBFB999999999999A+0xBFB999999999999A = 0xBFC999999999999A + { input: [-0.1, -0.1], expected: [reinterpretU64AsF64(0xBFC999999999999An)] }, // ~-0.2 + { input: [0.1, -0.1], expected: [0] }, + { input: [-0.1, 0.1], expected: [0] }, + ] as ScalarPairToIntervalCase[], } as const; g.test('additionInterval') .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/shader/execution/expression/binary/af_addition.spec.ts b/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts new file mode 100644 index 000000000000..777b801e131d --- /dev/null +++ b/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts @@ -0,0 +1,151 @@ +export const description = ` +Execution Tests for non-matrix AbstractFloat addition expression +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../gpu_test.js'; +import { TypeAbstractFloat, TypeVec } from '../../../../util/conversion.js'; +import { FP, FPVector } from '../../../../util/floating_point.js'; +import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; +import { onlyConstInputSource, run } from '../expression.js'; + +import { abstractBinary } from './binary.js'; + +const additionVectorScalarInterval = (v: number[], s: number): FPVector => { + return FP.abstract.toVector(v.map(e => FP.abstract.additionInterval(e, s))); +}; + +const additionScalarVectorInterval = (s: number, v: number[]): FPVector => { + return FP.abstract.toVector(v.map(e => FP.abstract.additionInterval(s, e))); +}; + +export const g = makeTestGroup(GPUTest); + +export const d = makeCaseCache('binary/af_addition', { + scalar: () => { + return FP.abstract.generateScalarPairToIntervalCases( + sparseF64Range(), + sparseF64Range(), + 'finite', + FP.abstract.additionInterval + ); + }, + vec2_scalar: () => { + return FP.abstract.generateVectorScalarToVectorCases( + sparseVectorF64Range(2), + sparseF64Range(), + 'finite', + additionVectorScalarInterval + ); + }, + vec3_scalar: () => { + return FP.abstract.generateVectorScalarToVectorCases( + sparseVectorF64Range(3), + sparseF64Range(), + 'finite', + additionVectorScalarInterval + ); + }, + vec4_scalar: () => { + return FP.abstract.generateVectorScalarToVectorCases( + sparseVectorF64Range(4), + sparseF64Range(), + 'finite', + additionVectorScalarInterval + ); + }, + scalar_vec2: () => { + return FP.abstract.generateScalarVectorToVectorCases( + sparseF64Range(), + sparseVectorF64Range(2), + 'finite', + additionScalarVectorInterval + ); + }, + scalar_vec3: () => { + return FP.abstract.generateScalarVectorToVectorCases( + sparseF64Range(), + sparseVectorF64Range(3), + 'finite', + additionScalarVectorInterval + ); + }, + scalar_vec4: () => { + return FP.abstract.generateScalarVectorToVectorCases( + sparseF64Range(), + sparseVectorF64Range(4), + 'finite', + additionScalarVectorInterval + ); + }, +}); + +g.test('scalar') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x + y +Accuracy: Correctly rounded +` + ) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('scalar'); + await run( + t, + abstractBinary('+'), + [TypeAbstractFloat, TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); + +g.test('vector_scalar') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x + y, where x is a vector and y is a scalar +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4] as const)) + .fn(async t => { + const dim = t.params.dim; + const cases = await d.get(`vec${dim}_scalar`); + await run( + t, + abstractBinary('+'), + [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat], + TypeVec(dim, TypeAbstractFloat), + t.params, + cases + ); + }); + +g.test('scalar_vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x + y, where x is a scalar and y is a vector +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4] as const)) + .fn(async t => { + const dim = t.params.dim; + const cases = await d.get(`scalar_vec${dim}`); + await run( + t, + abstractBinary('+'), + [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)], + TypeVec(dim, TypeAbstractFloat), + t.params, + cases + ); + }); diff --git a/src/webgpu/shader/execution/expression/binary/binary.ts b/src/webgpu/shader/execution/expression/binary/binary.ts index 5642f164d149..f0b01b839b22 100644 --- a/src/webgpu/shader/execution/expression/binary/binary.ts +++ b/src/webgpu/shader/execution/expression/binary/binary.ts @@ -1,4 +1,9 @@ -import { ShaderBuilder, basicExpressionBuilder, compoundAssignmentBuilder } from '../expression.js'; +import { + ShaderBuilder, + basicExpressionBuilder, + compoundAssignmentBuilder, + abstractFloatShaderBuilder, +} from '../expression.js'; /* @returns a ShaderBuilder that evaluates a binary operation */ export function binary(op: string): ShaderBuilder { @@ -9,3 +14,8 @@ export function binary(op: string): ShaderBuilder { export function compoundBinary(op: string): ShaderBuilder { return compoundAssignmentBuilder(op); } + +/* @returns a ShaderBuilder that evaluates a binary operation that returns AbstractFloats */ +export function abstractBinary(op: string): ShaderBuilder { + return abstractFloatShaderBuilder(values => `(${values.map(v => `(${v})`).join(op)})`); +} diff --git a/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts b/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts index cf57768286db..d3c9bcfb02c3 100644 --- a/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts @@ -135,14 +135,6 @@ export const d = makeCaseCache('binary/f32_addition', { additionScalarVectorInterval ); }, - subtraction_const: () => { - return FP.f32.generateScalarPairToIntervalCases( - sparseF32Range(), - sparseF32Range(), - 'finite', - FP.f32.subtractionInterval - ); - }, }); g.test('scalar') diff --git a/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts b/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts index 67b2390bfa88..182c0d76a979 100644 --- a/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts +++ b/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts @@ -10,7 +10,7 @@ import { fullF64Range } from '../../../../util/math.js'; import { makeCaseCache } from '../case_cache.js'; import { onlyConstInputSource, run } from '../expression.js'; -import { abstract_unary } from './unary.js'; +import { abstractUnary } from './unary.js'; export const g = makeTestGroup(GPUTest); @@ -39,5 +39,5 @@ Accuracy: Correctly rounded ) .fn(async t => { const cases = await d.get('negation'); - await run(t, abstract_unary('-'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1); + await run(t, abstractUnary('-'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1); }); diff --git a/src/webgpu/shader/execution/expression/unary/unary.ts b/src/webgpu/shader/execution/expression/unary/unary.ts index 995ca3ea172d..160e4651783d 100644 --- a/src/webgpu/shader/execution/expression/unary/unary.ts +++ b/src/webgpu/shader/execution/expression/unary/unary.ts @@ -10,6 +10,6 @@ export function unary(op: string): ShaderBuilder { } /* @returns a ShaderBuilder that evaluates a prefix unary operation that returns AbstractFloats */ -export function abstract_unary(op: string): ShaderBuilder { +export function abstractUnary(op: string): ShaderBuilder { return abstractFloatShaderBuilder(value => `${op}(${value})`); } diff --git a/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index 746efe587fc8..9b2ec9bf5062 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -4834,7 +4834,7 @@ class FPAbstractTraits extends FPTraits { public readonly acoshAlternativeInterval = this.unimplementedScalarToInterval.bind(this); public readonly acoshPrimaryInterval = this.unimplementedScalarToInterval.bind(this); public readonly acoshIntervals = [this.acoshAlternativeInterval, this.acoshPrimaryInterval]; - public readonly additionInterval = this.unimplementedScalarPairToInterval.bind(this); + public readonly additionInterval = this.additionIntervalImpl.bind(this); public readonly additionMatrixMatrixInterval = this.unimplementedMatrixPairToMatrix.bind(this); public readonly asinInterval = this.unimplementedScalarToInterval.bind(this); public readonly asinhInterval = this.unimplementedScalarToInterval.bind(this); diff --git a/src/webgpu/util/math.ts b/src/webgpu/util/math.ts index 93b8c5d8d853..ad1021aaa55f 100644 --- a/src/webgpu/util/math.ts +++ b/src/webgpu/util/math.ts @@ -1728,6 +1728,38 @@ export function vectorF64Range(dim: number): number[][] { return kVectorF64Values[dim]; } +const kSparseVectorF64Values = { + 2: sparseF64Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]), + 3: sparseF64Range().map((f, idx) => [ + idx % 3 === 0 ? f : idx, + idx % 3 === 1 ? f : -idx, + idx % 3 === 2 ? f : idx, + ]), + 4: sparseF64Range().map((f, idx) => [ + idx % 4 === 0 ? f : idx, + idx % 4 === 1 ? f : -idx, + idx % 4 === 2 ? f : idx, + idx % 4 === 3 ? f : -idx, + ]), +}; + +/** + * Minimal set of vectors, indexed by dimension, that contain interesting f64 + * values. + * + * This is an even more stripped down version of `vectorF64Range` for when + * pairs of vectors are being tested. + * All the interesting floats from sparseF64 are guaranteed to be tested, but + * not in every position. + */ +export function sparseVectorF64Range(dim: number): number[][] { + assert( + dim === 2 || dim === 3 || dim === 4, + 'sparseVectorF64Range only accepts dimensions 2, 3, and 4' + ); + return kSparseVectorF64Values[dim]; +} + /** * @returns the result matrix in Array> type. * From 7f1e5afb4181f26f510e9a04160343a3458f0330 Mon Sep 17 00:00:00 2001 From: jzm-intel Date: Fri, 8 Sep 2023 10:36:02 +0800 Subject: [PATCH 016/166] wgsl: f16 built-in execution test for frexp (#2925) This PR make frexp function in util/math.ts handle f16 and f64 as well as f32, and add f16 execution test for built-in frexp. Issue: #1248, #2587 --- src/unittests/floating_point.spec.ts | 54 ++--- src/unittests/maths.spec.ts | 76 +++++-- .../expression/call/builtin/frexp.spec.ts | 202 +++++++++++++----- src/webgpu/util/math.ts | 159 ++++++++++---- 4 files changed, 356 insertions(+), 135 deletions(-) diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 8b1e588be858..a9a92e181c37 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -2557,33 +2557,33 @@ g.test('ceilInterval') const constants = FP[p.trait].constants(); // prettier-ignore return [ - { input: 0, expected: 0 }, - { input: 0.1, expected: 1 }, - { input: 0.9, expected: 1 }, - { input: 1.0, expected: 1 }, - { input: 1.1, expected: 2 }, - { input: 1.9, expected: 2 }, - { input: -0.1, expected: 0 }, - { input: -0.9, expected: 0 }, - { input: -1.0, expected: -1 }, - { input: -1.1, expected: -1 }, - { input: -1.9, expected: -1 }, - - // Edge cases - { input: constants.positive.infinity, expected: kUnboundedBounds }, - { input: constants.negative.infinity, expected: kUnboundedBounds }, - { input: constants.positive.max, expected: constants.positive.max }, - { input: constants.positive.min, expected: 1 }, - { input: constants.negative.min, expected: constants.negative.min }, - { input: constants.negative.max, expected: 0 }, - ...kCeilIntervalCases[p.trait], - - // 32-bit subnormals - { input: constants.positive.subnormal.max, expected: [0, 1] }, - { input: constants.positive.subnormal.min, expected: [0, 1] }, - { input: constants.negative.subnormal.min, expected: 0 }, - { input: constants.negative.subnormal.max, expected: 0 }, - ]; + { input: 0, expected: 0 }, + { input: 0.1, expected: 1 }, + { input: 0.9, expected: 1 }, + { input: 1.0, expected: 1 }, + { input: 1.1, expected: 2 }, + { input: 1.9, expected: 2 }, + { input: -0.1, expected: 0 }, + { input: -0.9, expected: 0 }, + { input: -1.0, expected: -1 }, + { input: -1.1, expected: -1 }, + { input: -1.9, expected: -1 }, + + // Edge cases + { input: constants.positive.infinity, expected: kUnboundedBounds }, + { input: constants.negative.infinity, expected: kUnboundedBounds }, + { input: constants.positive.max, expected: constants.positive.max }, + { input: constants.positive.min, expected: 1 }, + { input: constants.negative.min, expected: constants.negative.min }, + { input: constants.negative.max, expected: 0 }, + ...kCeilIntervalCases[p.trait], + + // 32-bit subnormals + { input: constants.positive.subnormal.max, expected: [0, 1] }, + { input: constants.positive.subnormal.min, expected: [0, 1] }, + { input: constants.negative.subnormal.min, expected: 0 }, + { input: constants.negative.subnormal.max, expected: 0 }, + ]; }) ) .fn(t => { diff --git a/src/unittests/maths.spec.ts b/src/unittests/maths.spec.ts index bc505c0e5b29..fa3899e21e9a 100644 --- a/src/unittests/maths.spec.ts +++ b/src/unittests/maths.spec.ts @@ -1058,20 +1058,9 @@ interface frexpCase { exp: number; } -g.test('frexp') - .paramsSimple([ - { input: 0, fract: 0, exp: 0 }, - { input: -0, fract: -0, exp: 0 }, - { input: Number.POSITIVE_INFINITY, fract: Number.POSITIVE_INFINITY, exp: 0 }, - { input: Number.NEGATIVE_INFINITY, fract: Number.NEGATIVE_INFINITY, exp: 0 }, - { input: 0.5, fract: 0.5, exp: 0 }, - { input: -0.5, fract: -0.5, exp: 0 }, - { input: 1, fract: 0.5, exp: 1 }, - { input: -1, fract: -0.5, exp: 1 }, - { input: 2, fract: 0.5, exp: 2 }, - { input: -2, fract: -0.5, exp: 2 }, - { input: 10000, fract: 0.6103515625, exp: 14 }, - { input: -10000, fract: -0.6103515625, exp: 14 }, +// prettier-ignore +const kFrexpCases = { + f32: [ { input: kValue.f32.positive.max, fract: 0.9999999403953552, exp: 128 }, { input: kValue.f32.positive.min, fract: 0.5, exp: -125 }, { input: kValue.f32.negative.max, fract: -0.5, exp: -125 }, @@ -1080,15 +1069,68 @@ g.test('frexp') { input: kValue.f32.subnormal.positive.min, fract: 0.5, exp: -148 }, { input: kValue.f32.subnormal.negative.max, fract: -0.5, exp: -148 }, { input: kValue.f32.subnormal.negative.min, fract: -0.9999998807907104, exp: -126 }, - ]) + ] as frexpCase[], + f16: [ + { input: kValue.f16.positive.max, fract: 0.99951171875, exp: 16 }, + { input: kValue.f16.positive.min, fract: 0.5, exp: -13 }, + { input: kValue.f16.negative.max, fract: -0.5, exp: -13 }, + { input: kValue.f16.negative.min, fract: -0.99951171875, exp: 16 }, + { input: kValue.f16.subnormal.positive.max, fract: 0.9990234375, exp: -14 }, + { input: kValue.f16.subnormal.positive.min, fract: 0.5, exp: -23 }, + { input: kValue.f16.subnormal.negative.max, fract: -0.5, exp: -23 }, + { input: kValue.f16.subnormal.negative.min, fract: -0.9990234375, exp: -14 }, + ] as frexpCase[], + f64: [ + { input: kValue.f64.positive.max, fract: reinterpretU64AsF64(0x3fef_ffff_ffff_ffffn) /* ~0.9999999999999999 */, exp: 1024 }, + { input: kValue.f64.positive.min, fract: 0.5, exp: -1021 }, + { input: kValue.f64.negative.max, fract: -0.5, exp: -1021 }, + { input: kValue.f64.negative.min, fract: reinterpretU64AsF64(0xbfef_ffff_ffff_ffffn) /* ~-0.9999999999999999 */, exp: 1024 }, + { input: kValue.f64.subnormal.positive.max, fract: reinterpretU64AsF64(0x3fef_ffff_ffff_fffen) /* ~0.9999999999999998 */, exp: -1022 }, + { input: kValue.f64.subnormal.positive.min, fract: 0.5, exp: -1073 }, + { input: kValue.f64.subnormal.negative.max, fract: -0.5, exp: -1073 }, + { input: kValue.f64.subnormal.negative.min, fract: reinterpretU64AsF64(0xbfef_ffff_ffff_fffen) /* ~-0.9999999999999998 */, exp: -1022 }, + ] as frexpCase[], +} as const; + +g.test('frexp') + .params(u => + u + .combine('trait', ['f32', 'f16', 'f64'] as const) + .beginSubcases() + .expandWithParams(p => { + // prettier-ignore + return [ + // +/- 0.0 + { input: 0, fract: 0, exp: 0 }, + { input: -0, fract: -0, exp: 0 }, + // Normal float values that can be exactly represented by all float types + { input: 0.171875, fract: 0.6875, exp: -2 }, + { input: -0.171875, fract: -0.6875, exp: -2 }, + { input: 0.5, fract: 0.5, exp: 0 }, + { input: -0.5, fract: -0.5, exp: 0 }, + { input: 1, fract: 0.5, exp: 1 }, + { input: -1, fract: -0.5, exp: 1 }, + { input: 2, fract: 0.5, exp: 2 }, + { input: -2, fract: -0.5, exp: 2 }, + { input: 10000, fract: 0.6103515625, exp: 14 }, + { input: -10000, fract: -0.6103515625, exp: 14 }, + // Normal ans subnormal cases that are different for each type + ...kFrexpCases[p.trait], + // Inf and NaN + { input: Number.POSITIVE_INFINITY, fract: Number.POSITIVE_INFINITY, exp: 0 }, + { input: Number.NEGATIVE_INFINITY, fract: Number.NEGATIVE_INFINITY, exp: 0 }, + { input: Number.NaN, fract: Number.NaN, exp: 0 }, + ]; + }) + ) .fn(test => { const input = test.params.input; - const got = frexp(input); + const got = frexp(input, test.params.trait); const expect = { fract: test.params.fract, exp: test.params.exp }; test.expect( objectEquals(got, expect), - `frexp(${input}) returned { fract: ${got.fract}, exp: ${got.exp} }. Expected { fract: ${expect.fract}, exp: ${expect.exp} }` + `frexp(${input}, ${test.params.trait}) returned { fract: ${got.fract}, exp: ${got.exp} }. Expected { fract: ${expect.fract}, exp: ${expect.exp} }` ); }); diff --git a/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts index 6bee96d2e4c3..3d74fc354799 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts @@ -16,12 +16,22 @@ 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 { f32, i32, toVector, TypeF32, TypeI32, TypeVec } from '../../../../../util/conversion.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, - isSubnormalNumberF32, - quantizeToF32, + vectorF16Range, vectorF32Range, } from '../../../../../util/math.js'; import { makeCaseCache } from '../../case_cache.js'; @@ -45,72 +55,110 @@ function expBuilder(): ShaderBuilder { return basicExpressionBuilder(value => `frexp(${value}).exp`); } -/* @returns a fract Case for a given vector input */ -function makeVectorCaseFract(v: number[]): Case { - v = v.map(quantizeToF32); - if (v.some(e => e !== 0 && isSubnormalNumberF32(e))) { - return { input: toVector(v, f32), expected: skipUndefined(undefined) }; +/* @returns a fract Case for a given scalar or vector input */ +function makeVectorCaseFract(v: number | number[], trait: 'f32' | 'f16'): Case { + const fp = FP[trait]; + let toInput: (n: number[]) => Scalar | Vector; + let toOutput: (n: number[]) => Scalar | Vector; + if (v instanceof Array) { + // Input is vector + toInput = (n: number[]) => toVector(n, fp.scalarBuilder); + toOutput = (n: number[]) => toVector(n, fp.scalarBuilder); + } else { + // Input is scalar, also wrap it in an array. + v = [v]; + toInput = (n: number[]) => fp.scalarBuilder(n[0]); + toOutput = (n: 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).fract; + return frexp(e, trait).fract; }); - return { input: toVector(v, f32), expected: toVector(fs, f32) }; + return { input: toInput(v), expected: toOutput(fs) }; } -/* @returns an exp Case for a given vector input */ -function makeVectorCaseExp(v: number[]): Case { - v = v.map(quantizeToF32); - if (v.some(e => e !== 0 && isSubnormalNumberF32(e))) { - return { input: toVector(v, f32), expected: skipUndefined(undefined) }; +/* @returns an exp Case for a given scalar or vector input */ +function makeVectorCaseExp(v: number | number[], trait: 'f32' | 'f16'): Case { + const fp = FP[trait]; + let toInput: (n: number[]) => Scalar | Vector; + let toOutput: (n: number[]) => Scalar | Vector; + if (v instanceof Array) { + // Input is vector + toInput = (n: number[]) => toVector(n, fp.scalarBuilder); + toOutput = (n: number[]) => toVector(n, i32); + } else { + // Input is scalar, also wrap it in an array. + v = [v]; + toInput = (n: number[]) => fp.scalarBuilder(n[0]); + toOutput = (n: 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).exp; + return frexp(e, trait).exp; }); - return { input: toVector(v, f32), expected: toVector(fs, i32) }; + return { input: toInput(v), expected: toOutput(fs) }; } export const d = makeCaseCache('frexp', { f32_fract: () => { - const makeCase = (n: number): Case => { - n = quantizeToF32(n); - if (n !== 0 && isSubnormalNumberF32(n)) { - return { input: f32(n), expected: skipUndefined(undefined) }; - } - return { input: f32(n), expected: f32(frexp(n).fract) }; - }; - return fullF32Range().map(makeCase); + return fullF32Range().map(v => makeVectorCaseFract(v, 'f32')); }, f32_exp: () => { - const makeCase = (n: number): Case => { - n = quantizeToF32(n); - if (n !== 0 && isSubnormalNumberF32(n)) { - return { input: f32(n), expected: skipUndefined(undefined) }; - } - return { input: f32(n), expected: i32(frexp(n).exp) }; - }; - return fullF32Range().map(makeCase); + return fullF32Range().map(v => makeVectorCaseExp(v, 'f32')); }, f32_vec2_fract: () => { - return vectorF32Range(2).map(makeVectorCaseFract); + return vectorF32Range(2).map(v => makeVectorCaseFract(v, 'f32')); }, f32_vec2_exp: () => { - return vectorF32Range(2).map(makeVectorCaseExp); + return vectorF32Range(2).map(v => makeVectorCaseExp(v, 'f32')); }, f32_vec3_fract: () => { - return vectorF32Range(3).map(makeVectorCaseFract); + return vectorF32Range(3).map(v => makeVectorCaseFract(v, 'f32')); }, f32_vec3_exp: () => { - return vectorF32Range(3).map(makeVectorCaseExp); + return vectorF32Range(3).map(v => makeVectorCaseExp(v, 'f32')); }, f32_vec4_fract: () => { - return vectorF32Range(4).map(makeVectorCaseFract); + return vectorF32Range(4).map(v => makeVectorCaseFract(v, 'f32')); }, f32_vec4_exp: () => { - return vectorF32Range(4).map(makeVectorCaseExp); + 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')); }, }); @@ -120,7 +168,7 @@ g.test('f32_fract') ` T is f32 -struct __frexp_result { +struct __frexp_result_f32 { fract : f32, // fract part exp : i32 // exponent part } @@ -138,7 +186,7 @@ g.test('f32_exp') ` T is f32 -struct __frexp_result { +struct __frexp_result_f32 { fract : f32, // fract part exp : i32 // exponent part } @@ -156,7 +204,7 @@ g.test('f32_vec2_fract') ` T is vec2 -struct __frexp_result { +struct __frexp_result_vec2_f32 { fract : vec2, // fract part exp : vec2 // exponent part } @@ -264,14 +312,20 @@ g.test('f16_fract') ` T is f16 -struct __frexp_result { +struct __frexp_result_f16 { fract : f16, // fract part exp : i32 // exponent part } ` ) .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .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') @@ -279,14 +333,20 @@ g.test('f16_exp') ` T is f16 -struct __frexp_result { +struct __frexp_result_f16 { fract : f16, // fract part exp : i32 // exponent part } ` ) .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .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') @@ -294,14 +354,20 @@ g.test('f16_vec2_fract') ` T is vec2 -struct __frexp_result { +struct __frexp_result_vec2_f16 { fract : vec2, // fract part exp : vec2 // exponent part } ` ) .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .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') @@ -316,7 +382,13 @@ struct __frexp_result_vec2_f16 { ` ) .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .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') @@ -331,7 +403,13 @@ struct __frexp_result_vec3_f16 { ` ) .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .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') @@ -346,7 +424,13 @@ struct __frexp_result_vec3_f16 { ` ) .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .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') @@ -361,7 +445,13 @@ struct __frexp_result_vec4_f16 { ` ) .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .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') @@ -376,4 +466,10 @@ struct __frexp_result_vec4_f16 { ` ) .params(u => u.combine('inputSource', allInputSources)) - .unimplemented(); + .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/src/webgpu/util/math.ts b/src/webgpu/util/math.ts index ad1021aaa55f..30733e5fc017 100644 --- a/src/webgpu/util/math.ts +++ b/src/webgpu/util/math.ts @@ -1,5 +1,9 @@ import { assert } from '../../common/util/util.js'; -import { Float16Array } from '../../external/petamoriken/float16/float16.js'; +import { + Float16Array, + getFloat16, + setFloat16, +} from '../../external/petamoriken/float16/float16.js'; import { kBit, kValue } from './constants.js'; import { @@ -621,15 +625,6 @@ export function correctlyRoundedF16(n: number): number[] { return [Number.NEGATIVE_INFINITY]; } -/** - * Once-allocated ArrayBuffer/views to avoid overhead of allocation in frexp - * - * This makes frexp non-reentrant due to shared state between calls. - */ -const frexpData = new ArrayBuffer(4); -const frexpDataU32 = new Uint32Array(frexpData); -const frexpDataF32 = new Float32Array(frexpData); - /** * Calculates WGSL frexp * @@ -637,41 +632,129 @@ const frexpDataF32 = new Float32Array(frexpData); * val = fraction * 2 ^ exponent. * The fraction is 0.0 or its magnitude is in the range [0.5, 1.0). * - * Inspired by golang's implementation of frexp. - * - * This code is non-reentrant due to the use of a non-local data buffer and - * views. - * - * @param val the f32 to split + * @param val the float to split + * @param trait the float type, f32 or f16 or f64 * @returns the results of splitting val */ -export function frexp(val: number): { fract: number; exp: number } { - frexpDataF32[0] = val; - // Do not directly use val after this point, so that changes are reflected in - // both the f32 and u32 views. +export function frexp(val: number, trait: 'f32' | 'f16' | 'f64'): { fract: number; exp: number } { + const buffer = new ArrayBuffer(8); + const dataView = new DataView(buffer); + + // expBitCount and fractBitCount is the bitwidth of exponent and fractional part of the given FP type. + // expBias is the bias constant of exponent of the given FP type. + // Biased exponent (unsigned integer, i.e. the exponent part of float) = unbiased exponent (signed integer) + expBias. + let expBitCount: number, fractBitCount: number, expBias: number; + // To handle the exponent bits of given FP types (f16, f32, and f64), considering the highest 16 + // bits is enough. + // expMaskForHigh16Bits indicates the exponent bitfield in the highest 16 bits of the given FP + // type, and targetExpBitsForHigh16Bits is the exponent bits that corresponding to unbiased + // exponent -1, i.e. the exponent bits when the FP values is in range [0.5, 1.0). + let expMaskForHigh16Bits: number, targetExpBitsForHigh16Bits: number; + // Helper function that store the given FP value into buffer as the given FP types + let setFloatToBuffer: (v: number) => void; + // Helper function that read back FP value from buffer as the given FP types + let getFloatFromBuffer: () => number; + + let isFinite: (v: number) => boolean; + let isSubnormal: (v: number) => boolean; + + if (trait === 'f32') { + // f32 bit pattern: s_eeeeeeee_fffffff_ffffffffffffffff + expBitCount = 8; + fractBitCount = 23; + expBias = 127; + // The exponent bitmask for high 16 bits of f32. + expMaskForHigh16Bits = 0x7f80; + // The target exponent bits is equal to those for f32 0.5 = 0x3f000000. + targetExpBitsForHigh16Bits = 0x3f00; + isFinite = isFiniteF32; + isSubnormal = isSubnormalNumberF32; + // Enforce big-endian so that offset 0 is highest byte. + setFloatToBuffer = (v: number) => dataView.setFloat32(0, v, false); + getFloatFromBuffer = () => dataView.getFloat32(0, false); + } else if (trait === 'f16') { + // f16 bit pattern: s_eeeee_ffffffffff + expBitCount = 5; + fractBitCount = 10; + expBias = 15; + // The exponent bitmask for 16 bits of f16. + expMaskForHigh16Bits = 0x7c00; + // The target exponent bits is equal to those for f16 0.5 = 0x3800. + targetExpBitsForHigh16Bits = 0x3800; + isFinite = isFiniteF16; + isSubnormal = isSubnormalNumberF16; + // Enforce big-endian so that offset 0 is highest byte. + setFloatToBuffer = (v: number) => setFloat16(dataView, 0, v, false); + getFloatFromBuffer = () => getFloat16(dataView, 0, false); + } else { + assert(trait === 'f64'); + // f64 bit pattern: s_eeeeeeeeeee_ffff_ffffffffffffffffffffffffffffffffffffffffffffffff + expBitCount = 11; + fractBitCount = 52; + expBias = 1023; + // The exponent bitmask for 16 bits of f64. + expMaskForHigh16Bits = 0x7ff0; + // The target exponent bits is equal to those for f64 0.5 = 0x3fe0_0000_0000_0000. + targetExpBitsForHigh16Bits = 0x3fe0; + isFinite = Number.isFinite; + isSubnormal = isSubnormalNumberF64; + // Enforce big-endian so that offset 0 is highest byte. + setFloatToBuffer = (v: number) => dataView.setFloat64(0, v, false); + getFloatFromBuffer = () => dataView.getFloat64(0, false); + } + // Helper function that extract the unbiased exponent of the float in buffer. + const extractUnbiasedExpFromNormalFloatInBuffer = () => { + // Assert the float in buffer is finite normal float. + assert(isFinite(getFloatFromBuffer()) && !isSubnormal(getFloatFromBuffer())); + // Get the highest 16 bits of float as uint16, which can contain the whole exponent part for both f16, f32, and f64. + const high16BitsAsUint16 = dataView.getUint16(0, false); + // Return the unbiased exp by masking, shifting and unbiasing. + return ((high16BitsAsUint16 & expMaskForHigh16Bits) >> (16 - 1 - expBitCount)) - expBias; + }; + // Helper function that modify the exponent of float in buffer to make it in range [0.5, 1.0). + // By setting the unbiased exponent to -1, the fp value will be in range 2**-1 * [1.0, 2.0), i.e. [0.5, 1.0). + const modifyExpOfNormalFloatInBuffer = () => { + // Assert the float in buffer is finite normal float. + assert(isFinite(getFloatFromBuffer()) && !isSubnormal(getFloatFromBuffer())); + // Get the highest 16 bits of float as uint16, which contains the whole exponent part for both f16, f32, and f64. + const high16BitsAsUint16 = dataView.getUint16(0, false); + // Modify the exponent bits. + const modifiedHigh16Bits = + (high16BitsAsUint16 & ~expMaskForHigh16Bits) | targetExpBitsForHigh16Bits; + // Set back to buffer + dataView.setUint16(0, modifiedHigh16Bits, false); + }; - // Handles 0 and -0 - if (frexpDataF32[0] === 0) { - return { fract: frexpDataF32[0], exp: 0 }; + // +/- 0.0 + if (val === 0) { + return { fract: val, exp: 0 }; } - - // Covers NaNs, OOB and Infinities - if (!isFiniteF32(frexpDataF32[0])) { - return { fract: frexpDataF32[0], exp: 0 }; + // NaN and Inf + if (!isFinite(val)) { + return { fract: val, exp: 0 }; } - // Normalize if subnormal - let exp = 0; - if (isSubnormalNumberF32(frexpDataF32[0])) { - frexpDataF32[0] = frexpDataF32[0] * (1 << 23); - exp = -23; - } - exp += ((frexpDataU32[0] >> 23) & 0xff) - 126; // shift & mask, minus the bias + 1 + setFloatToBuffer(val); + // Don't use val below. Use helper functions working with buffer instead. - frexpDataU32[0] &= 0x807fffff; // mask the exponent bits - frexpDataU32[0] |= 0x3f000000; // extract the mantissa bits - const fract = frexpDataF32[0]; // Convert from bits to number - return { fract, exp }; + let exp = 0; + // Normailze the value if it is subnormal. Increase the exponent by multiplying a subnormal value + // with 2**fractBitCount will result in a finite normal FP value of the given FP type. + if (isSubnormal(getFloatFromBuffer())) { + setFloatToBuffer(getFloatFromBuffer() * 2 ** fractBitCount); + exp = -fractBitCount; + } + // A normal FP value v is represented as v = ((-1)**s)*(2**(unbiased exponent))*f, where f is in + // range [1.0, 2.0). By moving a factor 2 from f to exponent, we have + // v = ((-1)**s)*(2**(unbiased exponent + 1))*(f / 2), where (f / 2) is in range [0.5, 1.0), so + // the exp = (unbiased exponent + 1) and fract = ((-1)**s)*(f / 2) is what we expect to get from + // frexp function. Note that fract and v only differs in exponent bitfield as long as v is normal. + // Calc the result exp by getting the unbiased float exponent and plus 1. + exp += extractUnbiasedExpFromNormalFloatInBuffer() + 1; + // Modify the exponent of float in buffer to make it be in range [0.5, 1.0) to get fract. + modifyExpOfNormalFloatInBuffer(); + + return { fract: getFloatFromBuffer(), exp }; } /** From 01267244f661b8c9831bffa2a94187028e6823e3 Mon Sep 17 00:00:00 2001 From: Lokbondo Kung Date: Fri, 8 Sep 2023 17:08:02 -0700 Subject: [PATCH 017/166] Implements filtering tests for mag/min/mipmapFilters with additional float32-filterable formats. (#2915) * Adds magFilter op tests * Adds inital minFilter tests * Adds remaining sampling tests * Fix formatting issue * Redo min/magFilter tests for mirror, and adds more doc. --- .../operation/sampling/filter_mode.spec.ts | 1140 ++++++++++++++++- src/webgpu/capability_info.ts | 14 + src/webgpu/util/texture/base.ts | 18 + src/webgpu/util/texture/texel_data.ts | 2 +- src/webgpu/util/texture/texel_view.ts | 41 + src/webgpu/util/texture/texture_ok.ts | 14 +- 6 files changed, 1207 insertions(+), 22 deletions(-) diff --git a/src/webgpu/api/operation/sampling/filter_mode.spec.ts b/src/webgpu/api/operation/sampling/filter_mode.spec.ts index cf1d7682e102..63f4409ca158 100644 --- a/src/webgpu/api/operation/sampling/filter_mode.spec.ts +++ b/src/webgpu/api/operation/sampling/filter_mode.spec.ts @@ -1,14 +1,1138 @@ export const description = ` Tests the behavior of different filtering modes in minFilter/magFilter/mipmapFilter. - -TODO: -- Test exact sampling results with small tolerance. Tests should differentiate between different - values for all three filter modes to make sure none are missed or incorrect in implementations. -- (Likely unnecessary with the above.) Test exactly the expected number of samples are used. - Test this by setting up a rendering and asserting how many different shades result. `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; -import { GPUTest } from '../../../gpu_test.js'; +import { kAddressModes, kMipmapFilterModes } from '../../../capability_info.js'; +import { + EncodableTextureFormat, + kRenderableColorTextureFormats, + kTextureFormatInfo, +} from '../../../format_info.js'; +import { GPUTest, TextureTestMixin } from '../../../gpu_test.js'; +import { getTextureCopyLayout } from '../../../util/texture/layout.js'; +import { TexelView } from '../../../util/texture/texel_view.js'; + +// Simple checkerboard 2x2 texture used as a base for the sampling. +const kCheckerTextureSize = 2; +const kCheckerTextureData = [ + { R: 1.0, G: 1.0, B: 1.0, A: 1.0 }, + { R: 0.0, G: 0.0, B: 0.0, A: 1.0 }, + { R: 0.0, G: 0.0, B: 0.0, A: 1.0 }, + { R: 1.0, G: 1.0, B: 1.0, A: 1.0 }, +]; + +class FilterModeTest extends TextureTestMixin(GPUTest) { + runFilterRenderPipeline( + sampler: GPUSampler, + module: GPUShaderModule, + format: EncodableTextureFormat, + renderSize: number[], + vertexCount: number, + instanceCount: number + ) { + const sampleTexture = this.createTextureFromTexelView( + TexelView.fromTexelsAsColors(format, coord => { + const id = coord.x + coord.y * kCheckerTextureSize; + return kCheckerTextureData[id]; + }), + { + size: [kCheckerTextureSize, kCheckerTextureSize], + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST, + } + ); + const renderTexture = this.device.createTexture({ + format, + size: renderSize, + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + }); + const pipeline = this.device.createRenderPipeline({ + layout: 'auto', + vertex: { + module, + entryPoint: 'vs_main', + }, + fragment: { + module, + entryPoint: 'fs_main', + targets: [{ format }], + }, + }); + const bindgroup = this.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: sampler }, + { binding: 1, resource: sampleTexture.createView() }, + ], + }); + const commandEncoder = this.device.createCommandEncoder(); + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: renderTexture.createView(), + clearValue: [0, 0, 0, 0], + loadOp: 'clear', + storeOp: 'store', + }, + ], + }); + renderPass.setPipeline(pipeline); + renderPass.setBindGroup(0, bindgroup); + renderPass.draw(vertexCount, instanceCount); + renderPass.end(); + this.device.queue.submit([commandEncoder.finish()]); + return renderTexture; + } +} + +export const g = makeTestGroup(FilterModeTest); + +/* eslint-disable prettier/prettier */ + +/* For filter mode 'nearest', we need to check a 6x6 of pixels because 4x4s are identical when using + * address mode 'clamp-to-edge' and 'mirror-repeat'. The minFilter and magFilter tests are setup so + * that they both render the same results. (See the respective test for details.) The following + * table shows the expected results: + * u + * + * repeat clamp-to-edge mirror-repeat + * + * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│ + * │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │ + * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│ + * repeat │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │ + * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│ + * │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │ + * + * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│ + * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│ + * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│ + * v clamp-to-edge │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │ + * │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │ + * │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │ + * + * │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │ + * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│ + * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│ + * mirror-repeat │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │ + * │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │ + * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│ +*/ +const kNearestRenderSize = 6; +const kNearestRenderDim = [kNearestRenderSize, kNearestRenderSize]; +const kNearestURepeatVRepeat = [ + [1, 0, 1, 0, 1, 0], + [0, 1, 0, 1, 0, 1], + [1, 0, 1, 0, 1, 0], + [0, 1, 0, 1, 0, 1], + [1, 0, 1, 0, 1, 0], + [0, 1, 0, 1, 0, 1], +]; +const kNearestURepeatVClamped = [ + [1, 0, 1, 0, 1, 0], + [1, 0, 1, 0, 1, 0], + [1, 0, 1, 0, 1, 0], + [0, 1, 0, 1, 0, 1], + [0, 1, 0, 1, 0, 1], + [0, 1, 0, 1, 0, 1], +]; +const kNearestURepeatVMirror = [ + [0, 1, 0, 1, 0, 1], + [1, 0, 1, 0, 1, 0], + [1, 0, 1, 0, 1, 0], + [0, 1, 0, 1, 0, 1], + [0, 1, 0, 1, 0, 1], + [1, 0, 1, 0, 1, 0], +]; +const kNearestUClampedVRepeat = [ + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1], +]; +const kNearestUClampedVClamped = [ + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1], +]; +const kNearestUClampedVMirror = [ + [0, 0, 0, 1, 1, 1], + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1], + [1, 1, 1, 0, 0, 0], +]; +const kNearestUMirrorVRepeat = [ + [0, 1, 1, 0, 0, 1], + [1, 0, 0, 1, 1, 0], + [0, 1, 1, 0, 0, 1], + [1, 0, 0, 1, 1, 0], + [0, 1, 1, 0, 0, 1], + [1, 0, 0, 1, 1, 0], +]; +const kNearestUMirrorVClamped = [ + [0, 1, 1, 0, 0, 1], + [0, 1, 1, 0, 0, 1], + [0, 1, 1, 0, 0, 1], + [1, 0, 0, 1, 1, 0], + [1, 0, 0, 1, 1, 0], + [1, 0, 0, 1, 1, 0], +]; +const kNearestUMirrorVMirror = [ + [1, 0, 0, 1, 1, 0], + [0, 1, 1, 0, 0, 1], + [0, 1, 1, 0, 0, 1], + [1, 0, 0, 1, 1, 0], + [1, 0, 0, 1, 1, 0], + [0, 1, 1, 0, 0, 1], +]; + +/* For filter mode 'linear', the tests samples 16 points (to create a 4x4) on what the effective 8x8 + * expanded texture via the address modes looks like (see table below for what those look like). The + * sample points are selected such that no combination of address modes result in the same render. + * There is exactly one sample point in each sub 2x2 of the 8x8 texture, thereby yielding the 4x4 + * result. Note that sampling from the 8x8 texture instead of the 6x6 texture is necessary because + * that allows us to keep the results in powers of 2 to minimize floating point errors on different + * backends. + * + * The 8x8 effective textures: + * u + * + * repeat clamp-to-edge mirror-repeat + * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │ + * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│ + * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │ + * repeat │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│ + * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │ + * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│ + * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │ + * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│ + * + * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│ + * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│ + * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│ + * v clamp-to-edge │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│ + * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │ + * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │ + * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │ + * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │ + * + * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │ + * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │ + * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│ + * mirror-repeat │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│ + * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │ + * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │ + * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│ + * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│ + * + * + * Sample points: + * The sample points are always at a 25% corner of a pixel such that the contributions come from + * the 2x2 (doubly outlined) with ratios 1/16, 3/16, or 9/16. + * ╔══╤══╦══╤══╦══╤══╦══╤══╗ + * ║ │ ║ │ ║ │ ║ │ ║ + * ╟──┼──╫──┼──╫──┼──╫──┼──╢ + * ║ │▘ ║ ▝│ ║ │▘ ║ ▝│ ║ + * ╠══╪══╬══╪══╬══╪══╬══╪══╣ + * ║ │ ║ │ ║ │ ║ │ ║ + * ╟──┼──╫──┼──╫──┼──╫──┼──╢ + * ║ │▘ ║ ▝│ ║ │▘ ║ ▝│ ║ + * ╠══╪══╬══╪══╬══╪══╬══╪══╣ + * ║ │▖ ║ ▗│ ║ │▖ ║ ▗│ ║ + * ╟──┼──╫──┼──╫──┼──╫──┼──╢ + * ║ │ ║ │ ║ │ ║ │ ║ + * ╠══╪══╬══╪══╬══╪══╬══╪══╣ + * ║ │▖ ║ ▗│ ║ │▖ ║ ▗│ ║ + * ╟──┼──╫──┼──╫──┼──╫──┼──╢ + * ║ │ ║ │ ║ │ ║ │ ║ + * ╚══╧══╩══╧══╩══╧══╩══╧══╝ + */ +const kLinearRenderSize = 4; +const kLinearRenderDim = [kLinearRenderSize, kLinearRenderSize]; +const kLinearURepeatVRepeat = [ + [10, 6, 10, 6], + [10, 6, 10, 6], + [6, 10, 6, 10], + [6, 10, 6, 10], +]; +const kLinearURepeatVClamped = [ + [12, 4, 12, 4], + [12, 4, 12, 4], + [4, 12, 4, 12], + [4, 12, 4, 12], +]; +const kLinearURepeatVMirror = [ + [4, 12, 4, 12], + [12, 4, 12, 4], + [4, 12, 4, 12], + [12, 4, 12, 4], +]; +const kLinearUClampedVRepeat = [ + [12, 12, 4, 4], + [12, 12, 4, 4], + [4, 4, 12, 12], + [4, 4, 12, 12], +]; +const kLinearUClampedVClamped = [ + [16, 16, 0, 0], + [16, 16, 0, 0], + [0, 0, 16, 16], + [0, 0, 16, 16], +]; +const kLinearUClampedVMirror = [ + [0, 0, 16, 16], + [16, 16, 0, 0], + [0, 0, 16, 16], + [16, 16, 0, 0], +]; +const kLinearUMirrorVRepeat = [ + [4, 12, 4, 12], + [4, 12, 4, 12], + [12, 4, 12, 4], + [12, 4, 12, 4], +]; +const kLinearUMirrorVClamped = [ + [0, 16, 0, 16], + [0, 16, 0, 16], + [16, 0, 16, 0], + [16, 0, 16, 0], +]; +const kLinearUMirrorVMirror = [ + [16, 0, 16, 0], + [0, 16, 0, 16], + [16, 0, 16, 0], + [0, 16, 0, 16], +]; + +/* eslint-enable prettier/prettier */ + +function expectedNearestColors( + format: EncodableTextureFormat, + addressModeU: GPUAddressMode, + addressModeV: GPUAddressMode +): TexelView { + let expectedColors: number[][]; + switch (addressModeU) { + case 'clamp-to-edge': { + switch (addressModeV) { + case 'clamp-to-edge': + expectedColors = kNearestUClampedVClamped; + break; + case 'repeat': + expectedColors = kNearestUClampedVRepeat; + break; + case 'mirror-repeat': + expectedColors = kNearestUClampedVMirror; + break; + } + break; + } + case 'repeat': + switch (addressModeV) { + case 'clamp-to-edge': + expectedColors = kNearestURepeatVClamped; + break; + case 'repeat': + expectedColors = kNearestURepeatVRepeat; + break; + case 'mirror-repeat': + expectedColors = kNearestURepeatVMirror; + break; + } + break; + case 'mirror-repeat': + switch (addressModeV) { + case 'clamp-to-edge': + expectedColors = kNearestUMirrorVClamped; + break; + case 'repeat': + expectedColors = kNearestUMirrorVRepeat; + break; + case 'mirror-repeat': + expectedColors = kNearestUMirrorVMirror; + break; + } + break; + } + return TexelView.fromTexelsAsColors(format, coord => { + const c = expectedColors[coord.y][coord.x]; + return { R: c, G: c, B: c, A: 1.0 }; + }); +} +function expectedLinearColors( + format: EncodableTextureFormat, + addressModeU: GPUAddressMode, + addressModeV: GPUAddressMode +): TexelView { + let expectedColors: number[][]; + switch (addressModeU) { + case 'clamp-to-edge': { + switch (addressModeV) { + case 'clamp-to-edge': + expectedColors = kLinearUClampedVClamped; + break; + case 'repeat': + expectedColors = kLinearUClampedVRepeat; + break; + case 'mirror-repeat': + expectedColors = kLinearUClampedVMirror; + break; + } + break; + } + case 'repeat': + switch (addressModeV) { + case 'clamp-to-edge': + expectedColors = kLinearURepeatVClamped; + break; + case 'repeat': + expectedColors = kLinearURepeatVRepeat; + break; + case 'mirror-repeat': + expectedColors = kLinearURepeatVMirror; + break; + } + break; + case 'mirror-repeat': + switch (addressModeV) { + case 'clamp-to-edge': + expectedColors = kLinearUMirrorVClamped; + break; + case 'repeat': + expectedColors = kLinearUMirrorVRepeat; + break; + case 'mirror-repeat': + expectedColors = kLinearUMirrorVMirror; + break; + } + break; + } + return TexelView.fromTexelsAsColors(format, coord => { + const c = expectedColors[coord.y][coord.x]; + return { R: c / 16, G: c / 16, B: c / 16, A: 1.0 }; + }); +} +function expectedColors( + format: EncodableTextureFormat, + filterMode: GPUFilterMode, + addressModeU: GPUAddressMode, + addressModeV: GPUAddressMode +): TexelView { + switch (filterMode) { + case 'nearest': + return expectedNearestColors(format, addressModeU, addressModeV); + case 'linear': + return expectedLinearColors(format, addressModeU, addressModeV); + } +} + +/* For the magFilter tests, each rendered pixel is an instanced quad such that the center of the + * quad coincides with the center of the pixel. The uv coordinates for each quad are shifted + * according to the test so that the center of the quad is at the point we want to sample. + * + * For the grid offset logic, see this codelab for reference: + * https://codelabs.developers.google.com/your-first-webgpu-app#4 + */ + +/* The following diagram shows the UV shift (almost to scale) for what the pixel at cell (0,0) looks + * like w.r.t the UV of the texture if we just mapped the entire 2x2 texture to the quad. Note that + * the square representing the mapped location on the bottom left is actually slighly smaller than a + * pixel in order to ensure that we are magnifying the texture and hence using the magFilter. It + * should be fairly straightforwards to derive that for each pixel, we are shifting (.5, -.5) from + * the picture. + * + * ┌─┬─┬─┬─┬─┬─┐ + * ├─┼─┼─┼─┼─┼─┤ (0,0) (1,0) + * ├─┼─╔═╪═╗─┼─┤ ╔═══╗ + * ├─┼─╫─┼─╫─┼─┤ ║─┼─║ + * ├─┼─╚═╪═╝─┼─┤ ╚═══╝ (-.875,1.625) (-.625,1.625) + * ╔═╗─┼─┼─┼─┼─┤ (0,1) (1,1) ╔═╗ + * ╚═╝─┴─┴─┴─┴─┘ ╚═╝ + * (-.875,1.875) (-.625,1.875) + */ +g.test('magFilter,nearest') + .desc( + ` + Test that for filterable formats, magFilter 'nearest' mode correctly modifies the sampling. + - format= {} + - addressModeU= {'clamp-to-edge', 'repeat', 'mirror-repeat'} + - addressModeV= {'clamp-to-edge', 'repeat', 'mirror-repeat'} + ` + ) + .params(u => + u + .combine('format', kRenderableColorTextureFormats) + .filter(t => { + return ( + kTextureFormatInfo[t.format].color.type === 'float' || + kTextureFormatInfo[t.format].color.type === 'unfilterable-float' + ); + }) + .beginSubcases() + .combine('addressModeU', kAddressModes) + .combine('addressModeV', kAddressModes) + ) + .beforeAllSubcases(t => { + if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') { + t.selectDeviceOrSkipTestCase('float32-filterable'); + } + }) + .fn(t => { + const { format, addressModeU, addressModeV } = t.params; + const sampler = t.device.createSampler({ + addressModeU, + addressModeV, + magFilter: 'nearest', + }); + const module = t.device.createShaderModule({ + code: ` + @group(0) @binding(0) var s : sampler; + @group(0) @binding(1) var t : texture_2d; + + struct VertexOut { + @builtin(position) pos: vec4f, + @location(0) uv: vec2f, + }; + + @vertex + fn vs_main(@builtin(vertex_index) vi : u32, + @builtin(instance_index) ii: u32) -> VertexOut { + const grid = vec2f(${kNearestRenderSize}, ${kNearestRenderSize}); + const posBases = array( + vec2f(1, 1), vec2f(1, -1), vec2f(-1, -1), + vec2f(1, 1), vec2f(-1, -1), vec2f(-1, 1), + ); + const uvBases = array( + vec2f(1., 0.), vec2f(1., 1.), vec2f(0., 1.), + vec2f(1., 0.), vec2f(0., 1.), vec2f(0., 0.), + ); + + // Compute the offset of instance plane. + let cell = vec2f(f32(ii) % grid.x, floor(f32(ii) / grid.y)); + let cellOffset = cell / grid * 2; + let pos = (posBases[vi] + 1) / grid - 1 + cellOffset; + + // Compute the offset of the UVs. + let uvBase = uvBases[vi] * 0.25 + vec2f(-0.875, 1.625); + const uvPerPixelOffset = vec2f(0.5, -0.5); + return VertexOut(vec4f(pos, 0.0, 1.0), uvBase + uvPerPixelOffset * cell); + } + + @fragment + fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f { + return textureSample(t, s, uv); + } + `, + }); + const vertexCount = 6; + const instanceCount = kNearestRenderDim.reduce((sink, current) => sink * current); + const render = t.runFilterRenderPipeline( + sampler, + module, + format, + kNearestRenderDim, + vertexCount, + instanceCount + ); + t.expectTexelViewComparisonIsOkInTexture( + { texture: render }, + expectedColors(format, 'nearest', addressModeU, addressModeV), + kNearestRenderDim + ); + }); + +/* The following diagram shows the UV shift (almost to scale) for what the pixel at cell (0,0) (the + * dark square) looks like w.r.t the UV of the texture if we just mapped the entire 2x2 texture to + * the quad. The other small squares represent the other locations that we are sampling the texture + * at. The offsets are defined in the shader. + * + * ┌────┬────┬────┬────┬────┬────┬────┬────┐ + * │ │ │ │ │ │ │ │ │ + * │ │ │ │ │ │ │ │ │ + * ├────┼────┼────┼────┼────┼────┼────┼────┤ + * │ │□ │ □│ │ │□ │ □│ │ + * │ │ │ │ │ │ │ │ │ + * ├────┼────┼────┼────┼────┼────┼────┼────┤ + * │ │ │ │ │ │ │ │ │ + * │ │ │ │ │ │ │ │ │ (0,0) (1,0) + * ├────┼────┼────╔════╪════╗────┼────┼────┤ ╔═════════╗ + * │ │□ │ □║ │ ║□ │ □│ │ ║ │ ║ + * │ │ │ ║ │ ║ │ │ │ ║ │ ║ + * ├────┼────┼────╫────┼────╫────┼────┼────┤ ║────┼────║ + * │ │ │ ║ │ ║ │ │ │ ║ │ ║ + * │ │□ │ □║ │ ║□ │ □│ │ ║ │ ║ + * ├────┼────┼────╚════╪════╝────┼────┼────┤ ╚═════════╝ + * │ │ │ │ │ │ │ │ │ (0,1) (1,1) + * │ │ │ │ │ │ │ │ │ + * ├────┼────┼────┼────┼────┼────┼────┼────┤ + * │ │ │ │ │ │ │ │ │ (-1,1.75) (-.75,1.75) + * │ │■ │ □│ │ │□ │ □│ │ ■ + * ├────┼────┼────┼────┼────┼────┼────┼────┤ (-1,2) (-.75,2) + * │ │ │ │ │ │ │ │ │ + * │ │ │ │ │ │ │ │ │ + * └────┴────┴────┴────┴────┴────┴────┴────┘ + */ +g.test('magFilter,linear') + .desc( + ` + Test that for filterable formats, magFilter 'linear' mode correctly modifies the sampling. + - format= {} + - addressModeU= {'clamp-to-edge', 'repeat', 'mirror-repeat'} + - addressModeV= {'clamp-to-edge', 'repeat', 'mirror-repeat'} + ` + ) + .params(u => + u + .combine('format', kRenderableColorTextureFormats) + .filter(t => { + return ( + kTextureFormatInfo[t.format].color.type === 'float' || + kTextureFormatInfo[t.format].color.type === 'unfilterable-float' + ); + }) + .beginSubcases() + .combine('addressModeU', kAddressModes) + .combine('addressModeV', kAddressModes) + ) + .beforeAllSubcases(t => { + if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') { + t.selectDeviceOrSkipTestCase('float32-filterable'); + } + }) + .fn(t => { + const { format, addressModeU, addressModeV } = t.params; + const sampler = t.device.createSampler({ + addressModeU, + addressModeV, + magFilter: 'linear', + }); + const module = t.device.createShaderModule({ + code: ` + @group(0) @binding(0) var s : sampler; + @group(0) @binding(1) var t : texture_2d; + + struct VertexOut { + @builtin(position) pos: vec4f, + @location(0) uv: vec2f, + }; + + @vertex + fn vs_main(@builtin(vertex_index) vi : u32, + @builtin(instance_index) ii: u32) -> VertexOut { + const grid = vec2f(${kLinearRenderSize}, ${kLinearRenderSize}); + const posBases = array( + vec2f(1, 1), vec2f(1, -1), vec2f(-1, -1), + vec2f(1, 1), vec2f(-1, -1), vec2f(-1, 1), + ); + const uvBases = array( + vec2f(1., 0.), vec2f(1., 1.), vec2f(0., 1.), + vec2f(1., 0.), vec2f(0., 1.), vec2f(0., 0.), + ); + + // Compute the offset of instance plane. + let cell = vec2f(f32(ii) % grid.x, floor(f32(ii) / grid.y)); + let cellOffset = cell / grid * 2; + let pos = (posBases[vi] + 1) / grid - 1 + cellOffset; + + // Compute the offset of the UVs. + const uOffsets = array(0., 0.75, 2., 2.75); + const vOffsets = array(0., 1., 1.75, 2.75); + let uvBase = uvBases[vi] * 0.25 + vec2f(-1., 1.75); + let uvPixelOffset = vec2f(uOffsets[u32(cell.x)], -vOffsets[u32(cell.y)]); + return VertexOut(vec4f(pos, 0.0, 1.0), uvBase + uvPixelOffset); + } + + @fragment + fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f { + return textureSample(t, s, uv); + } + `, + }); + const vertexCount = 6; + const instanceCount = kLinearRenderDim.reduce((sink, current) => sink * current); + const render = t.runFilterRenderPipeline( + sampler, + module, + format, + kLinearRenderDim, + vertexCount, + instanceCount + ); + t.expectTexelViewComparisonIsOkInTexture( + { texture: render }, + expectedColors(format, 'linear', addressModeU, addressModeV), + kLinearRenderDim + ); + }); + +/* For the minFilter tests, each rendered pixel is a small instanced quad that is UV mapped such + * that it is either the 6x6 or 8x8 textures from above. Each quad in each cell is then offsetted + * and scaled so that the target sample point coincides with the center of the pixel and the texture + * is significantly smaller than the pixel to force minFilter mode. + * + * For the grid offset logic, see this codelab for reference: + * https://codelabs.developers.google.com/your-first-webgpu-app#4 + */ + +/* The following diagram depicts a single pixel and the sub-pixel sized 6x6 textured quad. The + * distances shown in the diagram are pre-grid transformation and relative to the quad. Notice that + * for cell (0,0) marked with an x, we need to offset the center by (5/12,5/12), and per cell, the + * offset is (-1/6, -1/6). + * + * + * ┌───────────────────────────────────────────────┐ + * │ │ + * │ │ + * │ │ + * │ │ + * │ │ + * │ ┌───┬───┬───┬───┬───┬───┐ │ + * │ │ │ │ │ │ │ │ │ + * │ ├───┼───┼───┼───┼───┼───┤ │ + * │ │ │ │ │ │ │ │ │ + * │ ├───┼───┼───┼───┼───┼───┤ │ + * │ │ │ │ │ │ │ │ │ + * │ ├───┼───┼───x───┼───┼───┤ │ ┐ + * │ │ │ │ │ │ │ │ │ │ + * │ ├───┼───┼───┼───┼───┼───┤ │ │ 5/12 + * │ │ │ │ │ │ │ │ │ ┐ │ + * │ ├───┼───┼───┼───┼───┼───┤ │ │ 1/6 │ + * │ │ x │ │ │ │ │ │ │ ┘ ┘ + * │ └───┴───┴───┴───┴───┴───┘ │ + * │ │ + * │ │ + * │ │ + * │ │ + * │ │ + * └───────────────────────────────────────────────┘ + */ +g.test('minFilter,nearest') + .desc( + ` + Test that for filterable formats, minFilter 'nearest' mode correctly modifies the sampling. + - format= {} + - addressModeU= {'clamp-to-edge', 'repeat', 'mirror-repeat'} + - addressModeV= {'clamp-to-edge', 'repeat', 'mirror-repeat'} + ` + ) + .params(u => + u + .combine('format', kRenderableColorTextureFormats) + .filter(t => { + return ( + kTextureFormatInfo[t.format].color.type === 'float' || + kTextureFormatInfo[t.format].color.type === 'unfilterable-float' + ); + }) + .beginSubcases() + .combine('addressModeU', kAddressModes) + .combine('addressModeV', kAddressModes) + ) + .beforeAllSubcases(t => { + if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') { + t.selectDeviceOrSkipTestCase('float32-filterable'); + } + }) + .fn(t => { + const { format, addressModeU, addressModeV } = t.params; + const sampler = t.device.createSampler({ + addressModeU, + addressModeV, + minFilter: 'nearest', + }); + const module = t.device.createShaderModule({ + code: ` + @group(0) @binding(0) var s : sampler; + @group(0) @binding(1) var t : texture_2d; + + struct VertexOut { + @builtin(position) pos: vec4f, + @location(0) uv: vec2f, + }; + + @vertex + fn vs_main(@builtin(vertex_index) vi : u32, + @builtin(instance_index) ii: u32) -> VertexOut { + const grid = vec2f(${kNearestRenderSize}, ${kNearestRenderSize}); + const posBases = array( + vec2f(.5, .5), vec2f(.5, -.5), vec2f(-.5, -.5), + vec2f(.5, .5), vec2f(-.5, -.5), vec2f(-.5, .5), + ); + // Choose UVs so that the quad ends up being the 6x6 texture. + const uvBases = array( + vec2f(2., -1.), vec2f(2., 2.), vec2f(-1., 2.), + vec2f(2., -1.), vec2f(-1., 2.), vec2f(-1., -1.), + ); + + let cell = vec2f(f32(ii) % grid.x, floor(f32(ii) / grid.y)); + + // Compute the offset of instance plane (pre-grid transformation). + const constantPlaneOffset = vec2f(5. / 12., 5. / 12.); + const perPixelOffset = vec2f(1. / 6., 1. / 6.); + let posBase = posBases[vi] + constantPlaneOffset - perPixelOffset * cell; + + // Apply the grid transformation. + let cellOffset = cell / grid * 2; + let absPos = (posBase + 1) / grid - 1 + cellOffset; + + return VertexOut(vec4f(absPos, 0.0, 1.0), uvBases[vi]); + } + + @fragment + fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f { + return textureSample(t, s, uv); + } + `, + }); + const vertexCount = 6; + const instanceCount = kNearestRenderDim.reduce((sink, current) => sink * current); + const render = t.runFilterRenderPipeline( + sampler, + module, + format, + kNearestRenderDim, + vertexCount, + instanceCount + ); + t.expectTexelViewComparisonIsOkInTexture( + { texture: render }, + expectedColors(format, 'nearest', addressModeU, addressModeV), + kNearestRenderDim + ); + }); + +/* The following diagram shows the sub-pixel quad and the relative distances between the sample + * points and the origin. The pixel is not shown in this diagram but is a 2x bounding box around the + * quad similar to the one in the diagram for minFilter,nearest above. The dark square is where the + * cell (0,0) is, and the offsets are all relative to that point. + * + * 11/32 + * ┌─────────────┐ + * + * 3/16 5/16 3/16 + * ┌───────┬───────────┬───────┐ + * + * ┌────┬────┬────┬────┬────┬────┬────┬────┐ + * │ │ │ │ │ │ │ │ │ + * │ │ │ │ │ │ │ │ │ + * ├────┼────┼────┼────┼────┼────┼────┼────┤ + * │ │□ │ □│ │ │□ │ □│ │ ┐ + * │ │ │ │ │ │ │ │ │ │ + * ├────┼────┼────┼────┼────┼────┼────┼────┤ │ + * │ │ │ │ │ │ │ │ │ │ 1/4 + * │ │ │ │ │ │ │ │ │ │ + * ├────┼────┼────┼────┼────┼────┼────┼────┤ │ + * │ │□ │ □│ │ │□ │ □│ │ ┤ + * │ │ │ │ │ │ │ │ │ │ + * ├────┼────┼────┼────x────┼────┼────┼────┤ │ 3/16 ┐ + * │ │ │ │ │ │ │ │ │ │ │ + * │ │□ │ □│ │ │□ │ □│ │ ┤ │ + * ├────┼────┼────┼────┼────┼────┼────┼────┤ │ │ + * │ │ │ │ │ │ │ │ │ │ │ 11/32 + * │ │ │ │ │ │ │ │ │ │ 1/4 │ + * ├────┼────┼────┼────┼────┼────┼────┼────┤ │ │ + * │ │ │ │ │ │ │ │ │ │ │ + * │ │■ │ □│ │ │□ │ □│ │ ┘ ┘ + * ├────┼────┼────┼────┼────┼────┼────┼────┤ + * │ │ │ │ │ │ │ │ │ + * │ │ │ │ │ │ │ │ │ + * └────┴────┴────┴────┴────┴────┴────┴────┘ + */ +g.test('minFilter,linear') + .desc( + ` + Test that for filterable formats, minFilter 'linear' mode correctly modifies the sampling. + - format= {} + - addressModeU= {'clamp-to-edge', 'repeat', 'mirror-repeat'} + - addressModeV= {'clamp-to-edge', 'repeat', 'mirror-repeat'} + ` + ) + .params(u => + u + .combine('format', kRenderableColorTextureFormats) + .filter(t => { + return ( + kTextureFormatInfo[t.format].color.type === 'float' || + kTextureFormatInfo[t.format].color.type === 'unfilterable-float' + ); + }) + .beginSubcases() + .combine('addressModeU', kAddressModes) + .combine('addressModeV', kAddressModes) + ) + .beforeAllSubcases(t => { + if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') { + t.selectDeviceOrSkipTestCase('float32-filterable'); + } + }) + .fn(t => { + const { format, addressModeU, addressModeV } = t.params; + const sampler = t.device.createSampler({ + addressModeU, + addressModeV, + minFilter: 'linear', + }); + const module = t.device.createShaderModule({ + code: ` + @group(0) @binding(0) var s : sampler; + @group(0) @binding(1) var t : texture_2d; + + struct VertexOut { + @builtin(position) pos: vec4f, + @location(0) uv: vec2f, + }; + + @vertex + fn vs_main(@builtin(vertex_index) vi : u32, + @builtin(instance_index) ii: u32) -> VertexOut { + const grid = vec2f(${kLinearRenderSize}, ${kLinearRenderSize}); + const posBases = array( + vec2f(.5, .5), vec2f(.5, -.5), vec2f(-.5, -.5), + vec2f(.5, .5), vec2f(-.5, -.5), vec2f(-.5, .5), + ); + // Choose UVs so that the quad ends up being the 8x8 texture. + const uvBases = array( + vec2f(2.5, -1.5), vec2f(2.5, 2.5), vec2f(-1.5, 2.5), + vec2f(2.5, -1.5), vec2f(-1.5, 2.5), vec2f(-1.5, -1.5), + ); + + let cell = vec2f(f32(ii) % grid.x, floor(f32(ii) / grid.y)); + + // Compute the offset of instance plane (pre-grid transformation). + const constantPlaneOffset = vec2f(11. / 32., 11. / 32.); + const xOffsets = array(0., 3. / 16., 1. / 2., 11. / 16.); + const yOffsets = array(0., 1. / 4., 7. / 16., 11. / 16.); + let pixelOffset = vec2f(xOffsets[u32(cell.x)], yOffsets[u32(cell.y)]); + let posBase = posBases[vi] + constantPlaneOffset - pixelOffset; + + // Compute the offset of instance plane. + let cellOffset = cell / grid * 2; + let absPos = (posBase + 1) / grid - 1 + cellOffset; + + return VertexOut(vec4f(absPos, 0.0, 1.0), uvBases[vi]); + } + + @fragment + fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f { + return textureSample(t, s, uv); + } + `, + }); + const vertexCount = 6; + const instanceCount = kLinearRenderDim.reduce((sink, current) => sink * current); + const render = t.runFilterRenderPipeline( + sampler, + module, + format, + kLinearRenderDim, + vertexCount, + instanceCount + ); + t.expectTexelViewComparisonIsOkInTexture( + { texture: render }, + expectedColors(format, 'linear', addressModeU, addressModeV), + kLinearRenderDim + ); + }); + +g.test('mipmapFilter') + .desc( + ` + Test that for filterable formats, mipmapFilter modes correctly modifies the sampling. + - format= {} + - filterMode= {'nearest', 'linear'} + ` + ) + .params(u => + u + .combine('format', kRenderableColorTextureFormats) + .filter(t => { + return ( + kTextureFormatInfo[t.format].color.type === 'float' || + kTextureFormatInfo[t.format].color.type === 'unfilterable-float' + ); + }) + .beginSubcases() + .combine('filterMode', kMipmapFilterModes) + ) + .beforeAllSubcases(t => { + if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') { + t.selectDeviceOrSkipTestCase('float32-filterable'); + } + }) + .fn(t => { + const { format, filterMode } = t.params; + // Takes a 8x8/4x4 mipmapped texture and renders it on multiple quads with different UVs such + // that each instanced quad from left to right emulates moving the quad further and further from + // the camera. Each quad is then rendered to a single pixel in a 1-dimensional texture. Since + // the 8x8 is fully black and the 4x4 is fully white, we should see the pixels increase in + // brightness from left to right when sampling linearly, and jump from black to white when + // sampling for the nearest mip level. + const kTextureSize = 8; + const kRenderSize = 8; + + const sampler = t.device.createSampler({ + mipmapFilter: filterMode, + }); + const sampleTexture = t.createTextureFromTexelViewsMultipleMipmaps( + [ + TexelView.fromTexelsAsColors(format, () => { + return { R: 0.0, G: 0.0, B: 0.0, A: 1.0 }; + }), + TexelView.fromTexelsAsColors(format, coord => { + return { R: 1.0, G: 1.0, B: 1.0, A: 1.0 }; + }), + ], + { + size: [kTextureSize, 1], + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST, + } + ); + const renderTexture = t.device.createTexture({ + format, + size: [kRenderSize, 1], + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + }); + const module = t.device.createShaderModule({ + code: ` + @group(0) @binding(0) var s : sampler; + @group(0) @binding(1) var t : texture_2d; + + struct VertexOut { + @builtin(position) pos: vec4f, + @location(0) uv: vec2f, + }; + + @vertex + fn vs_main(@builtin(vertex_index) vi : u32, + @builtin(instance_index) ii: u32) -> VertexOut { + const grid = vec2f(${kRenderSize}., 1.); + const pos = array( + vec2f( 1.0, 1.0), vec2f( 1.0, -1.0), vec2f(-1.0, -1.0), + vec2f( 1.0, 1.0), vec2f(-1.0, -1.0), vec2f(-1.0, 1.0), + ); + const uv = array( + vec2f(1., 0.), vec2f(1., 1.), vec2f(0., 1.), + vec2f(1., 0.), vec2f(0., 1.), vec2f(0., 0.), + ); + + // Compute the offset of the plane. + let cell = vec2f(f32(ii) % grid.x, 0.); + let cellOffset = cell / grid * 2; + let absPos = (pos[vi] + 1) / grid - 1 + cellOffset; + let uvFactor = (1. / 8.) * (1 + (f32(ii) / (grid.x - 1))); + return VertexOut(vec4f(absPos, 0.0, 1.0), uv[vi] * uvFactor); + } + + @fragment + fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f { + return textureSample(t, s, uv); + } + `, + }); + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + vertex: { + module, + entryPoint: 'vs_main', + }, + fragment: { + module, + entryPoint: 'fs_main', + targets: [{ format }], + }, + }); + const bindgroup = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: sampler }, + { binding: 1, resource: sampleTexture.createView() }, + ], + }); + const commandEncoder = t.device.createCommandEncoder(); + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: renderTexture.createView(), + clearValue: [0, 0, 0, 0], + loadOp: 'clear', + storeOp: 'store', + }, + ], + }); + renderPass.setPipeline(pipeline); + renderPass.setBindGroup(0, bindgroup); + renderPass.draw(6, kRenderSize); + renderPass.end(); + t.device.queue.submit([commandEncoder.finish()]); + + // Since mipmap filtering varies across different backends, we verify that the result exhibits + // filtered characteristics without strict value equalities via copies to a buffer. + const buffer = t.copyWholeTextureToNewBufferSimple(renderTexture, 0); + t.expectGPUBufferValuesPassCheck( + buffer, + actual => { + // Convert the buffer to texel view so we can do comparisons. + const layout = getTextureCopyLayout(format, '2d', [kRenderSize, 1, 1]); + const view = TexelView.fromTextureDataByReference(format, actual, { + bytesPerRow: layout.bytesPerRow, + rowsPerImage: layout.rowsPerImage, + subrectOrigin: [0, 0, 0], + subrectSize: [kRenderSize, 1, 1], + }); -export const g = makeTestGroup(GPUTest); + // We only check the R component for the conditions, since all components should be equal if + // specified in the format. + switch (filterMode) { + case 'linear': { + // For 'linear' mode, we check that the resulting 1d image is monotonically increasing. + for (let x = 1; x < kRenderSize; x++) { + const { R: Ri } = view.color({ x: x - 1, y: 0, z: 0 }); + const { R: Rj } = view.color({ x, y: 0, z: 0 }); + if (Ri! >= Rj!) { + return Error( + 'Linear filtering on mipmaps should be a monotonically increasing sequence:\n' + + view.toString( + { x: 0, y: 0, z: 0 }, + { width: kRenderSize, height: 1, depthOrArrayLayers: 1 } + ) + ); + } + } + break; + } + case 'nearest': { + // For 'nearest' mode, we check that the resulting 1d image changes from 0.0 to 1.0 + // exactly once. + let changes = 0; + for (let x = 1; x < kRenderSize; x++) { + const { R: Ri } = view.color({ x: x - 1, y: 0, z: 0 }); + const { R: Rj } = view.color({ x, y: 0, z: 0 }); + if (Ri! !== Rj!) { + changes++; + } + } + if (changes !== 1) { + return Error( + `Nearest filtering on mipmaps should change exacly once but found (${changes}):\n` + + view.toString( + { x: 0, y: 0, z: 0 }, + { width: kRenderSize, height: 1, depthOrArrayLayers: 1 } + ) + ); + } + break; + } + } + return undefined; + }, + { srcByteOffset: 0, type: Uint8Array, typedLength: buffer.size } + ); + }); diff --git a/src/webgpu/capability_info.ts b/src/webgpu/capability_info.ts index b99884836935..4da0efb1fde6 100644 --- a/src/webgpu/capability_info.ts +++ b/src/webgpu/capability_info.ts @@ -586,6 +586,20 @@ export const kShaderStageCombinationsWithStage: readonly GPUShaderStageFlags[] = */ export const kTextureSampleCounts = [1, 4] as const; +// Sampler info + +/** List of all mipmap filter modes. */ +export const kMipmapFilterModes: readonly GPUMipmapFilterMode[] = ['nearest', 'linear']; +assertTypeTrue>(); + +/** List of address modes. */ +export const kAddressModes: readonly GPUAddressMode[] = [ + 'clamp-to-edge', + 'repeat', + 'mirror-repeat', +]; +assertTypeTrue>(); + // Blend factors and Blend components /** List of all GPUBlendFactor values. */ diff --git a/src/webgpu/util/texture/base.ts b/src/webgpu/util/texture/base.ts index 15a645d5e129..67b4fc715627 100644 --- a/src/webgpu/util/texture/base.ts +++ b/src/webgpu/util/texture/base.ts @@ -223,3 +223,21 @@ export function reifyTextureViewDescriptor( arrayLayerCount, }; } + +/** + * Get generator of all the coordinates in a subrect. + * @param subrectOrigin - Subrect origin + * @param subrectSize - Subrect size + */ +export function* fullSubrectCoordinates( + subrectOrigin: Required, + subrectSize: Required +): Generator> { + for (let z = subrectOrigin.z; z < subrectOrigin.z + subrectSize.depthOrArrayLayers; ++z) { + for (let y = subrectOrigin.y; y < subrectOrigin.y + subrectSize.height; ++y) { + for (let x = subrectOrigin.x; x < subrectOrigin.x + subrectSize.width; ++x) { + yield { x, y, z }; + } + } + } +} diff --git a/src/webgpu/util/texture/texel_data.ts b/src/webgpu/util/texture/texel_data.ts index fda2207faca6..602b68db353d 100644 --- a/src/webgpu/util/texture/texel_data.ts +++ b/src/webgpu/util/texture/texel_data.ts @@ -898,7 +898,7 @@ export function getSingleDataType(format: UncompressedTextureFormat): ComponentD } /** - * Get traits for generating code to readback data from a component. + * Get traits for generating code to readback data from a component. * @param {ComponentDataType} dataType - The input component data type. * @returns A dictionary containing the respective `ReadbackTypedArray` and `shaderType`. */ diff --git a/src/webgpu/util/texture/texel_view.ts b/src/webgpu/util/texture/texel_view.ts index aa452de1c948..fea23b674e50 100644 --- a/src/webgpu/util/texture/texel_view.ts +++ b/src/webgpu/util/texture/texel_view.ts @@ -1,7 +1,9 @@ import { assert, memcpy } from '../../../common/util/util.js'; import { kTextureFormatInfo, EncodableTextureFormat } from '../../format_info.js'; +import { generatePrettyTable } from '../pretty_diff_tables.js'; import { reifyExtent3D, reifyOrigin3D } from '../unions.js'; +import { fullSubrectCoordinates } from './base.js'; import { kTexelRepresentationInfo, makeClampToRange, PerTexelComponent } from './texel_data.js'; /** Function taking some x,y,z coordinates and returning `Readonly`. */ @@ -157,4 +159,43 @@ export class TexelView { } } } + + /** Returns a pretty table string of the given coordinates and their values. */ + // MAINTENANCE_TODO: Unify some internal helpers with those in texture_ok.ts. + toString(subrectOrigin: Required, subrectSize: Required) { + const info = kTextureFormatInfo[this.format]; + const repr = kTexelRepresentationInfo[this.format]; + + const integerSampleType = info.sampleType === 'uint' || info.sampleType === 'sint'; + const numberToString = integerSampleType + ? (n: number) => n.toFixed() + : (n: number) => n.toPrecision(6); + + const componentOrderStr = repr.componentOrder.join(',') + ':'; + const subrectCoords = [...fullSubrectCoordinates(subrectOrigin, subrectSize)]; + + const printCoords = (function* () { + yield* [' coords', '==', 'X,Y,Z:']; + for (const coords of subrectCoords) yield `${coords.x},${coords.y},${coords.z}`; + })(); + const printActualBytes = (function* (t: TexelView) { + yield* [' act. texel bytes (little-endian)', '==', '0x:']; + for (const coords of subrectCoords) { + yield Array.from(t.bytes(coords), b => b.toString(16).padStart(2, '0')).join(' '); + } + })(this); + const printActualColors = (function* (t: TexelView) { + yield* [' act. colors', '==', componentOrderStr]; + for (const coords of subrectCoords) { + const pixel = t.color(coords); + yield `${repr.componentOrder.map(ch => numberToString(pixel[ch]!)).join(',')}`; + } + })(this); + + const opts = { + fillToWidth: 120, + numberToString, + }; + return `${generatePrettyTable(opts, [printCoords, printActualBytes, printActualColors])}`; + } } diff --git a/src/webgpu/util/texture/texture_ok.ts b/src/webgpu/util/texture/texture_ok.ts index d2fb8d9a2ef2..7b85489246a7 100644 --- a/src/webgpu/util/texture/texture_ok.ts +++ b/src/webgpu/util/texture/texture_ok.ts @@ -5,6 +5,7 @@ import { numbersApproximatelyEqual } from '../conversion.js'; import { generatePrettyTable } from '../pretty_diff_tables.js'; import { reifyExtent3D, reifyOrigin3D } from '../unions.js'; +import { fullSubrectCoordinates } from './base.js'; import { getTextureSubCopyLayout } from './layout.js'; import { kTexelRepresentationInfo, PerTexelComponent, TexelComponent } from './texel_data.js'; import { TexelView } from './texel_view.js'; @@ -187,19 +188,6 @@ function createTextureCopyForMapRead( return { buffer, bytesPerRow, rowsPerImage }; } -function* fullSubrectCoordinates( - subrectOrigin: Required, - subrectSize: Required -): Generator> { - for (let z = subrectOrigin.z; z < subrectOrigin.z + subrectSize.depthOrArrayLayers; ++z) { - for (let y = subrectOrigin.y; y < subrectOrigin.y + subrectSize.height; ++y) { - for (let x = subrectOrigin.x; x < subrectOrigin.x + subrectSize.width; ++x) { - yield { x, y, z }; - } - } - } -} - export function findFailedPixels( format: EncodableTextureFormat, subrectOrigin: Required, From a8f254a08954557a80c6f2ff9422c36fadcac039 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 16 Jun 2023 16:50:06 -0700 Subject: [PATCH 018/166] Allow unused variables starting with underscore --- .eslintrc.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 96d88695f3a1..14c03a55e481 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -52,7 +52,10 @@ "@typescript-eslint/no-this-alias": "warn", "@typescript-eslint/no-unnecessary-type-assertion": "warn", "@typescript-eslint/no-unnecessary-type-constraint": "warn", - "@typescript-eslint/no-unused-vars": ["warn", { "vars": "all", "args": "none" }], + "@typescript-eslint/no-unused-vars": [ + "warn", + { "vars": "all", "args": "none", "varsIgnorePattern": "^_" } + ], "@typescript-eslint/prefer-as-const": "warn", "@typescript-eslint/prefer-for-of": "warn", "@typescript-eslint/prefer-namespace-keyword": "warn", From da1527dba015b610c46ba523312b462e532adca8 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 16 Jun 2023 16:51:45 -0700 Subject: [PATCH 019/166] websocket-logger tool --- src/common/internal/websocket_logger.ts | 52 ++++++++++++++++++++++++ tools/websocket-logger/.gitignore | 1 + tools/websocket-logger/README.md | 9 ++++ tools/websocket-logger/main.js | 25 ++++++++++++ tools/websocket-logger/package-lock.json | 39 ++++++++++++++++++ tools/websocket-logger/package.json | 14 +++++++ 6 files changed, 140 insertions(+) create mode 100644 src/common/internal/websocket_logger.ts create mode 100644 tools/websocket-logger/.gitignore create mode 100644 tools/websocket-logger/README.md create mode 100755 tools/websocket-logger/main.js create mode 100644 tools/websocket-logger/package-lock.json create mode 100644 tools/websocket-logger/package.json diff --git a/src/common/internal/websocket_logger.ts b/src/common/internal/websocket_logger.ts new file mode 100644 index 000000000000..30246df843e4 --- /dev/null +++ b/src/common/internal/websocket_logger.ts @@ -0,0 +1,52 @@ +/** + * - 'uninitialized' means we haven't tried to connect yet + * - Promise means it's pending + * - 'failed' means it failed (this is the most common case, where the logger isn't running) + * - WebSocket means it succeeded + */ +let connection: Promise | WebSocket | 'failed' | 'uninitialized' = + 'uninitialized'; + +/** + * Log a string to a websocket at `localhost:59497`. See `tools/websocket-logger`. + * + * This does nothing if a connection couldn't be established on the first call. + */ +export function logToWebsocket(msg: string) { + if (connection === 'failed') { + return; + } + + if (connection === 'uninitialized') { + connection = new Promise(resolve => { + if (typeof WebSocket === 'undefined') { + resolve('failed'); + return; + } + + const ws = new WebSocket('ws://localhost:59497/optional_cts_websocket_logger'); + ws.onopen = () => { + resolve(ws); + }; + ws.onerror = () => { + connection = 'failed'; + resolve('failed'); + }; + ws.onclose = () => { + connection = 'failed'; + resolve('failed'); + }; + }); + void connection.then(resolved => { + connection = resolved; + }); + } + + void (async () => { + // connection may be a promise or a value here. Either is OK to await. + const ws = await connection; + if (ws !== 'failed') { + ws.send(msg); + } + })(); +} diff --git a/tools/websocket-logger/.gitignore b/tools/websocket-logger/.gitignore new file mode 100644 index 000000000000..1c0f45a79cc2 --- /dev/null +++ b/tools/websocket-logger/.gitignore @@ -0,0 +1 @@ +/wslog-*.txt diff --git a/tools/websocket-logger/README.md b/tools/websocket-logger/README.md new file mode 100644 index 000000000000..ebd4e4f3076c --- /dev/null +++ b/tools/websocket-logger/README.md @@ -0,0 +1,9 @@ +This simple utility receives messages via a WebSocket and writes them out to both the command line +and a file called `wslog-TIMESTAMP.txt` in the working directory. + +It can be used to receive logs from CTS in a way that's resistant to test crashes and totally +independent of which runtime is being used (e.g. standalone, WPT, Node). +It's used in particular to capture timing results for predefining "chunking" of the CTS for WPT. + +To set up, use `npm install`. +To launch, use `npm start`. diff --git a/tools/websocket-logger/main.js b/tools/websocket-logger/main.js new file mode 100755 index 000000000000..4a5a89e7620a --- /dev/null +++ b/tools/websocket-logger/main.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node + +import fs from 'fs/promises'; +import { WebSocketServer } from 'ws'; + +const wss = new WebSocketServer({ port: 59497 }); + +const timestamp = new Date().toISOString().slice(0, 19).replace(/[:]/g, '-') +const filename = `wslog-${timestamp}.txt` +const f = await fs.open(filename, 'w'); +console.log(`Writing to ${filename}`); +console.log('Ctrl-C to stop'); + +process.on('SIGINT', () => { + console.log(`\nWritten to ${filename}`); + process.exit(); +}); + +wss.on('connection', async ws => { + ws.on('message', data => { + const s = data.toString(); + f.write(s + '\n'); + console.log(s); + }); +}); diff --git a/tools/websocket-logger/package-lock.json b/tools/websocket-logger/package-lock.json new file mode 100644 index 000000000000..b43ae34804e4 --- /dev/null +++ b/tools/websocket-logger/package-lock.json @@ -0,0 +1,39 @@ +{ + "name": "websocket-logger", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "websocket-logger", + "version": "0.0.0", + "license": "BSD-3-Clause", + "dependencies": { + "ws": "^8.13.0" + }, + "bin": { + "websocket-logger": "main.js" + } + }, + "node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/tools/websocket-logger/package.json b/tools/websocket-logger/package.json new file mode 100644 index 000000000000..66585968bd5f --- /dev/null +++ b/tools/websocket-logger/package.json @@ -0,0 +1,14 @@ +{ + "name": "websocket-logger", + "version": "0.0.0", + "author": "WebGPU CTS Contributors", + "private": true, + "license": "BSD-3-Clause", + "type": "module", + "scripts": { + "start": "node main.js" + }, + "dependencies": { + "ws": "^8.13.0" + } +} From f42aeb4d43526482a59e2b49533520ebd03dec63 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 16 Jun 2023 17:00:57 -0700 Subject: [PATCH 020/166] Check that if the test is skipped, all subcases are skipped And fix state tracking so that subcases are actually marked skipped in the logger --- src/common/internal/logging/test_case_recorder.ts | 14 ++++++++++++-- src/common/internal/test_group.ts | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/common/internal/logging/test_case_recorder.ts b/src/common/internal/logging/test_case_recorder.ts index 7507bbdec647..f1923d43d425 100644 --- a/src/common/internal/logging/test_case_recorder.ts +++ b/src/common/internal/logging/test_case_recorder.ts @@ -19,7 +19,8 @@ const kMinSeverityForStack = LogSeverity.Warn; /** Holds onto a LiveTestCaseResult owned by the Logger, and writes the results into it. */ export class TestCaseRecorder { - private result: LiveTestCaseResult; + readonly result: LiveTestCaseResult; + public unskippedSubcaseCount: number = 0; private inSubCase: boolean = false; private subCaseStatus = LogSeverity.Pass; private finalCaseStatus = LogSeverity.Pass; @@ -42,12 +43,18 @@ export class TestCaseRecorder { } finish(): void { - assert(this.startTime >= 0, 'finish() before start()'); + // This is a framework error. If this assert is hit, it won't be localized + // to a test. The whole test run will fail out. + assert(this.startTime >= 0, 'internal error: finish() before start()'); const timeMilliseconds = now() - this.startTime; // Round to next microsecond to avoid storing useless .xxxx00000000000002 in results. this.result.timems = Math.ceil(timeMilliseconds * 1000) / 1000; + if (this.finalCaseStatus === LogSeverity.Skip && this.unskippedSubcaseCount !== 0) { + this.threw(new Error('internal error: case is "skip" but has unskipped subcases')); + } + // Convert numeric enum back to string (but expose 'exception' as 'fail') this.result.status = this.finalCaseStatus === LogSeverity.Pass @@ -67,6 +74,9 @@ export class TestCaseRecorder { } endSubCase(expectedStatus: Expectation) { + if (this.subCaseStatus !== LogSeverity.Skip) { + this.unskippedSubcaseCount++; + } try { if (expectedStatus === 'fail') { if (this.subCaseStatus <= LogSeverity.Warn) { diff --git a/src/common/internal/test_group.ts b/src/common/internal/test_group.ts index bf5c67e4abdf..c85b195f5ec9 100644 --- a/src/common/internal/test_group.ts +++ b/src/common/internal/test_group.ts @@ -460,10 +460,10 @@ class RunCaseSpecific implements RunCase { // An error from init or test may have been a SkipTestCase. // An error from finalize may have been an eventualAsyncExpectation failure // or unexpected validation/OOM error from the GPUDevice. + rec.threw(ex); if (throwSkip && ex instanceof SkipTestCase) { throw ex; } - rec.threw(ex); } finally { try { rec.endSubCase(expectedStatus); From 691e6b4606de182dadedf6eacab09b5e9ab2836a Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 8 Sep 2023 00:24:03 -0700 Subject: [PATCH 021/166] Tools for generating timing metadata and auto-chunking WPT --- Gruntfile.js | 7 +- src/common/framework/metadata.ts | 28 +++ src/common/internal/file_loader.ts | 19 +- .../internal/logging/test_case_recorder.ts | 8 +- src/common/internal/test_group.ts | 58 +++++ src/common/internal/tree.ts | 83 ++++++- src/common/tools/checklist.ts | 8 +- src/common/tools/crawl.ts | 71 +++++- src/common/tools/gen_wpt_cts_html.ts | 226 ++++++++++++++---- src/common/tools/merge_listing_times.ts | 177 ++++++++++++++ src/unittests/loaders_and_trees.spec.ts | 4 +- tools/gen_wpt_cfg_chunked2sec.json | 6 + tools/gen_wpt_cfg_unchunked.json | 5 + tools/merge_listing_times | 35 +++ 14 files changed, 654 insertions(+), 81 deletions(-) create mode 100644 src/common/framework/metadata.ts create mode 100644 src/common/tools/merge_listing_times.ts create mode 100644 tools/gen_wpt_cfg_chunked2sec.json create mode 100644 tools/gen_wpt_cfg_unchunked.json create mode 100755 tools/merge_listing_times diff --git a/Gruntfile.js b/Gruntfile.js index 05d70074afc3..a3d42a91ab31 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -28,7 +28,11 @@ module.exports = function (grunt) { }, 'generate-wpt-cts-html': { cmd: 'node', - args: ['tools/gen_wpt_cts_html', 'out-wpt/cts.https.html', 'src/common/templates/cts.https.html'], + args: ['tools/gen_wpt_cts_html', 'tools/gen_wpt_cfg_unchunked.json'], + }, + 'generate-wpt-cts-html-chunked2sec': { + cmd: 'node', + args: ['tools/gen_wpt_cts_html', 'tools/gen_wpt_cfg_chunked2sec.json'], }, 'generate-cache': { cmd: 'node', @@ -181,6 +185,7 @@ module.exports = function (grunt) { 'copy:out-wpt-generated', 'copy:out-wpt-htmlfiles', 'run:generate-wpt-cts-html', + 'run:generate-wpt-cts-html-chunked2sec', ]); grunt.registerTask('build-done-message', () => { process.stderr.write('\nBuild completed! Running checks/tests'); diff --git a/src/common/framework/metadata.ts b/src/common/framework/metadata.ts new file mode 100644 index 000000000000..2c2a1ef79478 --- /dev/null +++ b/src/common/framework/metadata.ts @@ -0,0 +1,28 @@ +import { assert } from '../util/util.js'; + +/** Metadata about tests (that can't be derived at runtime). */ +export type TestMetadata = { + /** + * Estimated average time-per-subcase, in milliseconds. + * This is used to determine chunking granularity when exporting to WPT with + * chunking enabled (like out-wpt/cts-chunked2sec.https.html). + */ + subcaseMS: number; +}; + +export type TestMetadataListing = { + [testQuery: string]: TestMetadata; +}; + +export function loadMetadataForSuite(suiteDir: string): TestMetadataListing | null { + assert(typeof require !== 'undefined', 'loadMetadataForSuite is only implemented on Node'); + const fs = require('fs'); + + const metadataFile = `${suiteDir}/listing_meta.json`; + if (!fs.existsSync(metadataFile)) { + return null; + } + + const metadata: TestMetadataListing = JSON.parse(fs.readFileSync(metadataFile, 'utf8')); + return metadata; +} diff --git a/src/common/internal/file_loader.ts b/src/common/internal/file_loader.ts index 3b6afef7ac78..dddedf768830 100644 --- a/src/common/internal/file_loader.ts +++ b/src/common/internal/file_loader.ts @@ -69,16 +69,21 @@ export abstract class TestFileLoader extends EventTarget { return ret; } - async loadTree(query: TestQuery, subqueriesToExpand: string[] = []): Promise { - const tree = await loadTreeForQuery( - this, - query, - subqueriesToExpand.map(s => { + async loadTree( + query: TestQuery, + { + subqueriesToExpand = [], + maxChunkTime = Infinity, + }: { subqueriesToExpand?: string[]; maxChunkTime?: number } = {} + ): Promise { + const tree = await loadTreeForQuery(this, query, { + subqueriesToExpand: subqueriesToExpand.map(s => { const q = parseQuery(s); assert(q.level >= 2, () => `subqueriesToExpand entries should not be multi-file:\n ${q}`); return q; - }) - ); + }), + maxChunkTime, + }); this.dispatchEvent(new MessageEvent('finish')); return tree; } diff --git a/src/common/internal/logging/test_case_recorder.ts b/src/common/internal/logging/test_case_recorder.ts index f1923d43d425..ca37ba4b0287 100644 --- a/src/common/internal/logging/test_case_recorder.ts +++ b/src/common/internal/logging/test_case_recorder.ts @@ -20,7 +20,7 @@ const kMinSeverityForStack = LogSeverity.Warn; /** Holds onto a LiveTestCaseResult owned by the Logger, and writes the results into it. */ export class TestCaseRecorder { readonly result: LiveTestCaseResult; - public unskippedSubcaseCount: number = 0; + public nonskippedSubcaseCount: number = 0; private inSubCase: boolean = false; private subCaseStatus = LogSeverity.Pass; private finalCaseStatus = LogSeverity.Pass; @@ -51,8 +51,8 @@ export class TestCaseRecorder { // Round to next microsecond to avoid storing useless .xxxx00000000000002 in results. this.result.timems = Math.ceil(timeMilliseconds * 1000) / 1000; - if (this.finalCaseStatus === LogSeverity.Skip && this.unskippedSubcaseCount !== 0) { - this.threw(new Error('internal error: case is "skip" but has unskipped subcases')); + if (this.finalCaseStatus === LogSeverity.Skip && this.nonskippedSubcaseCount !== 0) { + this.threw(new Error('internal error: case is "skip" but has nonskipped subcases')); } // Convert numeric enum back to string (but expose 'exception' as 'fail') @@ -75,7 +75,7 @@ export class TestCaseRecorder { endSubCase(expectedStatus: Expectation) { if (this.subCaseStatus !== LogSeverity.Skip) { - this.unskippedSubcaseCount++; + this.nonskippedSubcaseCount++; } try { if (expectedStatus === 'fail') { diff --git a/src/common/internal/test_group.ts b/src/common/internal/test_group.ts index c85b195f5ec9..e446628bc0b0 100644 --- a/src/common/internal/test_group.ts +++ b/src/common/internal/test_group.ts @@ -28,6 +28,8 @@ import { import { validQueryPart } from '../internal/query/validQueryPart.js'; import { assert, unreachable } from '../util/util.js'; +import { logToWebsocket } from './websocket_logger.js'; + export type RunFn = ( rec: TestCaseRecorder, expectations?: TestQueryWithExpectation[] @@ -41,6 +43,7 @@ export interface TestCaseID { export interface RunCase { readonly id: TestCaseID; readonly isUnimplemented: boolean; + computeSubcaseCount(): number; run( rec: TestCaseRecorder, selfQuery: TestQuerySingleCase, @@ -60,6 +63,8 @@ export function makeTestGroup(fixture: FixtureClass): Test export interface IterableTestGroup { iterate(): Iterable; validate(): void; + /** Returns the file-relative test paths of tests which have >0 cases. */ + collectNonEmptyTests(): { testPath: string[] }[]; } export interface IterableTest { testPath: string[]; @@ -127,6 +132,16 @@ export class TestGroup implements TestGroupBuilder { test.validate(); } } + + collectNonEmptyTests(): { testPath: string[] }[] { + const testPaths = []; + for (const test of this.tests) { + if (test.computeCaseCount() > 0) { + testPaths.push({ testPath: test.testPath }); + } + } + return testPaths; + } } interface TestBuilderWithName extends TestBuilderWithParams { @@ -268,6 +283,7 @@ class TestBuilder { }; } + /** Perform various validation/"lint" chenks. */ validate(): void { const testPathString = this.testPath.join(kPathSeparator); assert(this.testFn !== undefined, () => { @@ -307,6 +323,18 @@ class TestBuilder { } } + computeCaseCount(): number { + if (this.testCases === undefined) { + return 1; + } + + let caseCount = 0; + for (const [_caseParams, _subcases] of builderIterateCasesWithSubcases(this.testCases, null)) { + caseCount++; + } + return caseCount; + } + params( cases: ((unit: CaseParamsBuilder<{}>) => ParamsBuilderBase<{}, {}>) | ParamsBuilderBase<{}, {}> ): TestBuilder { @@ -434,6 +462,18 @@ class RunCaseSpecific implements RunCase { this.testCreationStack = testCreationStack; } + computeSubcaseCount(): number { + if (this.subcases) { + let count = 0; + for (const _subcase of this.subcases) { + count++; + } + return count; + } else { + return 1; + } + } + async runTest( rec: TestCaseRecorder, sharedState: SubcaseBatchState, @@ -656,6 +696,24 @@ class RunCaseSpecific implements RunCase { rec.threw(ex); } finally { rec.finish(); + + const msg: CaseTimingLogLine = { + q: selfQuery.toString(), + timems: rec.result.timems, + nonskippedSubcaseCount: rec.nonskippedSubcaseCount, + }; + logToWebsocket(JSON.stringify(msg)); } } } + +export type CaseTimingLogLine = { + q: string; + /** Total time it took to execute the case. */ + timems: number; + /** + * Number of subcases that ran in the case (excluding skipped subcases, so + * they don't dilute the average per-subcase time. + */ + nonskippedSubcaseCount: number; +}; diff --git a/src/common/internal/tree.ts b/src/common/internal/tree.ts index 6cdce2d39a60..594837059ca7 100644 --- a/src/common/internal/tree.ts +++ b/src/common/internal/tree.ts @@ -1,3 +1,4 @@ +import { loadMetadataForSuite, TestMetadataListing } from '../framework/metadata.js'; import { globalTestConfig } from '../framework/test_config.js'; import { RunCase, RunFn } from '../internal/test_group.js'; import { assert, now } from '../util/util.js'; @@ -48,12 +49,13 @@ interface TestTreeNodeBase { * one (e.g. s:f:* relative to s:f,*), but something that is readable. */ readonly readableRelativeName: string; - subtreeCounts?: { tests: number; nodesWithTODO: number }; + subtreeCounts?: { tests: number; nodesWithTODO: number; totalTimeMS: number }; + subcaseCount?: number; } export interface TestSubtree extends TestTreeNodeBase { readonly children: Map; - readonly collapsible: boolean; + collapsible: boolean; description?: string; readonly testCreationStack?: Error; } @@ -62,6 +64,7 @@ export interface TestTreeLeaf extends TestTreeNodeBase { readonly run: RunFn; readonly isUnimplemented?: boolean; subtreeCounts?: undefined; + subcaseCount: number; } export type TestTreeNode = TestSubtree | TestTreeLeaf; @@ -89,9 +92,8 @@ export class TestTree { readonly forQuery: TestQuery; readonly root: TestSubtree; - constructor(forQuery: TestQuery, root: TestSubtree) { + private constructor(forQuery: TestQuery, root: TestSubtree) { this.forQuery = forQuery; - TestTree.propagateCounts(root); this.root = root; assert( root.query.level === 1 && root.query.depthInLevel === 0, @@ -99,6 +101,24 @@ export class TestTree { ); } + static async create( + forQuery: TestQuery, + root: TestSubtree, + maxChunkTime: number + ): Promise { + const suite = forQuery.suite; + + let chunking = undefined; + if (Number.isFinite(maxChunkTime)) { + const metadata = loadMetadataForSuite(`./src/${suite}`); + assert(metadata !== null, `metadata for ${suite} is missing, but maxChunkTime was requested`); + chunking = { metadata, maxChunkTime }; + } + await TestTree.propagateCounts(root, chunking); + + return new TestTree(forQuery, root); + } + /** * Iterate through the leaves of a version of the tree which has been pruned to exclude * subtrees which: @@ -185,16 +205,51 @@ export class TestTree { } /** Propagate the subtreeTODOs/subtreeTests state upward from leaves to parent nodes. */ - static propagateCounts(subtree: TestSubtree): { tests: number; nodesWithTODO: number } { - subtree.subtreeCounts ??= { tests: 0, nodesWithTODO: 0 }; + static async propagateCounts( + subtree: TestSubtree, + chunking: { metadata: TestMetadataListing; maxChunkTime: number } | undefined + ): Promise<{ tests: number; nodesWithTODO: number; totalTimeMS: number; subcaseCount: number }> { + subtree.subtreeCounts ??= { tests: 0, nodesWithTODO: 0, totalTimeMS: 0 }; + subtree.subcaseCount = 0; for (const [, child] of subtree.children) { if ('children' in child) { - const counts = TestTree.propagateCounts(child); + const counts = await TestTree.propagateCounts(child, chunking); subtree.subtreeCounts.tests += counts.tests; subtree.subtreeCounts.nodesWithTODO += counts.nodesWithTODO; + subtree.subtreeCounts.totalTimeMS += counts.totalTimeMS; + subtree.subcaseCount += counts.subcaseCount; + } else { + subtree.subcaseCount = child.subcaseCount; } } - return subtree.subtreeCounts; + + // If we're chunking based on a maxChunkTime, then at each + // TestQueryMultiCase node of the tree we look at its total time. If the + // total time is larger than the maxChunkTime, we set collapsible=false to + // make sure it gets split up in the output. Note: + // - TestQueryMultiTest and higher nodes are never set to collapsible anyway, so we ignore them. + // - TestQuerySingleCase nodes can't be collapsed, so we ignore them. + if (chunking && subtree.query instanceof TestQueryMultiCase) { + const testLevelQuery = new TestQueryMultiCase( + subtree.query.suite, + subtree.query.filePathParts, + subtree.query.testPathParts, + {} + ).toString(); + + const metadata = chunking.metadata; + + const subcaseTiming: number | undefined = metadata[testLevelQuery]?.subcaseMS; + if (subcaseTiming !== undefined) { + const totalTiming = subcaseTiming * subtree.subcaseCount; + subtree.subtreeCounts.totalTimeMS = totalTiming; + if (totalTiming > chunking.maxChunkTime) { + subtree.collapsible = false; + } + } + } + + return { ...subtree.subtreeCounts, subcaseCount: subtree.subcaseCount ?? 0 }; } /** Displays counts in the format `(Nodes with TODOs) / (Total test count)`. */ @@ -229,7 +284,10 @@ export class TestTree { export async function loadTreeForQuery( loader: TestFileLoader, queryToLoad: TestQuery, - subqueriesToExpand: TestQuery[] + { + subqueriesToExpand, + maxChunkTime = Infinity, + }: { subqueriesToExpand: TestQuery[]; maxChunkTime?: number } ): Promise { const suite = queryToLoad.suite; const specs = await loader.listing(suite); @@ -347,7 +405,7 @@ export async function loadTreeForQuery( isCollapsible ); // This is 1 test. Set tests=1 then count TODOs. - subtreeL2.subtreeCounts ??= { tests: 1, nodesWithTODO: 0 }; + subtreeL2.subtreeCounts ??= { tests: 1, nodesWithTODO: 0, totalTimeMS: 0 }; if (t.description) setSubtreeDescriptionAndCountTODOs(subtreeL2, t.description); let caseFilter = null; @@ -391,7 +449,7 @@ export async function loadTreeForQuery( } assert(foundCase, `Query \`${queryToLoad.toString()}\` does not match any cases`); - return new TestTree(queryToLoad, subtreeL0); + return TestTree.create(queryToLoad, subtreeL0, maxChunkTime); } function setSubtreeDescriptionAndCountTODOs( @@ -400,7 +458,7 @@ function setSubtreeDescriptionAndCountTODOs( ) { assert(subtree.description === undefined); subtree.description = description.trim(); - subtree.subtreeCounts ??= { tests: 0, nodesWithTODO: 0 }; + subtree.subtreeCounts ??= { tests: 0, nodesWithTODO: 0, totalTimeMS: 0 }; if (subtree.description.indexOf('TODO') !== -1) { subtree.subtreeCounts.nodesWithTODO++; } @@ -569,6 +627,7 @@ function insertLeaf(parent: TestSubtree, query: TestQuerySingleCase, t: RunCase) query, run: (rec, expectations) => t.run(rec, query, expectations || []), isUnimplemented: t.isUnimplemented, + subcaseCount: t.computeSubcaseCount(), }; // This is a leaf (e.g. s:f:t:x=1;* -> s:f:t:x=1). The key is always ''. diff --git a/src/common/tools/checklist.ts b/src/common/tools/checklist.ts index 393990e26f99..e301cfb2c82b 100644 --- a/src/common/tools/checklist.ts +++ b/src/common/tools/checklist.ts @@ -122,11 +122,9 @@ function checkForUnmatchedSubtreesAndDoneness( checkForOverlappingQueries(queriesInSuite); const suiteQuery = new TestQueryMultiFile(suite, []); console.log(` Loading tree ${suiteQuery}...`); - const tree = await loadTreeForQuery( - loader, - suiteQuery, - queriesInSuite.map(q => q.query) - ); + const tree = await loadTreeForQuery(loader, suiteQuery, { + subqueriesToExpand: queriesInSuite.map(q => q.query), + }); console.log(' Found no invalid queries in the checklist. Checking for unmatched tests...'); const subtreeCount = checkForUnmatchedSubtreesAndDoneness(tree, queriesInSuite); console.log(` No unmatched tests or done/todo mismatches among ${subtreeCount} subtrees!`); diff --git a/src/common/tools/crawl.ts b/src/common/tools/crawl.ts index 0381e0835151..eadabe4f77f2 100644 --- a/src/common/tools/crawl.ts +++ b/src/common/tools/crawl.ts @@ -5,7 +5,9 @@ import * as fs from 'fs'; import * as path from 'path'; +import { loadMetadataForSuite } from '../framework/metadata.js'; import { SpecFile } from '../internal/file_loader.js'; +import { TestQueryMultiCase } from '../internal/query/query.js'; import { validQueryPart } from '../internal/query/validQueryPart.js'; import { TestSuiteListingEntry, TestSuiteListing } from '../internal/test_suite_listing.js'; import { assert, unreachable } from '../util/util.js'; @@ -48,6 +50,17 @@ export async function crawl(suiteDir: string, validate: boolean): Promise(), + }; + } + } + // Crawl files and convert paths to be POSIX-style, relative to suiteDir. const filesToEnumerate = (await crawlFilesRecursively(suiteDir)) .map(f => path.relative(suiteDir, f).replace(/\\/g, '/')) @@ -58,6 +71,7 @@ export async function crawl(suiteDir: string, validate: boolean): Promise { - let argsPrefixes = ['?q=']; - let expectationLines = new Set(); + // Load the config + switch (process.argv.length) { + case 3: { + const configFile = process.argv[2]; + const configJSON: ConfigJSON = JSON.parse(await fs.readFile(configFile, 'utf8')); + const jsonFileDir = path.dirname(configFile); + + config = { + suite: configJSON.suite, + out: path.resolve(jsonFileDir, configJSON.out), + template: path.resolve(jsonFileDir, configJSON.template), + maxChunkTimeMS: configJSON.maxChunkTimeMS ?? Infinity, + argumentsPrefixes: configJSON.argumentsPrefixes ?? ['?q='], + }; + if (configJSON.expectations) { + config.expectations = { + file: path.resolve(jsonFileDir, configJSON.expectations.file), + prefix: configJSON.expectations.prefix, + }; + } + break; + } + case 4: + case 7: + case 8: { + const [ + _nodeBinary, + _thisScript, + outFile, + templateFile, + argsPrefixesFile, + expectationsFile, + expectationsPrefix, + suite = 'webgpu', + ] = process.argv; + + config = { + out: outFile, + template: templateFile, + suite, + maxChunkTimeMS: Infinity, + argumentsPrefixes: ['?q='], + }; + if (process.argv.length >= 7) { + config.argumentsPrefixes = (await fs.readFile(argsPrefixesFile, 'utf8')) + .split(/\r?\n/) + .filter(a => a.length); + config.expectations = { + file: expectationsFile, + prefix: expectationsPrefix, + }; + } + break; + } + default: + console.error('incorrect number of arguments!'); + printUsageAndExit(1); + } + + const useChunking = Number.isFinite(config.maxChunkTimeMS); - if (process.argv.length >= 7) { - // Prefixes sorted from longest to shortest - const argsPrefixesFromFile = (await fs.readFile(argsPrefixesFile, 'utf8')) - .split(/\r?\n/) - .filter(a => a.length) - .sort((a, b) => b.length - a.length); - if (argsPrefixesFromFile.length) argsPrefixes = argsPrefixesFromFile; + // Sort prefixes from longest to shortest + config.argumentsPrefixes.sort((a, b) => b.length - a.length); + + // Load expectations (if any) + let expectationLines = new Set(); + if (config.expectations) { expectationLines = new Set( - (await fs.readFile(expectationsFile, 'utf8')).split(/\r?\n/).filter(l => l.length) + (await fs.readFile(config.expectations.file, 'utf8')).split(/\r?\n/).filter(l => l.length) ); } const expectations: Map = new Map(); - for (const prefix of argsPrefixes) { + for (const prefix of config.argumentsPrefixes) { expectations.set(prefix, []); } expLoop: for (const exp of expectationLines) { // Take each expectation for the longest prefix it matches. - for (const argsPrefix of argsPrefixes) { - const prefix = expectationsPrefix + argsPrefix; + for (const argsPrefix of config.argumentsPrefixes) { + const prefix = config.expectations!.prefix + argsPrefix; if (exp.startsWith(prefix)) { expectations.get(argsPrefix)!.push(exp.substring(prefix.length)); continue expLoop; @@ -79,26 +178,53 @@ const [ } const loader = new DefaultTestFileLoader(); - const lines: Array = []; - for (const prefix of argsPrefixes) { - const rootQuery = new TestQueryMultiFile(suite, []); - const tree = await loader.loadTree(rootQuery, expectations.get(prefix)); + const lines = []; + for (const prefix of config.argumentsPrefixes) { + const rootQuery = new TestQueryMultiFile(config.suite, []); + const tree = await loader.loadTree(rootQuery, { + subqueriesToExpand: expectations.get(prefix), + maxChunkTime: config.maxChunkTimeMS, + }); lines.push(undefined); // output blank line between prefixes + const prefixComment = { comment: `Prefix: "${prefix}"` }; // contents will be updated later + if (useChunking) lines.push(prefixComment); + + const filesSeen = new Set(); + const testsSeen = new Set(); + let variantCount = 0; + const alwaysExpandThroughLevel = 2; // expand to, at minimum, every test. - for (const { query } of tree.iterateCollapsedNodes({ alwaysExpandThroughLevel })) { - const urlQueryString = prefix + query.toString(); // "?worker=0&q=..." + for (const { query, subtreeCounts } of tree.iterateCollapsedNodes({ + alwaysExpandThroughLevel, + })) { + assert(query instanceof TestQueryMultiCase); + const queryString = query.toString(); // Check for a safe-ish path length limit. Filename must be <= 255, and on Windows the whole // path must be <= 259. Leave room for e.g.: // 'c:\b\s\w\xxxxxxxx\layout-test-results\external\wpt\webgpu\cts_worker=0_q=...-actual.txt' assert( - urlQueryString.length < 185, - 'Generated test variant would produce too-long -actual.txt filename. \ -Try broadening suppressions to avoid long test variant names. ' + - urlQueryString + queryString.length < 185, + `Generated test variant would produce too-long -actual.txt filename. Possible solutions: +- Reduce the length of the parts of the test query +- Reduce the parameterization of the test +- Make the test function faster and regenerate the listing_meta entry +- Reduce the specificity of test expectations (if you're using them) +${queryString}` + ); + + lines.push({ + urlQueryString: prefix + query.toString(), // "?worker=0&q=..." + comment: useChunking ? `estimated: ${subtreeCounts?.totalTimeMS.toFixed(3)} ms` : undefined, + }); + + variantCount++; + filesSeen.add(new TestQueryMultiTest(query.suite, query.filePathParts, []).toString()); + testsSeen.add( + new TestQueryMultiCase(query.suite, query.filePathParts, query.testPathParts, {}).toString() ); - lines.push(urlQueryString); } + prefixComment.comment += `; ${variantCount} variants generated from ${testsSeen.size} tests in ${filesSeen.size} files`; } await generateFile(lines); })().catch(ex => { @@ -106,19 +232,21 @@ Try broadening suppressions to avoid long test variant names. ' + process.exit(1); }); -async function generateFile(lines: Array): Promise { +async function generateFile( + lines: Array<{ urlQueryString?: string; comment?: string } | undefined> +): Promise { let result = ''; result += '\n'; - result += await fs.readFile(templateFile, 'utf8'); + result += await fs.readFile(config.template, 'utf8'); for (const line of lines) { - if (line === undefined) { - result += '\n'; - } else { - result += `\n`; + if (line !== undefined) { + if (line.urlQueryString) result += ``; + if (line.comment) result += ``; } + result += '\n'; } - await fs.writeFile(outFile, result); + await fs.writeFile(config.out, result); } diff --git a/src/common/tools/merge_listing_times.ts b/src/common/tools/merge_listing_times.ts new file mode 100644 index 000000000000..0a32b3c520be --- /dev/null +++ b/src/common/tools/merge_listing_times.ts @@ -0,0 +1,177 @@ +import * as fs from 'fs'; +import * as process from 'process'; +import * as readline from 'readline'; + +import { TestMetadataListing } from '../framework/metadata.js'; +import { parseQuery } from '../internal/query/parseQuery.js'; +import { TestQueryMultiCase, TestQuerySingleCase } from '../internal/query/query.js'; +import { CaseTimingLogLine } from '../internal/test_group.js'; +import { assert } from '../util/util.js'; + +// For information on listing_meta.json file maintenance, please read +// tools/merge_listing_times first. + +function usage(rc: number): never { + console.error(`Usage: tools/merge_listing_times [options] SUITES... -- [TIMING_LOG_FILES...] + +Options: + --help Print this message and exit. + +Reads raw case timing data for each suite in SUITES, from all TIMING_LOG_FILES +(see below), and merges it into the src/*/listing_meta.json files checked into +the repository. The timing data in the listing_meta.json files is updated with +the newly-observed timing data *if the new timing is slower*. That is, it will +only increase the values in the listing_meta.json file, and will only cause WPT +chunks to become smaller. + +If there are no TIMING_LOG_FILES, this just regenerates (reformats) the file +using the data already present. + +In more detail: + +- Reads per-case timing data in any of the SUITES, from all TIMING_LOG_FILES + (ignoring skipped cases), and averages it over the number of subcases. + In the case of cases that have run multiple times, takes the max of each. +- Compiles the average time-per-subcase for each test seen. +- For each suite seen, loads its listing_meta.json, takes the max of the old and + new data, and writes it back out. + +How to generate TIMING_LOG_FILES files: + +- Launch the 'websocket-logger' tool (see its README.md), which listens for + log messages on localhost:59497. +- Run the tests you want to capture data for, on the same system. Since + logging is done through the websocket side-channel, you can run the tests + under any runtime (standalone, WPT, etc.) as long as WebSocket support is + available (always true in browsers). +- Run \`tools/merge_listing_times webgpu -- tools/websocket-logger/wslog-*.txt\` +`); + process.exit(rc); +} + +const kHeader = `{ + "_comment": "SEMI AUTO-GENERATED: Please read tools/merge_listing_times.", +`; +const kFooter = `\ + "_end": "" +} +`; + +const argv = process.argv; +if (argv.some(v => v.startsWith('-') && v !== '--') || argv.every(v => v !== '--')) { + usage(0); +} +const suites = []; +const timingLogFilenames = []; +let seenDashDash = false; +for (const arg of argv.slice(2)) { + if (arg === '--') { + seenDashDash = true; + continue; + } else if (arg.startsWith('-')) { + usage(0); + } + + if (seenDashDash) { + timingLogFilenames.push(arg); + } else { + suites.push(arg); + } +} +if (!seenDashDash) { + usage(0); +} + +void (async () => { + // Read the log files to find the log line for each *case* query. If a case + // ran multiple times, take the one with the largest average subcase time. + const caseTimes = new Map(); + for (const timingLogFilename of timingLogFilenames) { + const rl = readline.createInterface({ + input: fs.createReadStream(timingLogFilename), + crlfDelay: Infinity, + }); + + for await (const line of rl) { + const parsed: CaseTimingLogLine = JSON.parse(line); + + const prev = caseTimes.get(parsed.q); + if (prev !== undefined) { + const timePerSubcase = parsed.timems / Math.max(1, parsed.nonskippedSubcaseCount); + const prevTimePerSubcase = prev.timems / Math.max(1, prev.nonskippedSubcaseCount); + + if (timePerSubcase > prevTimePerSubcase) { + caseTimes.set(parsed.q, parsed); + } + } else { + caseTimes.set(parsed.q, parsed); + } + } + } + + // Accumulate total times per test. Map of suite -> query -> {totalTimeMS, caseCount}. + const testTimes = new Map>(); + for (const suite of suites) { + testTimes.set(suite, new Map()); + } + for (const [caseQString, caseTime] of caseTimes) { + const caseQ = parseQuery(caseQString); + assert(caseQ instanceof TestQuerySingleCase); + const suite = caseQ.suite; + const suiteTestTimes = testTimes.get(suite); + if (suiteTestTimes === undefined) { + continue; + } + + const testQ = new TestQueryMultiCase(suite, caseQ.filePathParts, caseQ.testPathParts, {}); + const testQString = testQ.toString(); + + const prev = suiteTestTimes.get(testQString); + if (prev !== undefined) { + prev.totalTimeMS += caseTime.timems; + prev.subcaseCount += caseTime.nonskippedSubcaseCount; + } else { + suiteTestTimes.set(testQString, { + totalTimeMS: caseTime.timems, + subcaseCount: caseTime.nonskippedSubcaseCount, + }); + } + } + + for (const suite of suites) { + const currentMetadata: TestMetadataListing = JSON.parse( + fs.readFileSync(`./src/${suite}/listing_meta.json`, 'utf8') + ); + + const metadata = { ...currentMetadata }; + for (const [testQString, { totalTimeMS, subcaseCount }] of testTimes.get(suite)!) { + const avgTime = totalTimeMS / Math.max(1, subcaseCount); + if (testQString in metadata) { + metadata[testQString].subcaseMS = Math.max(metadata[testQString].subcaseMS, avgTime); + } else { + metadata[testQString] = { subcaseMS: avgTime }; + } + } + + writeListings(suite, metadata); + } +})(); + +function writeListings(suite: string, metadata: TestMetadataListing) { + const output = fs.createWriteStream(`./src/${suite}/listing_meta.json`); + try { + output.write(kHeader); + const keys = Object.keys(metadata).sort(); + for (const k of keys) { + if (k.startsWith('_')) { + // Ignore json "_comments". + continue; + } + assert(k.indexOf('"') === -1); + output.write(` "${k}": { "subcaseMS": ${metadata[k].subcaseMS.toFixed(3)} },\n`); + } + output.write(kFooter); + } finally { + output.close(); + } +} diff --git a/src/unittests/loaders_and_trees.spec.ts b/src/unittests/loaders_and_trees.spec.ts index 080fa762a5fd..c7ff1fa43a84 100644 --- a/src/unittests/loaders_and_trees.spec.ts +++ b/src/unittests/loaders_and_trees.spec.ts @@ -699,7 +699,9 @@ async function testIterateCollapsed( includeEmptySubtrees = false ) { t.debug(`expandThrough=${alwaysExpandThroughLevel} expectations=${expectations}`); - const treePromise = t.loader.loadTree(new TestQueryMultiFile('suite1', []), expectations); + const treePromise = t.loader.loadTree(new TestQueryMultiFile('suite1', []), { + subqueriesToExpand: expectations, + }); if (expectedResult === 'throws') { t.shouldReject('Error', treePromise, 'loadTree should have thrown Error'); return; diff --git a/tools/gen_wpt_cfg_chunked2sec.json b/tools/gen_wpt_cfg_chunked2sec.json new file mode 100644 index 000000000000..1d13e85c5846 --- /dev/null +++ b/tools/gen_wpt_cfg_chunked2sec.json @@ -0,0 +1,6 @@ +{ + "suite": "webgpu", + "out": "../out-wpt/cts-chunked2sec.https.html", + "template": "../src/common/templates/cts.https.html", + "maxChunkTimeMS": 2000 +} diff --git a/tools/gen_wpt_cfg_unchunked.json b/tools/gen_wpt_cfg_unchunked.json new file mode 100644 index 000000000000..ffe06d5633c5 --- /dev/null +++ b/tools/gen_wpt_cfg_unchunked.json @@ -0,0 +1,5 @@ +{ + "suite": "webgpu", + "out": "../out-wpt/cts.https.html", + "template": "../src/common/templates/cts.https.html" +} diff --git a/tools/merge_listing_times b/tools/merge_listing_times new file mode 100755 index 000000000000..4d33fe69b383 --- /dev/null +++ b/tools/merge_listing_times @@ -0,0 +1,35 @@ +#!/usr/bin/env node + +require('../src/common/tools/setup-ts-in-node.js'); + +// See help message in this file for info on how to use the tool. +require('../src/common/tools/merge_listing_times.ts'); + +// ## listing_meta.json File Maintenance ## +// +// listing_meta.json files are SEMI AUTO-GENERATED. +// +// The raw data may be edited manually, to add entries or change timing values. +// This is a complete listing of tests in the CTS, which can be used for other +// scripting purposes too. Presubmit checks will fail when it gets out of sync. +// +// The subcaseMS values are estimates. They can be set to 0 if for some reason +// you can't estimate the time (or there's an existing test with a long name and +// slow subcases that would result in query strings that are too long). +// +// If you're developing new tests and need to update this file, it may be +// easiest to do so manually. Run your tests in your development environment and +// see how long they take. Record the average time per *subcase* into the +// listing_meta.json file. +// +// Timing data can also be captured in bulk and "merged" into this file using +// the 'merge_listing_times' tool. This is useful when a large number of tests +// change or otherwise a lot of tests need to be updated. It can also be used +// without any inputs to reformat the listing_meta.json file. Please read the +// documentation of the tool (see above) for more information. +// +// Finally, note this data is typically captured by developers using higher-end +// computers, so typical test machines might execute more slowly. For this +// reason, the WPT chunking should be configured to generate chunks much shorter +// than 5 seconds (a typical default time limit in WPT test executors) so they +// should still execute in under 5 seconds on lower-end computers. From 76e56df43e92187f9961679a35b254dc65432235 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Fri, 8 Sep 2023 00:24:15 -0700 Subject: [PATCH 022/166] Add generated metadata for webgpu:* --- src/webgpu/listing_meta.json | 1902 ++++++++++++++++++++++++++++++++++ 1 file changed, 1902 insertions(+) create mode 100644 src/webgpu/listing_meta.json diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json new file mode 100644 index 000000000000..ae925f390019 --- /dev/null +++ b/src/webgpu/listing_meta.json @@ -0,0 +1,1902 @@ +{ + "_comment": "SEMI AUTO-GENERATED: Please read tools/merge_listing_times.", + "webgpu:api,operation,adapter,requestAdapter:requestAdapter:*": { "subcaseMS": 152.083 }, + "webgpu:api,operation,adapter,requestAdapter:requestAdapter_no_parameters:*": { "subcaseMS": 384.601 }, + "webgpu:api,operation,adapter,requestAdapterInfo:adapter_info:*": { "subcaseMS": 136.601 }, + "webgpu:api,operation,adapter,requestAdapterInfo:adapter_info_with_hints:*": { "subcaseMS": 0.101 }, + "webgpu:api,operation,adapter,requestDevice:default:*": { "subcaseMS": 19.450 }, + "webgpu:api,operation,adapter,requestDevice:features,known:*": { "subcaseMS": 9.637 }, + "webgpu:api,operation,adapter,requestDevice:features,unknown:*": { "subcaseMS": 13.600 }, + "webgpu:api,operation,adapter,requestDevice:invalid:*": { "subcaseMS": 27.801 }, + "webgpu:api,operation,adapter,requestDevice:limit,better_than_supported:*": { "subcaseMS": 3.614 }, + "webgpu:api,operation,adapter,requestDevice:limit,worse_than_default:*": { "subcaseMS": 6.711 }, + "webgpu:api,operation,adapter,requestDevice:limits,supported:*": { "subcaseMS": 4.579 }, + "webgpu:api,operation,adapter,requestDevice:limits,unknown:*": { "subcaseMS": 0.601 }, + "webgpu:api,operation,adapter,requestDevice:stale:*": { "subcaseMS": 3.590 }, + "webgpu:api,operation,buffers,map:mapAsync,mapState:*": { "subcaseMS": 6.178 }, + "webgpu:api,operation,buffers,map:mapAsync,read,typedArrayAccess:*": { "subcaseMS": 10.759 }, + "webgpu:api,operation,buffers,map:mapAsync,read:*": { "subcaseMS": 8.996 }, + "webgpu:api,operation,buffers,map:mapAsync,write,unchanged_ranges_preserved:*": { "subcaseMS": 13.050 }, + "webgpu:api,operation,buffers,map:mapAsync,write:*": { "subcaseMS": 3.944 }, + "webgpu:api,operation,buffers,map:mappedAtCreation,mapState:*": { "subcaseMS": 4.626 }, + "webgpu:api,operation,buffers,map:mappedAtCreation:*": { "subcaseMS": 1.039 }, + "webgpu:api,operation,buffers,map:remapped_for_write:*": { "subcaseMS": 0.930 }, + "webgpu:api,operation,buffers,map_ArrayBuffer:postMessage:*": { "subcaseMS": 64.775 }, + "webgpu:api,operation,buffers,map_detach:while_mapped:*": { "subcaseMS": 1.386 }, + "webgpu:api,operation,buffers,map_oom:mappedAtCreation:*": { "subcaseMS": 0.827 }, + "webgpu:api,operation,buffers,threading:destroyed:*": { "subcaseMS": 0.700 }, + "webgpu:api,operation,buffers,threading:serialize:*": { "subcaseMS": 0.900 }, + "webgpu:api,operation,command_buffer,basic:b2t2b:*": { "subcaseMS": 16.801 }, + "webgpu:api,operation,command_buffer,basic:b2t2t2b:*": { "subcaseMS": 16.101 }, + "webgpu:api,operation,command_buffer,basic:empty:*": { "subcaseMS": 14.000 }, + "webgpu:api,operation,command_buffer,clearBuffer:clear:*": { "subcaseMS": 0.538 }, + "webgpu:api,operation,command_buffer,copyBufferToBuffer:copy_order:*": { "subcaseMS": 13.401 }, + "webgpu:api,operation,command_buffer,copyBufferToBuffer:single:*": { "subcaseMS": 0.195 }, + "webgpu:api,operation,command_buffer,copyBufferToBuffer:state_transitions:*": { "subcaseMS": 19.600 }, + "webgpu:api,operation,command_buffer,copyTextureToTexture:color_textures,compressed,array:*": { "subcaseMS": 0.382 }, + "webgpu:api,operation,command_buffer,copyTextureToTexture:color_textures,compressed,non_array:*": { "subcaseMS": 0.281 }, + "webgpu:api,operation,command_buffer,copyTextureToTexture:color_textures,non_compressed,array:*": { "subcaseMS": 1.607 }, + "webgpu:api,operation,command_buffer,copyTextureToTexture:color_textures,non_compressed,non_array:*": { "subcaseMS": 0.477 }, + "webgpu:api,operation,command_buffer,copyTextureToTexture:copy_depth_stencil:*": { "subcaseMS": 0.983 }, + "webgpu:api,operation,command_buffer,copyTextureToTexture:copy_multisampled_color:*": { "subcaseMS": 21.700 }, + "webgpu:api,operation,command_buffer,copyTextureToTexture:copy_multisampled_depth:*": { "subcaseMS": 5.901 }, + "webgpu:api,operation,command_buffer,copyTextureToTexture:zero_sized:*": { "subcaseMS": 0.741 }, + "webgpu:api,operation,command_buffer,image_copy:mip_levels:*": { "subcaseMS": 1.244 }, + "webgpu:api,operation,command_buffer,image_copy:offsets_and_sizes:*": { "subcaseMS": 0.960 }, + "webgpu:api,operation,command_buffer,image_copy:offsets_and_sizes_copy_depth_stencil:*": { "subcaseMS": 1.502 }, + "webgpu:api,operation,command_buffer,image_copy:origins_and_extents:*": { "subcaseMS": 0.618 }, + "webgpu:api,operation,command_buffer,image_copy:rowsPerImage_and_bytesPerRow:*": { "subcaseMS": 1.001 }, + "webgpu:api,operation,command_buffer,image_copy:rowsPerImage_and_bytesPerRow_depth_stencil:*": { "subcaseMS": 1.863 }, + "webgpu:api,operation,command_buffer,image_copy:undefined_params:*": { "subcaseMS": 3.144 }, + "webgpu:api,operation,command_buffer,programmable,state_tracking:bind_group_before_pipeline:*": { "subcaseMS": 3.375 }, + "webgpu:api,operation,command_buffer,programmable,state_tracking:bind_group_indices:*": { "subcaseMS": 2.872 }, + "webgpu:api,operation,command_buffer,programmable,state_tracking:bind_group_multiple_sets:*": { "subcaseMS": 12.300 }, + "webgpu:api,operation,command_buffer,programmable,state_tracking:bind_group_order:*": { "subcaseMS": 4.428 }, + "webgpu:api,operation,command_buffer,programmable,state_tracking:compatible_pipelines:*": { "subcaseMS": 12.334 }, + "webgpu:api,operation,command_buffer,programmable,state_tracking:one_bind_group_multiple_slots:*": { "subcaseMS": 9.734 }, + "webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,alpha_to_coverage:*": { "subcaseMS": 12.125 }, + "webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,basic:*": { "subcaseMS": 13.125 }, + "webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,depth:*": { "subcaseMS": 14.407 }, + "webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,empty:*": { "subcaseMS": 16.801 }, + "webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,initial:*": { "subcaseMS": 40.000 }, + "webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,multi_resolve:*": { "subcaseMS": 15.900 }, + "webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,sample_mask:*": { "subcaseMS": 13.352 }, + "webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,scissor:*": { "subcaseMS": 13.138 }, + "webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,stencil:*": { "subcaseMS": 10.300 }, + "webgpu:api,operation,command_buffer,render,state_tracking:change_pipeline_before_and_after_vertex_buffer:*": { "subcaseMS": 14.900 }, + "webgpu:api,operation,command_buffer,render,state_tracking:set_index_buffer_before_non_indexed_draw:*": { "subcaseMS": 16.301 }, + "webgpu:api,operation,command_buffer,render,state_tracking:set_index_buffer_without_changing_buffer:*": { "subcaseMS": 16.601 }, + "webgpu:api,operation,command_buffer,render,state_tracking:set_vertex_buffer_but_not_used_in_draw:*": { "subcaseMS": 17.300 }, + "webgpu:api,operation,command_buffer,render,state_tracking:set_vertex_buffer_without_changing_buffer:*": { "subcaseMS": 16.400 }, + "webgpu:api,operation,compute,basic:large_dispatch:*": { "subcaseMS": 9.237 }, + "webgpu:api,operation,compute,basic:memcpy:*": { "subcaseMS": 16.901 }, + "webgpu:api,operation,compute_pipeline,overrides:basic:*": { "subcaseMS": 15.100 }, + "webgpu:api,operation,compute_pipeline,overrides:multi_entry_points:*": { "subcaseMS": 15.900 }, + "webgpu:api,operation,compute_pipeline,overrides:numeric_id:*": { "subcaseMS": 14.300 }, + "webgpu:api,operation,compute_pipeline,overrides:precision:*": { "subcaseMS": 16.151 }, + "webgpu:api,operation,compute_pipeline,overrides:shared_shader_module:*": { "subcaseMS": 14.951 }, + "webgpu:api,operation,compute_pipeline,overrides:workgroup_size:*": { "subcaseMS": 13.184 }, + "webgpu:api,operation,device,lost:lost_on_destroy:*": { "subcaseMS": 37.500 }, + "webgpu:api,operation,device,lost:not_lost_on_gc:*": { "subcaseMS": 2066.500 }, + "webgpu:api,operation,device,lost:same_object:*": { "subcaseMS": 16.601 }, + "webgpu:api,operation,labels:object_has_descriptor_label:*": { "subcaseMS": 1.942 }, + "webgpu:api,operation,labels:wrappers_do_not_share_labels:*": { "subcaseMS": 13.701 }, + "webgpu:api,operation,memory_sync,buffer,multiple_buffers:multiple_pairs_of_dispatches_in_one_compute_pass:*": { "subcaseMS": 28.701 }, + "webgpu:api,operation,memory_sync,buffer,multiple_buffers:multiple_pairs_of_draws_in_one_render_bundle:*": { "subcaseMS": 30.200 }, + "webgpu:api,operation,memory_sync,buffer,multiple_buffers:multiple_pairs_of_draws_in_one_render_pass:*": { "subcaseMS": 11.900 }, + "webgpu:api,operation,memory_sync,buffer,multiple_buffers:rw:*": { "subcaseMS": 30.427 }, + "webgpu:api,operation,memory_sync,buffer,multiple_buffers:wr:*": { "subcaseMS": 30.007 }, + "webgpu:api,operation,memory_sync,buffer,multiple_buffers:ww:*": { "subcaseMS": 25.575 }, + "webgpu:api,operation,memory_sync,buffer,single_buffer:rw:*": { "subcaseMS": 18.337 }, + "webgpu:api,operation,memory_sync,buffer,single_buffer:two_dispatches_in_the_same_compute_pass:*": { "subcaseMS": 17.500 }, + "webgpu:api,operation,memory_sync,buffer,single_buffer:two_draws_in_the_same_render_bundle:*": { "subcaseMS": 18.100 }, + "webgpu:api,operation,memory_sync,buffer,single_buffer:two_draws_in_the_same_render_pass:*": { "subcaseMS": 4.925 }, + "webgpu:api,operation,memory_sync,buffer,single_buffer:wr:*": { "subcaseMS": 18.296 }, + "webgpu:api,operation,memory_sync,buffer,single_buffer:ww:*": { "subcaseMS": 18.802 }, + "webgpu:api,operation,memory_sync,texture,same_subresource:rw,single_pass,load_resolve:*": { "subcaseMS": 1.200 }, + "webgpu:api,operation,memory_sync,texture,same_subresource:rw,single_pass,load_store:*": { "subcaseMS": 14.200 }, + "webgpu:api,operation,memory_sync,texture,same_subresource:rw:*": { "subcaseMS": 10.908 }, + "webgpu:api,operation,memory_sync,texture,same_subresource:wr:*": { "subcaseMS": 10.684 }, + "webgpu:api,operation,memory_sync,texture,same_subresource:ww:*": { "subcaseMS": 11.198 }, + "webgpu:api,operation,onSubmittedWorkDone:many,parallel:*": { "subcaseMS": 111.601 }, + "webgpu:api,operation,onSubmittedWorkDone:many,parallel_order:*": { "subcaseMS": 33.000 }, + "webgpu:api,operation,onSubmittedWorkDone:many,serial:*": { "subcaseMS": 254.400 }, + "webgpu:api,operation,onSubmittedWorkDone:with_work:*": { "subcaseMS": 12.400 }, + "webgpu:api,operation,onSubmittedWorkDone:without_work:*": { "subcaseMS": 10.901 }, + "webgpu:api,operation,pipeline,default_layout:getBindGroupLayout_js_object:*": { "subcaseMS": 1.300 }, + "webgpu:api,operation,pipeline,default_layout:incompatible_with_explicit:*": { "subcaseMS": 1.101 }, + "webgpu:api,operation,pipeline,default_layout:layout:*": { "subcaseMS": 11.500 }, + "webgpu:api,operation,queue,writeBuffer:array_types:*": { "subcaseMS": 12.032 }, + "webgpu:api,operation,queue,writeBuffer:multiple_writes_at_different_offsets_and_sizes:*": { "subcaseMS": 2.087 }, + "webgpu:api,operation,reflection:buffer_reflection_attributes:*": { "subcaseMS": 0.800 }, + "webgpu:api,operation,reflection:query_set_reflection_attributes:*": { "subcaseMS": 0.634 }, + "webgpu:api,operation,reflection:texture_reflection_attributes:*": { "subcaseMS": 1.829 }, + "webgpu:api,operation,render_pass,clear_value:layout:*": { "subcaseMS": 1.401 }, + "webgpu:api,operation,render_pass,clear_value:loaded:*": { "subcaseMS": 14.300 }, + "webgpu:api,operation,render_pass,clear_value:srgb:*": { "subcaseMS": 5.601 }, + "webgpu:api,operation,render_pass,clear_value:stencil_clear_value:*": { "subcaseMS": 12.660 }, + "webgpu:api,operation,render_pass,clear_value:stored:*": { "subcaseMS": 12.100 }, + "webgpu:api,operation,render_pass,resolve:render_pass_resolve:*": { "subcaseMS": 1.029 }, + "webgpu:api,operation,render_pass,storeOp:render_pass_store_op,color_attachment_only:*": { "subcaseMS": 3.607 }, + "webgpu:api,operation,render_pass,storeOp:render_pass_store_op,color_attachment_with_depth_stencil_attachment:*": { "subcaseMS": 10.125 }, + "webgpu:api,operation,render_pass,storeOp:render_pass_store_op,depth_stencil_attachment_only:*": { "subcaseMS": 3.754 }, + "webgpu:api,operation,render_pass,storeOp:render_pass_store_op,multiple_color_attachments:*": { "subcaseMS": 4.263 }, + "webgpu:api,operation,render_pass,storeop2:storeOp_controls_whether_1x1_drawn_quad_is_stored:*": { "subcaseMS": 17.500 }, + "webgpu:api,operation,render_pipeline,culling_tests:culling:*": { "subcaseMS": 2.346 }, + "webgpu:api,operation,render_pipeline,overrides:basic:*": { "subcaseMS": 3.075 }, + "webgpu:api,operation,render_pipeline,overrides:multi_entry_points:*": { "subcaseMS": 5.400 }, + "webgpu:api,operation,render_pipeline,overrides:precision:*": { "subcaseMS": 7.675 }, + "webgpu:api,operation,render_pipeline,overrides:shared_shader_module:*": { "subcaseMS": 5.683 }, + "webgpu:api,operation,render_pipeline,pipeline_output_targets:color,attachments:*": { "subcaseMS": 1.984 }, + "webgpu:api,operation,render_pipeline,pipeline_output_targets:color,component_count,blend:*": { "subcaseMS": 1.731 }, + "webgpu:api,operation,render_pipeline,pipeline_output_targets:color,component_count:*": { "subcaseMS": 6.284 }, + "webgpu:api,operation,render_pipeline,primitive_topology:basic:*": { "subcaseMS": 11.822 }, + "webgpu:api,operation,render_pipeline,primitive_topology:unaligned_vertex_count:*": { "subcaseMS": 10.851 }, + "webgpu:api,operation,render_pipeline,sample_mask:alpha_to_coverage_mask:*": { "subcaseMS": 68.512 }, + "webgpu:api,operation,render_pipeline,sample_mask:fragment_output_mask:*": { "subcaseMS": 6.154 }, + "webgpu:api,operation,render_pipeline,vertex_only_render_pipeline:draw_depth_and_stencil_with_vertex_only_pipeline:*": { "subcaseMS": 14.100 }, + "webgpu:api,operation,rendering,basic:clear:*": { "subcaseMS": 3.700 }, + "webgpu:api,operation,rendering,basic:fullscreen_quad:*": { "subcaseMS": 16.601 }, + "webgpu:api,operation,rendering,basic:large_draw:*": { "subcaseMS": 2335.425 }, + "webgpu:api,operation,rendering,color_target_state:blend_constant,initial:*": { "subcaseMS": 33.901 }, + "webgpu:api,operation,rendering,color_target_state:blend_constant,not_inherited:*": { "subcaseMS": 41.601 }, + "webgpu:api,operation,rendering,color_target_state:blend_constant,setting:*": { "subcaseMS": 12.434 }, + "webgpu:api,operation,rendering,color_target_state:blending,GPUBlendComponent:*": { "subcaseMS": 6.454 }, + "webgpu:api,operation,rendering,color_target_state:blending,clamping:*": { "subcaseMS": 22.669 }, + "webgpu:api,operation,rendering,color_target_state:blending,formats:*": { "subcaseMS": 10.350 }, + "webgpu:api,operation,rendering,color_target_state:color_write_mask,blending_disabled:*": { "subcaseMS": 11.450 }, + "webgpu:api,operation,rendering,color_target_state:color_write_mask,channel_work:*": { "subcaseMS": 24.850 }, + "webgpu:api,operation,rendering,depth:depth_compare_func:*": { "subcaseMS": 10.123 }, + "webgpu:api,operation,rendering,depth:depth_disabled:*": { "subcaseMS": 19.801 }, + "webgpu:api,operation,rendering,depth:depth_test_fail:*": { "subcaseMS": 13.434 }, + "webgpu:api,operation,rendering,depth:depth_write_disabled:*": { "subcaseMS": 13.050 }, + "webgpu:api,operation,rendering,depth:reverse_depth:*": { "subcaseMS": 14.100 }, + "webgpu:api,operation,rendering,depth_bias:depth_bias:*": { "subcaseMS": 12.386 }, + "webgpu:api,operation,rendering,depth_bias:depth_bias_24bit_format:*": { "subcaseMS": 9.934 }, + "webgpu:api,operation,rendering,depth_clip_clamp:depth_clamp_and_clip:*": { "subcaseMS": 13.807 }, + "webgpu:api,operation,rendering,depth_clip_clamp:depth_test_input_clamped:*": { "subcaseMS": 13.005 }, + "webgpu:api,operation,rendering,draw:arguments:*": { "subcaseMS": 11.174 }, + "webgpu:api,operation,rendering,draw:default_arguments:*": { "subcaseMS": 4.446 }, + "webgpu:api,operation,rendering,draw:largeish_buffer:*": { "subcaseMS": 0.601 }, + "webgpu:api,operation,rendering,draw:vertex_attributes,basic:*": { "subcaseMS": 21.049 }, + "webgpu:api,operation,rendering,draw:vertex_attributes,formats:*": { "subcaseMS": 0.901 }, + "webgpu:api,operation,rendering,indirect_draw:basics:*": { "subcaseMS": 2.138 }, + "webgpu:api,operation,rendering,stencil:stencil_compare_func:*": { "subcaseMS": 10.328 }, + "webgpu:api,operation,rendering,stencil:stencil_depthFailOp_operation:*": { "subcaseMS": 10.323 }, + "webgpu:api,operation,rendering,stencil:stencil_failOp_operation:*": { "subcaseMS": 11.108 }, + "webgpu:api,operation,rendering,stencil:stencil_passOp_operation:*": { "subcaseMS": 11.123 }, + "webgpu:api,operation,rendering,stencil:stencil_read_write_mask:*": { "subcaseMS": 11.492 }, + "webgpu:api,operation,rendering,stencil:stencil_reference_initialized:*": { "subcaseMS": 13.234 }, + "webgpu:api,operation,resource_init,buffer:copy_buffer_to_buffer_copy_source:*": { "subcaseMS": 15.500 }, + "webgpu:api,operation,resource_init,buffer:copy_buffer_to_texture:*": { "subcaseMS": 8.350 }, + "webgpu:api,operation,resource_init,buffer:copy_texture_to_partial_buffer:*": { "subcaseMS": 0.960 }, + "webgpu:api,operation,resource_init,buffer:index_buffer:*": { "subcaseMS": 7.950 }, + "webgpu:api,operation,resource_init,buffer:indirect_buffer_for_dispatch_indirect:*": { "subcaseMS": 8.850 }, + "webgpu:api,operation,resource_init,buffer:indirect_buffer_for_draw_indirect:*": { "subcaseMS": 7.050 }, + "webgpu:api,operation,resource_init,buffer:map_partial_buffer:*": { "subcaseMS": 5.250 }, + "webgpu:api,operation,resource_init,buffer:map_whole_buffer:*": { "subcaseMS": 15.550 }, + "webgpu:api,operation,resource_init,buffer:mapped_at_creation_partial_buffer:*": { "subcaseMS": 3.300 }, + "webgpu:api,operation,resource_init,buffer:mapped_at_creation_whole_buffer:*": { "subcaseMS": 6.467 }, + "webgpu:api,operation,resource_init,buffer:partial_write_buffer:*": { "subcaseMS": 5.167 }, + "webgpu:api,operation,resource_init,buffer:readonly_storage_buffer:*": { "subcaseMS": 8.100 }, + "webgpu:api,operation,resource_init,buffer:resolve_query_set_to_partial_buffer:*": { "subcaseMS": 6.401 }, + "webgpu:api,operation,resource_init,buffer:storage_buffer:*": { "subcaseMS": 8.750 }, + "webgpu:api,operation,resource_init,buffer:uniform_buffer:*": { "subcaseMS": 7.250 }, + "webgpu:api,operation,resource_init,buffer:vertex_buffer:*": { "subcaseMS": 17.100 }, + "webgpu:api,operation,resource_init,texture_zero:uninitialized_texture_is_zero:*": { "subcaseMS": 3.578 }, + "webgpu:api,operation,sampling,anisotropy:anisotropic_filter_checkerboard:*": { "subcaseMS": 24.900 }, + "webgpu:api,operation,sampling,anisotropy:anisotropic_filter_mipmap_color:*": { "subcaseMS": 11.550 }, + "webgpu:api,operation,sampling,filter_mode:magFilter,linear:*": { "subcaseMS": 1.138 }, + "webgpu:api,operation,sampling,filter_mode:magFilter,nearest:*": { "subcaseMS": 1.283 }, + "webgpu:api,operation,sampling,filter_mode:minFilter,linear:*": { "subcaseMS": 1.146 }, + "webgpu:api,operation,sampling,filter_mode:minFilter,nearest:*": { "subcaseMS": 1.057 }, + "webgpu:api,operation,sampling,filter_mode:mipmapFilter:*": { "subcaseMS": 3.445 }, + "webgpu:api,operation,shader_module,compilation_info:getCompilationInfo_returns:*": { "subcaseMS": 0.284 }, + "webgpu:api,operation,shader_module,compilation_info:line_number_and_position:*": { "subcaseMS": 1.867 }, + "webgpu:api,operation,shader_module,compilation_info:offset_and_length:*": { "subcaseMS": 1.648 }, + "webgpu:api,operation,texture_view,format_reinterpretation:render_and_resolve_attachment:*": { "subcaseMS": 14.488 }, + "webgpu:api,operation,texture_view,format_reinterpretation:texture_binding:*": { "subcaseMS": 17.225 }, + "webgpu:api,operation,texture_view,read:aspect:*": { "subcaseMS": 0.601 }, + "webgpu:api,operation,texture_view,read:dimension:*": { "subcaseMS": 0.701 }, + "webgpu:api,operation,texture_view,read:format:*": { "subcaseMS": 1.100 }, + "webgpu:api,operation,texture_view,write:aspect:*": { "subcaseMS": 0.700 }, + "webgpu:api,operation,texture_view,write:dimension:*": { "subcaseMS": 0.601 }, + "webgpu:api,operation,texture_view,write:format:*": { "subcaseMS": 0.600 }, + "webgpu:api,operation,uncapturederror:constructor:*": { "subcaseMS": 0.200 }, + "webgpu:api,operation,uncapturederror:iff_uncaptured:*": { "subcaseMS": 0.101 }, + "webgpu:api,operation,uncapturederror:only_original_device_is_event_target:*": { "subcaseMS": 0.101 }, + "webgpu:api,operation,uncapturederror:uncapturederror_from_non_originating_thread:*": { "subcaseMS": 0.201 }, + "webgpu:api,operation,vertex_state,correctness:array_stride_zero:*": { "subcaseMS": 4.246 }, + "webgpu:api,operation,vertex_state,correctness:buffers_with_varying_step_mode:*": { "subcaseMS": 6.100 }, + "webgpu:api,operation,vertex_state,correctness:discontiguous_location_and_attribs:*": { "subcaseMS": 15.100 }, + "webgpu:api,operation,vertex_state,correctness:max_buffers_and_attribs:*": { "subcaseMS": 18.577 }, + "webgpu:api,operation,vertex_state,correctness:non_zero_array_stride_and_attribute_offset:*": { "subcaseMS": 3.816 }, + "webgpu:api,operation,vertex_state,correctness:overlapping_attributes:*": { "subcaseMS": 17.470 }, + "webgpu:api,operation,vertex_state,correctness:setVertexBuffer_offset_and_attribute_offset:*": { "subcaseMS": 2.848 }, + "webgpu:api,operation,vertex_state,correctness:vertex_buffer_used_multiple_times_interleaved:*": { "subcaseMS": 5.398 }, + "webgpu:api,operation,vertex_state,correctness:vertex_buffer_used_multiple_times_overlapped:*": { "subcaseMS": 5.388 }, + "webgpu:api,operation,vertex_state,correctness:vertex_format_to_shader_format_conversion:*": { "subcaseMS": 3.697 }, + "webgpu:api,operation,vertex_state,index_format:index_format,change_pipeline_after_setIndexBuffer:*": { "subcaseMS": 12.550 }, + "webgpu:api,operation,vertex_state,index_format:index_format,setIndexBuffer_before_setPipeline:*": { "subcaseMS": 13.300 }, + "webgpu:api,operation,vertex_state,index_format:index_format,setIndexBuffer_different_formats:*": { "subcaseMS": 12.601 }, + "webgpu:api,operation,vertex_state,index_format:index_format,uint16:*": { "subcaseMS": 5.300 }, + "webgpu:api,operation,vertex_state,index_format:index_format,uint32:*": { "subcaseMS": 5.900 }, + "webgpu:api,operation,vertex_state,index_format:primitive_restart:*": { "subcaseMS": 12.080 }, + "webgpu:api,validation,buffer,create:createBuffer_invalid_and_oom:*": { "subcaseMS": 1.500 }, + "webgpu:api,validation,buffer,create:limit:*": { "subcaseMS": 31.433 }, + "webgpu:api,validation,buffer,create:size:*": { "subcaseMS": 5.570 }, + "webgpu:api,validation,buffer,create:usage:*": { "subcaseMS": 3.971 }, + "webgpu:api,validation,buffer,destroy:all_usages:*": { "subcaseMS": 3.250 }, + "webgpu:api,validation,buffer,destroy:error_buffer:*": { "subcaseMS": 29.700 }, + "webgpu:api,validation,buffer,destroy:twice:*": { "subcaseMS": 5.367 }, + "webgpu:api,validation,buffer,destroy:while_mapped:*": { "subcaseMS": 1.150 }, + "webgpu:api,validation,buffer,mapping:gc_behavior,mapAsync:*": { "subcaseMS": 32.200 }, + "webgpu:api,validation,buffer,mapping:gc_behavior,mappedAtCreation:*": { "subcaseMS": 76.200 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,disjoinRanges_many:*": { "subcaseMS": 73.700 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,disjointRanges:*": { "subcaseMS": 2.257 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,offsetAndSizeAlignment,mapped:*": { "subcaseMS": 3.119 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,offsetAndSizeAlignment,mappedAtCreation:*": { "subcaseMS": 5.611 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,sizeAndOffsetOOB,mapped:*": { "subcaseMS": 0.886 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,sizeAndOffsetOOB,mappedAtCreation:*": { "subcaseMS": 4.415 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,state,destroyed:*": { "subcaseMS": 61.301 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,state,invalid_mappedAtCreation:*": { "subcaseMS": 12.401 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,state,mapped:*": { "subcaseMS": 8.200 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,state,mappedAgain:*": { "subcaseMS": 8.150 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,state,mappedAtCreation:*": { "subcaseMS": 2.960 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,state,mappingPending:*": { "subcaseMS": 28.600 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,state,unmapped:*": { "subcaseMS": 16.000 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,subrange,mapped:*": { "subcaseMS": 63.150 }, + "webgpu:api,validation,buffer,mapping:getMappedRange,subrange,mappedAtCreation:*": { "subcaseMS": 1.500 }, + "webgpu:api,validation,buffer,mapping:mapAsync,abort_over_invalid_error:*": { "subcaseMS": 3.725 }, + "webgpu:api,validation,buffer,mapping:mapAsync,earlyRejection:*": { "subcaseMS": 12.900 }, + "webgpu:api,validation,buffer,mapping:mapAsync,invalidBuffer:*": { "subcaseMS": 18.000 }, + "webgpu:api,validation,buffer,mapping:mapAsync,offsetAndSizeAlignment:*": { "subcaseMS": 1.794 }, + "webgpu:api,validation,buffer,mapping:mapAsync,offsetAndSizeOOB:*": { "subcaseMS": 0.953 }, + "webgpu:api,validation,buffer,mapping:mapAsync,sizeUnspecifiedOOB:*": { "subcaseMS": 2.212 }, + "webgpu:api,validation,buffer,mapping:mapAsync,state,destroyed:*": { "subcaseMS": 15.450 }, + "webgpu:api,validation,buffer,mapping:mapAsync,state,mapped:*": { "subcaseMS": 16.050 }, + "webgpu:api,validation,buffer,mapping:mapAsync,state,mappedAtCreation:*": { "subcaseMS": 15.900 }, + "webgpu:api,validation,buffer,mapping:mapAsync,state,mappingPending:*": { "subcaseMS": 16.700 }, + "webgpu:api,validation,buffer,mapping:mapAsync,usage:*": { "subcaseMS": 1.203 }, + "webgpu:api,validation,buffer,mapping:unmap,state,destroyed:*": { "subcaseMS": 12.701 }, + "webgpu:api,validation,buffer,mapping:unmap,state,mapped:*": { "subcaseMS": 9.600 }, + "webgpu:api,validation,buffer,mapping:unmap,state,mappedAtCreation:*": { "subcaseMS": 8.950 }, + "webgpu:api,validation,buffer,mapping:unmap,state,mappingPending:*": { "subcaseMS": 22.951 }, + "webgpu:api,validation,buffer,mapping:unmap,state,unmapped:*": { "subcaseMS": 74.200 }, + "webgpu:api,validation,capability_checks,features,query_types:createQuerySet:*": { "subcaseMS": 10.451 }, + "webgpu:api,validation,capability_checks,features,query_types:writeTimestamp:*": { "subcaseMS": 1.200 }, + "webgpu:api,validation,capability_checks,features,texture_formats:canvas_configuration:*": { "subcaseMS": 4.339 }, + "webgpu:api,validation,capability_checks,features,texture_formats:canvas_configuration_view_formats:*": { "subcaseMS": 4.522 }, + "webgpu:api,validation,capability_checks,features,texture_formats:depth_stencil_state:*": { "subcaseMS": 15.701 }, + "webgpu:api,validation,capability_checks,features,texture_formats:render_bundle_encoder_descriptor_depth_stencil_format:*": { "subcaseMS": 0.800 }, + "webgpu:api,validation,capability_checks,features,texture_formats:texture_descriptor:*": { "subcaseMS": 3.830 }, + "webgpu:api,validation,capability_checks,features,texture_formats:texture_descriptor_view_formats:*": { "subcaseMS": 5.734 }, + "webgpu:api,validation,capability_checks,features,texture_formats:texture_view_descriptor:*": { "subcaseMS": 4.113 }, + "webgpu:api,validation,capability_checks,limits,maxBindGroups:createPipeline,at_over:*": { "subcaseMS": 10.990 }, + "webgpu:api,validation,capability_checks,limits,maxBindGroups:createPipelineLayout,at_over:*": { "subcaseMS": 9.310 }, + "webgpu:api,validation,capability_checks,limits,maxBindGroups:setBindGroup,at_over:*": { "subcaseMS": 9.984 }, + "webgpu:api,validation,capability_checks,limits,maxBindingsPerBindGroup:createBindGroupLayout,at_over:*": { "subcaseMS": 12.441 }, + "webgpu:api,validation,capability_checks,limits,maxBindingsPerBindGroup:createPipeline,at_over:*": { "subcaseMS": 11.179 }, + "webgpu:api,validation,capability_checks,limits,maxBindingsPerBindGroup:validate:*": { "subcaseMS": 12.401 }, + "webgpu:api,validation,capability_checks,limits,maxBufferSize:createBuffer,at_over:*": { "subcaseMS": 146.130 }, + "webgpu:api,validation,capability_checks,limits,maxColorAttachmentBytesPerSample:beginRenderPass,at_over:*": { "subcaseMS": 9.396 }, + "webgpu:api,validation,capability_checks,limits,maxColorAttachmentBytesPerSample:createRenderBundle,at_over:*": { "subcaseMS": 12.093 }, + "webgpu:api,validation,capability_checks,limits,maxColorAttachmentBytesPerSample:createRenderPipeline,at_over:*": { "subcaseMS": 11.818 }, + "webgpu:api,validation,capability_checks,limits,maxColorAttachments:beginRenderPass,at_over:*": { "subcaseMS": 10.320 }, + "webgpu:api,validation,capability_checks,limits,maxColorAttachments:createRenderBundle,at_over:*": { "subcaseMS": 12.681 }, + "webgpu:api,validation,capability_checks,limits,maxColorAttachments:createRenderPipeline,at_over:*": { "subcaseMS": 10.450 }, + "webgpu:api,validation,capability_checks,limits,maxColorAttachments:validate,maxColorAttachmentBytesPerSample:*": { "subcaseMS": 1.101 }, + "webgpu:api,validation,capability_checks,limits,maxComputeInvocationsPerWorkgroup:createComputePipeline,at_over:*": { "subcaseMS": 13.735 }, + "webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupSizeX:createComputePipeline,at_over:*": { "subcaseMS": 14.465 }, + "webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupSizeY:createComputePipeline,at_over:*": { "subcaseMS": 14.131 }, + "webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupSizeZ:createComputePipeline,at_over:*": { "subcaseMS": 14.920 }, + "webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupStorageSize:createComputePipeline,at_over:*": { "subcaseMS": 12.009 }, + "webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupsPerDimension:dispatchWorkgroups,at_over:*": { "subcaseMS": 13.310 }, + "webgpu:api,validation,capability_checks,limits,maxDynamicStorageBuffersPerPipelineLayout:createBindGroupLayout,at_over:*": { "subcaseMS": 15.680 }, + "webgpu:api,validation,capability_checks,limits,maxDynamicUniformBuffersPerPipelineLayout:createBindGroupLayout,at_over:*": { "subcaseMS": 10.268 }, + "webgpu:api,validation,capability_checks,limits,maxInterStageShaderComponents:createRenderPipeline,at_over:*": { "subcaseMS": 12.916 }, + "webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:*": { "subcaseMS": 13.700 }, + "webgpu:api,validation,capability_checks,limits,maxSampledTexturesPerShaderStage:createBindGroupLayout,at_over:*": { "subcaseMS": 47.857 }, + "webgpu:api,validation,capability_checks,limits,maxSampledTexturesPerShaderStage:createPipeline,at_over:*": { "subcaseMS": 45.611 }, + "webgpu:api,validation,capability_checks,limits,maxSampledTexturesPerShaderStage:createPipelineLayout,at_over:*": { "subcaseMS": 26.153 }, + "webgpu:api,validation,capability_checks,limits,maxSamplersPerShaderStage:createBindGroupLayout,at_over:*": { "subcaseMS": 9.645 }, + "webgpu:api,validation,capability_checks,limits,maxSamplersPerShaderStage:createPipeline,at_over:*": { "subcaseMS": 11.959 }, + "webgpu:api,validation,capability_checks,limits,maxSamplersPerShaderStage:createPipelineLayout,at_over:*": { "subcaseMS": 10.427 }, + "webgpu:api,validation,capability_checks,limits,maxStorageBufferBindingSize:createBindGroup,at_over:*": { "subcaseMS": 51.810 }, + "webgpu:api,validation,capability_checks,limits,maxStorageBufferBindingSize:validate,maxBufferSize:*": { "subcaseMS": 0.900 }, + "webgpu:api,validation,capability_checks,limits,maxStorageBuffersPerShaderStage:createBindGroupLayout,at_over:*": { "subcaseMS": 4.565 }, + "webgpu:api,validation,capability_checks,limits,maxStorageBuffersPerShaderStage:createPipeline,at_over:*": { "subcaseMS": 7.884 }, + "webgpu:api,validation,capability_checks,limits,maxStorageBuffersPerShaderStage:createPipelineLayout,at_over:*": { "subcaseMS": 5.007 }, + "webgpu:api,validation,capability_checks,limits,maxStorageTexturesPerShaderStage:createBindGroupLayout,at_over:*": { "subcaseMS": 5.147 }, + "webgpu:api,validation,capability_checks,limits,maxStorageTexturesPerShaderStage:createPipeline,at_over:*": { "subcaseMS": 6.804 }, + "webgpu:api,validation,capability_checks,limits,maxStorageTexturesPerShaderStage:createPipelineLayout,at_over:*": { "subcaseMS": 5.457 }, + "webgpu:api,validation,capability_checks,limits,maxTextureArrayLayers:createTexture,at_over:*": { "subcaseMS": 13.651 }, + "webgpu:api,validation,capability_checks,limits,maxTextureDimension1D:createTexture,at_over:*": { "subcaseMS": 23.431 }, + "webgpu:api,validation,capability_checks,limits,maxTextureDimension2D:configure,at_over:*": { "subcaseMS": 8.280 }, + "webgpu:api,validation,capability_checks,limits,maxTextureDimension2D:createTexture,at_over:*": { "subcaseMS": 8.981 }, + "webgpu:api,validation,capability_checks,limits,maxTextureDimension2D:getCurrentTexture,at_over:*": { "subcaseMS": 21.886 }, + "webgpu:api,validation,capability_checks,limits,maxTextureDimension3D:createTexture,at_over:*": { "subcaseMS": 9.410 }, + "webgpu:api,validation,capability_checks,limits,maxUniformBufferBindingSize:createBindGroup,at_over:*": { "subcaseMS": 6.785 }, + "webgpu:api,validation,capability_checks,limits,maxUniformBufferBindingSize:validate,maxBufferSize:*": { "subcaseMS": 1.700 }, + "webgpu:api,validation,capability_checks,limits,maxUniformBuffersPerShaderStage:createBindGroupLayout,at_over:*": { "subcaseMS": 5.858 }, + "webgpu:api,validation,capability_checks,limits,maxUniformBuffersPerShaderStage:createPipeline,at_over:*": { "subcaseMS": 9.105 }, + "webgpu:api,validation,capability_checks,limits,maxUniformBuffersPerShaderStage:createPipelineLayout,at_over:*": { "subcaseMS": 6.109 }, + "webgpu:api,validation,capability_checks,limits,maxVertexAttributes:createRenderPipeline,at_over:*": { "subcaseMS": 9.090 }, + "webgpu:api,validation,capability_checks,limits,maxVertexBufferArrayStride:createRenderPipeline,at_over:*": { "subcaseMS": 10.060 }, + "webgpu:api,validation,capability_checks,limits,maxVertexBuffers:createRenderPipeline,at_over:*": { "subcaseMS": 8.903 }, + "webgpu:api,validation,capability_checks,limits,maxVertexBuffers:setVertexBuffer,at_over:*": { "subcaseMS": 7.695 }, + "webgpu:api,validation,capability_checks,limits,minStorageBufferOffsetAlignment:createBindGroup,at_over:*": { "subcaseMS": 9.650 }, + "webgpu:api,validation,capability_checks,limits,minStorageBufferOffsetAlignment:setBindGroup,at_over:*": { "subcaseMS": 8.931 }, + "webgpu:api,validation,capability_checks,limits,minStorageBufferOffsetAlignment:validate,greaterThanOrEqualTo32:*": { "subcaseMS": 31.801 }, + "webgpu:api,validation,capability_checks,limits,minStorageBufferOffsetAlignment:validate,powerOf2:*": { "subcaseMS": 2.400 }, + "webgpu:api,validation,capability_checks,limits,minUniformBufferOffsetAlignment:createBindGroup,at_over:*": { "subcaseMS": 9.301 }, + "webgpu:api,validation,capability_checks,limits,minUniformBufferOffsetAlignment:setBindGroup,at_over:*": { "subcaseMS": 10.341 }, + "webgpu:api,validation,capability_checks,limits,minUniformBufferOffsetAlignment:validate,greaterThanOrEqualTo32:*": { "subcaseMS": 2.400 }, + "webgpu:api,validation,capability_checks,limits,minUniformBufferOffsetAlignment:validate,powerOf2:*": { "subcaseMS": 2.301 }, + "webgpu:api,validation,compute_pipeline:basic:*": { "subcaseMS": 28.050 }, + "webgpu:api,validation,compute_pipeline:limits,invocations_per_workgroup,each_component:*": { "subcaseMS": 6.582 }, + "webgpu:api,validation,compute_pipeline:limits,invocations_per_workgroup:*": { "subcaseMS": 8.092 }, + "webgpu:api,validation,compute_pipeline:limits,workgroup_storage_size:*": { "subcaseMS": 4.025 }, + "webgpu:api,validation,compute_pipeline:overrides,identifier:*": { "subcaseMS": 5.312 }, + "webgpu:api,validation,compute_pipeline:overrides,uninitialized:*": { "subcaseMS": 7.801 }, + "webgpu:api,validation,compute_pipeline:overrides,value,type_error:*": { "subcaseMS": 9.675 }, + "webgpu:api,validation,compute_pipeline:overrides,value,validation_error,f16:*": { "subcaseMS": 5.908 }, + "webgpu:api,validation,compute_pipeline:overrides,value,validation_error:*": { "subcaseMS": 13.918 }, + "webgpu:api,validation,compute_pipeline:overrides,workgroup_size,limits,workgroup_storage_size:*": { "subcaseMS": 10.800 }, + "webgpu:api,validation,compute_pipeline:overrides,workgroup_size,limits:*": { "subcaseMS": 14.751 }, + "webgpu:api,validation,compute_pipeline:overrides,workgroup_size:*": { "subcaseMS": 6.376 }, + "webgpu:api,validation,compute_pipeline:pipeline_layout,device_mismatch:*": { "subcaseMS": 1.175 }, + "webgpu:api,validation,compute_pipeline:shader_module,compute:*": { "subcaseMS": 6.867 }, + "webgpu:api,validation,compute_pipeline:shader_module,device_mismatch:*": { "subcaseMS": 15.350 }, + "webgpu:api,validation,compute_pipeline:shader_module,invalid:*": { "subcaseMS": 2.500 }, + "webgpu:api,validation,createBindGroup:bind_group_layout,device_mismatch:*": { "subcaseMS": 15.800 }, + "webgpu:api,validation,createBindGroup:binding_count_mismatch:*": { "subcaseMS": 1.822 }, + "webgpu:api,validation,createBindGroup:binding_must_be_present_in_layout:*": { "subcaseMS": 3.311 }, + "webgpu:api,validation,createBindGroup:binding_must_contain_resource_defined_in_layout:*": { "subcaseMS": 0.340 }, + "webgpu:api,validation,createBindGroup:binding_resources,device_mismatch:*": { "subcaseMS": 4.850 }, + "webgpu:api,validation,createBindGroup:buffer,effective_buffer_binding_size:*": { "subcaseMS": 0.263 }, + "webgpu:api,validation,createBindGroup:buffer,resource_binding_size:*": { "subcaseMS": 0.845 }, + "webgpu:api,validation,createBindGroup:buffer,resource_offset:*": { "subcaseMS": 4.558 }, + "webgpu:api,validation,createBindGroup:buffer,resource_state:*": { "subcaseMS": 0.301 }, + "webgpu:api,validation,createBindGroup:buffer,usage:*": { "subcaseMS": 0.525 }, + "webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:*": { "subcaseMS": 1.871 }, + "webgpu:api,validation,createBindGroup:minBindingSize:*": { "subcaseMS": 3.391 }, + "webgpu:api,validation,createBindGroup:multisampled_validation:*": { "subcaseMS": 13.325 }, + "webgpu:api,validation,createBindGroup:sampler,compare_function_with_binding_type:*": { "subcaseMS": 0.702 }, + "webgpu:api,validation,createBindGroup:sampler,device_mismatch:*": { "subcaseMS": 1.750 }, + "webgpu:api,validation,createBindGroup:storage_texture,format:*": { "subcaseMS": 5.045 }, + "webgpu:api,validation,createBindGroup:storage_texture,mip_level_count:*": { "subcaseMS": 8.426 }, + "webgpu:api,validation,createBindGroup:storage_texture,usage:*": { "subcaseMS": 3.817 }, + "webgpu:api,validation,createBindGroup:texture,resource_state:*": { "subcaseMS": 2.542 }, + "webgpu:api,validation,createBindGroup:texture_binding_must_have_correct_usage:*": { "subcaseMS": 1.150 }, + "webgpu:api,validation,createBindGroup:texture_must_have_correct_component_type:*": { "subcaseMS": 10.767 }, + "webgpu:api,validation,createBindGroup:texture_must_have_correct_dimension:*": { "subcaseMS": 3.288 }, + "webgpu:api,validation,createBindGroupLayout:duplicate_bindings:*": { "subcaseMS": 1.200 }, + "webgpu:api,validation,createBindGroupLayout:max_dynamic_buffers:*": { "subcaseMS": 2.800 }, + "webgpu:api,validation,createBindGroupLayout:max_resources_per_stage,in_bind_group_layout:*": { "subcaseMS": 0.915 }, + "webgpu:api,validation,createBindGroupLayout:max_resources_per_stage,in_pipeline_layout:*": { "subcaseMS": 0.682 }, + "webgpu:api,validation,createBindGroupLayout:maximum_binding_limit:*": { "subcaseMS": 0.400 }, + "webgpu:api,validation,createBindGroupLayout:multisampled_validation:*": { "subcaseMS": 0.452 }, + "webgpu:api,validation,createBindGroupLayout:storage_texture,formats:*": { "subcaseMS": 4.996 }, + "webgpu:api,validation,createBindGroupLayout:storage_texture,layout_dimension:*": { "subcaseMS": 3.829 }, + "webgpu:api,validation,createBindGroupLayout:visibility,VERTEX_shader_stage_buffer_type:*": { "subcaseMS": 1.342 }, + "webgpu:api,validation,createBindGroupLayout:visibility,VERTEX_shader_stage_storage_texture_access:*": { "subcaseMS": 4.394 }, + "webgpu:api,validation,createBindGroupLayout:visibility:*": { "subcaseMS": 1.926 }, + "webgpu:api,validation,createPipelineLayout:bind_group_layouts,device_mismatch:*": { "subcaseMS": 1.200 }, + "webgpu:api,validation,createPipelineLayout:number_of_bind_group_layouts_exceeds_the_maximum_value:*": { "subcaseMS": 3.500 }, + "webgpu:api,validation,createPipelineLayout:number_of_dynamic_buffers_exceeds_the_maximum_value:*": { "subcaseMS": 2.658 }, + "webgpu:api,validation,createSampler:lodMinAndMaxClamp:*": { "subcaseMS": 0.610 }, + "webgpu:api,validation,createSampler:maxAnisotropy:*": { "subcaseMS": 0.979 }, + "webgpu:api,validation,createTexture:dimension_type_and_format_compatibility:*": { "subcaseMS": 4.062 }, + "webgpu:api,validation,createTexture:mipLevelCount,bound_check,bigger_than_integer_bit_width:*": { "subcaseMS": 2.301 }, + "webgpu:api,validation,createTexture:mipLevelCount,bound_check:*": { "subcaseMS": 0.801 }, + "webgpu:api,validation,createTexture:mipLevelCount,format:*": { "subcaseMS": 1.258 }, + "webgpu:api,validation,createTexture:sampleCount,valid_sampleCount_with_other_parameter_varies:*": { "subcaseMS": 0.525 }, + "webgpu:api,validation,createTexture:sampleCount,various_sampleCount_with_all_formats:*": { "subcaseMS": 2.336 }, + "webgpu:api,validation,createTexture:sample_count,1d_2d_array_3d:*": { "subcaseMS": 2.480 }, + "webgpu:api,validation,createTexture:texture_size,1d_texture:*": { "subcaseMS": 1.372 }, + "webgpu:api,validation,createTexture:texture_size,2d_texture,compressed_format:*": { "subcaseMS": 4.108 }, + "webgpu:api,validation,createTexture:texture_size,2d_texture,uncompressed_format:*": { "subcaseMS": 4.729 }, + "webgpu:api,validation,createTexture:texture_size,3d_texture,compressed_format:*": { "subcaseMS": 4.322 }, + "webgpu:api,validation,createTexture:texture_size,3d_texture,uncompressed_format:*": { "subcaseMS": 2.039 }, + "webgpu:api,validation,createTexture:texture_size,default_value_and_smallest_size,compressed_format:*": { "subcaseMS": 1.863 }, + "webgpu:api,validation,createTexture:texture_size,default_value_and_smallest_size,uncompressed_format:*": { "subcaseMS": 1.694 }, + "webgpu:api,validation,createTexture:texture_usage:*": { "subcaseMS": 0.870 }, + "webgpu:api,validation,createTexture:viewFormats:*": { "subcaseMS": 0.632 }, + "webgpu:api,validation,createTexture:zero_size_and_usage:*": { "subcaseMS": 3.250 }, + "webgpu:api,validation,createView:array_layers:*": { "subcaseMS": 0.491 }, + "webgpu:api,validation,createView:aspect:*": { "subcaseMS": 5.556 }, + "webgpu:api,validation,createView:cube_faces_square:*": { "subcaseMS": 19.340 }, + "webgpu:api,validation,createView:dimension:*": { "subcaseMS": 9.291 }, + "webgpu:api,validation,createView:format:*": { "subcaseMS": 0.742 }, + "webgpu:api,validation,createView:mip_levels:*": { "subcaseMS": 0.436 }, + "webgpu:api,validation,createView:texture_state:*": { "subcaseMS": 0.400 }, + "webgpu:api,validation,debugMarker:push_pop_call_count_unbalance,command_encoder:*": { "subcaseMS": 1.522 }, + "webgpu:api,validation,debugMarker:push_pop_call_count_unbalance,render_compute_pass:*": { "subcaseMS": 0.601 }, + "webgpu:api,validation,encoding,beginComputePass:timestampWrites,invalid_query_set:*": { "subcaseMS": 0.201 }, + "webgpu:api,validation,encoding,beginComputePass:timestampWrites,query_index:*": { "subcaseMS": 0.201 }, + "webgpu:api,validation,encoding,beginComputePass:timestampWrites,query_set_type:*": { "subcaseMS": 0.401 }, + "webgpu:api,validation,encoding,beginComputePass:timestamp_query_set,device_mismatch:*": { "subcaseMS": 0.301 }, + "webgpu:api,validation,encoding,beginRenderPass:color_attachments,device_mismatch:*": { "subcaseMS": 10.750 }, + "webgpu:api,validation,encoding,beginRenderPass:depth_stencil_attachment,device_mismatch:*": { "subcaseMS": 26.100 }, + "webgpu:api,validation,encoding,beginRenderPass:occlusion_query_set,device_mismatch:*": { "subcaseMS": 0.850 }, + "webgpu:api,validation,encoding,beginRenderPass:timestamp_query_set,device_mismatch:*": { "subcaseMS": 0.301 }, + "webgpu:api,validation,encoding,cmds,clearBuffer:buffer,device_mismatch:*": { "subcaseMS": 7.350 }, + "webgpu:api,validation,encoding,cmds,clearBuffer:buffer_state:*": { "subcaseMS": 44.500 }, + "webgpu:api,validation,encoding,cmds,clearBuffer:buffer_usage:*": { "subcaseMS": 4.000 }, + "webgpu:api,validation,encoding,cmds,clearBuffer:default_args:*": { "subcaseMS": 0.233 }, + "webgpu:api,validation,encoding,cmds,clearBuffer:offset_alignment:*": { "subcaseMS": 2.086 }, + "webgpu:api,validation,encoding,cmds,clearBuffer:out_of_bounds:*": { "subcaseMS": 0.213 }, + "webgpu:api,validation,encoding,cmds,clearBuffer:overflow:*": { "subcaseMS": 0.350 }, + "webgpu:api,validation,encoding,cmds,clearBuffer:size_alignment:*": { "subcaseMS": 0.300 }, + "webgpu:api,validation,encoding,cmds,compute_pass:dispatch_sizes:*": { "subcaseMS": 4.062 }, + "webgpu:api,validation,encoding,cmds,compute_pass:indirect_dispatch_buffer,device_mismatch:*": { "subcaseMS": 21.050 }, + "webgpu:api,validation,encoding,cmds,compute_pass:indirect_dispatch_buffer,usage:*": { "subcaseMS": 0.534 }, + "webgpu:api,validation,encoding,cmds,compute_pass:indirect_dispatch_buffer_state:*": { "subcaseMS": 2.093 }, + "webgpu:api,validation,encoding,cmds,compute_pass:pipeline,device_mismatch:*": { "subcaseMS": 7.600 }, + "webgpu:api,validation,encoding,cmds,compute_pass:set_pipeline:*": { "subcaseMS": 1.000 }, + "webgpu:api,validation,encoding,cmds,copyBufferToBuffer:buffer,device_mismatch:*": { "subcaseMS": 0.500 }, + "webgpu:api,validation,encoding,cmds,copyBufferToBuffer:buffer_state:*": { "subcaseMS": 3.178 }, + "webgpu:api,validation,encoding,cmds,copyBufferToBuffer:buffer_usage:*": { "subcaseMS": 0.591 }, + "webgpu:api,validation,encoding,cmds,copyBufferToBuffer:copy_offset_alignment:*": { "subcaseMS": 0.400 }, + "webgpu:api,validation,encoding,cmds,copyBufferToBuffer:copy_out_of_bounds:*": { "subcaseMS": 0.200 }, + "webgpu:api,validation,encoding,cmds,copyBufferToBuffer:copy_overflow:*": { "subcaseMS": 0.301 }, + "webgpu:api,validation,encoding,cmds,copyBufferToBuffer:copy_size_alignment:*": { "subcaseMS": 0.680 }, + "webgpu:api,validation,encoding,cmds,copyBufferToBuffer:copy_within_same_buffer:*": { "subcaseMS": 0.401 }, + "webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_aspects:*": { "subcaseMS": 2.182 }, + "webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_ranges:*": { "subcaseMS": 11.442 }, + "webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_ranges_with_compressed_texture_formats:*": { "subcaseMS": 0.334 }, + "webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_with_invalid_or_destroyed_texture:*": { "subcaseMS": 4.844 }, + "webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_within_same_texture:*": { "subcaseMS": 0.301 }, + "webgpu:api,validation,encoding,cmds,copyTextureToTexture:depth_stencil_copy_restrictions:*": { "subcaseMS": 0.480 }, + "webgpu:api,validation,encoding,cmds,copyTextureToTexture:mipmap_level:*": { "subcaseMS": 0.879 }, + "webgpu:api,validation,encoding,cmds,copyTextureToTexture:multisampled_copy_restrictions:*": { "subcaseMS": 0.301 }, + "webgpu:api,validation,encoding,cmds,copyTextureToTexture:sample_count:*": { "subcaseMS": 4.125 }, + "webgpu:api,validation,encoding,cmds,copyTextureToTexture:texture,device_mismatch:*": { "subcaseMS": 0.567 }, + "webgpu:api,validation,encoding,cmds,copyTextureToTexture:texture_format_compatibility:*": { "subcaseMS": 0.341 }, + "webgpu:api,validation,encoding,cmds,copyTextureToTexture:texture_usage:*": { "subcaseMS": 2.308 }, + "webgpu:api,validation,encoding,cmds,debug:debug_group:*": { "subcaseMS": 3.640 }, + "webgpu:api,validation,encoding,cmds,debug:debug_group_balanced:*": { "subcaseMS": 1.978 }, + "webgpu:api,validation,encoding,cmds,debug:debug_marker:*": { "subcaseMS": 0.960 }, + "webgpu:api,validation,encoding,cmds,index_access:out_of_bounds:*": { "subcaseMS": 7.139 }, + "webgpu:api,validation,encoding,cmds,index_access:out_of_bounds_zero_sized_index_buffer:*": { "subcaseMS": 12.400 }, + "webgpu:api,validation,encoding,cmds,render,draw:buffer_binding_overlap:*": { "subcaseMS": 0.446 }, + "webgpu:api,validation,encoding,cmds,render,draw:index_buffer_OOB:*": { "subcaseMS": 5.825 }, + "webgpu:api,validation,encoding,cmds,render,draw:last_buffer_setting_take_account:*": { "subcaseMS": 30.801 }, + "webgpu:api,validation,encoding,cmds,render,draw:max_draw_count:*": { "subcaseMS": 3.521 }, + "webgpu:api,validation,encoding,cmds,render,draw:unused_buffer_bound:*": { "subcaseMS": 1.413 }, + "webgpu:api,validation,encoding,cmds,render,draw:vertex_buffer_OOB:*": { "subcaseMS": 0.767 }, + "webgpu:api,validation,encoding,cmds,render,dynamic_state:setBlendConstant:*": { "subcaseMS": 0.367 }, + "webgpu:api,validation,encoding,cmds,render,dynamic_state:setScissorRect,x_y_width_height_nonnegative:*": { "subcaseMS": 2.900 }, + "webgpu:api,validation,encoding,cmds,render,dynamic_state:setScissorRect,xy_rect_contained_in_attachment:*": { "subcaseMS": 1.325 }, + "webgpu:api,validation,encoding,cmds,render,dynamic_state:setStencilReference:*": { "subcaseMS": 3.450 }, + "webgpu:api,validation,encoding,cmds,render,dynamic_state:setViewport,depth_rangeAndOrder:*": { "subcaseMS": 1.667 }, + "webgpu:api,validation,encoding,cmds,render,dynamic_state:setViewport,x_y_width_height_nonnegative:*": { "subcaseMS": 0.400 }, + "webgpu:api,validation,encoding,cmds,render,dynamic_state:setViewport,xy_rect_contained_in_attachment:*": { "subcaseMS": 0.200 }, + "webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_buffer,device_mismatch:*": { "subcaseMS": 2.000 }, + "webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_buffer_state:*": { "subcaseMS": 2.708 }, + "webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_buffer_usage:*": { "subcaseMS": 2.733 }, + "webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_offset_alignment:*": { "subcaseMS": 2.758 }, + "webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_offset_oob:*": { "subcaseMS": 0.725 }, + "webgpu:api,validation,encoding,cmds,render,setIndexBuffer:index_buffer,device_mismatch:*": { "subcaseMS": 7.800 }, + "webgpu:api,validation,encoding,cmds,render,setIndexBuffer:index_buffer_state:*": { "subcaseMS": 5.200 }, + "webgpu:api,validation,encoding,cmds,render,setIndexBuffer:index_buffer_usage:*": { "subcaseMS": 2.467 }, + "webgpu:api,validation,encoding,cmds,render,setIndexBuffer:offset_alignment:*": { "subcaseMS": 2.642 }, + "webgpu:api,validation,encoding,cmds,render,setIndexBuffer:offset_and_size_oob:*": { "subcaseMS": 1.067 }, + "webgpu:api,validation,encoding,cmds,render,setPipeline:invalid_pipeline:*": { "subcaseMS": 0.525 }, + "webgpu:api,validation,encoding,cmds,render,setPipeline:pipeline,device_mismatch:*": { "subcaseMS": 8.500 }, + "webgpu:api,validation,encoding,cmds,render,setVertexBuffer:offset_alignment:*": { "subcaseMS": 2.550 }, + "webgpu:api,validation,encoding,cmds,render,setVertexBuffer:offset_and_size_oob:*": { "subcaseMS": 0.200 }, + "webgpu:api,validation,encoding,cmds,render,setVertexBuffer:slot:*": { "subcaseMS": 5.300 }, + "webgpu:api,validation,encoding,cmds,render,setVertexBuffer:vertex_buffer,device_mismatch:*": { "subcaseMS": 7.850 }, + "webgpu:api,validation,encoding,cmds,render,setVertexBuffer:vertex_buffer_state:*": { "subcaseMS": 5.200 }, + "webgpu:api,validation,encoding,cmds,render,setVertexBuffer:vertex_buffer_usage:*": { "subcaseMS": 0.301 }, + "webgpu:api,validation,encoding,cmds,render,state_tracking:all_needed_index_buffer_should_be_bound:*": { "subcaseMS": 14.101 }, + "webgpu:api,validation,encoding,cmds,render,state_tracking:all_needed_vertex_buffer_should_be_bound:*": { "subcaseMS": 31.900 }, + "webgpu:api,validation,encoding,cmds,render,state_tracking:vertex_buffers_do_not_inherit_between_render_passes:*": { "subcaseMS": 3.400 }, + "webgpu:api,validation,encoding,cmds,render,state_tracking:vertex_buffers_inherit_from_previous_pipeline:*": { "subcaseMS": 31.701 }, + "webgpu:api,validation,encoding,cmds,setBindGroup:bind_group,device_mismatch:*": { "subcaseMS": 6.975 }, + "webgpu:api,validation,encoding,cmds,setBindGroup:buffer_dynamic_offsets:*": { "subcaseMS": 1.990 }, + "webgpu:api,validation,encoding,cmds,setBindGroup:dynamic_offsets_match_expectations_in_pass_encoder:*": { "subcaseMS": 3.949 }, + "webgpu:api,validation,encoding,cmds,setBindGroup:dynamic_offsets_passed_but_not_expected:*": { "subcaseMS": 0.900 }, + "webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:*": { "subcaseMS": 5.417 }, + "webgpu:api,validation,encoding,cmds,setBindGroup:u32array_start_and_length:*": { "subcaseMS": 3.020 }, + "webgpu:api,validation,encoding,createRenderBundleEncoder:attachment_state,empty_color_formats:*": { "subcaseMS": 0.450 }, + "webgpu:api,validation,encoding,createRenderBundleEncoder:attachment_state,limits,maxColorAttachmentBytesPerSample,aligned:*": { "subcaseMS": 2.641 }, + "webgpu:api,validation,encoding,createRenderBundleEncoder:attachment_state,limits,maxColorAttachmentBytesPerSample,unaligned:*": { "subcaseMS": 0.750 }, + "webgpu:api,validation,encoding,createRenderBundleEncoder:attachment_state,limits,maxColorAttachments:*": { "subcaseMS": 0.145 }, + "webgpu:api,validation,encoding,createRenderBundleEncoder:depth_stencil_readonly:*": { "subcaseMS": 1.804 }, + "webgpu:api,validation,encoding,createRenderBundleEncoder:depth_stencil_readonly_with_undefined_depth:*": { "subcaseMS": 14.825 }, + "webgpu:api,validation,encoding,createRenderBundleEncoder:valid_texture_formats:*": { "subcaseMS": 2.130 }, + "webgpu:api,validation,encoding,encoder_open_state:compute_pass_commands:*": { "subcaseMS": 4.208 }, + "webgpu:api,validation,encoding,encoder_open_state:non_pass_commands:*": { "subcaseMS": 26.191 }, + "webgpu:api,validation,encoding,encoder_open_state:render_bundle_commands:*": { "subcaseMS": 2.850 }, + "webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:*": { "subcaseMS": 3.620 }, + "webgpu:api,validation,encoding,encoder_state:call_after_successful_finish:*": { "subcaseMS": 0.800 }, + "webgpu:api,validation,encoding,encoder_state:pass_end_invalid_order:*": { "subcaseMS": 1.303 }, + "webgpu:api,validation,encoding,encoder_state:pass_end_none:*": { "subcaseMS": 8.150 }, + "webgpu:api,validation,encoding,encoder_state:pass_end_twice,basic:*": { "subcaseMS": 0.300 }, + "webgpu:api,validation,encoding,encoder_state:pass_end_twice,render_pass_invalid:*": { "subcaseMS": 15.850 }, + "webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bgl_binding_mismatch:*": { "subcaseMS": 1.301 }, + "webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bgl_resource_type_mismatch:*": { "subcaseMS": 0.977 }, + "webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bgl_visibility_mismatch:*": { "subcaseMS": 0.608 }, + "webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bind_groups_and_pipeline_layout_mismatch:*": { "subcaseMS": 1.535 }, + "webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:buffer_binding,render_pipeline:*": { "subcaseMS": 1.734 }, + "webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:empty_bind_group_layouts_requires_empty_bind_groups,compute_pass:*": { "subcaseMS": 2.325 }, + "webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:empty_bind_group_layouts_requires_empty_bind_groups,render_pass:*": { "subcaseMS": 10.838 }, + "webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:sampler_binding,render_pipeline:*": { "subcaseMS": 10.523 }, + "webgpu:api,validation,encoding,queries,begin_end:nesting:*": { "subcaseMS": 1.101 }, + "webgpu:api,validation,encoding,queries,begin_end:occlusion_query,begin_end_balance:*": { "subcaseMS": 0.820 }, + "webgpu:api,validation,encoding,queries,begin_end:occlusion_query,begin_end_invalid_nesting:*": { "subcaseMS": 1.000 }, + "webgpu:api,validation,encoding,queries,begin_end:occlusion_query,disjoint_queries_with_same_query_index:*": { "subcaseMS": 0.550 }, + "webgpu:api,validation,encoding,queries,general:occlusion_query,invalid_query_set:*": { "subcaseMS": 1.651 }, + "webgpu:api,validation,encoding,queries,general:occlusion_query,query_index:*": { "subcaseMS": 0.500 }, + "webgpu:api,validation,encoding,queries,general:occlusion_query,query_type:*": { "subcaseMS": 4.702 }, + "webgpu:api,validation,encoding,queries,general:timestamp_query,device_mismatch:*": { "subcaseMS": 0.101 }, + "webgpu:api,validation,encoding,queries,general:timestamp_query,invalid_query_set:*": { "subcaseMS": 0.101 }, + "webgpu:api,validation,encoding,queries,general:timestamp_query,query_type_and_index:*": { "subcaseMS": 0.301 }, + "webgpu:api,validation,encoding,queries,resolveQuerySet:destination_buffer_usage:*": { "subcaseMS": 16.050 }, + "webgpu:api,validation,encoding,queries,resolveQuerySet:destination_offset_alignment:*": { "subcaseMS": 0.325 }, + "webgpu:api,validation,encoding,queries,resolveQuerySet:first_query_and_query_count:*": { "subcaseMS": 0.250 }, + "webgpu:api,validation,encoding,queries,resolveQuerySet:query_set_buffer,device_mismatch:*": { "subcaseMS": 1.000 }, + "webgpu:api,validation,encoding,queries,resolveQuerySet:queryset_and_destination_buffer_state:*": { "subcaseMS": 9.078 }, + "webgpu:api,validation,encoding,queries,resolveQuerySet:resolve_buffer_oob:*": { "subcaseMS": 6.300 }, + "webgpu:api,validation,encoding,render_bundle:color_formats_mismatch:*": { "subcaseMS": 10.940 }, + "webgpu:api,validation,encoding,render_bundle:depth_stencil_formats_mismatch:*": { "subcaseMS": 4.050 }, + "webgpu:api,validation,encoding,render_bundle:depth_stencil_readonly_mismatch:*": { "subcaseMS": 4.488 }, + "webgpu:api,validation,encoding,render_bundle:device_mismatch:*": { "subcaseMS": 0.633 }, + "webgpu:api,validation,encoding,render_bundle:empty_bundle_list:*": { "subcaseMS": 30.301 }, + "webgpu:api,validation,encoding,render_bundle:sample_count_mismatch:*": { "subcaseMS": 8.325 }, + "webgpu:api,validation,error_scope:balanced_nesting:*": { "subcaseMS": 56.817 }, + "webgpu:api,validation,error_scope:balanced_siblings:*": { "subcaseMS": 95.950 }, + "webgpu:api,validation,error_scope:current_scope:*": { "subcaseMS": 1177.650 }, + "webgpu:api,validation,error_scope:empty:*": { "subcaseMS": 0.801 }, + "webgpu:api,validation,error_scope:parent_scope:*": { "subcaseMS": 11.601 }, + "webgpu:api,validation,error_scope:simple:*": { "subcaseMS": 10.317 }, + "webgpu:api,validation,getBindGroupLayout:index_range,auto_layout:*": { "subcaseMS": 6.300 }, + "webgpu:api,validation,getBindGroupLayout:index_range,explicit_layout:*": { "subcaseMS": 30.334 }, + "webgpu:api,validation,getBindGroupLayout:unique_js_object,auto_layout:*": { "subcaseMS": 1.601 }, + "webgpu:api,validation,getBindGroupLayout:unique_js_object,explicit_layout:*": { "subcaseMS": 0.900 }, + "webgpu:api,validation,gpu_external_texture_expiration:import_and_use_in_different_microtask:*": { "subcaseMS": 40.700 }, + "webgpu:api,validation,gpu_external_texture_expiration:import_and_use_in_different_task:*": { "subcaseMS": 41.901 }, + "webgpu:api,validation,gpu_external_texture_expiration:import_from_different_video_frame:*": { "subcaseMS": 82.101 }, + "webgpu:api,validation,gpu_external_texture_expiration:import_multiple_times_in_same_task_scope:*": { "subcaseMS": 130.150 }, + "webgpu:api,validation,gpu_external_texture_expiration:use_import_to_refresh:*": { "subcaseMS": 48.700 }, + "webgpu:api,validation,gpu_external_texture_expiration:webcodec_video_frame_close_expire_immediately:*": { "subcaseMS": 48.801 }, + "webgpu:api,validation,image_copy,buffer_related:buffer,device_mismatch:*": { "subcaseMS": 0.400 }, + "webgpu:api,validation,image_copy,buffer_related:buffer_state:*": { "subcaseMS": 1.034 }, + "webgpu:api,validation,image_copy,buffer_related:bytes_per_row_alignment:*": { "subcaseMS": 2.635 }, + "webgpu:api,validation,image_copy,buffer_related:usage:*": { "subcaseMS": 0.384 }, + "webgpu:api,validation,image_copy,buffer_texture_copies:depth_stencil_format,copy_buffer_offset:*": { "subcaseMS": 4.996 }, + "webgpu:api,validation,image_copy,buffer_texture_copies:depth_stencil_format,copy_buffer_size:*": { "subcaseMS": 1.728 }, + "webgpu:api,validation,image_copy,buffer_texture_copies:depth_stencil_format,copy_usage_and_aspect:*": { "subcaseMS": 6.467 }, + "webgpu:api,validation,image_copy,buffer_texture_copies:device_mismatch:*": { "subcaseMS": 2.767 }, + "webgpu:api,validation,image_copy,buffer_texture_copies:sample_count:*": { "subcaseMS": 14.575 }, + "webgpu:api,validation,image_copy,buffer_texture_copies:texture_buffer_usages:*": { "subcaseMS": 1.001 }, + "webgpu:api,validation,image_copy,layout_related:bound_on_bytes_per_row:*": { "subcaseMS": 1.133 }, + "webgpu:api,validation,image_copy,layout_related:bound_on_offset:*": { "subcaseMS": 0.833 }, + "webgpu:api,validation,image_copy,layout_related:bound_on_rows_per_image:*": { "subcaseMS": 2.666 }, + "webgpu:api,validation,image_copy,layout_related:copy_end_overflows_u64:*": { "subcaseMS": 0.567 }, + "webgpu:api,validation,image_copy,layout_related:offset_alignment:*": { "subcaseMS": 1.107 }, + "webgpu:api,validation,image_copy,layout_related:required_bytes_in_copy:*": { "subcaseMS": 1.051 }, + "webgpu:api,validation,image_copy,layout_related:rows_per_image_alignment:*": { "subcaseMS": 2.239 }, + "webgpu:api,validation,image_copy,texture_related:copy_rectangle:*": { "subcaseMS": 0.599 }, + "webgpu:api,validation,image_copy,texture_related:format:*": { "subcaseMS": 4.790 }, + "webgpu:api,validation,image_copy,texture_related:mip_level:*": { "subcaseMS": 2.632 }, + "webgpu:api,validation,image_copy,texture_related:origin_alignment:*": { "subcaseMS": 1.252 }, + "webgpu:api,validation,image_copy,texture_related:sample_count:*": { "subcaseMS": 5.717 }, + "webgpu:api,validation,image_copy,texture_related:size_alignment:*": { "subcaseMS": 1.076 }, + "webgpu:api,validation,image_copy,texture_related:texture,device_mismatch:*": { "subcaseMS": 5.417 }, + "webgpu:api,validation,image_copy,texture_related:usage:*": { "subcaseMS": 1.224 }, + "webgpu:api,validation,image_copy,texture_related:valid:*": { "subcaseMS": 3.678 }, + "webgpu:api,validation,query_set,create:count:*": { "subcaseMS": 0.967 }, + "webgpu:api,validation,query_set,destroy:invalid_queryset:*": { "subcaseMS": 0.801 }, + "webgpu:api,validation,query_set,destroy:twice:*": { "subcaseMS": 0.700 }, + "webgpu:api,validation,queue,buffer_mapped:copyBufferToBuffer:*": { "subcaseMS": 36.601 }, + "webgpu:api,validation,queue,buffer_mapped:copyBufferToTexture:*": { "subcaseMS": 33.000 }, + "webgpu:api,validation,queue,buffer_mapped:copyTextureToBuffer:*": { "subcaseMS": 32.301 }, + "webgpu:api,validation,queue,buffer_mapped:map_command_recording_order:*": { "subcaseMS": 3.091 }, + "webgpu:api,validation,queue,buffer_mapped:writeBuffer:*": { "subcaseMS": 34.901 }, + "webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:OOB,destination:*": { "subcaseMS": 0.512 }, + "webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:OOB,source:*": { "subcaseMS": 0.389 }, + "webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:destination_texture,device_mismatch:*": { "subcaseMS": 35.550 }, + "webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:destination_texture,format:*": { "subcaseMS": 2.180 }, + "webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:destination_texture,mipLevel:*": { "subcaseMS": 5.834 }, + "webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:destination_texture,sample_count:*": { "subcaseMS": 35.500 }, + "webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:destination_texture,state:*": { "subcaseMS": 26.667 }, + "webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:destination_texture,usage:*": { "subcaseMS": 22.760 }, + "webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:source_canvas,state:*": { "subcaseMS": 10.250 }, + "webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:source_image,crossOrigin:*": { "subcaseMS": 15.435 }, + "webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:source_imageBitmap,state:*": { "subcaseMS": 9.100 }, + "webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:source_offscreenCanvas,state:*": { "subcaseMS": 11.334 }, + "webgpu:api,validation,queue,destroyed,buffer:copyBufferToBuffer:*": { "subcaseMS": 0.800 }, + "webgpu:api,validation,queue,destroyed,buffer:copyBufferToTexture:*": { "subcaseMS": 1.401 }, + "webgpu:api,validation,queue,destroyed,buffer:copyTextureToBuffer:*": { "subcaseMS": 1.500 }, + "webgpu:api,validation,queue,destroyed,buffer:resolveQuerySet:*": { "subcaseMS": 16.550 }, + "webgpu:api,validation,queue,destroyed,buffer:setBindGroup:*": { "subcaseMS": 2.983 }, + "webgpu:api,validation,queue,destroyed,buffer:setIndexBuffer:*": { "subcaseMS": 8.150 }, + "webgpu:api,validation,queue,destroyed,buffer:setVertexBuffer:*": { "subcaseMS": 8.550 }, + "webgpu:api,validation,queue,destroyed,buffer:writeBuffer:*": { "subcaseMS": 2.151 }, + "webgpu:api,validation,queue,destroyed,query_set:beginOcclusionQuery:*": { "subcaseMS": 17.401 }, + "webgpu:api,validation,queue,destroyed,query_set:resolveQuerySet:*": { "subcaseMS": 16.401 }, + "webgpu:api,validation,queue,destroyed,query_set:writeTimestamp:*": { "subcaseMS": 0.901 }, + "webgpu:api,validation,queue,destroyed,texture:beginRenderPass:*": { "subcaseMS": 0.350 }, + "webgpu:api,validation,queue,destroyed,texture:copyBufferToTexture:*": { "subcaseMS": 16.550 }, + "webgpu:api,validation,queue,destroyed,texture:copyTextureToBuffer:*": { "subcaseMS": 15.900 }, + "webgpu:api,validation,queue,destroyed,texture:copyTextureToTexture:*": { "subcaseMS": 8.500 }, + "webgpu:api,validation,queue,destroyed,texture:setBindGroup:*": { "subcaseMS": 5.783 }, + "webgpu:api,validation,queue,destroyed,texture:writeTexture:*": { "subcaseMS": 16.601 }, + "webgpu:api,validation,queue,submit:command_buffer,device_mismatch:*": { "subcaseMS": 0.467 }, + "webgpu:api,validation,queue,writeBuffer:buffer,device_mismatch:*": { "subcaseMS": 16.000 }, + "webgpu:api,validation,queue,writeBuffer:buffer_state:*": { "subcaseMS": 6.201 }, + "webgpu:api,validation,queue,writeBuffer:ranges:*": { "subcaseMS": 17.600 }, + "webgpu:api,validation,queue,writeBuffer:usages:*": { "subcaseMS": 8.525 }, + "webgpu:api,validation,queue,writeTexture:sample_count:*": { "subcaseMS": 2.050 }, + "webgpu:api,validation,queue,writeTexture:texture,device_mismatch:*": { "subcaseMS": 7.850 }, + "webgpu:api,validation,queue,writeTexture:texture_state:*": { "subcaseMS": 18.567 }, + "webgpu:api,validation,queue,writeTexture:usages:*": { "subcaseMS": 0.700 }, + "webgpu:api,validation,render_pass,attachment_compatibility:render_pass_and_bundle,color_count:*": { "subcaseMS": 0.627 }, + "webgpu:api,validation,render_pass,attachment_compatibility:render_pass_and_bundle,color_format:*": { "subcaseMS": 0.200 }, + "webgpu:api,validation,render_pass,attachment_compatibility:render_pass_and_bundle,color_sparse:*": { "subcaseMS": 0.784 }, + "webgpu:api,validation,render_pass,attachment_compatibility:render_pass_and_bundle,depth_format:*": { "subcaseMS": 1.000 }, + "webgpu:api,validation,render_pass,attachment_compatibility:render_pass_and_bundle,device_mismatch:*": { "subcaseMS": 0.650 }, + "webgpu:api,validation,render_pass,attachment_compatibility:render_pass_and_bundle,sample_count:*": { "subcaseMS": 0.775 }, + "webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,color_count:*": { "subcaseMS": 0.543 }, + "webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,color_format:*": { "subcaseMS": 0.400 }, + "webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,color_sparse:*": { "subcaseMS": 0.511 }, + "webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,depth_format:*": { "subcaseMS": 0.840 }, + "webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,depth_stencil_read_only_write_state:*": { "subcaseMS": 0.361 }, + "webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,sample_count:*": { "subcaseMS": 0.456 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:attachments,color_depth_mismatch:*": { "subcaseMS": 33.000 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:attachments,layer_count:*": { "subcaseMS": 18.667 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:attachments,mip_level_count:*": { "subcaseMS": 5.468 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:attachments,one_color_attachment:*": { "subcaseMS": 33.401 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:attachments,one_depth_stencil_attachment:*": { "subcaseMS": 15.301 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:attachments,same_size:*": { "subcaseMS": 33.400 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,empty:*": { "subcaseMS": 0.400 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,limits,maxColorAttachmentBytesPerSample,aligned:*": { "subcaseMS": 1.825 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,limits,maxColorAttachmentBytesPerSample,unaligned:*": { "subcaseMS": 17.151 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,limits,maxColorAttachments:*": { "subcaseMS": 0.950 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,non_multisampled:*": { "subcaseMS": 32.601 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,sample_count:*": { "subcaseMS": 33.600 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:depth_stencil_attachment,depth_clear_value:*": { "subcaseMS": 39.956 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:depth_stencil_attachment,loadOp_storeOp_match_depthReadOnly_stencilReadOnly:*": { "subcaseMS": 1.701 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:depth_stencil_attachment,sample_counts_mismatch:*": { "subcaseMS": 15.801 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:occlusionQuerySet,query_set_type:*": { "subcaseMS": 32.400 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,array_layer_count:*": { "subcaseMS": 32.200 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,different_format:*": { "subcaseMS": 1.500 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,different_size:*": { "subcaseMS": 0.901 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,error_state:*": { "subcaseMS": 1.101 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,format_supports_resolve:*": { "subcaseMS": 3.370 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,mipmap_level_count:*": { "subcaseMS": 33.201 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,sample_count:*": { "subcaseMS": 32.500 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,single_sample_count:*": { "subcaseMS": 0.601 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,usage:*": { "subcaseMS": 15.125 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:timestampWrite,query_index:*": { "subcaseMS": 0.200 }, + "webgpu:api,validation,render_pass,render_pass_descriptor:timestampWrites,query_set_type:*": { "subcaseMS": 0.501 }, + "webgpu:api,validation,render_pass,resolve:resolve_attachment:*": { "subcaseMS": 6.205 }, + "webgpu:api,validation,render_pipeline,depth_stencil_state:depth_test:*": { "subcaseMS": 3.407 }, + "webgpu:api,validation,render_pipeline,depth_stencil_state:depth_write,frag_depth:*": { "subcaseMS": 6.465 }, + "webgpu:api,validation,render_pipeline,depth_stencil_state:depth_write:*": { "subcaseMS": 4.113 }, + "webgpu:api,validation,render_pipeline,depth_stencil_state:format:*": { "subcaseMS": 3.521 }, + "webgpu:api,validation,render_pipeline,depth_stencil_state:stencil_test:*": { "subcaseMS": 3.124 }, + "webgpu:api,validation,render_pipeline,depth_stencil_state:stencil_write:*": { "subcaseMS": 3.183 }, + "webgpu:api,validation,render_pipeline,fragment_state:color_target_exists:*": { "subcaseMS": 29.150 }, + "webgpu:api,validation,render_pipeline,fragment_state:limits,maxColorAttachmentBytesPerSample,aligned:*": { "subcaseMS": 0.991 }, + "webgpu:api,validation,render_pipeline,fragment_state:limits,maxColorAttachmentBytesPerSample,unaligned:*": { "subcaseMS": 14.750 }, + "webgpu:api,validation,render_pipeline,fragment_state:limits,maxColorAttachments:*": { "subcaseMS": 9.351 }, + "webgpu:api,validation,render_pipeline,fragment_state:pipeline_output_targets,blend:*": { "subcaseMS": 0.551 }, + "webgpu:api,validation,render_pipeline,fragment_state:pipeline_output_targets:*": { "subcaseMS": 0.497 }, + "webgpu:api,validation,render_pipeline,fragment_state:targets_blend:*": { "subcaseMS": 1.203 }, + "webgpu:api,validation,render_pipeline,fragment_state:targets_format_filterable:*": { "subcaseMS": 2.143 }, + "webgpu:api,validation,render_pipeline,fragment_state:targets_format_renderable:*": { "subcaseMS": 3.339 }, + "webgpu:api,validation,render_pipeline,fragment_state:targets_write_mask:*": { "subcaseMS": 12.272 }, + "webgpu:api,validation,render_pipeline,inter_stage:interpolation_sampling:*": { "subcaseMS": 3.126 }, + "webgpu:api,validation,render_pipeline,inter_stage:interpolation_type:*": { "subcaseMS": 4.071 }, + "webgpu:api,validation,render_pipeline,inter_stage:location,mismatch:*": { "subcaseMS": 7.280 }, + "webgpu:api,validation,render_pipeline,inter_stage:location,subset:*": { "subcaseMS": 1.250 }, + "webgpu:api,validation,render_pipeline,inter_stage:location,superset:*": { "subcaseMS": 0.901 }, + "webgpu:api,validation,render_pipeline,inter_stage:max_components_count,input:*": { "subcaseMS": 6.560 }, + "webgpu:api,validation,render_pipeline,inter_stage:max_components_count,output:*": { "subcaseMS": 8.426 }, + "webgpu:api,validation,render_pipeline,inter_stage:max_shader_variable_location:*": { "subcaseMS": 11.050 }, + "webgpu:api,validation,render_pipeline,inter_stage:type:*": { "subcaseMS": 6.170 }, + "webgpu:api,validation,render_pipeline,misc:basic:*": { "subcaseMS": 0.901 }, + "webgpu:api,validation,render_pipeline,misc:pipeline_layout,device_mismatch:*": { "subcaseMS": 8.700 }, + "webgpu:api,validation,render_pipeline,misc:vertex_state_only:*": { "subcaseMS": 1.125 }, + "webgpu:api,validation,render_pipeline,multisample_state:alpha_to_coverage,count:*": { "subcaseMS": 3.200 }, + "webgpu:api,validation,render_pipeline,multisample_state:alpha_to_coverage,sample_mask:*": { "subcaseMS": 0.725 }, + "webgpu:api,validation,render_pipeline,multisample_state:count:*": { "subcaseMS": 2.325 }, + "webgpu:api,validation,render_pipeline,overrides:identifier,fragment:*": { "subcaseMS": 6.330 }, + "webgpu:api,validation,render_pipeline,overrides:identifier,vertex:*": { "subcaseMS": 4.784 }, + "webgpu:api,validation,render_pipeline,overrides:uninitialized,fragment:*": { "subcaseMS": 11.525 }, + "webgpu:api,validation,render_pipeline,overrides:uninitialized,vertex:*": { "subcaseMS": 5.513 }, + "webgpu:api,validation,render_pipeline,overrides:value,type_error,fragment:*": { "subcaseMS": 7.700 }, + "webgpu:api,validation,render_pipeline,overrides:value,type_error,vertex:*": { "subcaseMS": 5.200 }, + "webgpu:api,validation,render_pipeline,overrides:value,validation_error,f16,fragment:*": { "subcaseMS": 4.708 }, + "webgpu:api,validation,render_pipeline,overrides:value,validation_error,f16,vertex:*": { "subcaseMS": 5.610 }, + "webgpu:api,validation,render_pipeline,overrides:value,validation_error,fragment:*": { "subcaseMS": 6.840 }, + "webgpu:api,validation,render_pipeline,overrides:value,validation_error,vertex:*": { "subcaseMS": 6.022 }, + "webgpu:api,validation,render_pipeline,primitive_state:strip_index_format:*": { "subcaseMS": 5.267 }, + "webgpu:api,validation,render_pipeline,primitive_state:unclipped_depth:*": { "subcaseMS": 1.025 }, + "webgpu:api,validation,render_pipeline,shader_module:device_mismatch:*": { "subcaseMS": 0.700 }, + "webgpu:api,validation,render_pipeline,shader_module:invalid,fragment:*": { "subcaseMS": 5.800 }, + "webgpu:api,validation,render_pipeline,shader_module:invalid,vertex:*": { "subcaseMS": 15.151 }, + "webgpu:api,validation,render_pipeline,vertex_state:many_attributes_overlapping:*": { "subcaseMS": 2.000 }, + "webgpu:api,validation,render_pipeline,vertex_state:max_vertex_attribute_limit:*": { "subcaseMS": 2.817 }, + "webgpu:api,validation,render_pipeline,vertex_state:max_vertex_buffer_array_stride_limit:*": { "subcaseMS": 1.972 }, + "webgpu:api,validation,render_pipeline,vertex_state:max_vertex_buffer_limit:*": { "subcaseMS": 4.550 }, + "webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_contained_in_stride:*": { "subcaseMS": 0.244 }, + "webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_offset_alignment:*": { "subcaseMS": 1.213 }, + "webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_shaderLocation_limit:*": { "subcaseMS": 0.649 }, + "webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_shaderLocation_unique:*": { "subcaseMS": 0.200 }, + "webgpu:api,validation,render_pipeline,vertex_state:vertex_buffer_array_stride_limit_alignment:*": { "subcaseMS": 0.300 }, + "webgpu:api,validation,render_pipeline,vertex_state:vertex_shader_input_location_in_vertex_state:*": { "subcaseMS": 0.819 }, + "webgpu:api,validation,render_pipeline,vertex_state:vertex_shader_input_location_limit:*": { "subcaseMS": 7.000 }, + "webgpu:api,validation,render_pipeline,vertex_state:vertex_shader_type_matches_attribute_format:*": { "subcaseMS": 1.647 }, + "webgpu:api,validation,resource_usages,buffer,in_pass_encoder:subresources,buffer_usage_in_compute_pass_with_two_dispatches:*": { "subcaseMS": 2.950 }, + "webgpu:api,validation,resource_usages,buffer,in_pass_encoder:subresources,buffer_usage_in_one_compute_pass_with_no_dispatch:*": { "subcaseMS": 1.913 }, + "webgpu:api,validation,resource_usages,buffer,in_pass_encoder:subresources,buffer_usage_in_one_compute_pass_with_one_dispatch:*": { "subcaseMS": 0.834 }, + "webgpu:api,validation,resource_usages,buffer,in_pass_encoder:subresources,buffer_usage_in_one_render_pass_with_no_draw:*": { "subcaseMS": 1.458 }, + "webgpu:api,validation,resource_usages,buffer,in_pass_encoder:subresources,buffer_usage_in_one_render_pass_with_one_draw:*": { "subcaseMS": 0.987 }, + "webgpu:api,validation,resource_usages,buffer,in_pass_encoder:subresources,buffer_usage_in_one_render_pass_with_two_draws:*": { "subcaseMS": 2.027 }, + "webgpu:api,validation,resource_usages,buffer,in_pass_misc:subresources,buffer_usages_in_copy_and_pass:*": { "subcaseMS": 7.673 }, + "webgpu:api,validation,resource_usages,buffer,in_pass_misc:subresources,reset_buffer_usage_before_dispatch:*": { "subcaseMS": 8.242 }, + "webgpu:api,validation,resource_usages,buffer,in_pass_misc:subresources,reset_buffer_usage_before_draw:*": { "subcaseMS": 4.953 }, + "webgpu:api,validation,resource_usages,texture,in_pass_encoder:bindings_in_bundle:*": { "subcaseMS": 3.281 }, + "webgpu:api,validation,resource_usages,texture,in_pass_encoder:replaced_binding:*": { "subcaseMS": 0.888 }, + "webgpu:api,validation,resource_usages,texture,in_pass_encoder:scope,basic,render:*": { "subcaseMS": 8.500 }, + "webgpu:api,validation,resource_usages,texture,in_pass_encoder:scope,dispatch:*": { "subcaseMS": 12.034 }, + "webgpu:api,validation,resource_usages,texture,in_pass_encoder:scope,pass_boundary,compute:*": { "subcaseMS": 16.550 }, + "webgpu:api,validation,resource_usages,texture,in_pass_encoder:scope,pass_boundary,render:*": { "subcaseMS": 8.700 }, + "webgpu:api,validation,resource_usages,texture,in_pass_encoder:shader_stages_and_visibility,attachment_write:*": { "subcaseMS": 4.425 }, + "webgpu:api,validation,resource_usages,texture,in_pass_encoder:shader_stages_and_visibility,storage_write:*": { "subcaseMS": 1.415 }, + "webgpu:api,validation,resource_usages,texture,in_pass_encoder:subresources_and_binding_types_combination_for_aspect:*": { "subcaseMS": 1.152 }, + "webgpu:api,validation,resource_usages,texture,in_pass_encoder:subresources_and_binding_types_combination_for_color:*": { "subcaseMS": 2.052 }, + "webgpu:api,validation,resource_usages,texture,in_pass_encoder:unused_bindings_in_pipeline:*": { "subcaseMS": 4.219 }, + "webgpu:api,validation,resource_usages,texture,in_render_common:subresources,color_attachment_and_bind_group:*": { "subcaseMS": 3.042 }, + "webgpu:api,validation,resource_usages,texture,in_render_common:subresources,color_attachments:*": { "subcaseMS": 3.175 }, + "webgpu:api,validation,resource_usages,texture,in_render_common:subresources,depth_stencil_attachment_and_bind_group:*": { "subcaseMS": 1.667 }, + "webgpu:api,validation,resource_usages,texture,in_render_common:subresources,depth_stencil_texture_in_bind_groups:*": { "subcaseMS": 3.050 }, + "webgpu:api,validation,resource_usages,texture,in_render_common:subresources,multiple_bind_groups:*": { "subcaseMS": 3.045 }, + "webgpu:api,validation,resource_usages,texture,in_render_misc:subresources,set_bind_group_on_same_index_color_texture:*": { "subcaseMS": 4.541 }, + "webgpu:api,validation,resource_usages,texture,in_render_misc:subresources,set_bind_group_on_same_index_depth_stencil_texture:*": { "subcaseMS": 0.925 }, + "webgpu:api,validation,resource_usages,texture,in_render_misc:subresources,set_unused_bind_group:*": { "subcaseMS": 6.200 }, + "webgpu:api,validation,resource_usages,texture,in_render_misc:subresources,texture_usages_in_copy_and_render_pass:*": { "subcaseMS": 4.763 }, + "webgpu:api,validation,shader_module,entry_point:compute:*": { "subcaseMS": 4.439 }, + "webgpu:api,validation,shader_module,entry_point:fragment:*": { "subcaseMS": 5.865 }, + "webgpu:api,validation,shader_module,entry_point:vertex:*": { "subcaseMS": 5.803 }, + "webgpu:api,validation,shader_module,overrides:id_conflict:*": { "subcaseMS": 36.700 }, + "webgpu:api,validation,shader_module,overrides:name_conflict:*": { "subcaseMS": 1.500 }, + "webgpu:api,validation,state,device_lost,destroy:command,clearBuffer:*": { "subcaseMS": 11.826 }, + "webgpu:api,validation,state,device_lost,destroy:command,computePass,dispatch:*": { "subcaseMS": 75.850 }, + "webgpu:api,validation,state,device_lost,destroy:command,copyBufferToBuffer:*": { "subcaseMS": 32.100 }, + "webgpu:api,validation,state,device_lost,destroy:command,copyBufferToTexture:*": { "subcaseMS": 1.450 }, + "webgpu:api,validation,state,device_lost,destroy:command,copyTextureToBuffer:*": { "subcaseMS": 32.400 }, + "webgpu:api,validation,state,device_lost,destroy:command,copyTextureToTexture:*": { "subcaseMS": 9.650 }, + "webgpu:api,validation,state,device_lost,destroy:command,renderPass,draw:*": { "subcaseMS": 26.526 }, + "webgpu:api,validation,state,device_lost,destroy:command,renderPass,renderBundle:*": { "subcaseMS": 21.125 }, + "webgpu:api,validation,state,device_lost,destroy:command,resolveQuerySet:*": { "subcaseMS": 32.725 }, + "webgpu:api,validation,state,device_lost,destroy:command,writeTimestamp:*": { "subcaseMS": 0.704 }, + "webgpu:api,validation,state,device_lost,destroy:createBindGroup:*": { "subcaseMS": 91.575 }, + "webgpu:api,validation,state,device_lost,destroy:createBindGroupLayout:*": { "subcaseMS": 22.984 }, + "webgpu:api,validation,state,device_lost,destroy:createBuffer:*": { "subcaseMS": 5.030 }, + "webgpu:api,validation,state,device_lost,destroy:createCommandEncoder:*": { "subcaseMS": 35.100 }, + "webgpu:api,validation,state,device_lost,destroy:createComputePipeline:*": { "subcaseMS": 39.750 }, + "webgpu:api,validation,state,device_lost,destroy:createComputePipelineAsync:*": { "subcaseMS": 11.476 }, + "webgpu:api,validation,state,device_lost,destroy:createPipelineLayout:*": { "subcaseMS": 22.145 }, + "webgpu:api,validation,state,device_lost,destroy:createQuerySet:*": { "subcaseMS": 30.001 }, + "webgpu:api,validation,state,device_lost,destroy:createRenderBundleEncoder:*": { "subcaseMS": 13.350 }, + "webgpu:api,validation,state,device_lost,destroy:createRenderPipeline:*": { "subcaseMS": 39.450 }, + "webgpu:api,validation,state,device_lost,destroy:createRenderPipelineAsync:*": { "subcaseMS": 19.025 }, + "webgpu:api,validation,state,device_lost,destroy:createSampler:*": { "subcaseMS": 31.401 }, + "webgpu:api,validation,state,device_lost,destroy:createShaderModule:*": { "subcaseMS": 19.750 }, + "webgpu:api,validation,state,device_lost,destroy:createTexture,2d,compressed_format:*": { "subcaseMS": 14.241 }, + "webgpu:api,validation,state,device_lost,destroy:createTexture,2d,uncompressed_format:*": { "subcaseMS": 7.622 }, + "webgpu:api,validation,state,device_lost,destroy:createView,2d,compressed_format:*": { "subcaseMS": 19.612 }, + "webgpu:api,validation,state,device_lost,destroy:createView,2d,uncompressed_format:*": { "subcaseMS": 19.895 }, + "webgpu:api,validation,state,device_lost,destroy:importExternalTexture:*": { "subcaseMS": 92.051 }, + "webgpu:api,validation,state,device_lost,destroy:queue,copyExternalImageToTexture,canvas:*": { "subcaseMS": 28.596 }, + "webgpu:api,validation,state,device_lost,destroy:queue,copyExternalImageToTexture,imageBitmap:*": { "subcaseMS": 31.950 }, + "webgpu:api,validation,state,device_lost,destroy:queue,writeBuffer:*": { "subcaseMS": 18.851 }, + "webgpu:api,validation,state,device_lost,destroy:queue,writeTexture,2d,compressed_format:*": { "subcaseMS": 18.115 }, + "webgpu:api,validation,state,device_lost,destroy:queue,writeTexture,2d,uncompressed_format:*": { "subcaseMS": 17.620 }, + "webgpu:api,validation,texture,bgra8unorm_storage:configure_storage_usage_on_canvas_context_with_bgra8unorm_storage:*": { "subcaseMS": 3.230 }, + "webgpu:api,validation,texture,bgra8unorm_storage:configure_storage_usage_on_canvas_context_without_bgra8unorm_storage:*": { "subcaseMS": 1.767 }, + "webgpu:api,validation,texture,bgra8unorm_storage:create_bind_group_layout:*": { "subcaseMS": 21.500 }, + "webgpu:api,validation,texture,bgra8unorm_storage:create_shader_module_with_bgra8unorm_storage:*": { "subcaseMS": 11.201 }, + "webgpu:api,validation,texture,bgra8unorm_storage:create_shader_module_without_bgra8unorm_storage:*": { "subcaseMS": 1.601 }, + "webgpu:api,validation,texture,bgra8unorm_storage:create_texture:*": { "subcaseMS": 22.900 }, + "webgpu:api,validation,texture,destroy:base:*": { "subcaseMS": 4.000 }, + "webgpu:api,validation,texture,destroy:invalid_texture:*": { "subcaseMS": 27.200 }, + "webgpu:api,validation,texture,destroy:submit_a_destroyed_texture_as_attachment:*": { "subcaseMS": 11.812 }, + "webgpu:api,validation,texture,destroy:twice:*": { "subcaseMS": 1.400 }, + "webgpu:api,validation,texture,float32_filterable:create_bind_group:*": { "subcaseMS": 0.901 }, + "webgpu:api,validation,texture,rg11b10ufloat_renderable:begin_render_bundle_encoder:*": { "subcaseMS": 1.101 }, + "webgpu:api,validation,texture,rg11b10ufloat_renderable:begin_render_pass_msaa_and_resolve:*": { "subcaseMS": 0.900 }, + "webgpu:api,validation,texture,rg11b10ufloat_renderable:begin_render_pass_single_sampled:*": { "subcaseMS": 1.200 }, + "webgpu:api,validation,texture,rg11b10ufloat_renderable:create_render_pipeline:*": { "subcaseMS": 2.400 }, + "webgpu:api,validation,texture,rg11b10ufloat_renderable:create_texture:*": { "subcaseMS": 12.700 }, + "webgpu:compat,api,validation,encoding,cmds,copyTextureToBuffer:compressed:*": { "subcaseMS": 202.929 }, + "webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,compute_pass,unused:*": { "subcaseMS": 1.501 }, + "webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,compute_pass,used:*": { "subcaseMS": 49.405 }, + "webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,render_pass,unused:*": { "subcaseMS": 16.002 }, + "webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,render_pass,used:*": { "subcaseMS": 0.000 }, + "webgpu:compat,api,validation,render_pipeline,fragment_state:colorState:*": { "subcaseMS": 32.604 }, + "webgpu:compat,api,validation,render_pipeline,shader_module:sample_mask:*": { "subcaseMS": 14.801 }, + "webgpu:compat,api,validation,texture,createTexture:unsupportedTextureFormats:*": { "subcaseMS": 0.700 }, + "webgpu:compat,api,validation,texture,createTexture:unsupportedTextureViewFormats:*": { "subcaseMS": 0.601 }, + "webgpu:compat,api,validation,texture,cubeArray:cube_array:*": { "subcaseMS": 13.701 }, + "webgpu:examples:basic,async:*": { "subcaseMS": 16.401 }, + "webgpu:examples:basic,builder_cases:*": { "subcaseMS": 7.275 }, + "webgpu:examples:basic,builder_cases_subcases:*": { "subcaseMS": 0.425 }, + "webgpu:examples:basic,builder_subcases:*": { "subcaseMS": 0.175 }, + "webgpu:examples:basic,builder_subcases_short:*": { "subcaseMS": 3.300 }, + "webgpu:examples:basic,plain_cases:*": { "subcaseMS": 8.450 }, + "webgpu:examples:basic,plain_cases_private:*": { "subcaseMS": 9.850 }, + "webgpu:examples:basic:*": { "subcaseMS": 0.901 }, + "webgpu:examples:gpu,async:*": { "subcaseMS": 1.600 }, + "webgpu:examples:gpu,buffers:*": { "subcaseMS": 17.301 }, + "webgpu:examples:gpu,with_texture_compression,bc:*": { "subcaseMS": 7.500 }, + "webgpu:examples:gpu,with_texture_compression,etc2:*": { "subcaseMS": 0.750 }, + "webgpu:examples:not_implemented_yet,with_plan:*": { "subcaseMS": 0.500 }, + "webgpu:examples:not_implemented_yet,without_plan:*": { "subcaseMS": 0.701 }, + "webgpu:examples:test_name:*": { "subcaseMS": 14.601 }, + "webgpu:idl,constants,flags:BufferUsage,count:*": { "subcaseMS": 0.301 }, + "webgpu:idl,constants,flags:BufferUsage,values:*": { "subcaseMS": 0.120 }, + "webgpu:idl,constants,flags:ColorWrite,count:*": { "subcaseMS": 0.101 }, + "webgpu:idl,constants,flags:ColorWrite,values:*": { "subcaseMS": 0.101 }, + "webgpu:idl,constants,flags:ShaderStage,count:*": { "subcaseMS": 0.101 }, + "webgpu:idl,constants,flags:ShaderStage,values:*": { "subcaseMS": 0.034 }, + "webgpu:idl,constants,flags:TextureUsage,count:*": { "subcaseMS": 0.101 }, + "webgpu:idl,constants,flags:TextureUsage,values:*": { "subcaseMS": 0.040 }, + "webgpu:shader,execution,expression,binary,af_addition:scalar:*": { "subcaseMS": 290.000 }, + "webgpu:shader,execution,expression,binary,af_addition:scalar_vector:*": { "subcaseMS": 665.234 }, + "webgpu:shader,execution,expression,binary,af_addition:vector_scalar:*": { "subcaseMS": 664.434 }, + "webgpu:shader,execution,expression,binary,af_comparison:equals:*": { "subcaseMS": 23.000 }, + "webgpu:shader,execution,expression,binary,af_comparison:greater_equals:*": { "subcaseMS": 20.651 }, + "webgpu:shader,execution,expression,binary,af_comparison:greater_than:*": { "subcaseMS": 19.901 }, + "webgpu:shader,execution,expression,binary,af_comparison:less_equals:*": { "subcaseMS": 19.651 }, + "webgpu:shader,execution,expression,binary,af_comparison:less_than:*": { "subcaseMS": 19.975 }, + "webgpu:shader,execution,expression,binary,af_comparison:not_equals:*": { "subcaseMS": 19.651 }, + "webgpu:shader,execution,expression,binary,bitwise:bitwise_and:*": { "subcaseMS": 20.982 }, + "webgpu:shader,execution,expression,binary,bitwise:bitwise_and_compound:*": { "subcaseMS": 22.513 }, + "webgpu:shader,execution,expression,binary,bitwise:bitwise_exclusive_or:*": { "subcaseMS": 21.294 }, + "webgpu:shader,execution,expression,binary,bitwise:bitwise_exclusive_or_compound:*": { "subcaseMS": 21.326 }, + "webgpu:shader,execution,expression,binary,bitwise:bitwise_or:*": { "subcaseMS": 23.782 }, + "webgpu:shader,execution,expression,binary,bitwise:bitwise_or_compound:*": { "subcaseMS": 27.088 }, + "webgpu:shader,execution,expression,binary,bitwise_shift:shift_left_concrete:*": { "subcaseMS": 10.466 }, + "webgpu:shader,execution,expression,binary,bitwise_shift:shift_left_concrete_compound:*": { "subcaseMS": 9.657 }, + "webgpu:shader,execution,expression,binary,bitwise_shift:shift_right_concrete:*": { "subcaseMS": 11.744 }, + "webgpu:shader,execution,expression,binary,bitwise_shift:shift_right_concrete_compound:*": { "subcaseMS": 11.097 }, + "webgpu:shader,execution,expression,binary,bool_logical:and:*": { "subcaseMS": 7.325 }, + "webgpu:shader,execution,expression,binary,bool_logical:and_compound:*": { "subcaseMS": 8.044 }, + "webgpu:shader,execution,expression,binary,bool_logical:and_short_circuit:*": { "subcaseMS": 8.950 }, + "webgpu:shader,execution,expression,binary,bool_logical:equals:*": { "subcaseMS": 7.075 }, + "webgpu:shader,execution,expression,binary,bool_logical:not_equals:*": { "subcaseMS": 8.800 }, + "webgpu:shader,execution,expression,binary,bool_logical:or:*": { "subcaseMS": 6.663 }, + "webgpu:shader,execution,expression,binary,bool_logical:or_compound:*": { "subcaseMS": 7.407 }, + "webgpu:shader,execution,expression,binary,bool_logical:or_short_circuit:*": { "subcaseMS": 10.050 }, + "webgpu:shader,execution,expression,binary,f16_addition:scalar:*": { "subcaseMS": 6.807 }, + "webgpu:shader,execution,expression,binary,f16_addition:scalar_compound:*": { "subcaseMS": 4.010 }, + "webgpu:shader,execution,expression,binary,f16_addition:scalar_vector:*": { "subcaseMS": 2.606 }, + "webgpu:shader,execution,expression,binary,f16_addition:vector_scalar:*": { "subcaseMS": 3.006 }, + "webgpu:shader,execution,expression,binary,f16_addition:vector_scalar_compound:*": { "subcaseMS": 2.503 }, + "webgpu:shader,execution,expression,binary,f16_comparison:equals:*": { "subcaseMS": 3.907 }, + "webgpu:shader,execution,expression,binary,f16_comparison:greater_equals:*": { "subcaseMS": 3.507 }, + "webgpu:shader,execution,expression,binary,f16_comparison:greater_than:*": { "subcaseMS": 3.908 }, + "webgpu:shader,execution,expression,binary,f16_comparison:less_equals:*": { "subcaseMS": 3.108 }, + "webgpu:shader,execution,expression,binary,f16_comparison:less_than:*": { "subcaseMS": 3.508 }, + "webgpu:shader,execution,expression,binary,f16_comparison:not_equals:*": { "subcaseMS": 3.405 }, + "webgpu:shader,execution,expression,binary,f16_division:scalar:*": { "subcaseMS": 3.105 }, + "webgpu:shader,execution,expression,binary,f16_division:scalar_compound:*": { "subcaseMS": 4.011 }, + "webgpu:shader,execution,expression,binary,f16_division:scalar_vector:*": { "subcaseMS": 2.406 }, + "webgpu:shader,execution,expression,binary,f16_division:vector_scalar:*": { "subcaseMS": 3.006 }, + "webgpu:shader,execution,expression,binary,f16_division:vector_scalar_compound:*": { "subcaseMS": 3.005 }, + "webgpu:shader,execution,expression,binary,f16_multiplication:scalar:*": { "subcaseMS": 4.010 }, + "webgpu:shader,execution,expression,binary,f16_multiplication:scalar_compound:*": { "subcaseMS": 3.906 }, + "webgpu:shader,execution,expression,binary,f16_multiplication:scalar_vector:*": { "subcaseMS": 2.708 }, + "webgpu:shader,execution,expression,binary,f16_multiplication:vector_scalar:*": { "subcaseMS": 3.306 }, + "webgpu:shader,execution,expression,binary,f16_multiplication:vector_scalar_compound:*": { "subcaseMS": 2.501 }, + "webgpu:shader,execution,expression,binary,f16_subtraction:scalar:*": { "subcaseMS": 3.406 }, + "webgpu:shader,execution,expression,binary,f16_subtraction:scalar_compound:*": { "subcaseMS": 4.203 }, + "webgpu:shader,execution,expression,binary,f16_subtraction:scalar_vector:*": { "subcaseMS": 2.602 }, + "webgpu:shader,execution,expression,binary,f16_subtraction:vector_scalar:*": { "subcaseMS": 2.605 }, + "webgpu:shader,execution,expression,binary,f16_subtraction:vector_scalar_compound:*": { "subcaseMS": 2.604 }, + "webgpu:shader,execution,expression,binary,f32_addition:scalar:*": { "subcaseMS": 17.788 }, + "webgpu:shader,execution,expression,binary,f32_addition:scalar_compound:*": { "subcaseMS": 9.919 }, + "webgpu:shader,execution,expression,binary,f32_addition:scalar_vector:*": { "subcaseMS": 12.600 }, + "webgpu:shader,execution,expression,binary,f32_addition:vector_scalar:*": { "subcaseMS": 12.550 }, + "webgpu:shader,execution,expression,binary,f32_addition:vector_scalar_compound:*": { "subcaseMS": 12.142 }, + "webgpu:shader,execution,expression,binary,f32_comparison:equals:*": { "subcaseMS": 9.638 }, + "webgpu:shader,execution,expression,binary,f32_comparison:greater_equals:*": { "subcaseMS": 7.882 }, + "webgpu:shader,execution,expression,binary,f32_comparison:greater_than:*": { "subcaseMS": 7.388 }, + "webgpu:shader,execution,expression,binary,f32_comparison:less_equals:*": { "subcaseMS": 6.632 }, + "webgpu:shader,execution,expression,binary,f32_comparison:less_than:*": { "subcaseMS": 6.969 }, + "webgpu:shader,execution,expression,binary,f32_comparison:not_equals:*": { "subcaseMS": 6.819 }, + "webgpu:shader,execution,expression,binary,f32_division:scalar:*": { "subcaseMS": 19.688 }, + "webgpu:shader,execution,expression,binary,f32_division:scalar_compound:*": { "subcaseMS": 8.294 }, + "webgpu:shader,execution,expression,binary,f32_division:scalar_vector:*": { "subcaseMS": 19.142 }, + "webgpu:shader,execution,expression,binary,f32_division:vector_scalar:*": { "subcaseMS": 17.900 }, + "webgpu:shader,execution,expression,binary,f32_division:vector_scalar_compound:*": { "subcaseMS": 9.859 }, + "webgpu:shader,execution,expression,binary,f32_matrix_addition:matrix:*": { "subcaseMS": 35.020 }, + "webgpu:shader,execution,expression,binary,f32_matrix_addition:matrix_compound:*": { "subcaseMS": 27.534 }, + "webgpu:shader,execution,expression,binary,f32_matrix_matrix_multiplication:matrix_matrix:*": { "subcaseMS": 134.680 }, + "webgpu:shader,execution,expression,binary,f32_matrix_matrix_multiplication:matrix_matrix_compound:*": { "subcaseMS": 24.848 }, + "webgpu:shader,execution,expression,binary,f32_matrix_scalar_multiplication:matrix_scalar:*": { "subcaseMS": 96.756 }, + "webgpu:shader,execution,expression,binary,f32_matrix_scalar_multiplication:matrix_scalar_compound:*": { "subcaseMS": 21.181 }, + "webgpu:shader,execution,expression,binary,f32_matrix_scalar_multiplication:scalar_matrix:*": { "subcaseMS": 21.600 }, + "webgpu:shader,execution,expression,binary,f32_matrix_subtraction:matrix:*": { "subcaseMS": 34.489 }, + "webgpu:shader,execution,expression,binary,f32_matrix_subtraction:matrix_compound:*": { "subcaseMS": 27.645 }, + "webgpu:shader,execution,expression,binary,f32_matrix_vector_multiplication:matrix_vector:*": { "subcaseMS": 105.139 }, + "webgpu:shader,execution,expression,binary,f32_matrix_vector_multiplication:vector_matrix:*": { "subcaseMS": 22.501 }, + "webgpu:shader,execution,expression,binary,f32_matrix_vector_multiplication:vector_matrix_compound:*": { "subcaseMS": 16.217 }, + "webgpu:shader,execution,expression,binary,f32_multiplication:scalar:*": { "subcaseMS": 26.382 }, + "webgpu:shader,execution,expression,binary,f32_multiplication:scalar_compound:*": { "subcaseMS": 10.250 }, + "webgpu:shader,execution,expression,binary,f32_multiplication:scalar_vector:*": { "subcaseMS": 35.359 }, + "webgpu:shader,execution,expression,binary,f32_multiplication:vector_scalar:*": { "subcaseMS": 34.834 }, + "webgpu:shader,execution,expression,binary,f32_multiplication:vector_scalar_compound:*": { "subcaseMS": 11.609 }, + "webgpu:shader,execution,expression,binary,f32_remainder:scalar:*": { "subcaseMS": 21.982 }, + "webgpu:shader,execution,expression,binary,f32_remainder:scalar_compound:*": { "subcaseMS": 8.844 }, + "webgpu:shader,execution,expression,binary,f32_remainder:scalar_vector:*": { "subcaseMS": 10.650 }, + "webgpu:shader,execution,expression,binary,f32_remainder:vector_scalar:*": { "subcaseMS": 9.525 }, + "webgpu:shader,execution,expression,binary,f32_remainder:vector_scalar_compound:*": { "subcaseMS": 9.925 }, + "webgpu:shader,execution,expression,binary,f32_subtraction:scalar:*": { "subcaseMS": 12.813 }, + "webgpu:shader,execution,expression,binary,f32_subtraction:scalar_compound:*": { "subcaseMS": 9.213 }, + "webgpu:shader,execution,expression,binary,f32_subtraction:scalar_vector:*": { "subcaseMS": 14.125 }, + "webgpu:shader,execution,expression,binary,f32_subtraction:vector_scalar:*": { "subcaseMS": 13.292 }, + "webgpu:shader,execution,expression,binary,f32_subtraction:vector_scalar_compound:*": { "subcaseMS": 13.150 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:addition:*": { "subcaseMS": 23.975 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:addition_compound:*": { "subcaseMS": 9.219 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:addition_scalar_vector:*": { "subcaseMS": 33.059 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:addition_vector_scalar:*": { "subcaseMS": 32.475 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:addition_vector_scalar_compound:*": { "subcaseMS": 30.875 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:division:*": { "subcaseMS": 8.444 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:division_compound:*": { "subcaseMS": 8.407 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:division_scalar_vector:*": { "subcaseMS": 27.809 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:division_vector_scalar:*": { "subcaseMS": 28.550 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:division_vector_scalar_compound:*": { "subcaseMS": 28.950 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:multiplication:*": { "subcaseMS": 8.976 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:multiplication_compound:*": { "subcaseMS": 9.601 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:multiplication_scalar_vector:*": { "subcaseMS": 33.742 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:multiplication_vector_scalar:*": { "subcaseMS": 33.042 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:multiplication_vector_scalar_compound:*": { "subcaseMS": 31.425 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:remainder:*": { "subcaseMS": 8.600 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:remainder_compound:*": { "subcaseMS": 9.119 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:remainder_scalar_vector:*": { "subcaseMS": 27.192 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:remainder_vector_scalar:*": { "subcaseMS": 27.284 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:remainder_vector_scalar_compound:*": { "subcaseMS": 29.875 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:subtraction:*": { "subcaseMS": 9.513 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:subtraction_compound:*": { "subcaseMS": 7.994 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:subtraction_scalar_vector:*": { "subcaseMS": 34.034 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:subtraction_vector_scalar:*": { "subcaseMS": 32.642 }, + "webgpu:shader,execution,expression,binary,i32_arithmetic:subtraction_vector_scalar_compound:*": { "subcaseMS": 30.400 }, + "webgpu:shader,execution,expression,binary,i32_comparison:equals:*": { "subcaseMS": 9.544 }, + "webgpu:shader,execution,expression,binary,i32_comparison:greater_equals:*": { "subcaseMS": 7.657 }, + "webgpu:shader,execution,expression,binary,i32_comparison:greater_than:*": { "subcaseMS": 7.169 }, + "webgpu:shader,execution,expression,binary,i32_comparison:less_equals:*": { "subcaseMS": 8.063 }, + "webgpu:shader,execution,expression,binary,i32_comparison:less_than:*": { "subcaseMS": 7.894 }, + "webgpu:shader,execution,expression,binary,i32_comparison:not_equals:*": { "subcaseMS": 7.588 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:addition:*": { "subcaseMS": 9.806 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:addition_compound:*": { "subcaseMS": 8.494 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:addition_scalar_vector:*": { "subcaseMS": 10.409 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:addition_vector_scalar:*": { "subcaseMS": 9.676 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:addition_vector_scalar_compound:*": { "subcaseMS": 9.925 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:division:*": { "subcaseMS": 7.138 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:division_compound:*": { "subcaseMS": 7.544 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:division_scalar_vector:*": { "subcaseMS": 9.959 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:division_vector_scalar:*": { "subcaseMS": 9.767 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:division_vector_scalar_compound:*": { "subcaseMS": 10.167 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:multiplication:*": { "subcaseMS": 7.544 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:multiplication_compound:*": { "subcaseMS": 7.332 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:multiplication_scalar_vector:*": { "subcaseMS": 9.867 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:multiplication_vector_scalar:*": { "subcaseMS": 9.159 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:multiplication_vector_scalar_compound:*": { "subcaseMS": 9.667 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:remainder:*": { "subcaseMS": 8.188 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:remainder_compound:*": { "subcaseMS": 7.994 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:remainder_scalar_vector:*": { "subcaseMS": 9.842 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:remainder_vector_scalar:*": { "subcaseMS": 10.292 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:remainder_vector_scalar_compound:*": { "subcaseMS": 9.617 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:subtraction:*": { "subcaseMS": 16.119 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:subtraction_compound:*": { "subcaseMS": 7.982 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:subtraction_scalar_vector:*": { "subcaseMS": 9.842 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:subtraction_vector_scalar:*": { "subcaseMS": 9.667 }, + "webgpu:shader,execution,expression,binary,u32_arithmetic:subtraction_vector_scalar_compound:*": { "subcaseMS": 10.859 }, + "webgpu:shader,execution,expression,binary,u32_comparison:equals:*": { "subcaseMS": 8.938 }, + "webgpu:shader,execution,expression,binary,u32_comparison:greater_equals:*": { "subcaseMS": 6.732 }, + "webgpu:shader,execution,expression,binary,u32_comparison:greater_than:*": { "subcaseMS": 7.232 }, + "webgpu:shader,execution,expression,binary,u32_comparison:less_equals:*": { "subcaseMS": 7.844 }, + "webgpu:shader,execution,expression,binary,u32_comparison:less_than:*": { "subcaseMS": 6.700 }, + "webgpu:shader,execution,expression,binary,u32_comparison:not_equals:*": { "subcaseMS": 6.850 }, + "webgpu:shader,execution,expression,call,builtin,abs:abstract_float:*": { "subcaseMS": 16.809 }, + "webgpu:shader,execution,expression,call,builtin,abs:abstract_int:*": { "subcaseMS": 16.810 }, + "webgpu:shader,execution,expression,call,builtin,abs:f16:*": { "subcaseMS": 22.910 }, + "webgpu:shader,execution,expression,call,builtin,abs:f32:*": { "subcaseMS": 9.844 }, + "webgpu:shader,execution,expression,call,builtin,abs:i32:*": { "subcaseMS": 7.088 }, + "webgpu:shader,execution,expression,call,builtin,abs:u32:*": { "subcaseMS": 7.513 }, + "webgpu:shader,execution,expression,call,builtin,acos:abstract_float:*": { "subcaseMS": 15.505 }, + "webgpu:shader,execution,expression,call,builtin,acos:f16:*": { "subcaseMS": 26.005 }, + "webgpu:shader,execution,expression,call,builtin,acos:f32:*": { "subcaseMS": 33.063 }, + "webgpu:shader,execution,expression,call,builtin,acosh:abstract_float:*": { "subcaseMS": 17.210 }, + "webgpu:shader,execution,expression,call,builtin,acosh:f16:*": { "subcaseMS": 23.306 }, + "webgpu:shader,execution,expression,call,builtin,acosh:f32:*": { "subcaseMS": 12.588 }, + "webgpu:shader,execution,expression,call,builtin,all:bool:*": { "subcaseMS": 6.938 }, + "webgpu:shader,execution,expression,call,builtin,any:bool:*": { "subcaseMS": 6.475 }, + "webgpu:shader,execution,expression,call,builtin,arrayLength:binding_subregion:*": { "subcaseMS": 19.900 }, + "webgpu:shader,execution,expression,call,builtin,arrayLength:multiple_elements:*": { "subcaseMS": 6.261 }, + "webgpu:shader,execution,expression,call,builtin,arrayLength:read_only:*": { "subcaseMS": 4.500 }, + "webgpu:shader,execution,expression,call,builtin,arrayLength:single_element:*": { "subcaseMS": 6.569 }, + "webgpu:shader,execution,expression,call,builtin,arrayLength:struct_member:*": { "subcaseMS": 6.819 }, + "webgpu:shader,execution,expression,call,builtin,asin:abstract_float:*": { "subcaseMS": 16.606 }, + "webgpu:shader,execution,expression,call,builtin,asin:f16:*": { "subcaseMS": 6.708 }, + "webgpu:shader,execution,expression,call,builtin,asin:f32:*": { "subcaseMS": 33.969 }, + "webgpu:shader,execution,expression,call,builtin,asinh:abstract_float:*": { "subcaseMS": 23.305 }, + "webgpu:shader,execution,expression,call,builtin,asinh:f16:*": { "subcaseMS": 16.509 }, + "webgpu:shader,execution,expression,call,builtin,asinh:f32:*": { "subcaseMS": 9.731 }, + "webgpu:shader,execution,expression,call,builtin,atan2:abstract_float:*": { "subcaseMS": 24.705 }, + "webgpu:shader,execution,expression,call,builtin,atan2:f16:*": { "subcaseMS": 32.506 }, + "webgpu:shader,execution,expression,call,builtin,atan2:f32:*": { "subcaseMS": 25.938 }, + "webgpu:shader,execution,expression,call,builtin,atan:abstract_float:*": { "subcaseMS": 32.408 }, + "webgpu:shader,execution,expression,call,builtin,atan:f16:*": { "subcaseMS": 21.106 }, + "webgpu:shader,execution,expression,call,builtin,atan:f32:*": { "subcaseMS": 10.251 }, + "webgpu:shader,execution,expression,call,builtin,atanh:abstract_float:*": { "subcaseMS": 16.807 }, + "webgpu:shader,execution,expression,call,builtin,atanh:f16:*": { "subcaseMS": 26.507 }, + "webgpu:shader,execution,expression,call,builtin,atanh:f32:*": { "subcaseMS": 12.332 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicAdd:add_storage:*": { "subcaseMS": 6.482 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicAdd:add_workgroup:*": { "subcaseMS": 7.222 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicAnd:and_storage:*": { "subcaseMS": 6.711 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicAnd:and_workgroup:*": { "subcaseMS": 8.028 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicCompareExchangeWeak:compare_exchange_weak_storage_advanced:*": { "subcaseMS": 10.090 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicCompareExchangeWeak:compare_exchange_weak_storage_basic:*": { "subcaseMS": 9.529 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicCompareExchangeWeak:compare_exchange_weak_workgroup_advanced:*": { "subcaseMS": 10.012 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicCompareExchangeWeak:compare_exchange_weak_workgroup_basic:*": { "subcaseMS": 10.368 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicExchange:exchange_storage_advanced:*": { "subcaseMS": 8.755 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicExchange:exchange_storage_basic:*": { "subcaseMS": 5.725 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicExchange:exchange_workgroup_advanced:*": { "subcaseMS": 9.885 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicExchange:exchange_workgroup_basic:*": { "subcaseMS": 6.966 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicLoad:load_storage:*": { "subcaseMS": 5.354 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicLoad:load_workgroup:*": { "subcaseMS": 6.269 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicMax:max_storage:*": { "subcaseMS": 6.116 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicMax:max_workgroup:*": { "subcaseMS": 7.010 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicMin:min_storage:*": { "subcaseMS": 6.235 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicMin:min_workgroup:*": { "subcaseMS": 7.307 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicOr:or_storage:*": { "subcaseMS": 6.791 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicOr:or_workgroup:*": { "subcaseMS": 7.814 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicStore:store_storage_advanced:*": { "subcaseMS": 5.707 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicStore:store_storage_basic:*": { "subcaseMS": 5.524 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicStore:store_workgroup_advanced:*": { "subcaseMS": 6.029 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicStore:store_workgroup_basic:*": { "subcaseMS": 6.632 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicSub:sub_storage:*": { "subcaseMS": 5.757 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicSub:sub_workgroup:*": { "subcaseMS": 7.238 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicXor:xor_storage:*": { "subcaseMS": 6.807 }, + "webgpu:shader,execution,expression,call,builtin,atomics,atomicXor:xor_workgroup:*": { "subcaseMS": 7.821 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:f16_to_f16:*": { "subcaseMS": 21.112 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:f32_to_f32:*": { "subcaseMS": 8.625 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:f32_to_i32:*": { "subcaseMS": 8.175 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:f32_to_u32:*": { "subcaseMS": 8.016 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:f32_to_vec2h:*": { "subcaseMS": 22.212 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:i32_to_f32:*": { "subcaseMS": 31.814 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:i32_to_i32:*": { "subcaseMS": 23.863 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:i32_to_u32:*": { "subcaseMS": 7.263 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:i32_to_vec2h:*": { "subcaseMS": 28.214 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:u32_to_f32:*": { "subcaseMS": 20.716 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:u32_to_i32:*": { "subcaseMS": 6.982 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:u32_to_u32:*": { "subcaseMS": 6.907 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:u32_to_vec2h:*": { "subcaseMS": 22.210 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:vec2f_to_vec4h:*": { "subcaseMS": 24.015 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:vec2h_to_f32:*": { "subcaseMS": 21.412 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:vec2h_to_i32:*": { "subcaseMS": 38.312 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:vec2h_to_u32:*": { "subcaseMS": 23.711 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:vec2i_to_vec4h:*": { "subcaseMS": 23.211 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:vec2u_to_vec4h:*": { "subcaseMS": 23.010 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:vec4h_to_vec2f:*": { "subcaseMS": 22.812 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:vec4h_to_vec2i:*": { "subcaseMS": 20.915 }, + "webgpu:shader,execution,expression,call,builtin,bitcast:vec4h_to_vec2u:*": { "subcaseMS": 29.514 }, + "webgpu:shader,execution,expression,call,builtin,ceil:abstract_float:*": { "subcaseMS": 23.611 }, + "webgpu:shader,execution,expression,call,builtin,ceil:f16:*": { "subcaseMS": 29.209 }, + "webgpu:shader,execution,expression,call,builtin,ceil:f32:*": { "subcaseMS": 11.132 }, + "webgpu:shader,execution,expression,call,builtin,clamp:abstract_float:*": { "subcaseMS": 28.706 }, + "webgpu:shader,execution,expression,call,builtin,clamp:abstract_int:*": { "subcaseMS": 18.104 }, + "webgpu:shader,execution,expression,call,builtin,clamp:f16:*": { "subcaseMS": 32.809 }, + "webgpu:shader,execution,expression,call,builtin,clamp:f32:*": { "subcaseMS": 159.926 }, + "webgpu:shader,execution,expression,call,builtin,clamp:i32:*": { "subcaseMS": 54.200 }, + "webgpu:shader,execution,expression,call,builtin,clamp:u32:*": { "subcaseMS": 272.419 }, + "webgpu:shader,execution,expression,call,builtin,cos:abstract_float:*": { "subcaseMS": 16.706 }, + "webgpu:shader,execution,expression,call,builtin,cos:f16:*": { "subcaseMS": 23.905 }, + "webgpu:shader,execution,expression,call,builtin,cos:f32:*": { "subcaseMS": 25.275 }, + "webgpu:shader,execution,expression,call,builtin,cosh:abstract_float:*": { "subcaseMS": 22.909 }, + "webgpu:shader,execution,expression,call,builtin,cosh:f16:*": { "subcaseMS": 17.409 }, + "webgpu:shader,execution,expression,call,builtin,cosh:f32:*": { "subcaseMS": 9.694 }, + "webgpu:shader,execution,expression,call,builtin,countLeadingZeros:i32:*": { "subcaseMS": 7.494 }, + "webgpu:shader,execution,expression,call,builtin,countLeadingZeros:u32:*": { "subcaseMS": 8.088 }, + "webgpu:shader,execution,expression,call,builtin,countOneBits:i32:*": { "subcaseMS": 7.400 }, + "webgpu:shader,execution,expression,call,builtin,countOneBits:u32:*": { "subcaseMS": 8.644 }, + "webgpu:shader,execution,expression,call,builtin,countTrailingZeros:i32:*": { "subcaseMS": 7.844 }, + "webgpu:shader,execution,expression,call,builtin,countTrailingZeros:u32:*": { "subcaseMS": 7.851 }, + "webgpu:shader,execution,expression,call,builtin,cross:abstract_float:*": { "subcaseMS": 3.002 }, + "webgpu:shader,execution,expression,call,builtin,cross:f16:*": { "subcaseMS": 16.101 }, + "webgpu:shader,execution,expression,call,builtin,cross:f32:*": { "subcaseMS": 664.926 }, + "webgpu:shader,execution,expression,call,builtin,degrees:abstract_float:*": { "subcaseMS": 43.808 }, + "webgpu:shader,execution,expression,call,builtin,degrees:f16:*": { "subcaseMS": 29.308 }, + "webgpu:shader,execution,expression,call,builtin,degrees:f32:*": { "subcaseMS": 23.894 }, + "webgpu:shader,execution,expression,call,builtin,determinant:abstract_float:*": { "subcaseMS": 15.306 }, + "webgpu:shader,execution,expression,call,builtin,determinant:f16:*": { "subcaseMS": 22.806 }, + "webgpu:shader,execution,expression,call,builtin,determinant:f32:*": { "subcaseMS": 10.742 }, + "webgpu:shader,execution,expression,call,builtin,distance:abstract_float:*": { "subcaseMS": 14.503 }, + "webgpu:shader,execution,expression,call,builtin,distance:f16:*": { "subcaseMS": 24.508 }, + "webgpu:shader,execution,expression,call,builtin,distance:f32:*": { "subcaseMS": 875.325 }, + "webgpu:shader,execution,expression,call,builtin,distance:f32_vec2:*": { "subcaseMS": 9.826 }, + "webgpu:shader,execution,expression,call,builtin,distance:f32_vec3:*": { "subcaseMS": 10.901 }, + "webgpu:shader,execution,expression,call,builtin,distance:f32_vec4:*": { "subcaseMS": 12.700 }, + "webgpu:shader,execution,expression,call,builtin,dot:abstract_float:*": { "subcaseMS": 8.902 }, + "webgpu:shader,execution,expression,call,builtin,dot:abstract_int:*": { "subcaseMS": 2.902 }, + "webgpu:shader,execution,expression,call,builtin,dot:f16:*": { "subcaseMS": 3.102 }, + "webgpu:shader,execution,expression,call,builtin,dot:f32_vec2:*": { "subcaseMS": 210.350 }, + "webgpu:shader,execution,expression,call,builtin,dot:f32_vec3:*": { "subcaseMS": 11.176 }, + "webgpu:shader,execution,expression,call,builtin,dot:f32_vec4:*": { "subcaseMS": 11.876 }, + "webgpu:shader,execution,expression,call,builtin,dot:i32:*": { "subcaseMS": 3.103 }, + "webgpu:shader,execution,expression,call,builtin,dot:u32:*": { "subcaseMS": 3.101 }, + "webgpu:shader,execution,expression,call,builtin,dpdx:f32:*": { "subcaseMS": 22.804 }, + "webgpu:shader,execution,expression,call,builtin,dpdxCoarse:f32:*": { "subcaseMS": 22.404 }, + "webgpu:shader,execution,expression,call,builtin,dpdxFine:f32:*": { "subcaseMS": 17.708 }, + "webgpu:shader,execution,expression,call,builtin,dpdy:f32:*": { "subcaseMS": 17.006 }, + "webgpu:shader,execution,expression,call,builtin,dpdyCoarse:f32:*": { "subcaseMS": 17.909 }, + "webgpu:shader,execution,expression,call,builtin,dpdyFine:f32:*": { "subcaseMS": 16.806 }, + "webgpu:shader,execution,expression,call,builtin,exp2:abstract_float:*": { "subcaseMS": 22.705 }, + "webgpu:shader,execution,expression,call,builtin,exp2:f16:*": { "subcaseMS": 23.908 }, + "webgpu:shader,execution,expression,call,builtin,exp2:f32:*": { "subcaseMS": 12.169 }, + "webgpu:shader,execution,expression,call,builtin,exp:abstract_float:*": { "subcaseMS": 17.210 }, + "webgpu:shader,execution,expression,call,builtin,exp:f16:*": { "subcaseMS": 17.605 }, + "webgpu:shader,execution,expression,call,builtin,exp:f32:*": { "subcaseMS": 12.557 }, + "webgpu:shader,execution,expression,call,builtin,extractBits:i32:*": { "subcaseMS": 8.125 }, + "webgpu:shader,execution,expression,call,builtin,extractBits:u32:*": { "subcaseMS": 7.838 }, + "webgpu:shader,execution,expression,call,builtin,faceForward:abstract_float:*": { "subcaseMS": 14.306 }, + "webgpu:shader,execution,expression,call,builtin,faceForward:f16:*": { "subcaseMS": 7.906 }, + "webgpu:shader,execution,expression,call,builtin,faceForward:f32_vec2:*": { "subcaseMS": 1054.800 }, + "webgpu:shader,execution,expression,call,builtin,faceForward:f32_vec3:*": { "subcaseMS": 162.675 }, + "webgpu:shader,execution,expression,call,builtin,faceForward:f32_vec4:*": { "subcaseMS": 180.325 }, + "webgpu:shader,execution,expression,call,builtin,firstLeadingBit:i32:*": { "subcaseMS": 20.613 }, + "webgpu:shader,execution,expression,call,builtin,firstLeadingBit:u32:*": { "subcaseMS": 9.363 }, + "webgpu:shader,execution,expression,call,builtin,firstTrailingBit:i32:*": { "subcaseMS": 8.132 }, + "webgpu:shader,execution,expression,call,builtin,firstTrailingBit:u32:*": { "subcaseMS": 9.047 }, + "webgpu:shader,execution,expression,call,builtin,floor:abstract_float:*": { "subcaseMS": 34.108 }, + "webgpu:shader,execution,expression,call,builtin,floor:f16:*": { "subcaseMS": 30.708 }, + "webgpu:shader,execution,expression,call,builtin,floor:f32:*": { "subcaseMS": 10.119 }, + "webgpu:shader,execution,expression,call,builtin,fma:abstract_float:*": { "subcaseMS": 18.208 }, + "webgpu:shader,execution,expression,call,builtin,fma:f16:*": { "subcaseMS": 27.805 }, + "webgpu:shader,execution,expression,call,builtin,fma:f32:*": { "subcaseMS": 80.388 }, + "webgpu:shader,execution,expression,call,builtin,fract:abstract_float:*": { "subcaseMS": 17.408 }, + "webgpu:shader,execution,expression,call,builtin,fract:f16:*": { "subcaseMS": 17.106 }, + "webgpu:shader,execution,expression,call,builtin,fract:f32:*": { "subcaseMS": 12.269 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f16_exp:*": { "subcaseMS": 8.503 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f16_fract:*": { "subcaseMS": 17.900 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f16_vec2_exp:*": { "subcaseMS": 1.801 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f16_vec2_fract:*": { "subcaseMS": 2.802 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f16_vec3_exp:*": { "subcaseMS": 1.701 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f16_vec3_fract:*": { "subcaseMS": 1.702 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f16_vec4_exp:*": { "subcaseMS": 1.603 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f16_vec4_fract:*": { "subcaseMS": 1.503 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f32_exp:*": { "subcaseMS": 8.501 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f32_fract:*": { "subcaseMS": 27.475 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f32_vec2_exp:*": { "subcaseMS": 8.300 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f32_vec2_fract:*": { "subcaseMS": 8.876 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f32_vec3_exp:*": { "subcaseMS": 8.975 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f32_vec3_fract:*": { "subcaseMS": 9.700 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f32_vec4_exp:*": { "subcaseMS": 10.250 }, + "webgpu:shader,execution,expression,call,builtin,frexp:f32_vec4_fract:*": { "subcaseMS": 11.800 }, + "webgpu:shader,execution,expression,call,builtin,fwidth:f32:*": { "subcaseMS": 29.807 }, + "webgpu:shader,execution,expression,call,builtin,fwidthCoarse:f32:*": { "subcaseMS": 17.110 }, + "webgpu:shader,execution,expression,call,builtin,fwidthFine:f32:*": { "subcaseMS": 16.906 }, + "webgpu:shader,execution,expression,call,builtin,insertBits:integer:*": { "subcaseMS": 9.569 }, + "webgpu:shader,execution,expression,call,builtin,inversesqrt:abstract_float:*": { "subcaseMS": 24.310 }, + "webgpu:shader,execution,expression,call,builtin,inversesqrt:f16:*": { "subcaseMS": 21.411 }, + "webgpu:shader,execution,expression,call,builtin,inversesqrt:f32:*": { "subcaseMS": 50.125 }, + "webgpu:shader,execution,expression,call,builtin,ldexp:abstract_float:*": { "subcaseMS": 32.909 }, + "webgpu:shader,execution,expression,call,builtin,ldexp:f16:*": { "subcaseMS": 36.705 }, + "webgpu:shader,execution,expression,call,builtin,ldexp:f32:*": { "subcaseMS": 66.419 }, + "webgpu:shader,execution,expression,call,builtin,length:abstract_float:*": { "subcaseMS": 31.303 }, + "webgpu:shader,execution,expression,call,builtin,length:f16:*": { "subcaseMS": 28.405 }, + "webgpu:shader,execution,expression,call,builtin,length:f32:*": { "subcaseMS": 107.275 }, + "webgpu:shader,execution,expression,call,builtin,length:f32_vec2:*": { "subcaseMS": 9.751 }, + "webgpu:shader,execution,expression,call,builtin,length:f32_vec3:*": { "subcaseMS": 10.825 }, + "webgpu:shader,execution,expression,call,builtin,length:f32_vec4:*": { "subcaseMS": 9.476 }, + "webgpu:shader,execution,expression,call,builtin,log2:abstract_float:*": { "subcaseMS": 23.607 }, + "webgpu:shader,execution,expression,call,builtin,log2:f16:*": { "subcaseMS": 9.404 }, + "webgpu:shader,execution,expression,call,builtin,log2:f32:*": { "subcaseMS": 27.838 }, + "webgpu:shader,execution,expression,call,builtin,log:abstract_float:*": { "subcaseMS": 17.911 }, + "webgpu:shader,execution,expression,call,builtin,log:f16:*": { "subcaseMS": 8.603 }, + "webgpu:shader,execution,expression,call,builtin,log:f32:*": { "subcaseMS": 26.725 }, + "webgpu:shader,execution,expression,call,builtin,max:abstract_float:*": { "subcaseMS": 25.508 }, + "webgpu:shader,execution,expression,call,builtin,max:abstract_int:*": { "subcaseMS": 33.508 }, + "webgpu:shader,execution,expression,call,builtin,max:f16:*": { "subcaseMS": 37.404 }, + "webgpu:shader,execution,expression,call,builtin,max:f32:*": { "subcaseMS": 300.619 }, + "webgpu:shader,execution,expression,call,builtin,max:i32:*": { "subcaseMS": 7.350 }, + "webgpu:shader,execution,expression,call,builtin,max:u32:*": { "subcaseMS": 6.700 }, + "webgpu:shader,execution,expression,call,builtin,min:abstract_float:*": { "subcaseMS": 30.405 }, + "webgpu:shader,execution,expression,call,builtin,min:abstract_int:*": { "subcaseMS": 19.806 }, + "webgpu:shader,execution,expression,call,builtin,min:f16:*": { "subcaseMS": 8.006 }, + "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:matching_abstract_float:*": { "subcaseMS": 23.706 }, + "webgpu:shader,execution,expression,call,builtin,mix:matching_f16:*": { "subcaseMS": 21.910 }, + "webgpu:shader,execution,expression,call,builtin,mix:matching_f32:*": { "subcaseMS": 100.907 }, + "webgpu:shader,execution,expression,call,builtin,mix:monmatching_f16:*": { "subcaseMS": 10.408 }, + "webgpu:shader,execution,expression,call,builtin,mix:nonmatching_abstract_float:*": { "subcaseMS": 24.605 }, + "webgpu:shader,execution,expression,call,builtin,mix:nonmatching_f32:*": { "subcaseMS": 14.205 }, + "webgpu:shader,execution,expression,call,builtin,modf:f16_fract:*": { "subcaseMS": 4.202 }, + "webgpu:shader,execution,expression,call,builtin,modf:f16_vec2_fract:*": { "subcaseMS": 9.200 }, + "webgpu:shader,execution,expression,call,builtin,modf:f16_vec2_whole:*": { "subcaseMS": 8.404 }, + "webgpu:shader,execution,expression,call,builtin,modf:f16_vec3_fract:*": { "subcaseMS": 3.102 }, + "webgpu:shader,execution,expression,call,builtin,modf:f16_vec3_whole:*": { "subcaseMS": 7.202 }, + "webgpu:shader,execution,expression,call,builtin,modf:f16_vec4_fract:*": { "subcaseMS": 8.503 }, + "webgpu:shader,execution,expression,call,builtin,modf:f16_vec4_whole:*": { "subcaseMS": 3.001 }, + "webgpu:shader,execution,expression,call,builtin,modf:f16_whole:*": { "subcaseMS": 17.103 }, + "webgpu:shader,execution,expression,call,builtin,modf:f32_fract:*": { "subcaseMS": 22.850 }, + "webgpu:shader,execution,expression,call,builtin,modf:f32_vec2_fract:*": { "subcaseMS": 9.451 }, + "webgpu:shader,execution,expression,call,builtin,modf:f32_vec2_whole:*": { "subcaseMS": 10.950 }, + "webgpu:shader,execution,expression,call,builtin,modf:f32_vec3_fract:*": { "subcaseMS": 9.526 }, + "webgpu:shader,execution,expression,call,builtin,modf:f32_vec3_whole:*": { "subcaseMS": 14.950 }, + "webgpu:shader,execution,expression,call,builtin,modf:f32_vec4_fract:*": { "subcaseMS": 11.151 }, + "webgpu:shader,execution,expression,call,builtin,modf:f32_vec4_whole:*": { "subcaseMS": 13.550 }, + "webgpu:shader,execution,expression,call,builtin,modf:f32_whole:*": { "subcaseMS": 10.725 }, + "webgpu:shader,execution,expression,call,builtin,normalize:abstract_float:*": { "subcaseMS": 28.508 }, + "webgpu:shader,execution,expression,call,builtin,normalize:f16:*": { "subcaseMS": 24.205 }, + "webgpu:shader,execution,expression,call,builtin,normalize:f32_vec2:*": { "subcaseMS": 65.975 }, + "webgpu:shader,execution,expression,call,builtin,normalize:f32_vec3:*": { "subcaseMS": 12.825 }, + "webgpu:shader,execution,expression,call,builtin,normalize:f32_vec4:*": { "subcaseMS": 14.500 }, + "webgpu:shader,execution,expression,call,builtin,pack2x16float:pack:*": { "subcaseMS": 284.150 }, + "webgpu:shader,execution,expression,call,builtin,pack2x16snorm:pack:*": { "subcaseMS": 9.925 }, + "webgpu:shader,execution,expression,call,builtin,pack2x16unorm:pack:*": { "subcaseMS": 9.525 }, + "webgpu:shader,execution,expression,call,builtin,pack4x8snorm:pack:*": { "subcaseMS": 14.751 }, + "webgpu:shader,execution,expression,call,builtin,pack4x8unorm:pack:*": { "subcaseMS": 14.575 }, + "webgpu:shader,execution,expression,call,builtin,pow:abstract_float:*": { "subcaseMS": 23.106 }, + "webgpu:shader,execution,expression,call,builtin,pow:f16:*": { "subcaseMS": 18.407 }, + "webgpu:shader,execution,expression,call,builtin,pow:f32:*": { "subcaseMS": 151.269 }, + "webgpu:shader,execution,expression,call,builtin,quantizeToF16:f32:*": { "subcaseMS": 11.063 }, + "webgpu:shader,execution,expression,call,builtin,radians:abstract_float:*": { "subcaseMS": 20.005 }, + "webgpu:shader,execution,expression,call,builtin,radians:f16:*": { "subcaseMS": 18.707 }, + "webgpu:shader,execution,expression,call,builtin,radians:f32:*": { "subcaseMS": 11.988 }, + "webgpu:shader,execution,expression,call,builtin,reflect:abstract_float:*": { "subcaseMS": 20.405 }, + "webgpu:shader,execution,expression,call,builtin,reflect:f16:*": { "subcaseMS": 15.806 }, + "webgpu:shader,execution,expression,call,builtin,reflect:f32_vec2:*": { "subcaseMS": 116.425 }, + "webgpu:shader,execution,expression,call,builtin,reflect:f32_vec3:*": { "subcaseMS": 14.575 }, + "webgpu:shader,execution,expression,call,builtin,reflect:f32_vec4:*": { "subcaseMS": 14.601 }, + "webgpu:shader,execution,expression,call,builtin,refract:abstract_float:*": { "subcaseMS": 21.305 }, + "webgpu:shader,execution,expression,call,builtin,refract:f16:*": { "subcaseMS": 31.104 }, + "webgpu:shader,execution,expression,call,builtin,refract:f32_vec2:*": { "subcaseMS": 3235.401 }, + "webgpu:shader,execution,expression,call,builtin,refract:f32_vec3:*": { "subcaseMS": 228.150 }, + "webgpu:shader,execution,expression,call,builtin,refract:f32_vec4:*": { "subcaseMS": 235.700 }, + "webgpu:shader,execution,expression,call,builtin,reverseBits:i32:*": { "subcaseMS": 9.594 }, + "webgpu:shader,execution,expression,call,builtin,reverseBits:u32:*": { "subcaseMS": 7.969 }, + "webgpu:shader,execution,expression,call,builtin,round:abstract_float:*": { "subcaseMS": 19.408 }, + "webgpu:shader,execution,expression,call,builtin,round:f16:*": { "subcaseMS": 30.509 }, + "webgpu:shader,execution,expression,call,builtin,round:f32:*": { "subcaseMS": 12.407 }, + "webgpu:shader,execution,expression,call,builtin,saturate:abstract_float:*": { "subcaseMS": 24.607 }, + "webgpu:shader,execution,expression,call,builtin,saturate:f16:*": { "subcaseMS": 23.407 }, + "webgpu:shader,execution,expression,call,builtin,saturate:f32:*": { "subcaseMS": 12.444 }, + "webgpu:shader,execution,expression,call,builtin,select:scalar:*": { "subcaseMS": 6.882 }, + "webgpu:shader,execution,expression,call,builtin,select:vector:*": { "subcaseMS": 7.096 }, + "webgpu:shader,execution,expression,call,builtin,sign:abstract_float:*": { "subcaseMS": 31.708 }, + "webgpu:shader,execution,expression,call,builtin,sign:abstract_int:*": { "subcaseMS": 25.806 }, + "webgpu:shader,execution,expression,call,builtin,sign:f16:*": { "subcaseMS": 25.103 }, + "webgpu:shader,execution,expression,call,builtin,sign:f32:*": { "subcaseMS": 8.188 }, + "webgpu:shader,execution,expression,call,builtin,sign:i32:*": { "subcaseMS": 10.225 }, + "webgpu:shader,execution,expression,call,builtin,sin:abstract_float:*": { "subcaseMS": 19.206 }, + "webgpu:shader,execution,expression,call,builtin,sin:f16:*": { "subcaseMS": 8.707 }, + "webgpu:shader,execution,expression,call,builtin,sin:f32:*": { "subcaseMS": 26.826 }, + "webgpu:shader,execution,expression,call,builtin,sinh:abstract_float:*": { "subcaseMS": 22.009 }, + "webgpu:shader,execution,expression,call,builtin,sinh:f16:*": { "subcaseMS": 23.905 }, + "webgpu:shader,execution,expression,call,builtin,sinh:f32:*": { "subcaseMS": 11.038 }, + "webgpu:shader,execution,expression,call,builtin,smoothstep:abstract_float:*": { "subcaseMS": 23.807 }, + "webgpu:shader,execution,expression,call,builtin,smoothstep:f16:*": { "subcaseMS": 23.404 }, + "webgpu:shader,execution,expression,call,builtin,smoothstep:f32:*": { "subcaseMS": 88.063 }, + "webgpu:shader,execution,expression,call,builtin,sqrt:abstract_float:*": { "subcaseMS": 19.004 }, + "webgpu:shader,execution,expression,call,builtin,sqrt:f16:*": { "subcaseMS": 22.908 }, + "webgpu:shader,execution,expression,call,builtin,sqrt:f32:*": { "subcaseMS": 10.813 }, + "webgpu:shader,execution,expression,call,builtin,step:abstract_float:*": { "subcaseMS": 19.104 }, + "webgpu:shader,execution,expression,call,builtin,step:f16:*": { "subcaseMS": 32.508 }, + "webgpu:shader,execution,expression,call,builtin,step:f32:*": { "subcaseMS": 291.363 }, + "webgpu:shader,execution,expression,call,builtin,storageBarrier:barrier:*": { "subcaseMS": 0.801 }, + "webgpu:shader,execution,expression,call,builtin,storageBarrier:stage:*": { "subcaseMS": 2.402 }, + "webgpu:shader,execution,expression,call,builtin,tan:abstract_float:*": { "subcaseMS": 31.007 }, + "webgpu:shader,execution,expression,call,builtin,tan:f16:*": { "subcaseMS": 31.306 }, + "webgpu:shader,execution,expression,call,builtin,tan:f32:*": { "subcaseMS": 13.532 }, + "webgpu:shader,execution,expression,call,builtin,tanh:abstract_float:*": { "subcaseMS": 18.406 }, + "webgpu:shader,execution,expression,call,builtin,tanh:f16:*": { "subcaseMS": 25.211 }, + "webgpu:shader,execution,expression,call,builtin,tanh:f32:*": { "subcaseMS": 32.719 }, + "webgpu:shader,execution,expression,call,builtin,textureDimension:depth:*": { "subcaseMS": 20.801 }, + "webgpu:shader,execution,expression,call,builtin,textureDimension:external:*": { "subcaseMS": 1.700 }, + "webgpu:shader,execution,expression,call,builtin,textureDimension:sampled:*": { "subcaseMS": 16.506 }, + "webgpu:shader,execution,expression,call,builtin,textureDimension:storage:*": { "subcaseMS": 25.907 }, + "webgpu:shader,execution,expression,call,builtin,textureGather:depth_2d_coords:*": { "subcaseMS": 11.601 }, + "webgpu:shader,execution,expression,call,builtin,textureGather:depth_3d_coords:*": { "subcaseMS": 2.200 }, + "webgpu:shader,execution,expression,call,builtin,textureGather:depth_array_2d_coords:*": { "subcaseMS": 23.801 }, + "webgpu:shader,execution,expression,call,builtin,textureGather:depth_array_3d_coords:*": { "subcaseMS": 10.200 }, + "webgpu:shader,execution,expression,call,builtin,textureGather:sampled_2d_coords:*": { "subcaseMS": 343.301 }, + "webgpu:shader,execution,expression,call,builtin,textureGather:sampled_3d_coords:*": { "subcaseMS": 63.200 }, + "webgpu:shader,execution,expression,call,builtin,textureGather:sampled_array_2d_coords:*": { "subcaseMS": 304.401 }, + "webgpu:shader,execution,expression,call,builtin,textureGather:sampled_array_3d_coords:*": { "subcaseMS": 60.700 }, + "webgpu:shader,execution,expression,call,builtin,textureGatherCompare:array_2d_coords:*": { "subcaseMS": 291.301 }, + "webgpu:shader,execution,expression,call,builtin,textureGatherCompare:array_3d_coords:*": { "subcaseMS": 191.101 }, + "webgpu:shader,execution,expression,call,builtin,textureGatherCompare:sampled_array_2d_coords:*": { "subcaseMS": 57.600 }, + "webgpu:shader,execution,expression,call,builtin,textureGatherCompare:sampled_array_3d_coords:*": { "subcaseMS": 10.101 }, + "webgpu:shader,execution,expression,call,builtin,textureLoad:arrayed:*": { "subcaseMS": 30.501 }, + "webgpu:shader,execution,expression,call,builtin,textureLoad:depth:*": { "subcaseMS": 3.200 }, + "webgpu:shader,execution,expression,call,builtin,textureLoad:external:*": { "subcaseMS": 1.401 }, + "webgpu:shader,execution,expression,call,builtin,textureLoad:multisampled:*": { "subcaseMS": 11.601 }, + "webgpu:shader,execution,expression,call,builtin,textureLoad:sampled_1d:*": { "subcaseMS": 83.312 }, + "webgpu:shader,execution,expression,call,builtin,textureLoad:sampled_2d:*": { "subcaseMS": 96.737 }, + "webgpu:shader,execution,expression,call,builtin,textureLoad:sampled_3d:*": { "subcaseMS": 158.534 }, + "webgpu:shader,execution,expression,call,builtin,textureNumLayers:arrayed:*": { "subcaseMS": 8.102 }, + "webgpu:shader,execution,expression,call,builtin,textureNumLayers:sampled:*": { "subcaseMS": 2.101 }, + "webgpu:shader,execution,expression,call,builtin,textureNumLayers:storage:*": { "subcaseMS": 8.000 }, + "webgpu:shader,execution,expression,call,builtin,textureNumLevels:depth:*": { "subcaseMS": 3.801 }, + "webgpu:shader,execution,expression,call,builtin,textureNumLevels:sampled:*": { "subcaseMS": 6.201 }, + "webgpu:shader,execution,expression,call,builtin,textureNumSamples:depth:*": { "subcaseMS": 1.101 }, + "webgpu:shader,execution,expression,call,builtin,textureNumSamples:sampled:*": { "subcaseMS": 6.600 }, + "webgpu:shader,execution,expression,call,builtin,textureSample:control_flow:*": { "subcaseMS": 2.801 }, + "webgpu:shader,execution,expression,call,builtin,textureSample:depth_2d_coords:*": { "subcaseMS": 12.301 }, + "webgpu:shader,execution,expression,call,builtin,textureSample:depth_3d_coords:*": { "subcaseMS": 2.101 }, + "webgpu:shader,execution,expression,call,builtin,textureSample:depth_array_2d_coords:*": { "subcaseMS": 92.601 }, + "webgpu:shader,execution,expression,call,builtin,textureSample:depth_array_3d_coords:*": { "subcaseMS": 20.301 }, + "webgpu:shader,execution,expression,call,builtin,textureSample:sampled_1d_coords:*": { "subcaseMS": 1.200 }, + "webgpu:shader,execution,expression,call,builtin,textureSample:sampled_2d_coords:*": { "subcaseMS": 12.500 }, + "webgpu:shader,execution,expression,call,builtin,textureSample:sampled_3d_coords:*": { "subcaseMS": 36.002 }, + "webgpu:shader,execution,expression,call,builtin,textureSample:sampled_array_2d_coords:*": { "subcaseMS": 92.500 }, + "webgpu:shader,execution,expression,call,builtin,textureSample:sampled_array_3d_coords:*": { "subcaseMS": 20.200 }, + "webgpu:shader,execution,expression,call,builtin,textureSample:stage:*": { "subcaseMS": 3.000 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleBias:arrayed_2d_coords:*": { "subcaseMS": 585.100 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleBias:arrayed_3d_coords:*": { "subcaseMS": 121.600 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleBias:control_flow:*": { "subcaseMS": 2.502 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleBias:sampled_2d_coords:*": { "subcaseMS": 48.601 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleBias:sampled_3d_coords:*": { "subcaseMS": 133.600 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleBias:stage:*": { "subcaseMS": 2.803 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleCompare:2d_coords:*": { "subcaseMS": 24.000 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleCompare:3d_coords:*": { "subcaseMS": 9.000 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleCompare:arrayed_2d_coords:*": { "subcaseMS": 295.601 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleCompare:arrayed_3d_coords:*": { "subcaseMS": 60.301 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleCompare:control_flow:*": { "subcaseMS": 2.702 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleCompare:stage:*": { "subcaseMS": 7.701 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:2d_coords:*": { "subcaseMS": 30.401 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:3d_coords:*": { "subcaseMS": 10.301 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:arrayed_2d_coords:*": { "subcaseMS": 705.100 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:arrayed_3d_coords:*": { "subcaseMS": 622.700 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:control_flow:*": { "subcaseMS": 2.202 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:stage:*": { "subcaseMS": 7.901 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleGrad:sampled_2d_coords:*": { "subcaseMS": 82.401 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleGrad:sampled_3d_coords:*": { "subcaseMS": 309.101 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleGrad:sampled_array_2d_coords:*": { "subcaseMS": 352.900 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleGrad:sampled_array_3d_coords:*": { "subcaseMS": 332.000 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleLevel:depth_2d_coords:*": { "subcaseMS": 545.401 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleLevel:depth_3d_coords:*": { "subcaseMS": 183.000 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleLevel:depth_array_2d_coords:*": { "subcaseMS": 547.500 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleLevel:sampled_2d_coords:*": { "subcaseMS": 35.601 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleLevel:sampled_3d_coords:*": { "subcaseMS": 118.901 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleLevel:sampled_array_2d_coords:*": { "subcaseMS": 822.400 }, + "webgpu:shader,execution,expression,call,builtin,textureSampleLevel:sampled_array_3d_coords:*": { "subcaseMS": 817.200 }, + "webgpu:shader,execution,expression,call,builtin,textureStore:store_1d_coords:*": { "subcaseMS": 19.907 }, + "webgpu:shader,execution,expression,call,builtin,textureStore:store_2d_coords:*": { "subcaseMS": 28.809 }, + "webgpu:shader,execution,expression,call,builtin,textureStore:store_3d_coords:*": { "subcaseMS": 37.206 }, + "webgpu:shader,execution,expression,call,builtin,textureStore:store_array_2d_coords:*": { "subcaseMS": 98.804 }, + "webgpu:shader,execution,expression,call,builtin,transpose:abstract_float:*": { "subcaseMS": 35.014 }, + "webgpu:shader,execution,expression,call,builtin,transpose:f16:*": { "subcaseMS": 33.311 }, + "webgpu:shader,execution,expression,call,builtin,transpose:f32:*": { "subcaseMS": 8.184 }, + "webgpu:shader,execution,expression,call,builtin,trunc:abstract_float:*": { "subcaseMS": 16.007 }, + "webgpu:shader,execution,expression,call,builtin,trunc:f16:*": { "subcaseMS": 16.705 }, + "webgpu:shader,execution,expression,call,builtin,trunc:f32:*": { "subcaseMS": 9.376 }, + "webgpu:shader,execution,expression,call,builtin,unpack2x16float:unpack:*": { "subcaseMS": 11.651 }, + "webgpu:shader,execution,expression,call,builtin,unpack2x16snorm:unpack:*": { "subcaseMS": 9.275 }, + "webgpu:shader,execution,expression,call,builtin,unpack2x16unorm:unpack:*": { "subcaseMS": 8.701 }, + "webgpu:shader,execution,expression,call,builtin,unpack4x8snorm:unpack:*": { "subcaseMS": 12.275 }, + "webgpu:shader,execution,expression,call,builtin,unpack4x8unorm:unpack:*": { "subcaseMS": 11.776 }, + "webgpu:shader,execution,expression,call,builtin,workgroupBarrier:barrier:*": { "subcaseMS": 0.701 }, + "webgpu:shader,execution,expression,call,builtin,workgroupBarrier:stage:*": { "subcaseMS": 1.801 }, + "webgpu:shader,execution,expression,unary,af_arithmetic:negation:*": { "subcaseMS": 2165.950 }, + "webgpu:shader,execution,expression,unary,af_assignment:abstract:*": { "subcaseMS": 788.400 }, + "webgpu:shader,execution,expression,unary,af_assignment:f16:*": { "subcaseMS": 1.000 }, + "webgpu:shader,execution,expression,unary,af_assignment:f32:*": { "subcaseMS": 42.000 }, + "webgpu:shader,execution,expression,unary,bool_conversion:bool:*": { "subcaseMS": 8.357 }, + "webgpu:shader,execution,expression,unary,bool_conversion:f16:*": { "subcaseMS": 28.710 }, + "webgpu:shader,execution,expression,unary,bool_conversion:f32:*": { "subcaseMS": 8.513 }, + "webgpu:shader,execution,expression,unary,bool_conversion:i32:*": { "subcaseMS": 8.219 }, + "webgpu:shader,execution,expression,unary,bool_conversion:u32:*": { "subcaseMS": 7.401 }, + "webgpu:shader,execution,expression,unary,bool_logical:negation:*": { "subcaseMS": 6.413 }, + "webgpu:shader,execution,expression,unary,f32_arithmetic:negation:*": { "subcaseMS": 16.400 }, + "webgpu:shader,execution,expression,unary,f32_conversion:bool:*": { "subcaseMS": 7.182 }, + "webgpu:shader,execution,expression,unary,f32_conversion:f16:*": { "subcaseMS": 15.908 }, + "webgpu:shader,execution,expression,unary,f32_conversion:f32:*": { "subcaseMS": 7.538 }, + "webgpu:shader,execution,expression,unary,f32_conversion:f32_mat:*": { "subcaseMS": 7.759 }, + "webgpu:shader,execution,expression,unary,f32_conversion:i32:*": { "subcaseMS": 7.701 }, + "webgpu:shader,execution,expression,unary,f32_conversion:u32:*": { "subcaseMS": 7.132 }, + "webgpu:shader,execution,expression,unary,i32_arithmetic:negation:*": { "subcaseMS": 7.244 }, + "webgpu:shader,execution,expression,unary,i32_complement:i32_complement:*": { "subcaseMS": 9.075 }, + "webgpu:shader,execution,expression,unary,i32_conversion:bool:*": { "subcaseMS": 6.457 }, + "webgpu:shader,execution,expression,unary,i32_conversion:f16:*": { "subcaseMS": 21.310 }, + "webgpu:shader,execution,expression,unary,i32_conversion:f32:*": { "subcaseMS": 8.275 }, + "webgpu:shader,execution,expression,unary,i32_conversion:i32:*": { "subcaseMS": 7.707 }, + "webgpu:shader,execution,expression,unary,i32_conversion:u32:*": { "subcaseMS": 6.969 }, + "webgpu:shader,execution,expression,unary,u32_complement:u32_complement:*": { "subcaseMS": 7.632 }, + "webgpu:shader,execution,expression,unary,u32_conversion:abstract_int:*": { "subcaseMS": 20.406 }, + "webgpu:shader,execution,expression,unary,u32_conversion:bool:*": { "subcaseMS": 7.713 }, + "webgpu:shader,execution,expression,unary,u32_conversion:f16:*": { "subcaseMS": 14.705 }, + "webgpu:shader,execution,expression,unary,u32_conversion:f32:*": { "subcaseMS": 7.913 }, + "webgpu:shader,execution,expression,unary,u32_conversion:i32:*": { "subcaseMS": 8.319 }, + "webgpu:shader,execution,expression,unary,u32_conversion:u32:*": { "subcaseMS": 7.057 }, + "webgpu:shader,execution,float_parse:valid:*": { "subcaseMS": 6.801 }, + "webgpu:shader,execution,flow_control,call:call_basic:*": { "subcaseMS": 4.901 }, + "webgpu:shader,execution,flow_control,call:call_nested:*": { "subcaseMS": 5.500 }, + "webgpu:shader,execution,flow_control,call:call_repeated:*": { "subcaseMS": 10.851 }, + "webgpu:shader,execution,flow_control,complex:continue_in_switch_in_for_loop:*": { "subcaseMS": 13.650 }, + "webgpu:shader,execution,flow_control,eval_order:1d_array_assignment:*": { "subcaseMS": 17.500 }, + "webgpu:shader,execution,flow_control,eval_order:1d_array_compound_assignment:*": { "subcaseMS": 5.400 }, + "webgpu:shader,execution,flow_control,eval_order:1d_array_constructor:*": { "subcaseMS": 5.600 }, + "webgpu:shader,execution,flow_control,eval_order:1d_array_increment:*": { "subcaseMS": 5.500 }, + "webgpu:shader,execution,flow_control,eval_order:2d_array_assignment:*": { "subcaseMS": 11.000 }, + "webgpu:shader,execution,flow_control,eval_order:2d_array_compound_assignment:*": { "subcaseMS": 21.601 }, + "webgpu:shader,execution,flow_control,eval_order:2d_array_constructor:*": { "subcaseMS": 11.101 }, + "webgpu:shader,execution,flow_control,eval_order:2d_array_increment:*": { "subcaseMS": 10.601 }, + "webgpu:shader,execution,flow_control,eval_order:array_index:*": { "subcaseMS": 5.700 }, + "webgpu:shader,execution,flow_control,eval_order:array_index_lhs_assignment:*": { "subcaseMS": 11.301 }, + "webgpu:shader,execution,flow_control,eval_order:array_index_lhs_member_assignment:*": { "subcaseMS": 17.101 }, + "webgpu:shader,execution,flow_control,eval_order:array_index_via_ptrs:*": { "subcaseMS": 10.200 }, + "webgpu:shader,execution,flow_control,eval_order:array_index_via_struct_members:*": { "subcaseMS": 6.000 }, + "webgpu:shader,execution,flow_control,eval_order:binary_op:*": { "subcaseMS": 5.900 }, + "webgpu:shader,execution,flow_control,eval_order:binary_op_chain:*": { "subcaseMS": 21.000 }, + "webgpu:shader,execution,flow_control,eval_order:binary_op_chain_C_C_C_R:*": { "subcaseMS": 22.400 }, + "webgpu:shader,execution,flow_control,eval_order:binary_op_chain_C_C_R_C:*": { "subcaseMS": 6.601 }, + "webgpu:shader,execution,flow_control,eval_order:binary_op_chain_C_R_C_C:*": { "subcaseMS": 5.101 }, + "webgpu:shader,execution,flow_control,eval_order:binary_op_chain_R_C_C_C:*": { "subcaseMS": 6.000 }, + "webgpu:shader,execution,flow_control,eval_order:binary_op_lhs_const:*": { "subcaseMS": 5.401 }, + "webgpu:shader,execution,flow_control,eval_order:binary_op_parenthesized_expr:*": { "subcaseMS": 11.000 }, + "webgpu:shader,execution,flow_control,eval_order:binary_op_rhs_const:*": { "subcaseMS": 10.200 }, + "webgpu:shader,execution,flow_control,eval_order:bitwise_and:*": { "subcaseMS": 5.500 }, + "webgpu:shader,execution,flow_control,eval_order:bitwise_or:*": { "subcaseMS": 22.301 }, + "webgpu:shader,execution,flow_control,eval_order:builtin_fn_args:*": { "subcaseMS": 20.000 }, + "webgpu:shader,execution,flow_control,eval_order:logical_and:*": { "subcaseMS": 5.101 }, + "webgpu:shader,execution,flow_control,eval_order:logical_or:*": { "subcaseMS": 6.801 }, + "webgpu:shader,execution,flow_control,eval_order:matrix_index:*": { "subcaseMS": 9.900 }, + "webgpu:shader,execution,flow_control,eval_order:matrix_index_via_ptr:*": { "subcaseMS": 19.000 }, + "webgpu:shader,execution,flow_control,eval_order:nested_builtin_fn_args:*": { "subcaseMS": 10.500 }, + "webgpu:shader,execution,flow_control,eval_order:nested_fn_args:*": { "subcaseMS": 11.100 }, + "webgpu:shader,execution,flow_control,eval_order:nested_struct_constructor:*": { "subcaseMS": 10.500 }, + "webgpu:shader,execution,flow_control,eval_order:nested_vec4_constructor:*": { "subcaseMS": 10.700 }, + "webgpu:shader,execution,flow_control,eval_order:struct_constructor:*": { "subcaseMS": 5.701 }, + "webgpu:shader,execution,flow_control,eval_order:user_fn_args:*": { "subcaseMS": 5.801 }, + "webgpu:shader,execution,flow_control,eval_order:vec4_constructor:*": { "subcaseMS": 22.900 }, + "webgpu:shader,execution,flow_control,for:for_basic:*": { "subcaseMS": 14.150 }, + "webgpu:shader,execution,flow_control,for:for_break:*": { "subcaseMS": 5.700 }, + "webgpu:shader,execution,flow_control,for:for_complex_condition:*": { "subcaseMS": 12.450 }, + "webgpu:shader,execution,flow_control,for:for_complex_continuing:*": { "subcaseMS": 12.000 }, + "webgpu:shader,execution,flow_control,for:for_complex_initalizer:*": { "subcaseMS": 11.700 }, + "webgpu:shader,execution,flow_control,for:for_condition:*": { "subcaseMS": 6.050 }, + "webgpu:shader,execution,flow_control,for:for_continue:*": { "subcaseMS": 10.601 }, + "webgpu:shader,execution,flow_control,for:for_continuing:*": { "subcaseMS": 5.000 }, + "webgpu:shader,execution,flow_control,for:for_initalizer:*": { "subcaseMS": 7.751 }, + "webgpu:shader,execution,flow_control,for:nested_for_break:*": { "subcaseMS": 5.901 }, + "webgpu:shader,execution,flow_control,for:nested_for_continue:*": { "subcaseMS": 12.851 }, + "webgpu:shader,execution,flow_control,if:else_if:*": { "subcaseMS": 7.950 }, + "webgpu:shader,execution,flow_control,if:if_false:*": { "subcaseMS": 11.201 }, + "webgpu:shader,execution,flow_control,if:if_true:*": { "subcaseMS": 4.850 }, + "webgpu:shader,execution,flow_control,if:nested_if_else:*": { "subcaseMS": 11.650 }, + "webgpu:shader,execution,flow_control,loop:loop_break:*": { "subcaseMS": 6.000 }, + "webgpu:shader,execution,flow_control,loop:loop_continue:*": { "subcaseMS": 11.200 }, + "webgpu:shader,execution,flow_control,loop:loop_continuing_basic:*": { "subcaseMS": 12.450 }, + "webgpu:shader,execution,flow_control,loop:nested_loops:*": { "subcaseMS": 12.900 }, + "webgpu:shader,execution,flow_control,phony:phony_assign_call_basic:*": { "subcaseMS": 6.750 }, + "webgpu:shader,execution,flow_control,phony:phony_assign_call_builtin:*": { "subcaseMS": 12.001 }, + "webgpu:shader,execution,flow_control,phony:phony_assign_call_must_use:*": { "subcaseMS": 6.450 }, + "webgpu:shader,execution,flow_control,phony:phony_assign_call_nested:*": { "subcaseMS": 12.300 }, + "webgpu:shader,execution,flow_control,phony:phony_assign_call_nested_must_use:*": { "subcaseMS": 5.250 }, + "webgpu:shader,execution,flow_control,return:return:*": { "subcaseMS": 4.250 }, + "webgpu:shader,execution,flow_control,return:return_conditional_false:*": { "subcaseMS": 5.851 }, + "webgpu:shader,execution,flow_control,return:return_conditional_true:*": { "subcaseMS": 12.650 }, + "webgpu:shader,execution,flow_control,switch:switch:*": { "subcaseMS": 12.750 }, + "webgpu:shader,execution,flow_control,switch:switch_default:*": { "subcaseMS": 5.400 }, + "webgpu:shader,execution,flow_control,switch:switch_default_only:*": { "subcaseMS": 12.550 }, + "webgpu:shader,execution,flow_control,switch:switch_multiple_case:*": { "subcaseMS": 5.550 }, + "webgpu:shader,execution,flow_control,switch:switch_multiple_case_default:*": { "subcaseMS": 12.000 }, + "webgpu:shader,execution,flow_control,while:while_basic:*": { "subcaseMS": 5.951 }, + "webgpu:shader,execution,flow_control,while:while_break:*": { "subcaseMS": 12.450 }, + "webgpu:shader,execution,flow_control,while:while_continue:*": { "subcaseMS": 5.650 }, + "webgpu:shader,execution,flow_control,while:while_nested_break:*": { "subcaseMS": 12.701 }, + "webgpu:shader,execution,flow_control,while:while_nested_continue:*": { "subcaseMS": 5.450 }, + "webgpu:shader,execution,memory_model,atomicity:atomicity:*": { "subcaseMS": 77.201 }, + "webgpu:shader,execution,memory_model,barrier:workgroup_barrier_load_store:*": { "subcaseMS": 65.850 }, + "webgpu:shader,execution,memory_model,barrier:workgroup_barrier_store_load:*": { "subcaseMS": 78.800 }, + "webgpu:shader,execution,memory_model,barrier:workgroup_barrier_store_store:*": { "subcaseMS": 61.701 }, + "webgpu:shader,execution,memory_model,coherence:corr:*": { "subcaseMS": 238.167 }, + "webgpu:shader,execution,memory_model,coherence:corw1:*": { "subcaseMS": 250.467 }, + "webgpu:shader,execution,memory_model,coherence:corw2:*": { "subcaseMS": 244.384 }, + "webgpu:shader,execution,memory_model,coherence:cowr:*": { "subcaseMS": 250.484 }, + "webgpu:shader,execution,memory_model,coherence:coww:*": { "subcaseMS": 245.850 }, + "webgpu:shader,execution,memory_model,weak:2_plus_2_write:*": { "subcaseMS": 185.150 }, + "webgpu:shader,execution,memory_model,weak:load_buffer:*": { "subcaseMS": 184.900 }, + "webgpu:shader,execution,memory_model,weak:message_passing:*": { "subcaseMS": 196.550 }, + "webgpu:shader,execution,memory_model,weak:read:*": { "subcaseMS": 185.400 }, + "webgpu:shader,execution,memory_model,weak:store:*": { "subcaseMS": 184.500 }, + "webgpu:shader,execution,memory_model,weak:store_buffer:*": { "subcaseMS": 185.850 }, + "webgpu:shader,execution,padding:array_of_matCx3:*": { "subcaseMS": 8.650 }, + "webgpu:shader,execution,padding:array_of_struct:*": { "subcaseMS": 5.801 }, + "webgpu:shader,execution,padding:array_of_vec3:*": { "subcaseMS": 10.500 }, + "webgpu:shader,execution,padding:matCx3:*": { "subcaseMS": 10.050 }, + "webgpu:shader,execution,padding:struct_explicit:*": { "subcaseMS": 12.000 }, + "webgpu:shader,execution,padding:struct_implicit:*": { "subcaseMS": 33.201 }, + "webgpu:shader,execution,padding:struct_nested:*": { "subcaseMS": 21.400 }, + "webgpu:shader,execution,padding:vec3:*": { "subcaseMS": 8.700 }, + "webgpu:shader,execution,robust_access:linear_memory:*": { "subcaseMS": 5.293 }, + "webgpu:shader,execution,robust_access_vertex:vertex_buffer_access:*": { "subcaseMS": 6.487 }, + "webgpu:shader,execution,shader_io,compute_builtins:inputs:*": { "subcaseMS": 19.342 }, + "webgpu:shader,execution,shader_io,shared_structs:shared_between_stages:*": { "subcaseMS": 9.601 }, + "webgpu:shader,execution,shader_io,shared_structs:shared_with_buffer:*": { "subcaseMS": 20.701 }, + "webgpu:shader,execution,shader_io,shared_structs:shared_with_non_entry_point_function:*": { "subcaseMS": 6.801 }, + "webgpu:shader,execution,shadow:builtin:*": { "subcaseMS": 4.700 }, + "webgpu:shader,execution,shadow:declaration:*": { "subcaseMS": 9.700 }, + "webgpu:shader,execution,shadow:for_loop:*": { "subcaseMS": 17.201 }, + "webgpu:shader,execution,shadow:if:*": { "subcaseMS": 6.700 }, + "webgpu:shader,execution,shadow:loop:*": { "subcaseMS": 4.901 }, + "webgpu:shader,execution,shadow:switch:*": { "subcaseMS": 4.601 }, + "webgpu:shader,execution,shadow:while:*": { "subcaseMS": 7.400 }, + "webgpu:shader,execution,statement,increment_decrement:frexp_exp_increment:*": { "subcaseMS": 4.700 }, + "webgpu:shader,execution,statement,increment_decrement:scalar_i32_decrement:*": { "subcaseMS": 20.301 }, + "webgpu:shader,execution,statement,increment_decrement:scalar_i32_decrement_underflow:*": { "subcaseMS": 4.900 }, + "webgpu:shader,execution,statement,increment_decrement:scalar_i32_increment:*": { "subcaseMS": 17.801 }, + "webgpu:shader,execution,statement,increment_decrement:scalar_i32_increment_overflow:*": { "subcaseMS": 9.301 }, + "webgpu:shader,execution,statement,increment_decrement:scalar_u32_decrement:*": { "subcaseMS": 4.800 }, + "webgpu:shader,execution,statement,increment_decrement:scalar_u32_decrement_underflow:*": { "subcaseMS": 21.600 }, + "webgpu:shader,execution,statement,increment_decrement:scalar_u32_increment:*": { "subcaseMS": 5.900 }, + "webgpu:shader,execution,statement,increment_decrement:scalar_u32_increment_overflow:*": { "subcaseMS": 4.700 }, + "webgpu:shader,execution,statement,increment_decrement:vec2_element_decrement:*": { "subcaseMS": 5.200 }, + "webgpu:shader,execution,statement,increment_decrement:vec2_element_increment:*": { "subcaseMS": 5.000 }, + "webgpu:shader,execution,statement,increment_decrement:vec3_element_decrement:*": { "subcaseMS": 17.700 }, + "webgpu:shader,execution,statement,increment_decrement:vec3_element_increment:*": { "subcaseMS": 4.801 }, + "webgpu:shader,execution,statement,increment_decrement:vec4_element_decrement:*": { "subcaseMS": 5.300 }, + "webgpu:shader,execution,statement,increment_decrement:vec4_element_increment:*": { "subcaseMS": 6.300 }, + "webgpu:shader,execution,zero_init:compute,zero_init:*": { "subcaseMS": 2.944 }, + "webgpu:shader,validation,const_assert,const_assert:constant_expression_assert:*": { "subcaseMS": 1.456 }, + "webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_and_assert:*": { "subcaseMS": 1.493 }, + "webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_and_no_assert:*": { "subcaseMS": 1.339 }, + "webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_or_assert:*": { "subcaseMS": 1.501 }, + "webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_or_no_assert:*": { "subcaseMS": 1.373 }, + "webgpu:shader,validation,const_assert,const_assert:constant_expression_no_assert:*": { "subcaseMS": 1.655 }, + "webgpu:shader,validation,const_assert,const_assert:evaluation_stage:*": { "subcaseMS": 3.367 }, + "webgpu:shader,validation,decl,const:no_direct_recursion:*": { "subcaseMS": 0.951 }, + "webgpu:shader,validation,decl,const:no_indirect_recursion:*": { "subcaseMS": 0.950 }, + "webgpu:shader,validation,decl,const:no_indirect_recursion_via_array_size:*": { "subcaseMS": 2.601 }, + "webgpu:shader,validation,decl,const:no_indirect_recursion_via_struct_attribute:*": { "subcaseMS": 1.034 }, + "webgpu:shader,validation,decl,override:no_direct_recursion:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,decl,override:no_indirect_recursion:*": { "subcaseMS": 0.951 }, + "webgpu:shader,validation,decl,ptr_spelling:let_ptr_explicit_type_matches_var:*": { "subcaseMS": 1.500 }, + "webgpu:shader,validation,decl,ptr_spelling:let_ptr_reads:*": { "subcaseMS": 1.216 }, + "webgpu:shader,validation,decl,ptr_spelling:let_ptr_writes:*": { "subcaseMS": 1.250 }, + "webgpu:shader,validation,decl,ptr_spelling:ptr_address_space_never_uses_access_mode:*": { "subcaseMS": 1.141 }, + "webgpu:shader,validation,decl,ptr_spelling:ptr_bad_store_type:*": { "subcaseMS": 0.967 }, + "webgpu:shader,validation,decl,ptr_spelling:ptr_handle_space_invalid:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,decl,ptr_spelling:ptr_not_instantiable:*": { "subcaseMS": 1.310 }, + "webgpu:shader,validation,decl,var_access_mode:explicit_access_mode:*": { "subcaseMS": 1.373 }, + "webgpu:shader,validation,decl,var_access_mode:implicit_access_mode:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,decl,var_access_mode:read_access:*": { "subcaseMS": 1.177 }, + "webgpu:shader,validation,decl,var_access_mode:write_access:*": { "subcaseMS": 1.154 }, + "webgpu:shader,validation,expression,access,vector:vector:*": { "subcaseMS": 1.407 }, + "webgpu:shader,validation,expression,binary,bitwise_shift:shift_left_concrete:*": { "subcaseMS": 1.216 }, + "webgpu:shader,validation,expression,binary,bitwise_shift:shift_left_vec_size_mismatch:*": { "subcaseMS": 1.367 }, + "webgpu:shader,validation,expression,binary,bitwise_shift:shift_right_concrete:*": { "subcaseMS": 1.237 }, + "webgpu:shader,validation,expression,binary,bitwise_shift:shift_right_vec_size_mismatch:*": { "subcaseMS": 1.334 }, + "webgpu:shader,validation,expression,call,builtin,abs:values:*": { "subcaseMS": 0.391 }, + "webgpu:shader,validation,expression,call,builtin,acos:integer_argument:*": { "subcaseMS": 1.512 }, + "webgpu:shader,validation,expression,call,builtin,acos:values:*": { "subcaseMS": 0.342 }, + "webgpu:shader,validation,expression,call,builtin,acosh:integer_argument:*": { "subcaseMS": 1.234 }, + "webgpu:shader,validation,expression,call,builtin,acosh:values:*": { "subcaseMS": 0.217 }, + "webgpu:shader,validation,expression,call,builtin,asin:integer_argument:*": { "subcaseMS": 0.878 }, + "webgpu:shader,validation,expression,call,builtin,asin:values:*": { "subcaseMS": 0.359 }, + "webgpu:shader,validation,expression,call,builtin,asinh:integer_argument:*": { "subcaseMS": 1.267 }, + "webgpu:shader,validation,expression,call,builtin,asinh:values:*": { "subcaseMS": 0.372 }, + "webgpu:shader,validation,expression,call,builtin,atan2:integer_argument_x:*": { "subcaseMS": 0.912 }, + "webgpu:shader,validation,expression,call,builtin,atan2:integer_argument_y:*": { "subcaseMS": 0.867 }, + "webgpu:shader,validation,expression,call,builtin,atan2:values:*": { "subcaseMS": 0.359 }, + "webgpu:shader,validation,expression,call,builtin,atan:integer_argument:*": { "subcaseMS": 1.545 }, + "webgpu:shader,validation,expression,call,builtin,atan:values:*": { "subcaseMS": 0.335 }, + "webgpu:shader,validation,expression,call,builtin,atanh:integer_argument:*": { "subcaseMS": 0.912 }, + "webgpu:shader,validation,expression,call,builtin,atanh:values:*": { "subcaseMS": 0.231 }, + "webgpu:shader,validation,expression,call,builtin,atomics:stage:*": { "subcaseMS": 1.346 }, + "webgpu:shader,validation,expression,call,builtin,bitcast:bad_const_to_f32:*": { "subcaseMS": 0.844 }, + "webgpu:shader,validation,expression,call,builtin,bitcast:bad_to_f16:*": { "subcaseMS": 8.518 }, + "webgpu:shader,validation,expression,call,builtin,bitcast:bad_to_vec3h:*": { "subcaseMS": 17.641 }, + "webgpu:shader,validation,expression,call,builtin,bitcast:bad_type_constructible:*": { "subcaseMS": 1.214 }, + "webgpu:shader,validation,expression,call,builtin,bitcast:bad_type_nonconstructible:*": { "subcaseMS": 1.425 }, + "webgpu:shader,validation,expression,call,builtin,bitcast:valid_vec2h:*": { "subcaseMS": 3.405 }, + "webgpu:shader,validation,expression,call,builtin,bitcast:valid_vec4h:*": { "subcaseMS": 5.610 }, + "webgpu:shader,validation,expression,call,builtin,ceil:integer_argument:*": { "subcaseMS": 1.456 }, + "webgpu:shader,validation,expression,call,builtin,ceil:values:*": { "subcaseMS": 1.539 }, + "webgpu:shader,validation,expression,call,builtin,clamp:values:*": { "subcaseMS": 0.377 }, + "webgpu:shader,validation,expression,call,builtin,cos:integer_argument:*": { "subcaseMS": 1.601 }, + "webgpu:shader,validation,expression,call,builtin,cos:values:*": { "subcaseMS": 0.338 }, + "webgpu:shader,validation,expression,call,builtin,cosh:integer_argument:*": { "subcaseMS": 0.889 }, + "webgpu:shader,validation,expression,call,builtin,cosh:values:*": { "subcaseMS": 0.272 }, + "webgpu:shader,validation,expression,call,builtin,degrees:integer_argument:*": { "subcaseMS": 1.311 }, + "webgpu:shader,validation,expression,call,builtin,degrees:values:*": { "subcaseMS": 0.303 }, + "webgpu:shader,validation,expression,call,builtin,exp2:integer_argument:*": { "subcaseMS": 0.967 }, + "webgpu:shader,validation,expression,call,builtin,exp2:values:*": { "subcaseMS": 0.410 }, + "webgpu:shader,validation,expression,call,builtin,exp:integer_argument:*": { "subcaseMS": 1.356 }, + "webgpu:shader,validation,expression,call,builtin,exp:values:*": { "subcaseMS": 0.311 }, + "webgpu:shader,validation,expression,call,builtin,inverseSqrt:integer_argument:*": { "subcaseMS": 1.356 }, + "webgpu:shader,validation,expression,call,builtin,inverseSqrt:values:*": { "subcaseMS": 0.315 }, + "webgpu:shader,validation,expression,call,builtin,length:integer_argument:*": { "subcaseMS": 2.011 }, + "webgpu:shader,validation,expression,call,builtin,length:scalar:*": { "subcaseMS": 0.245 }, + "webgpu:shader,validation,expression,call,builtin,length:vec2:*": { "subcaseMS": 0.319 }, + "webgpu:shader,validation,expression,call,builtin,length:vec3:*": { "subcaseMS": 1.401 }, + "webgpu:shader,validation,expression,call,builtin,length:vec4:*": { "subcaseMS": 1.301 }, + "webgpu:shader,validation,expression,call,builtin,log2:integer_argument:*": { "subcaseMS": 1.034 }, + "webgpu:shader,validation,expression,call,builtin,log2:values:*": { "subcaseMS": 0.398 }, + "webgpu:shader,validation,expression,call,builtin,log:integer_argument:*": { "subcaseMS": 1.134 }, + "webgpu:shader,validation,expression,call,builtin,log:values:*": { "subcaseMS": 0.291 }, + "webgpu:shader,validation,expression,call,builtin,modf:integer_argument:*": { "subcaseMS": 1.089 }, + "webgpu:shader,validation,expression,call,builtin,modf:values:*": { "subcaseMS": 1.866 }, + "webgpu:shader,validation,expression,call,builtin,radians:integer_argument:*": { "subcaseMS": 1.811 }, + "webgpu:shader,validation,expression,call,builtin,radians:values:*": { "subcaseMS": 0.382 }, + "webgpu:shader,validation,expression,call,builtin,round:integer_argument:*": { "subcaseMS": 1.834 }, + "webgpu:shader,validation,expression,call,builtin,round:values:*": { "subcaseMS": 0.382 }, + "webgpu:shader,validation,expression,call,builtin,saturate:integer_argument:*": { "subcaseMS": 1.878 }, + "webgpu:shader,validation,expression,call,builtin,saturate:values:*": { "subcaseMS": 0.317 }, + "webgpu:shader,validation,expression,call,builtin,sign:unsigned_integer_argument:*": { "subcaseMS": 1.120 }, + "webgpu:shader,validation,expression,call,builtin,sign:values:*": { "subcaseMS": 0.343 }, + "webgpu:shader,validation,expression,call,builtin,sin:integer_argument:*": { "subcaseMS": 1.189 }, + "webgpu:shader,validation,expression,call,builtin,sin:values:*": { "subcaseMS": 0.349 }, + "webgpu:shader,validation,expression,call,builtin,sinh:integer_argument:*": { "subcaseMS": 1.078 }, + "webgpu:shader,validation,expression,call,builtin,sinh:values:*": { "subcaseMS": 0.357 }, + "webgpu:shader,validation,expression,call,builtin,sqrt:integer_argument:*": { "subcaseMS": 1.356 }, + "webgpu:shader,validation,expression,call,builtin,sqrt:values:*": { "subcaseMS": 0.302 }, + "webgpu:shader,validation,expression,call,builtin,tan:integer_argument:*": { "subcaseMS": 1.734 }, + "webgpu:shader,validation,expression,call,builtin,tan:values:*": { "subcaseMS": 0.350 }, + "webgpu:shader,validation,functions,alias_analysis:aliasing_inside_function:*": { "subcaseMS": 1.200 }, + "webgpu:shader,validation,functions,alias_analysis:member_accessors:*": { "subcaseMS": 1.656 }, + "webgpu:shader,validation,functions,alias_analysis:one_pointer_one_module_scope:*": { "subcaseMS": 1.598 }, + "webgpu:shader,validation,functions,alias_analysis:same_pointer_read_and_write:*": { "subcaseMS": 1.301 }, + "webgpu:shader,validation,functions,alias_analysis:subcalls:*": { "subcaseMS": 1.673 }, + "webgpu:shader,validation,functions,alias_analysis:two_pointers:*": { "subcaseMS": 1.537 }, + "webgpu:shader,validation,functions,restrictions:call_arg_types_match_params:*": { "subcaseMS": 1.518 }, + "webgpu:shader,validation,functions,restrictions:entry_point_call_target:*": { "subcaseMS": 1.734 }, + "webgpu:shader,validation,functions,restrictions:function_parameter_matching:*": { "subcaseMS": 1.953 }, + "webgpu:shader,validation,functions,restrictions:function_parameter_types:*": { "subcaseMS": 1.520 }, + "webgpu:shader,validation,functions,restrictions:function_return_types:*": { "subcaseMS": 1.535 }, + "webgpu:shader,validation,functions,restrictions:no_direct_recursion:*": { "subcaseMS": 2.500 }, + "webgpu:shader,validation,functions,restrictions:no_indirect_recursion:*": { "subcaseMS": 1.900 }, + "webgpu:shader,validation,functions,restrictions:param_names_must_differ:*": { "subcaseMS": 1.722 }, + "webgpu:shader,validation,functions,restrictions:param_number_matches_call:*": { "subcaseMS": 1.803 }, + "webgpu:shader,validation,functions,restrictions:param_scope_is_function_body:*": { "subcaseMS": 1.340 }, + "webgpu:shader,validation,functions,restrictions:vertex_returns_position:*": { "subcaseMS": 1.201 }, + "webgpu:shader,validation,parse,align:multi_align:*": { "subcaseMS": 1.200 }, + "webgpu:shader,validation,parse,align:parsing:*": { "subcaseMS": 1.272 }, + "webgpu:shader,validation,parse,align:placement:*": { "subcaseMS": 2.423 }, + "webgpu:shader,validation,parse,align:required_alignment:*": { "subcaseMS": 1.653 }, + "webgpu:shader,validation,parse,attribute:expressions:*": { "subcaseMS": 1.410 }, + "webgpu:shader,validation,parse,binary_ops:all:*": { "subcaseMS": 1.301 }, + "webgpu:shader,validation,parse,blankspace:blankspace:*": { "subcaseMS": 1.391 }, + "webgpu:shader,validation,parse,blankspace:bom:*": { "subcaseMS": 1.101 }, + "webgpu:shader,validation,parse,blankspace:null_characters:*": { "subcaseMS": 3.217 }, + "webgpu:shader,validation,parse,break:placement:*": { "subcaseMS": 1.254 }, + "webgpu:shader,validation,parse,builtin:parse:*": { "subcaseMS": 3.277 }, + "webgpu:shader,validation,parse,builtin:placement:*": { "subcaseMS": 1.267 }, + "webgpu:shader,validation,parse,comments:comments:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,parse,comments:line_comment_eof:*": { "subcaseMS": 4.500 }, + "webgpu:shader,validation,parse,comments:line_comment_terminators:*": { "subcaseMS": 1.021 }, + "webgpu:shader,validation,parse,comments:unterminated_block_comment:*": { "subcaseMS": 8.950 }, + "webgpu:shader,validation,parse,const:placement:*": { "subcaseMS": 1.167 }, + "webgpu:shader,validation,parse,const_assert:parse:*": { "subcaseMS": 1.400 }, + "webgpu:shader,validation,parse,diagnostic:conflicting_attribute_different_location:*": { "subcaseMS": 2.257 }, + "webgpu:shader,validation,parse,diagnostic:conflicting_attribute_same_location:*": { "subcaseMS": 1.400 }, + "webgpu:shader,validation,parse,diagnostic:conflicting_directive:*": { "subcaseMS": 1.244 }, + "webgpu:shader,validation,parse,diagnostic:invalid_locations:*": { "subcaseMS": 1.930 }, + "webgpu:shader,validation,parse,diagnostic:invalid_severity:*": { "subcaseMS": 1.361 }, + "webgpu:shader,validation,parse,diagnostic:valid_locations:*": { "subcaseMS": 1.368 }, + "webgpu:shader,validation,parse,diagnostic:valid_params:*": { "subcaseMS": 1.475 }, + "webgpu:shader,validation,parse,diagnostic:warning_unknown_rule:*": { "subcaseMS": 1.100 }, + "webgpu:shader,validation,parse,discard:placement:*": { "subcaseMS": 3.357 }, + "webgpu:shader,validation,parse,enable:enable:*": { "subcaseMS": 2.303 }, + "webgpu:shader,validation,parse,identifiers:alias_name:*": { "subcaseMS": 1.262 }, + "webgpu:shader,validation,parse,identifiers:function_const_name:*": { "subcaseMS": 1.298 }, + "webgpu:shader,validation,parse,identifiers:function_let_name:*": { "subcaseMS": 1.299 }, + "webgpu:shader,validation,parse,identifiers:function_name:*": { "subcaseMS": 1.242 }, + "webgpu:shader,validation,parse,identifiers:function_param_name:*": { "subcaseMS": 1.219 }, + "webgpu:shader,validation,parse,identifiers:function_var_name:*": { "subcaseMS": 1.326 }, + "webgpu:shader,validation,parse,identifiers:module_const_name:*": { "subcaseMS": 1.211 }, + "webgpu:shader,validation,parse,identifiers:module_var_name:*": { "subcaseMS": 1.218 }, + "webgpu:shader,validation,parse,identifiers:non_normalized:*": { "subcaseMS": 1.101 }, + "webgpu:shader,validation,parse,identifiers:override_name:*": { "subcaseMS": 1.228 }, + "webgpu:shader,validation,parse,identifiers:struct_name:*": { "subcaseMS": 1.230 }, + "webgpu:shader,validation,parse,literal:abstract_float:*": { "subcaseMS": 1.411 }, + "webgpu:shader,validation,parse,literal:abstract_int:*": { "subcaseMS": 1.296 }, + "webgpu:shader,validation,parse,literal:bools:*": { "subcaseMS": 2.901 }, + "webgpu:shader,validation,parse,literal:f16:*": { "subcaseMS": 45.119 }, + "webgpu:shader,validation,parse,literal:f32:*": { "subcaseMS": 1.393 }, + "webgpu:shader,validation,parse,literal:i32:*": { "subcaseMS": 1.541 }, + "webgpu:shader,validation,parse,literal:u32:*": { "subcaseMS": 1.379 }, + "webgpu:shader,validation,parse,must_use:builtin_must_use:*": { "subcaseMS": 1.400 }, + "webgpu:shader,validation,parse,must_use:builtin_no_must_use:*": { "subcaseMS": 1.206 }, + "webgpu:shader,validation,parse,must_use:call:*": { "subcaseMS": 1.275 }, + "webgpu:shader,validation,parse,must_use:declaration:*": { "subcaseMS": 1.523 }, + "webgpu:shader,validation,parse,pipeline_stage:compute_parsing:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,parse,pipeline_stage:duplicate_compute_on_function:*": { "subcaseMS": 2.651 }, + "webgpu:shader,validation,parse,pipeline_stage:duplicate_fragment_on_function:*": { "subcaseMS": 1.001 }, + "webgpu:shader,validation,parse,pipeline_stage:duplicate_vertex_on_function:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,parse,pipeline_stage:fragment_parsing:*": { "subcaseMS": 2.600 }, + "webgpu:shader,validation,parse,pipeline_stage:multiple_entry_points:*": { "subcaseMS": 1.100 }, + "webgpu:shader,validation,parse,pipeline_stage:placement:*": { "subcaseMS": 1.388 }, + "webgpu:shader,validation,parse,pipeline_stage:vertex_parsing:*": { "subcaseMS": 1.500 }, + "webgpu:shader,validation,parse,semicolon:after_assignment:*": { "subcaseMS": 1.400 }, + "webgpu:shader,validation,parse,semicolon:after_call:*": { "subcaseMS": 1.301 }, + "webgpu:shader,validation,parse,semicolon:after_case:*": { "subcaseMS": 1.301 }, + "webgpu:shader,validation,parse,semicolon:after_case_break:*": { "subcaseMS": 19.400 }, + "webgpu:shader,validation,parse,semicolon:after_compound_statement:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,parse,semicolon:after_continuing:*": { "subcaseMS": 0.900 }, + "webgpu:shader,validation,parse,semicolon:after_default_case:*": { "subcaseMS": 3.100 }, + "webgpu:shader,validation,parse,semicolon:after_default_case_break:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,parse,semicolon:after_discard:*": { "subcaseMS": 4.400 }, + "webgpu:shader,validation,parse,semicolon:after_enable:*": { "subcaseMS": 1.301 }, + "webgpu:shader,validation,parse,semicolon:after_fn_const_assert:*": { "subcaseMS": 1.400 }, + "webgpu:shader,validation,parse,semicolon:after_fn_const_decl:*": { "subcaseMS": 1.200 }, + "webgpu:shader,validation,parse,semicolon:after_fn_var_decl:*": { "subcaseMS": 1.101 }, + "webgpu:shader,validation,parse,semicolon:after_for:*": { "subcaseMS": 0.900 }, + "webgpu:shader,validation,parse,semicolon:after_for_break:*": { "subcaseMS": 1.201 }, + "webgpu:shader,validation,parse,semicolon:after_func_decl:*": { "subcaseMS": 1.200 }, + "webgpu:shader,validation,parse,semicolon:after_if:*": { "subcaseMS": 1.100 }, + "webgpu:shader,validation,parse,semicolon:after_if_else:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,parse,semicolon:after_let_decl:*": { "subcaseMS": 1.401 }, + "webgpu:shader,validation,parse,semicolon:after_loop:*": { "subcaseMS": 1.200 }, + "webgpu:shader,validation,parse,semicolon:after_loop_break:*": { "subcaseMS": 1.301 }, + "webgpu:shader,validation,parse,semicolon:after_loop_break_if:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,parse,semicolon:after_loop_continue:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,parse,semicolon:after_member:*": { "subcaseMS": 4.801 }, + "webgpu:shader,validation,parse,semicolon:after_module_const_decl:*": { "subcaseMS": 1.400 }, + "webgpu:shader,validation,parse,semicolon:after_module_var_decl:*": { "subcaseMS": 0.901 }, + "webgpu:shader,validation,parse,semicolon:after_return:*": { "subcaseMS": 1.201 }, + "webgpu:shader,validation,parse,semicolon:after_struct_decl:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,parse,semicolon:after_switch:*": { "subcaseMS": 1.101 }, + "webgpu:shader,validation,parse,semicolon:after_type_alias_decl:*": { "subcaseMS": 1.200 }, + "webgpu:shader,validation,parse,semicolon:after_while:*": { "subcaseMS": 0.901 }, + "webgpu:shader,validation,parse,semicolon:after_while_break:*": { "subcaseMS": 4.801 }, + "webgpu:shader,validation,parse,semicolon:after_while_continue:*": { "subcaseMS": 1.200 }, + "webgpu:shader,validation,parse,semicolon:compound_statement_multiple:*": { "subcaseMS": 0.800 }, + "webgpu:shader,validation,parse,semicolon:compound_statement_single:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,parse,semicolon:function_body_multiple:*": { "subcaseMS": 0.900 }, + "webgpu:shader,validation,parse,semicolon:function_body_single:*": { "subcaseMS": 0.800 }, + "webgpu:shader,validation,parse,semicolon:module_scope_multiple:*": { "subcaseMS": 0.900 }, + "webgpu:shader,validation,parse,semicolon:module_scope_single:*": { "subcaseMS": 2.100 }, + "webgpu:shader,validation,parse,source:empty:*": { "subcaseMS": 1.101 }, + "webgpu:shader,validation,parse,source:invalid_source:*": { "subcaseMS": 1.100 }, + "webgpu:shader,validation,parse,source:valid_source:*": { "subcaseMS": 1.101 }, + "webgpu:shader,validation,parse,unary_ops:all:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,parse,var_and_let:initializer_type:*": { "subcaseMS": 0.900 }, + "webgpu:shader,validation,parse,var_and_let:var_access_mode_bad_other_template_contents:*": { "subcaseMS": 4.071 }, + "webgpu:shader,validation,parse,var_and_let:var_access_mode_bad_template_delim:*": { "subcaseMS": 1.088 }, + "webgpu:shader,validation,shader_io,binding:binding:*": { "subcaseMS": 1.240 }, + "webgpu:shader,validation,shader_io,binding:binding_f16:*": { "subcaseMS": 0.500 }, + "webgpu:shader,validation,shader_io,binding:binding_without_group:*": { "subcaseMS": 0.901 }, + "webgpu:shader,validation,shader_io,builtins:duplicates:*": { "subcaseMS": 1.913 }, + "webgpu:shader,validation,shader_io,builtins:missing_vertex_position:*": { "subcaseMS": 0.975 }, + "webgpu:shader,validation,shader_io,builtins:nesting:*": { "subcaseMS": 2.700 }, + "webgpu:shader,validation,shader_io,builtins:reuse_builtin_name:*": { "subcaseMS": 1.202 }, + "webgpu:shader,validation,shader_io,builtins:stage_inout:*": { "subcaseMS": 1.231 }, + "webgpu:shader,validation,shader_io,builtins:type:*": { "subcaseMS": 1.314 }, + "webgpu:shader,validation,shader_io,entry_point:missing_attribute_on_param:*": { "subcaseMS": 4.801 }, + "webgpu:shader,validation,shader_io,entry_point:missing_attribute_on_param_struct:*": { "subcaseMS": 4.676 }, + "webgpu:shader,validation,shader_io,entry_point:missing_attribute_on_return_type:*": { "subcaseMS": 2.367 }, + "webgpu:shader,validation,shader_io,entry_point:missing_attribute_on_return_type_struct:*": { "subcaseMS": 1.101 }, + "webgpu:shader,validation,shader_io,entry_point:no_entry_point_provided:*": { "subcaseMS": 0.801 }, + "webgpu:shader,validation,shader_io,group:group:*": { "subcaseMS": 1.355 }, + "webgpu:shader,validation,shader_io,group:group_f16:*": { "subcaseMS": 0.400 }, + "webgpu:shader,validation,shader_io,group:group_without_binding:*": { "subcaseMS": 1.100 }, + "webgpu:shader,validation,shader_io,group_and_binding:binding_attributes:*": { "subcaseMS": 1.280 }, + "webgpu:shader,validation,shader_io,group_and_binding:different_entry_points:*": { "subcaseMS": 1.833 }, + "webgpu:shader,validation,shader_io,group_and_binding:function_scope:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,shader_io,group_and_binding:function_scope_texture:*": { "subcaseMS": 0.801 }, + "webgpu:shader,validation,shader_io,group_and_binding:private_function_scope:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,shader_io,group_and_binding:private_module_scope:*": { "subcaseMS": 1.301 }, + "webgpu:shader,validation,shader_io,group_and_binding:single_entry_point:*": { "subcaseMS": 1.380 }, + "webgpu:shader,validation,shader_io,id:id:*": { "subcaseMS": 1.132 }, + "webgpu:shader,validation,shader_io,id:id_fp16:*": { "subcaseMS": 1.001 }, + "webgpu:shader,validation,shader_io,id:id_in_function:*": { "subcaseMS": 0.750 }, + "webgpu:shader,validation,shader_io,id:id_non_override:*": { "subcaseMS": 0.767 }, + "webgpu:shader,validation,shader_io,id:id_struct_member:*": { "subcaseMS": 0.900 }, + "webgpu:shader,validation,shader_io,interpolate:duplicate:*": { "subcaseMS": 9.350 }, + "webgpu:shader,validation,shader_io,interpolate:integral_types:*": { "subcaseMS": 1.657 }, + "webgpu:shader,validation,shader_io,interpolate:interpolation_validation:*": { "subcaseMS": 1.193 }, + "webgpu:shader,validation,shader_io,interpolate:require_location:*": { "subcaseMS": 3.000 }, + "webgpu:shader,validation,shader_io,interpolate:type_and_sampling:*": { "subcaseMS": 1.383 }, + "webgpu:shader,validation,shader_io,invariant:not_valid_on_user_defined_io:*": { "subcaseMS": 1.100 }, + "webgpu:shader,validation,shader_io,invariant:parsing:*": { "subcaseMS": 1.438 }, + "webgpu:shader,validation,shader_io,invariant:valid_only_with_vertex_position_builtin:*": { "subcaseMS": 1.461 }, + "webgpu:shader,validation,shader_io,locations:duplicates:*": { "subcaseMS": 1.906 }, + "webgpu:shader,validation,shader_io,locations:location_fp16:*": { "subcaseMS": 0.501 }, + "webgpu:shader,validation,shader_io,locations:nesting:*": { "subcaseMS": 0.967 }, + "webgpu:shader,validation,shader_io,locations:stage_inout:*": { "subcaseMS": 1.850 }, + "webgpu:shader,validation,shader_io,locations:type:*": { "subcaseMS": 1.332 }, + "webgpu:shader,validation,shader_io,locations:validation:*": { "subcaseMS": 1.296 }, + "webgpu:shader,validation,shader_io,size:size:*": { "subcaseMS": 1.218 }, + "webgpu:shader,validation,shader_io,size:size_fp16:*": { "subcaseMS": 1.500 }, + "webgpu:shader,validation,shader_io,size:size_non_struct:*": { "subcaseMS": 0.929 }, + "webgpu:shader,validation,shader_io,workgroup_size:workgroup_size:*": { "subcaseMS": 1.227 }, + "webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_const:*": { "subcaseMS": 3.400 }, + "webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_fp16:*": { "subcaseMS": 0.700 }, + "webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_fragment_shader:*": { "subcaseMS": 1.301 }, + "webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_function:*": { "subcaseMS": 0.800 }, + "webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_var:*": { "subcaseMS": 2.101 }, + "webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_vertex_shader:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,types,alias:no_direct_recursion:*": { "subcaseMS": 1.450 }, + "webgpu:shader,validation,types,alias:no_indirect_recursion:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,types,alias:no_indirect_recursion_via_array_element:*": { "subcaseMS": 1.050 }, + "webgpu:shader,validation,types,alias:no_indirect_recursion_via_array_size:*": { "subcaseMS": 2.851 }, + "webgpu:shader,validation,types,alias:no_indirect_recursion_via_atomic:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,types,alias:no_indirect_recursion_via_matrix_element:*": { "subcaseMS": 0.851 }, + "webgpu:shader,validation,types,alias:no_indirect_recursion_via_ptr_store_type:*": { "subcaseMS": 1.050 }, + "webgpu:shader,validation,types,alias:no_indirect_recursion_via_struct_attribute:*": { "subcaseMS": 1.584 }, + "webgpu:shader,validation,types,alias:no_indirect_recursion_via_struct_member:*": { "subcaseMS": 1.000 }, + "webgpu:shader,validation,types,alias:no_indirect_recursion_via_vector_element:*": { "subcaseMS": 1.050 }, + "webgpu:shader,validation,types,struct:no_direct_recursion:*": { "subcaseMS": 0.951 }, + "webgpu:shader,validation,types,struct:no_indirect_recursion:*": { "subcaseMS": 0.901 }, + "webgpu:shader,validation,types,struct:no_indirect_recursion_via_array_element:*": { "subcaseMS": 0.901 }, + "webgpu:shader,validation,types,struct:no_indirect_recursion_via_array_size:*": { "subcaseMS": 0.900 }, + "webgpu:shader,validation,types,struct:no_indirect_recursion_via_struct_attribute:*": { "subcaseMS": 1.467 }, + "webgpu:shader,validation,types,struct:no_indirect_recursion_via_struct_member_nested_in_alias:*": { "subcaseMS": 0.950 }, + "webgpu:shader,validation,types,vector:vector:*": { "subcaseMS": 1.295 }, + "webgpu:shader,validation,uniformity,uniformity:basics:*": { "subcaseMS": 1.467 }, + "webgpu:shader,validation,uniformity,uniformity:binary_expressions:*": { "subcaseMS": 1.758 }, + "webgpu:shader,validation,uniformity,uniformity:compute_builtin_values:*": { "subcaseMS": 2.500 }, + "webgpu:shader,validation,uniformity,uniformity:fragment_builtin_values:*": { "subcaseMS": 1.300 }, + "webgpu:shader,validation,uniformity,uniformity:function_pointer_parameters:*": { "subcaseMS": 1.546 }, + "webgpu:shader,validation,uniformity,uniformity:function_variables:*": { "subcaseMS": 1.573 }, + "webgpu:shader,validation,uniformity,uniformity:functions:*": { "subcaseMS": 1.303 }, + "webgpu:shader,validation,uniformity,uniformity:pointers:*": { "subcaseMS": 1.738 }, + "webgpu:shader,validation,uniformity,uniformity:short_circuit_expressions:*": { "subcaseMS": 1.401 }, + "webgpu:shader,validation,uniformity,uniformity:unary_expressions:*": { "subcaseMS": 1.279 }, + "webgpu:util,texture,texel_data:float_texel_data_in_shader:*": { "subcaseMS": 2.042 }, + "webgpu:util,texture,texel_data:sint_texel_data_in_shader:*": { "subcaseMS": 2.573 }, + "webgpu:util,texture,texel_data:snorm_texel_data_in_shader:*": { "subcaseMS": 4.645 }, + "webgpu:util,texture,texel_data:ufloat_texel_data_in_shader:*": { "subcaseMS": 2.908 }, + "webgpu:util,texture,texel_data:uint_texel_data_in_shader:*": { "subcaseMS": 4.106 }, + "webgpu:util,texture,texel_data:unorm_texel_data_in_shader:*": { "subcaseMS": 5.179 }, + "webgpu:util,texture,texture_ok:float32:*": { "subcaseMS": 1.655 }, + "webgpu:util,texture,texture_ok:norm:*": { "subcaseMS": 4.019 }, + "webgpu:util,texture,texture_ok:snorm_min:*": { "subcaseMS": 17.250 }, + "webgpu:web_platform,canvas,configure:alpha_mode:*": { "subcaseMS": 4.075 }, + "webgpu:web_platform,canvas,configure:defaults:*": { "subcaseMS": 8.800 }, + "webgpu:web_platform,canvas,configure:device:*": { "subcaseMS": 14.800 }, + "webgpu:web_platform,canvas,configure:format:*": { "subcaseMS": 5.455 }, + "webgpu:web_platform,canvas,configure:size_zero_after_configure:*": { "subcaseMS": 4.425 }, + "webgpu:web_platform,canvas,configure:size_zero_before_configure:*": { "subcaseMS": 8.400 }, + "webgpu:web_platform,canvas,configure:usage:*": { "subcaseMS": 1.087 }, + "webgpu:web_platform,canvas,configure:viewFormats:*": { "subcaseMS": 0.899 }, + "webgpu:web_platform,canvas,context_creation:return_type:*": { "subcaseMS": 0.700 }, + "webgpu:web_platform,canvas,getCurrentTexture:configured:*": { "subcaseMS": 13.000 }, + "webgpu:web_platform,canvas,getCurrentTexture:expiry:*": { "subcaseMS": 2.925 }, + "webgpu:web_platform,canvas,getCurrentTexture:multiple_frames:*": { "subcaseMS": 32.400 }, + "webgpu:web_platform,canvas,getCurrentTexture:resize:*": { "subcaseMS": 16.601 }, + "webgpu:web_platform,canvas,getCurrentTexture:single_frames:*": { "subcaseMS": 10.800 }, + "webgpu:web_platform,canvas,getPreferredCanvasFormat:value:*": { "subcaseMS": 0.200 }, + "webgpu:web_platform,canvas,readbackFromWebGPUCanvas:drawTo2DCanvas:*": { "subcaseMS": 12.963 }, + "webgpu:web_platform,canvas,readbackFromWebGPUCanvas:offscreenCanvas,snapshot:*": { "subcaseMS": 27.148 }, + "webgpu:web_platform,canvas,readbackFromWebGPUCanvas:onscreenCanvas,snapshot:*": { "subcaseMS": 36.364 }, + "webgpu:web_platform,canvas,readbackFromWebGPUCanvas:onscreenCanvas,uploadToWebGL:*": { "subcaseMS": 15.859 }, + "webgpu:web_platform,canvas,readbackFromWebGPUCanvas:transferToImageBitmap_huge_size:*": { "subcaseMS": 571.100 }, + "webgpu:web_platform,canvas,readbackFromWebGPUCanvas:transferToImageBitmap_unconfigured_nonzero_size:*": { "subcaseMS": 3.200 }, + "webgpu:web_platform,canvas,readbackFromWebGPUCanvas:transferToImageBitmap_zero_size:*": { "subcaseMS": 7.551 }, + "webgpu:web_platform,copyToTexture,ImageBitmap:copy_subrect_from_2D_Canvas:*": { "subcaseMS": 5.329 }, + "webgpu:web_platform,copyToTexture,ImageBitmap:copy_subrect_from_ImageData:*": { "subcaseMS": 3.295 }, + "webgpu:web_platform,copyToTexture,ImageBitmap:from_ImageData:*": { "subcaseMS": 0.000 }, + "webgpu:web_platform,copyToTexture,ImageBitmap:from_canvas:*": { "subcaseMS": 0.000 }, + "webgpu:web_platform,copyToTexture,ImageData:copy_subrect_from_ImageData:*": { "subcaseMS": 3.167 }, + "webgpu:web_platform,copyToTexture,ImageData:from_ImageData:*": { "subcaseMS": 27.268 }, + "webgpu:web_platform,copyToTexture,canvas:color_space_conversion:*": { "subcaseMS": 15.391 }, + "webgpu:web_platform,copyToTexture,canvas:copy_contents_from_2d_context_canvas:*": { "subcaseMS": 3.437 }, + "webgpu:web_platform,copyToTexture,canvas:copy_contents_from_bitmaprenderer_context_canvas:*": { "subcaseMS": 3.504 }, + "webgpu:web_platform,copyToTexture,canvas:copy_contents_from_gl_context_canvas:*": { "subcaseMS": 14.659 }, + "webgpu:web_platform,copyToTexture,canvas:copy_contents_from_gpu_context_canvas:*": { "subcaseMS": 1.859 }, + "webgpu:web_platform,copyToTexture,image:copy_subrect_from_2D_Canvas:*": { "subcaseMS": 8.754 }, + "webgpu:web_platform,copyToTexture,image:from_image:*": { "subcaseMS": 21.869 }, + "webgpu:web_platform,copyToTexture,video:copy_from_video:*": { "subcaseMS": 25.101 }, + "webgpu:web_platform,external_texture,video:importExternalTexture,compute:*": { "subcaseMS": 36.270 }, + "webgpu:web_platform,external_texture,video:importExternalTexture,sample:*": { "subcaseMS": 33.380 }, + "webgpu:web_platform,external_texture,video:importExternalTexture,sampleWithRotationMetadata:*": { "subcaseMS": 34.968 }, + "webgpu:web_platform,external_texture,video:importExternalTexture,sampleWithVideoFrameWithVisibleRectParam:*": { "subcaseMS": 29.160 }, + "webgpu:web_platform,worker,worker:worker:*": { "subcaseMS": 245.901 }, + "_end": "" +} From 05e32a688431ef43ede7cc2eadcc891f7be2cfca Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Mon, 11 Sep 2023 10:23:16 -0400 Subject: [PATCH 023/166] wgsl: Add f16 negation execution tests (#2927) Issue #1626 --- src/unittests/floating_point.spec.ts | 23 +++++++++- .../expression/unary/f16_arithmetic.spec.ts | 44 +++++++++++++++++++ src/webgpu/util/floating_point.ts | 2 +- 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index a9a92e181c37..56c6598aa9e1 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -3110,6 +3110,27 @@ const kNegationIntervalCases = { { input: kValue.f32.subnormal.negative.min, expected: [0, kValue.f32.subnormal.positive.max] }, { input: kValue.f32.subnormal.negative.max, expected: [0, kValue.f32.subnormal.positive.min] }, ] as ScalarToIntervalCase[], + f16: [ + // Edge cases + { input: kValue.f16.infinity.positive, expected: kUnboundedBounds }, + { input: kValue.f16.infinity.negative, expected: kUnboundedBounds }, + { input: kValue.f16.positive.max, expected: kValue.f16.negative.min }, + { input: kValue.f16.positive.min, expected: kValue.f16.negative.max }, + { input: kValue.f16.negative.min, expected: kValue.f16.positive.max }, + { input: kValue.f16.negative.max, expected: kValue.f16.positive.min }, + + // Normals + { input: 0.1, expected: [kMinusOneULPFunctions['f16'](reinterpretU16AsF16(0xae66)), reinterpretU16AsF16(0xae66)] }, // ~-0.1 + { input: 1.9, expected: [reinterpretU16AsF16(0xbf9a), kPlusOneULPFunctions['f16'](reinterpretU16AsF16(0xbf9a))] }, // ~-1.9 + { input: -0.1, expected: [reinterpretU16AsF16(0x2e66), kPlusOneULPFunctions['f16'](reinterpretU16AsF16(0x2e66))] }, // ~0.1 + { input: -1.9, expected: [kMinusOneULPFunctions['f16'](reinterpretU16AsF16(0x3f9a)), reinterpretU16AsF16(0x3f9a)] }, // ~1.9 + + // Subnormals + { input: kValue.f16.subnormal.positive.max, expected: [kValue.f16.subnormal.negative.min, 0] }, + { input: kValue.f16.subnormal.positive.min, expected: [kValue.f16.subnormal.negative.max, 0] }, + { input: kValue.f16.subnormal.negative.min, expected: [0, kValue.f16.subnormal.positive.max] }, + { input: kValue.f16.subnormal.negative.max, expected: [0, kValue.f16.subnormal.positive.min] }, + ] as ScalarToIntervalCase[], abstract: [ // Edge cases { input: kValue.f64.infinity.positive, expected: kUnboundedBounds }, @@ -3136,7 +3157,7 @@ const kNegationIntervalCases = { g.test('negationInterval') .params(u => u - .combine('trait', ['f32', 'abstract'] as const) + .combine('trait', ['f32', 'f16', 'abstract'] as const) .beginSubcases() .expandWithParams(p => { // prettier-ignore diff --git a/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts b/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts new file mode 100644 index 000000000000..83d7579c077d --- /dev/null +++ b/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts @@ -0,0 +1,44 @@ +export const description = ` +Execution Tests for the f16 arithmetic unary expression operations +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../gpu_test.js'; +import { TypeF16 } from '../../../../util/conversion.js'; +import { FP } from '../../../../util/floating_point.js'; +import { fullF16Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; +import { allInputSources, run } from '../expression.js'; + +import { unary } from './unary.js'; + +export const g = makeTestGroup(GPUTest); + +export const d = makeCaseCache('unary/f16_arithmetic', { + negation: () => { + return FP.f16.generateScalarToIntervalCases( + fullF16Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }), + 'unfiltered', + FP.f16.negationInterval + ); + }, +}); + +g.test('negation') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: -x +Accuracy: Correctly rounded +` + ) + .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('negation'); + await run(t, unary('-'), [TypeF16], TypeF16, t.params, cases); + }); diff --git a/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index 9b2ec9bf5062..af28a4191ff9 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -5171,7 +5171,7 @@ class F16Traits extends FPTraits { public readonly multiplicationVectorMatrixInterval = this.unimplementedVectorMatrixToVector.bind( this ); - public readonly negationInterval = this.unimplementedScalarToInterval.bind(this); + public readonly negationInterval = this.negationIntervalImpl.bind(this); public readonly normalizeInterval = this.unimplementedVectorToVector.bind(this); public readonly powInterval = this.unimplementedScalarPairToInterval.bind(this); public readonly quantizeToF16Interval = this.quantizeToF16IntervalNotAvailable.bind(this); From 90edae12842f4b6cfeabf1e73d9a8770e2c20311 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Mon, 11 Sep 2023 19:22:00 +0100 Subject: [PATCH 024/166] Fix presubmits (`npm test`) Add missing entry to `listing_meta.json` --- src/webgpu/listing_meta.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index ae925f390019..db480c2c2b20 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -1401,6 +1401,7 @@ "webgpu:shader,execution,expression,unary,bool_conversion:i32:*": { "subcaseMS": 8.219 }, "webgpu:shader,execution,expression,unary,bool_conversion:u32:*": { "subcaseMS": 7.401 }, "webgpu:shader,execution,expression,unary,bool_logical:negation:*": { "subcaseMS": 6.413 }, + "webgpu:shader,execution,expression,unary,f16_arithmetic:negation:*": { "subcaseMS": 4.0 }, "webgpu:shader,execution,expression,unary,f32_arithmetic:negation:*": { "subcaseMS": 16.400 }, "webgpu:shader,execution,expression,unary,f32_conversion:bool:*": { "subcaseMS": 7.182 }, "webgpu:shader,execution,expression,unary,f32_conversion:f16:*": { "subcaseMS": 15.908 }, From f0044b916a5b5185b7ccf2cd6e57b8e4f329eaff Mon Sep 17 00:00:00 2001 From: Greggman Date: Mon, 11 Sep 2023 15:52:23 -0700 Subject: [PATCH 025/166] Compat: Test vertex_index, instance_index limits (#2940) @builtin(vertex_index) and @buildin(instance_index) each take an attribute in compat mode --- .../render_pipeline/vertex_state.spec.ts | 91 +++++++++++++++++++ src/webgpu/listing_meta.json | 1 + 2 files changed, 92 insertions(+) create mode 100644 src/webgpu/compat/api/validation/render_pipeline/vertex_state.spec.ts diff --git a/src/webgpu/compat/api/validation/render_pipeline/vertex_state.spec.ts b/src/webgpu/compat/api/validation/render_pipeline/vertex_state.spec.ts new file mode 100644 index 000000000000..ef72c50ce9b8 --- /dev/null +++ b/src/webgpu/compat/api/validation/render_pipeline/vertex_state.spec.ts @@ -0,0 +1,91 @@ +export const description = ` +Tests limitations of createRenderPipeline related to vertex state in compat mode. +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { range } from '../../../../../common/util/util.js'; +import { CompatibilityTest } from '../../../compatibility_test.js'; + +export const g = makeTestGroup(CompatibilityTest); + +g.test('maxVertexAttributesVertexIndexInstanceIndex') + .desc( + ` +Tests @builtin(vertex_index) and @builtin(instance_index) each count as an attribute. + +- Test that you can use maxVertexAttributes +- Test that you can not use maxVertexAttributes and @builtin(vertex_index) +- Test that you can not use maxVertexAttributes and @builtin(instance_index) +- Test that you can use maxVertexAttributes - 1 and @builtin(vertex_index) +- Test that you can use maxVertexAttributes - 1 and @builtin(instance_index) +- Test that you can not use maxVertexAttributes - 1 and both @builtin(vertex_index) and @builtin(instance_index) +- Test that you can use maxVertexAttributes - 2 and both @builtin(vertex_index) and @builtin(instance_index) + ` + ) + .params(u => + u + .combine('useVertexIndex', [false, true] as const) + .combine('useInstanceIndex', [false, true] as const) + .combine('numAttribsToReserve', [0, 1, 2] as const) + .combine('isAsync', [false, true] as const) + ) + .fn(t => { + const { useVertexIndex, useInstanceIndex, numAttribsToReserve, isAsync } = t.params; + const numAttribs = t.device.limits.maxVertexAttributes - numAttribsToReserve; + + const numBuiltinsUsed = (useVertexIndex ? 1 : 0) + (useInstanceIndex ? 1 : 0); + const isValid = numAttribs + numBuiltinsUsed <= t.device.limits.maxVertexAttributes; + + const inputs = range(numAttribs, i => `@location(${i}) v${i}: vec4f`); + const outputs = range(numAttribs, i => `v${i}`); + + if (useVertexIndex) { + inputs.push('@builtin(vertex_index) vNdx: u32'); + outputs.push('vec4f(f32(vNdx))'); + } + + if (useInstanceIndex) { + inputs.push('@builtin(instance_index) iNdx: u32'); + outputs.push('vec4f(f32(iNdx))'); + } + + const module = t.device.createShaderModule({ + code: ` + @fragment fn fs() -> @location(0) vec4f { + return vec4f(1); + } + @vertex fn vs(${inputs.join(', ')}) -> @builtin(position) vec4f { + return ${outputs.join(' + ')}; + } + `, + }); + + const pipelineDescriptor: GPURenderPipelineDescriptor = { + layout: 'auto', + vertex: { + module, + entryPoint: 'vs', + buffers: [ + { + arrayStride: 16, + attributes: range(numAttribs, i => ({ + shaderLocation: i, + format: 'float32x4', + offset: 0, + })), + }, + ], + }, + fragment: { + module, + entryPoint: 'fs', + targets: [ + { + format: 'rgba8unorm', + }, + ], + }, + }; + + t.doCreateRenderPipelineTest(isAsync, isValid, pipelineDescriptor); + }); diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index db480c2c2b20..b43b94657c0d 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -823,6 +823,7 @@ "webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,render_pass,used:*": { "subcaseMS": 0.000 }, "webgpu:compat,api,validation,render_pipeline,fragment_state:colorState:*": { "subcaseMS": 32.604 }, "webgpu:compat,api,validation,render_pipeline,shader_module:sample_mask:*": { "subcaseMS": 14.801 }, + "webgpu:compat,api,validation,render_pipeline,vertex_state:maxVertexAttributesVertexIndexInstanceIndex:*": { "subcaseMS": 3.7 }, "webgpu:compat,api,validation,texture,createTexture:unsupportedTextureFormats:*": { "subcaseMS": 0.700 }, "webgpu:compat,api,validation,texture,createTexture:unsupportedTextureViewFormats:*": { "subcaseMS": 0.601 }, "webgpu:compat,api,validation,texture,cubeArray:cube_array:*": { "subcaseMS": 13.701 }, From 0b49ea79daf566e9a77dab4c627f6e12c58815ad Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Tue, 12 Sep 2023 11:02:29 -0400 Subject: [PATCH 026/166] wgsl: Add AbstractFloat matrix addition execution tests (#2926) Issue #1626 --- src/unittests/floating_point.spec.ts | 375 +++++++++--------- src/webgpu/listing_meta.json | 3 +- .../binary/af_matrix_addition.spec.ts | 118 ++++++ .../shader/execution/expression/expression.ts | 86 +++- src/webgpu/util/conversion.ts | 6 +- src/webgpu/util/floating_point.ts | 2 +- src/webgpu/util/math.ts | 123 ++++++ 7 files changed, 513 insertions(+), 200 deletions(-) create mode 100644 src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 56c6598aa9e1..02239005c6ee 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -5841,193 +5841,200 @@ interface MatrixPairToMatrixCase { expected: (number | IntervalBounds)[][]; } -g.test('additionMatrixMatrixInterval_f32') - .paramsSubcasesOnly([ - // Only testing that different shapes of matrices are handled correctly - // here, to reduce test duplication. - // additionMatrixMatrixInterval uses AdditionIntervalOp for calculating intervals, - // so the testing for additionInterval covers the actual interval - // calculations. - { - input: [ - [ - [1, 2], - [3, 4], - ], - [ - [10, 20], - [30, 40], - ], - ], - expected: [ - [11, 22], - [33, 44], - ], - }, - { - input: [ - [ - [1, 2], - [3, 4], - [5, 6], - ], - [ - [10, 20], - [30, 40], - [50, 60], - ], - ], - expected: [ - [11, 22], - [33, 44], - [55, 66], - ], - }, - { - input: [ - [ - [1, 2], - [3, 4], - [5, 6], - [7, 8], - ], - [ - [10, 20], - [30, 40], - [50, 60], - [70, 80], - ], - ], - expected: [ - [11, 22], - [33, 44], - [55, 66], - [77, 88], - ], - }, - { - input: [ - [ - [1, 2, 3], - [4, 5, 6], - ], - [ - [10, 20, 30], - [40, 50, 60], - ], - ], - expected: [ - [11, 22, 33], - [44, 55, 66], - ], - }, - { - input: [ - [ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9], - ], - [ - [10, 20, 30], - [40, 50, 60], - [70, 80, 90], - ], - ], - expected: [ - [11, 22, 33], - [44, 55, 66], - [77, 88, 99], - ], - }, - { - input: [ - [ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9], - [10, 11, 12], - ], - [ - [10, 20, 30], - [40, 50, 60], - [70, 80, 90], - [1000, 1100, 1200], - ], - ], - expected: [ - [11, 22, 33], - [44, 55, 66], - [77, 88, 99], - [1010, 1111, 1212], - ], - }, - { - input: [ - [ - [1, 2, 3, 4], - [5, 6, 7, 8], - ], - [ - [10, 20, 30, 40], - [50, 60, 70, 80], - ], - ], - expected: [ - [11, 22, 33, 44], - [55, 66, 77, 88], - ], - }, - { - input: [ - [ - [1, 2, 3, 4], - [5, 6, 7, 8], - [9, 10, 11, 12], - ], - [ - [10, 20, 30, 40], - [50, 60, 70, 80], - [90, 1000, 1100, 1200], - ], - ], - expected: [ - [11, 22, 33, 44], - [55, 66, 77, 88], - [99, 1010, 1111, 1212], - ], - }, - { - input: [ - [ - [1, 2, 3, 4], - [5, 6, 7, 8], - [9, 10, 11, 12], - [13, 14, 15, 16], - ], - [ - [10, 20, 30, 40], - [50, 60, 70, 80], - [90, 1000, 1100, 1200], - [1300, 1400, 1500, 1600], - ], - ], - expected: [ - [11, 22, 33, 44], - [55, 66, 77, 88], - [99, 1010, 1111, 1212], - [1313, 1414, 1515, 1616], - ], - }, - ]) +g.test('additionMatrixMatrixInterval') + .params(u => + u + .combine('trait', ['f32', 'abstract'] as const) + .beginSubcases() + .expandWithParams(_ => { + // Only testing that different shapes of matrices are handled correctly + // here, to reduce test duplication. + // additionMatrixMatrixInterval uses AdditionIntervalOp for calculating intervals, + // so the testing for additionInterval covers the actual interval + // calculations. + return [ + { + input: [ + [ + [1, 2], + [3, 4], + ], + [ + [10, 20], + [30, 40], + ], + ], + expected: [ + [11, 22], + [33, 44], + ], + }, + { + input: [ + [ + [1, 2], + [3, 4], + [5, 6], + ], + [ + [10, 20], + [30, 40], + [50, 60], + ], + ], + expected: [ + [11, 22], + [33, 44], + [55, 66], + ], + }, + { + input: [ + [ + [1, 2], + [3, 4], + [5, 6], + [7, 8], + ], + [ + [10, 20], + [30, 40], + [50, 60], + [70, 80], + ], + ], + expected: [ + [11, 22], + [33, 44], + [55, 66], + [77, 88], + ], + }, + { + input: [ + [ + [1, 2, 3], + [4, 5, 6], + ], + [ + [10, 20, 30], + [40, 50, 60], + ], + ], + expected: [ + [11, 22, 33], + [44, 55, 66], + ], + }, + { + input: [ + [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + ], + [ + [10, 20, 30], + [40, 50, 60], + [70, 80, 90], + ], + ], + expected: [ + [11, 22, 33], + [44, 55, 66], + [77, 88, 99], + ], + }, + { + input: [ + [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + [10, 11, 12], + ], + [ + [10, 20, 30], + [40, 50, 60], + [70, 80, 90], + [1000, 1100, 1200], + ], + ], + expected: [ + [11, 22, 33], + [44, 55, 66], + [77, 88, 99], + [1010, 1111, 1212], + ], + }, + { + input: [ + [ + [1, 2, 3, 4], + [5, 6, 7, 8], + ], + [ + [10, 20, 30, 40], + [50, 60, 70, 80], + ], + ], + expected: [ + [11, 22, 33, 44], + [55, 66, 77, 88], + ], + }, + { + input: [ + [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + ], + [ + [10, 20, 30, 40], + [50, 60, 70, 80], + [90, 1000, 1100, 1200], + ], + ], + expected: [ + [11, 22, 33, 44], + [55, 66, 77, 88], + [99, 1010, 1111, 1212], + ], + }, + { + input: [ + [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + [13, 14, 15, 16], + ], + [ + [10, 20, 30, 40], + [50, 60, 70, 80], + [90, 1000, 1100, 1200], + [1300, 1400, 1500, 1600], + ], + ], + expected: [ + [11, 22, 33, 44], + [55, 66, 77, 88], + [99, 1010, 1111, 1212], + [1313, 1414, 1515, 1616], + ], + }, + ]; + }) + ) .fn(t => { - const x = t.params.input[0]; - const y = t.params.input[1]; - const expected = FP.f32.toMatrix(t.params.expected); - const got = FP.f32.additionMatrixMatrixInterval(x, y); + const [x, y] = t.params.input; + const trait = FP[t.params.trait]; + const expected = trait.toMatrix(t.params.expected); + const got = trait.additionMatrixMatrixInterval(x, y); t.expect( objectEquals(expected, got), - `f32.additionMatrixMatrixInterval([${JSON.stringify(x)}], [${JSON.stringify( + `${t.params.trait}.additionMatrixMatrixInterval([${JSON.stringify(x)}], [${JSON.stringify( y )}]) returned '[${JSON.stringify(got)}]'. Expected '[${JSON.stringify(expected)}]'` ); diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index b43b94657c0d..54dfc261ff34 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -859,6 +859,7 @@ "webgpu:shader,execution,expression,binary,af_comparison:less_equals:*": { "subcaseMS": 19.651 }, "webgpu:shader,execution,expression,binary,af_comparison:less_than:*": { "subcaseMS": 19.975 }, "webgpu:shader,execution,expression,binary,af_comparison:not_equals:*": { "subcaseMS": 19.651 }, + "webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*": { "subcaseMS": 11169.534 }, "webgpu:shader,execution,expression,binary,bitwise:bitwise_and:*": { "subcaseMS": 20.982 }, "webgpu:shader,execution,expression,binary,bitwise:bitwise_and_compound:*": { "subcaseMS": 22.513 }, "webgpu:shader,execution,expression,binary,bitwise:bitwise_exclusive_or:*": { "subcaseMS": 21.294 }, @@ -1402,7 +1403,7 @@ "webgpu:shader,execution,expression,unary,bool_conversion:i32:*": { "subcaseMS": 8.219 }, "webgpu:shader,execution,expression,unary,bool_conversion:u32:*": { "subcaseMS": 7.401 }, "webgpu:shader,execution,expression,unary,bool_logical:negation:*": { "subcaseMS": 6.413 }, - "webgpu:shader,execution,expression,unary,f16_arithmetic:negation:*": { "subcaseMS": 4.0 }, + "webgpu:shader,execution,expression,unary,f16_arithmetic:negation:*": { "subcaseMS": 117.604 }, "webgpu:shader,execution,expression,unary,f32_arithmetic:negation:*": { "subcaseMS": 16.400 }, "webgpu:shader,execution,expression,unary,f32_conversion:bool:*": { "subcaseMS": 7.182 }, "webgpu:shader,execution,expression,unary,f32_conversion:f16:*": { "subcaseMS": 15.908 }, diff --git a/src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts b/src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts new file mode 100644 index 000000000000..2897168d7225 --- /dev/null +++ b/src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts @@ -0,0 +1,118 @@ +export const description = ` +Execution Tests for matrix AbstractFloat addition expressions +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../gpu_test.js'; +import { TypeAbstractFloat, TypeMat } from '../../../../util/conversion.js'; +import { FP } from '../../../../util/floating_point.js'; +import { sparseMatrixF64Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; +import { onlyConstInputSource, run } from '../expression.js'; + +import { abstractBinary } from './binary.js'; + +export const g = makeTestGroup(GPUTest); + +export const d = makeCaseCache('binary/af_matrix_addition', { + mat2x2: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(2, 2), + sparseMatrixF64Range(2, 2), + 'finite', + FP.abstract.additionMatrixMatrixInterval + ); + }, + mat2x3: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(2, 3), + sparseMatrixF64Range(2, 3), + 'finite', + FP.abstract.additionMatrixMatrixInterval + ); + }, + mat2x4: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(2, 4), + sparseMatrixF64Range(2, 4), + 'finite', + FP.abstract.additionMatrixMatrixInterval + ); + }, + mat3x2: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(3, 2), + sparseMatrixF64Range(3, 2), + 'finite', + FP.abstract.additionMatrixMatrixInterval + ); + }, + mat3x3: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(3, 3), + sparseMatrixF64Range(3, 3), + 'finite', + FP.abstract.additionMatrixMatrixInterval + ); + }, + mat3x4: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(3, 4), + sparseMatrixF64Range(3, 4), + 'finite', + FP.abstract.additionMatrixMatrixInterval + ); + }, + mat4x2: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(4, 2), + sparseMatrixF64Range(4, 2), + 'finite', + FP.abstract.additionMatrixMatrixInterval + ); + }, + mat4x3: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(4, 3), + sparseMatrixF64Range(4, 3), + 'finite', + FP.abstract.additionMatrixMatrixInterval + ); + }, + mat4x4: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(4, 4), + sparseMatrixF64Range(4, 4), + 'finite', + FP.abstract.additionMatrixMatrixInterval + ); + }, +}); + +g.test('matrix') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x + y, where x and y are matrices +Accuracy: Correctly rounded +` + ) + .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(`mat${cols}x${rows}`); + await run( + t, + abstractBinary('+'), + [TypeMat(cols, rows, TypeAbstractFloat), TypeMat(cols, rows, TypeAbstractFloat)], + TypeMat(cols, rows, TypeAbstractFloat), + t.params, + cases + ); + }); diff --git a/src/webgpu/shader/execution/expression/expression.ts b/src/webgpu/shader/execution/expression/expression.ts index 8ff28c217fc4..aaa761a6fa88 100644 --- a/src/webgpu/shader/execution/expression/expression.ts +++ b/src/webgpu/shader/execution/expression/expression.ts @@ -101,7 +101,41 @@ function valueStride(ty: Type): number { // vec3s have padding to make them the same size as vec4s return 32; } - unreachable('Matrices of AbstractFloats have not yet been implemented'); + if (ty instanceof MatrixType) { + switch (ty.cols) { + case 2: + switch (ty.rows) { + case 2: + return 32; + case 3: + return 64; + case 4: + return 64; + } + break; + case 3: + switch (ty.rows) { + case 2: + return 48; + case 3: + return 96; + case 4: + return 96; + } + break; + case 4: + switch (ty.rows) { + case 2: + return 64; + case 3: + return 128; + case 4: + return 128; + } + break; + } + } + unreachable(`AbstractFloats have not yet been implemented for ${ty.toString()}`); } if (ty instanceof MatrixType) { @@ -486,9 +520,23 @@ struct Output { @size(${valueStride(resultType)}) value: array, };`; } - // TBD: Implement Matrix result support + + if (resultType instanceof MatrixType) { + const cols = resultType.cols; + const rows = resultType.rows === 2 ? 2 : 4; // 3 element rows have a padding element + output_struct = `struct AF { + low: u32, + high: u32, +}; + +struct Output { + @size(${valueStride(resultType)}) value: array, ${cols}>, +};`; + } + + assert(output_struct !== undefined, `No implementation for result type '${resultType}'`); } - assert(output_struct !== undefined, `No implementation for result type '${resultType}'`); + return `${output_struct} @group(0) @binding(0) var outputs : array; `; @@ -771,11 +819,14 @@ fn main() { * matrices, this string needs to include indexing into the * container. * @param case_idx index in the case output array to assign the result - * @param accessor string representing how access the AF that needs to be extracted. - * For scalars this should be left as ''. - * For vectors and matrices this will be an indexing operation, - * i.e. '[i]' - * */ + * @param accessor string representing how access to the AF that needs to be + * operated on. + * For scalars this should be left as ''. + * For vectors this will be an indexing operation, + * i.e. '[i]' + * For matrices this will double indexing operation, + * i.e. '[c][r]' + */ function abstractFloatSnippet(expr: string, case_idx: number, accessor: string = ''): string { // AbstractFloats are f64s under the hood. WebGPU does not support // putting f64s in buffers, so the result needs to be split up into u32s @@ -875,10 +926,23 @@ function abstractFloatCaseBody(expr: string, resultType: Type, i: number): strin if (resultType instanceof VectorType) { return [...Array(resultType.width).keys()] - .map(dim_idx => abstractFloatSnippet(expr, i, `[${dim_idx}]`)) + .map(idx => abstractFloatSnippet(expr, i, `[${idx}]`)) .join(' \n'); } - // TDB implement matrix support + + if (resultType instanceof MatrixType) { + const cols = resultType.cols; + const rows = resultType.rows; + const results: String[] = [...Array(cols * rows)]; + + for (let c = 0; c < cols; c++) { + for (let r = 0; r < rows; r++) { + results[c * rows + r] = abstractFloatSnippet(expr, i, `[${c}][${r}]`); + } + } + + return results.join(' \n'); + } unreachable(`Results of type '${resultType}' not yet implemented`); } @@ -912,8 +976,6 @@ ${wgslHeader(parameterTypes, resultType)} ${wgslOutputs(resultType, cases.length)} -${wgslValuesArray(parameterTypes, resultType, cases, expressionBuilder)} - @compute @workgroup_size(1) fn main() { ${body} diff --git a/src/webgpu/util/conversion.ts b/src/webgpu/util/conversion.ts index 8c22065ddad6..e1aa31566e38 100644 --- a/src/webgpu/util/conversion.ts +++ b/src/webgpu/util/conversion.ts @@ -714,8 +714,10 @@ export class MatrixType { this.cols = cols; this.rows = rows; assert( - elementType.kind === 'f32' || elementType.kind === 'f16', - "MatrixType can only have elementType of 'f32' or 'f16'" + elementType.kind === 'f32' || + elementType.kind === 'f16' || + elementType.kind === 'abstract-float', + "MatrixType can only have elementType of 'f32' or 'f16' or 'abstract-float'" ); this.elementType = elementType; } diff --git a/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index af28a4191ff9..7a0038f4b82e 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -4835,7 +4835,7 @@ class FPAbstractTraits extends FPTraits { public readonly acoshPrimaryInterval = this.unimplementedScalarToInterval.bind(this); public readonly acoshIntervals = [this.acoshAlternativeInterval, this.acoshPrimaryInterval]; public readonly additionInterval = this.additionIntervalImpl.bind(this); - public readonly additionMatrixMatrixInterval = this.unimplementedMatrixPairToMatrix.bind(this); + public readonly additionMatrixMatrixInterval = this.additionMatrixMatrixIntervalImpl.bind(this); public readonly asinInterval = this.unimplementedScalarToInterval.bind(this); public readonly asinhInterval = this.unimplementedScalarToInterval.bind(this); public readonly atanInterval = this.unimplementedScalarToInterval.bind(this); diff --git a/src/webgpu/util/math.ts b/src/webgpu/util/math.ts index 30733e5fc017..54bba7abfb71 100644 --- a/src/webgpu/util/math.ts +++ b/src/webgpu/util/math.ts @@ -1843,6 +1843,129 @@ export function sparseVectorF64Range(dim: number): number[][] { return kSparseVectorF64Values[dim]; } +const kSparseMatrixF64Values = { + 2: { + 2: kInterestingF64Values.map((f, idx) => [ + [idx % 4 === 0 ? f : idx, idx % 4 === 1 ? f : -idx], + [idx % 4 === 2 ? f : -idx, idx % 4 === 3 ? f : idx], + ]), + 3: kInterestingF64Values.map((f, idx) => [ + [idx % 6 === 0 ? f : idx, idx % 6 === 1 ? f : -idx, idx % 6 === 2 ? f : idx], + [idx % 6 === 3 ? f : -idx, idx % 6 === 4 ? f : idx, idx % 6 === 5 ? f : -idx], + ]), + 4: kInterestingF64Values.map((f, idx) => [ + [ + idx % 8 === 0 ? f : idx, + idx % 8 === 1 ? f : -idx, + idx % 8 === 2 ? f : idx, + idx % 8 === 3 ? f : -idx, + ], + [ + idx % 8 === 4 ? f : -idx, + idx % 8 === 5 ? f : idx, + idx % 8 === 6 ? f : -idx, + idx % 8 === 7 ? f : idx, + ], + ]), + }, + 3: { + 2: kInterestingF64Values.map((f, idx) => [ + [idx % 6 === 0 ? f : idx, idx % 6 === 1 ? f : -idx], + [idx % 6 === 2 ? f : -idx, idx % 6 === 3 ? f : idx], + [idx % 6 === 4 ? f : idx, idx % 6 === 5 ? f : -idx], + ]), + 3: kInterestingF64Values.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: kInterestingF64Values.map((f, idx) => [ + [ + idx % 12 === 0 ? f : idx, + idx % 12 === 1 ? f : -idx, + idx % 12 === 2 ? f : idx, + idx % 12 === 3 ? f : -idx, + ], + [ + idx % 12 === 4 ? f : -idx, + idx % 12 === 5 ? f : idx, + idx % 12 === 6 ? f : -idx, + idx % 12 === 7 ? f : idx, + ], + [ + idx % 12 === 8 ? f : idx, + idx % 12 === 9 ? f : -idx, + idx % 12 === 10 ? f : idx, + idx % 12 === 11 ? f : -idx, + ], + ]), + }, + 4: { + 2: kInterestingF64Values.map((f, idx) => [ + [idx % 8 === 0 ? f : idx, idx % 8 === 1 ? f : -idx], + [idx % 8 === 2 ? f : -idx, idx % 8 === 3 ? f : idx], + [idx % 8 === 4 ? f : idx, idx % 8 === 5 ? f : -idx], + [idx % 8 === 6 ? f : -idx, idx % 8 === 7 ? f : idx], + ]), + 3: kInterestingF64Values.map((f, idx) => [ + [idx % 12 === 0 ? f : idx, idx % 12 === 1 ? f : -idx, idx % 12 === 2 ? f : idx], + [idx % 12 === 3 ? f : -idx, idx % 12 === 4 ? f : idx, idx % 12 === 5 ? f : -idx], + [idx % 12 === 6 ? f : idx, idx % 12 === 7 ? f : -idx, idx % 12 === 8 ? f : idx], + [idx % 12 === 9 ? f : -idx, idx % 12 === 10 ? f : idx, idx % 12 === 11 ? f : -idx], + ]), + 4: kInterestingF64Values.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, + ], + ]), + }, +}; + +/** + * Returns a minimal set of matrices, indexed by dimension containing interesting + * float values. + * + * This is the matrix analogue of `sparseVectorF64Range`, so it is producing a + * minimal coverage set of matrices that test all the interesting f64 values. + * There is not a more expansive set of matrices, since matrices are even more + * expensive than vectors for increasing runtime with coverage. + * + * All the interesting floats from sparseF64 are guaranteed to be tested, but + * not in every position. + */ +export function sparseMatrixF64Range(c: number, r: number): number[][][] { + assert( + c === 2 || c === 3 || c === 4, + 'sparseMatrixF64Range only accepts column counts of 2, 3, and 4' + ); + assert( + r === 2 || r === 3 || r === 4, + 'sparseMatrixF64Range only accepts row counts of 2, 3, and 4' + ); + return kSparseMatrixF64Values[c][r]; +} + /** * @returns the result matrix in Array> type. * From 7536133f8ce7b4bcc4640131a96b53aa311dbe50 Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Tue, 12 Sep 2023 13:29:58 -0400 Subject: [PATCH 027/166] Add documentation for adding timing metadata (#2942) Fixes #2938 --- docs/adding_timing_metadata.md | 110 +++++++++++++++++++++++++++++++++ src/common/tools/crawl.ts | 2 +- tools/merge_listing_times | 3 + 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 docs/adding_timing_metadata.md diff --git a/docs/adding_timing_metadata.md b/docs/adding_timing_metadata.md new file mode 100644 index 000000000000..617d5365526d --- /dev/null +++ b/docs/adding_timing_metadata.md @@ -0,0 +1,110 @@ +## Problem + +When adding new tests to the CTS you may occasionally see an error like this +when running `npm test` or `npm run standalone` + +``` +ERROR: Tests missing from listing_meta.json. Please add the new tests (set subcaseMS to 0 if you cannot estimate it): + webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:* + +/home/runner/work/cts/cts/src/common/util/util.ts:38 + throw new Error(msg && (typeof msg === 'string' ? msg : msg())); + ^ +Error: + at assert (/home/runner/work/cts/cts/src/common/util/util.ts:38:11) + at crawl (/home/runner/work/cts/cts/src/common/tools/crawl.ts:155:11) +Warning: non-zero exit code 1 + Use --force to continue. + +Aborted due to warnings. +``` + +What this error message is trying to tell us, is that there is no entry for +`webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*` in +`src/webgpu/listing_meta.json`. + +These entries are estimates for the amount of time that subcases take to run, +and are used as inputs into the WPT tooling to attempt to portion out tests into +approximately same sized chunks. + +If a value has been defaulted to 0 by someone, you will see warnings like this +``` +... +WARNING: subcaseMS≤0 found in listing_meta.json (allowed, but try to avoid): + webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:* +... +``` + +These messages should be resolved by adding appropriate entries to the JSON +file. + +## Solution + +There exists tooling in the CTS repo for generating appropriate estimates for +these values, though they do require some manual intervention. The rest of this +doc will be a walkthrough of running these tools. + +### Default Value + +The first step is to add a default value for entry to +`src/webgpu/listing_meta.json`, since there is a chicken-and-egg problem for +updating these values. + +``` + "webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*": { "subcaseMS": 0 }, +``` + +(It should have a value of 0, since later tooling updates the value if the newer +value is higher) + +### Websocket Logger + +The first tool that needs to be run is `websocket-logger`, which uses a side +channel from WPT to report timing data when CTS is run via a websocket. This +should be run in a separate process/terminal, since it needs to stay running +throughout the following steps. + +At `tools/websocket-logger/` +``` +npm install +npm run +``` + +The output from this command will indicate where the results are being logged, +which will be needed later +``` +... +Writing to wslog-2023-09-11T18-57-34.txt +... +``` + +### Running CTS + +Now we need to run the specific cases in CTS, which requires serving the CTS +locally. + +At project root +``` +npm run standalone +npm start +``` + +Once this is started you can then direct a WebGPU enabled browser to the +specific CTS entry and run the tests, for example +``` +http://127.0.0.1:8080/standalone/q?webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:* +``` + +### Merging metadata + +The final step is to merge the new data that has been captured into the JSON +file. + +This can be done using the following command +``` +tools/merge_listing_times webgpu -- tools/websocket-logger/wslog-2023-09-11T18-57-34.txt +``` + +where the text file is the result file from websocket-logger. + +Now you just need to commit the pending diff in your repo. diff --git a/src/common/tools/crawl.ts b/src/common/tools/crawl.ts index eadabe4f77f2..cb1e5f6fc737 100644 --- a/src/common/tools/crawl.ts +++ b/src/common/tools/crawl.ts @@ -145,7 +145,7 @@ export async function crawl(suiteDir: string, validate: boolean): Promise Date: Tue, 12 Sep 2023 14:21:33 -0400 Subject: [PATCH 028/166] wgsl: Add non-matrix AbstractFloat subtraction execution tests (#2928) Issue #1626 Co-authored-by: jzm-intel --- src/unittests/floating_point.spec.ts | 18 ++- src/webgpu/listing_meta.json | 5 +- .../expression/binary/af_subtraction.spec.ts | 151 ++++++++++++++++++ src/webgpu/util/floating_point.ts | 2 +- 4 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 02239005c6ee..6fa4900225ce 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -4575,12 +4575,28 @@ const kSubtractionInterval64BitsNormalCases = { // Expect f16 interval [0xAE67-0x2E67, 0xAE66-0x2E66] { input: [-0.1, 0.1], expected: [reinterpretU16AsF16(0xae67)-reinterpretU16AsF16(0x2e67), reinterpretU16AsF16(0xae66)-reinterpretU16AsF16(0x2e66)] }, ] as ScalarPairToIntervalCase[], + abstract: [ + // 0.1 isn't exactly representable in f64, but will be quantized to an + // exact value when storing to a 'number' (0x3FB999999999999A). + // This is why below the expectations are not intervals. + { input: [0.1, 0], expected: [0.1] }, + { input: [0, -0.1], expected: [0.1] }, + { input: [-0.1, 0], expected: [-0.1] }, + { input: [0, 0.1], expected: [-0.1] }, + + { input: [0.1, 0.1], expected: [0] }, + { input: [-0.1, -0.1], expected: [0] }, + // f64 0x3FB999999999999A - 0xBFB999999999999A = 0x3FC999999999999A + { input: [0.1, -0.1], expected: [reinterpretU64AsF64(0x3fc999999999999an)] }, // ~0.2 + // f64 0xBFB999999999999A - 0x3FB999999999999A = 0xBFC999999999999A + { input: [-0.1, 0.1], expected: [reinterpretU64AsF64(0xbfc999999999999an) ] }, // ~-0.2, + ] as ScalarPairToIntervalCase[], } as const; g.test('subtractionInterval') .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 54dfc261ff34..7f5d27da6242 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -823,7 +823,7 @@ "webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,render_pass,used:*": { "subcaseMS": 0.000 }, "webgpu:compat,api,validation,render_pipeline,fragment_state:colorState:*": { "subcaseMS": 32.604 }, "webgpu:compat,api,validation,render_pipeline,shader_module:sample_mask:*": { "subcaseMS": 14.801 }, - "webgpu:compat,api,validation,render_pipeline,vertex_state:maxVertexAttributesVertexIndexInstanceIndex:*": { "subcaseMS": 3.7 }, + "webgpu:compat,api,validation,render_pipeline,vertex_state:maxVertexAttributesVertexIndexInstanceIndex:*": { "subcaseMS": 3.700 }, "webgpu:compat,api,validation,texture,createTexture:unsupportedTextureFormats:*": { "subcaseMS": 0.700 }, "webgpu:compat,api,validation,texture,createTexture:unsupportedTextureViewFormats:*": { "subcaseMS": 0.601 }, "webgpu:compat,api,validation,texture,cubeArray:cube_array:*": { "subcaseMS": 13.701 }, @@ -860,6 +860,9 @@ "webgpu:shader,execution,expression,binary,af_comparison:less_than:*": { "subcaseMS": 19.975 }, "webgpu:shader,execution,expression,binary,af_comparison:not_equals:*": { "subcaseMS": 19.651 }, "webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*": { "subcaseMS": 11169.534 }, + "webgpu:shader,execution,expression,binary,af_subtraction:scalar:*": { "subcaseMS": 960.626 }, + "webgpu:shader,execution,expression,binary,af_subtraction:scalar_vector:*": { "subcaseMS": 2336.534 }, + "webgpu:shader,execution,expression,binary,af_subtraction:vector_scalar:*": { "subcaseMS": 2437.701 }, "webgpu:shader,execution,expression,binary,bitwise:bitwise_and:*": { "subcaseMS": 20.982 }, "webgpu:shader,execution,expression,binary,bitwise:bitwise_and_compound:*": { "subcaseMS": 22.513 }, "webgpu:shader,execution,expression,binary,bitwise:bitwise_exclusive_or:*": { "subcaseMS": 21.294 }, diff --git a/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts b/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts new file mode 100644 index 000000000000..4faa21de33fd --- /dev/null +++ b/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts @@ -0,0 +1,151 @@ +export const description = ` +Execution Tests for non-matrix AbstractFloat subtraction expression +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../gpu_test.js'; +import { TypeAbstractFloat, TypeVec } from '../../../../util/conversion.js'; +import { FP, FPVector } from '../../../../util/floating_point.js'; +import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; +import { onlyConstInputSource, run } from '../expression.js'; + +import { abstractBinary } from './binary.js'; + +const subtractionVectorScalarInterval = (v: number[], s: number): FPVector => { + return FP.abstract.toVector(v.map(e => FP.abstract.subtractionInterval(e, s))); +}; + +const subtractionScalarVectorInterval = (s: number, v: number[]): FPVector => { + return FP.abstract.toVector(v.map(e => FP.abstract.subtractionInterval(s, e))); +}; + +export const g = makeTestGroup(GPUTest); + +export const d = makeCaseCache('abstractBinary/af_subtraction', { + scalar: () => { + return FP.abstract.generateScalarPairToIntervalCases( + sparseF64Range(), + sparseF64Range(), + 'finite', + FP.abstract.subtractionInterval + ); + }, + vec2_scalar: () => { + return FP.abstract.generateVectorScalarToVectorCases( + sparseVectorF64Range(2), + sparseF64Range(), + 'finite', + subtractionVectorScalarInterval + ); + }, + vec3_scalar: () => { + return FP.abstract.generateVectorScalarToVectorCases( + sparseVectorF64Range(3), + sparseF64Range(), + 'finite', + subtractionVectorScalarInterval + ); + }, + vec4_scalar: () => { + return FP.abstract.generateVectorScalarToVectorCases( + sparseVectorF64Range(4), + sparseF64Range(), + 'finite', + subtractionVectorScalarInterval + ); + }, + scalar_vec2: () => { + return FP.abstract.generateScalarVectorToVectorCases( + sparseF64Range(), + sparseVectorF64Range(2), + 'finite', + subtractionScalarVectorInterval + ); + }, + scalar_vec3: () => { + return FP.abstract.generateScalarVectorToVectorCases( + sparseF64Range(), + sparseVectorF64Range(3), + 'finite', + subtractionScalarVectorInterval + ); + }, + scalar_vec4: () => { + return FP.abstract.generateScalarVectorToVectorCases( + sparseF64Range(), + sparseVectorF64Range(4), + 'finite', + subtractionScalarVectorInterval + ); + }, +}); + +g.test('scalar') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x - y +Accuracy: Correctly rounded +` + ) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('scalar'); + await run( + t, + abstractBinary('-'), + [TypeAbstractFloat, TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); + +g.test('vector_scalar') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x - y, where x is a vector and y is a scalar +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4] as const)) + .fn(async t => { + const dim = t.params.dim; + const cases = await d.get(`vec${dim}_scalar`); + await run( + t, + abstractBinary('-'), + [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat], + TypeVec(dim, TypeAbstractFloat), + t.params, + cases + ); + }); + +g.test('scalar_vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x - y, where x is a scalar and y is a vector +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4] as const)) + .fn(async t => { + const dim = t.params.dim; + const cases = await d.get(`scalar_vec${dim}`); + await run( + t, + abstractBinary('-'), + [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)], + TypeVec(dim, TypeAbstractFloat), + t.params, + cases + ); + }); diff --git a/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index 7a0038f4b82e..99a54c4c5cf0 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -4902,7 +4902,7 @@ class FPAbstractTraits extends FPTraits { public readonly smoothStepInterval = this.unimplementedScalarTripleToInterval.bind(this); public readonly sqrtInterval = this.unimplementedScalarToInterval.bind(this); public readonly stepInterval = this.unimplementedScalarPairToInterval.bind(this); - public readonly subtractionInterval = this.unimplementedScalarPairToInterval.bind(this); + public readonly subtractionInterval = this.subtractionIntervalImpl.bind(this); public readonly subtractionMatrixMatrixInterval = this.unimplementedMatrixPairToMatrix.bind(this); public readonly tanInterval = this.unimplementedScalarToInterval.bind(this); public readonly tanhInterval = this.unimplementedScalarToInterval.bind(this); From fd0cf88ce7bfbf5466858bb142c8249d59b42069 Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Tue, 12 Sep 2023 15:04:17 -0400 Subject: [PATCH 029/166] wgsl: Add AbstractFloat matrix subtraction execution tests (#2929) Issue #1626 --- src/unittests/floating_point.spec.ts | 375 +++++++++--------- src/webgpu/listing_meta.json | 1 + .../binary/af_matrix_subtraction.spec.ts | 118 ++++++ src/webgpu/util/floating_point.ts | 4 +- 4 files changed, 313 insertions(+), 185 deletions(-) create mode 100644 src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 6fa4900225ce..2874aca830f9 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -6056,193 +6056,200 @@ g.test('additionMatrixMatrixInterval') ); }); -g.test('subtractionMatrixMatrixInterval_f32') - .paramsSubcasesOnly([ - // Only testing that different shapes of matrices are handled correctly - // here, to reduce test duplication. - // subtractionMatrixMatrixInterval uses SubtractionIntervalOp for calculating intervals, - // so the testing for subtractionInterval covers the actual interval - // calculations. - { - input: [ - [ - [-1, -2], - [-3, -4], - ], - [ - [10, 20], - [30, 40], - ], - ], - expected: [ - [-11, -22], - [-33, -44], - ], - }, - { - input: [ - [ - [-1, -2], - [-3, -4], - [-5, -6], - ], - [ - [10, 20], - [30, 40], - [50, 60], - ], - ], - expected: [ - [-11, -22], - [-33, -44], - [-55, -66], - ], - }, - { - input: [ - [ - [-1, -2], - [-3, -4], - [-5, -6], - [-7, -8], - ], - [ - [10, 20], - [30, 40], - [50, 60], - [70, 80], - ], - ], - expected: [ - [-11, -22], - [-33, -44], - [-55, -66], - [-77, -88], - ], - }, - { - input: [ - [ - [-1, -2, -3], - [-4, -5, -6], - ], - [ - [10, 20, 30], - [40, 50, 60], - ], - ], - expected: [ - [-11, -22, -33], - [-44, -55, -66], - ], - }, - { - input: [ - [ - [-1, -2, -3], - [-4, -5, -6], - [-7, -8, -9], - ], - [ - [10, 20, 30], - [40, 50, 60], - [70, 80, 90], - ], - ], - expected: [ - [-11, -22, -33], - [-44, -55, -66], - [-77, -88, -99], - ], - }, - { - input: [ - [ - [-1, -2, -3], - [-4, -5, -6], - [-7, -8, -9], - [-10, -11, -12], - ], - [ - [10, 20, 30], - [40, 50, 60], - [70, 80, 90], - [1000, 1100, 1200], - ], - ], - expected: [ - [-11, -22, -33], - [-44, -55, -66], - [-77, -88, -99], - [-1010, -1111, -1212], - ], - }, - { - input: [ - [ - [-1, -2, -3, -4], - [-5, -6, -7, -8], - ], - [ - [10, 20, 30, 40], - [50, 60, 70, 80], - ], - ], - expected: [ - [-11, -22, -33, -44], - [-55, -66, -77, -88], - ], - }, - { - input: [ - [ - [-1, -2, -3, -4], - [-5, -6, -7, -8], - [-9, -10, -11, -12], - ], - [ - [10, 20, 30, 40], - [50, 60, 70, 80], - [90, 1000, 1100, 1200], - ], - ], - expected: [ - [-11, -22, -33, -44], - [-55, -66, -77, -88], - [-99, -1010, -1111, -1212], - ], - }, - { - input: [ - [ - [-1, -2, -3, -4], - [-5, -6, -7, -8], - [-9, -10, -11, -12], - [-13, -14, -15, -16], - ], - [ - [10, 20, 30, 40], - [50, 60, 70, 80], - [90, 1000, 1100, 1200], - [1300, 1400, 1500, 1600], - ], - ], - expected: [ - [-11, -22, -33, -44], - [-55, -66, -77, -88], - [-99, -1010, -1111, -1212], - [-1313, -1414, -1515, -1616], - ], - }, - ]) +g.test('subtractionMatrixMatrixInterval') + .params(u => + u + .combine('trait', ['f32', 'abstract'] as const) + .beginSubcases() + .expandWithParams(_ => { + // Only testing that different shapes of matrices are handled correctly + // here, to reduce test duplication. + // subtractionMatrixMatrixInterval uses AdditionIntervalOp for calculating intervals, + // so the testing for subtractionInterval covers the actual interval + // calculations. + return [ + { + input: [ + [ + [1, 2], + [3, 4], + ], + [ + [-10, -20], + [-30, -40], + ], + ], + expected: [ + [11, 22], + [33, 44], + ], + }, + { + input: [ + [ + [1, 2], + [3, 4], + [5, 6], + ], + [ + [-10, -20], + [-30, -40], + [-50, -60], + ], + ], + expected: [ + [11, 22], + [33, 44], + [55, 66], + ], + }, + { + input: [ + [ + [1, 2], + [3, 4], + [5, 6], + [7, 8], + ], + [ + [-10, -20], + [-30, -40], + [-50, -60], + [-70, -80], + ], + ], + expected: [ + [11, 22], + [33, 44], + [55, 66], + [77, 88], + ], + }, + { + input: [ + [ + [1, 2, 3], + [4, 5, 6], + ], + [ + [-10, -20, -30], + [-40, -50, -60], + ], + ], + expected: [ + [11, 22, 33], + [44, 55, 66], + ], + }, + { + input: [ + [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + ], + [ + [-10, -20, -30], + [-40, -50, -60], + [-70, -80, -90], + ], + ], + expected: [ + [11, 22, 33], + [44, 55, 66], + [77, 88, 99], + ], + }, + { + input: [ + [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + [10, 11, 12], + ], + [ + [-10, -20, -30], + [-40, -50, -60], + [-70, -80, -90], + [-1000, -1100, -1200], + ], + ], + expected: [ + [11, 22, 33], + [44, 55, 66], + [77, 88, 99], + [1010, 1111, 1212], + ], + }, + { + input: [ + [ + [1, 2, 3, 4], + [5, 6, 7, 8], + ], + [ + [-10, -20, -30, -40], + [-50, -60, -70, -80], + ], + ], + expected: [ + [11, 22, 33, 44], + [55, 66, 77, 88], + ], + }, + { + input: [ + [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + ], + [ + [-10, -20, -30, -40], + [-50, -60, -70, -80], + [-90, -1000, -1100, -1200], + ], + ], + expected: [ + [11, 22, 33, 44], + [55, 66, 77, 88], + [99, 1010, 1111, 1212], + ], + }, + { + input: [ + [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + [13, 14, 15, 16], + ], + [ + [-10, -20, -30, -40], + [-50, -60, -70, -80], + [-90, -1000, -1100, -1200], + [-1300, -1400, -1500, -1600], + ], + ], + expected: [ + [11, 22, 33, 44], + [55, 66, 77, 88], + [99, 1010, 1111, 1212], + [1313, 1414, 1515, 1616], + ], + }, + ]; + }) + ) .fn(t => { - const x = t.params.input[0]; - const y = t.params.input[1]; - const expected = FP.f32.toMatrix(t.params.expected); - const got = FP.f32.subtractionMatrixMatrixInterval(x, y); + const [x, y] = t.params.input; + const trait = FP[t.params.trait]; + const expected = trait.toMatrix(t.params.expected); + const got = trait.subtractionMatrixMatrixInterval(x, y); t.expect( objectEquals(expected, got), - `f32.subtractionMatrixMatrixInterval([${JSON.stringify(x)}], [${JSON.stringify( + `${t.params.trait}.subtractionMatrixMatrixInterval([${JSON.stringify(x)}], [${JSON.stringify( y )}]) returned '[${JSON.stringify(got)}]'. Expected '[${JSON.stringify(expected)}]'` ); diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index 7f5d27da6242..b2430ae44361 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -860,6 +860,7 @@ "webgpu:shader,execution,expression,binary,af_comparison:less_than:*": { "subcaseMS": 19.975 }, "webgpu:shader,execution,expression,binary,af_comparison:not_equals:*": { "subcaseMS": 19.651 }, "webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*": { "subcaseMS": 11169.534 }, + "webgpu:shader,execution,expression,binary,af_matrix_subtraction:matrix:*": { "subcaseMS": 14060.956 }, "webgpu:shader,execution,expression,binary,af_subtraction:scalar:*": { "subcaseMS": 960.626 }, "webgpu:shader,execution,expression,binary,af_subtraction:scalar_vector:*": { "subcaseMS": 2336.534 }, "webgpu:shader,execution,expression,binary,af_subtraction:vector_scalar:*": { "subcaseMS": 2437.701 }, diff --git a/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts b/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts new file mode 100644 index 000000000000..0837b05ff415 --- /dev/null +++ b/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts @@ -0,0 +1,118 @@ +export const description = ` +Execution Tests for matrix AbstractFloat subtraction expression +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../gpu_test.js'; +import { TypeAbstractFloat, TypeMat } from '../../../../util/conversion.js'; +import { FP } from '../../../../util/floating_point.js'; +import { sparseMatrixF64Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; +import { onlyConstInputSource, run } from '../expression.js'; + +import { abstractBinary } from './binary.js'; + +export const g = makeTestGroup(GPUTest); + +export const d = makeCaseCache('abstractBinary/af_matrix_subtraction', { + mat2x2: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(2, 2), + sparseMatrixF64Range(2, 2), + 'finite', + FP.abstract.subtractionMatrixMatrixInterval + ); + }, + mat2x3: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(2, 3), + sparseMatrixF64Range(2, 3), + 'finite', + FP.abstract.subtractionMatrixMatrixInterval + ); + }, + mat2x4: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(2, 4), + sparseMatrixF64Range(2, 4), + 'finite', + FP.abstract.subtractionMatrixMatrixInterval + ); + }, + mat3x2: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(3, 2), + sparseMatrixF64Range(3, 2), + 'finite', + FP.abstract.subtractionMatrixMatrixInterval + ); + }, + mat3x3: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(3, 3), + sparseMatrixF64Range(3, 3), + 'finite', + FP.abstract.subtractionMatrixMatrixInterval + ); + }, + mat3x4: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(3, 4), + sparseMatrixF64Range(3, 4), + 'finite', + FP.abstract.subtractionMatrixMatrixInterval + ); + }, + mat4x2: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(4, 2), + sparseMatrixF64Range(4, 2), + 'finite', + FP.abstract.subtractionMatrixMatrixInterval + ); + }, + mat4x3: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(4, 3), + sparseMatrixF64Range(4, 3), + 'finite', + FP.abstract.subtractionMatrixMatrixInterval + ); + }, + mat4x4: () => { + return FP.abstract.generateMatrixPairToMatrixCases( + sparseMatrixF64Range(4, 4), + sparseMatrixF64Range(4, 4), + 'finite', + FP.abstract.subtractionMatrixMatrixInterval + ); + }, +}); + +g.test('matrix') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x - y, where x and y are matrices +Accuracy: Correctly rounded +` + ) + .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(`mat${cols}x${rows}`); + await run( + t, + abstractBinary('-'), + [TypeMat(cols, rows, TypeAbstractFloat), TypeMat(cols, rows, TypeAbstractFloat)], + TypeMat(cols, rows, TypeAbstractFloat), + t.params, + cases + ); + }); diff --git a/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index 99a54c4c5cf0..d304071bffd5 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -4903,7 +4903,9 @@ class FPAbstractTraits extends FPTraits { public readonly sqrtInterval = this.unimplementedScalarToInterval.bind(this); public readonly stepInterval = this.unimplementedScalarPairToInterval.bind(this); public readonly subtractionInterval = this.subtractionIntervalImpl.bind(this); - public readonly subtractionMatrixMatrixInterval = this.unimplementedMatrixPairToMatrix.bind(this); + public readonly subtractionMatrixMatrixInterval = this.subtractionMatrixMatrixIntervalImpl.bind( + this + ); public readonly tanInterval = this.unimplementedScalarToInterval.bind(this); public readonly tanhInterval = this.unimplementedScalarToInterval.bind(this); public readonly transposeInterval = this.unimplementedMatrixToMatrix.bind(this); From 69a2c926cf2de879f3e237b28e3e07143eb2f88c Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Tue, 12 Sep 2023 15:30:32 -0400 Subject: [PATCH 030/166] wgsl: Add AbstractFloat simple multiplication execution tests (#2930) Covers scalar * scalar, scalar * vector, and vector * scalar Issue #1626 --- src/unittests/floating_point.spec.ts | 53 ++++-- src/webgpu/listing_meta.json | 3 + .../binary/af_multiplication.spec.ts | 151 ++++++++++++++++++ src/webgpu/util/floating_point.ts | 2 +- 4 files changed, 191 insertions(+), 18 deletions(-) create mode 100644 src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 2874aca830f9..efefd36ce641 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -3695,16 +3695,16 @@ const kAdditionInterval64BitsNormalCases = { // 0.1 isn't exactly representable in f64, but will be quantized to an // exact value when storing to a 'number' (0x3FB999999999999A). // This is why below the expectations are not intervals. - { input: [0.1, 0], expected: [0.1] }, - { input: [0, 0.1], expected: [0.1] }, - { input: [-0.1, 0], expected: [-0.1] }, - { input: [0, -0.1], expected: [-0.1] }, + { input: [0.1, 0], expected: 0.1 }, + { input: [0, 0.1], expected: 0.1 }, + { input: [-0.1, 0], expected: -0.1 }, + { input: [0, -0.1], expected: -0.1 }, // f64 0x3FB999999999999A+0x3FB999999999999A = 0x3FC999999999999A - { input: [0.1, 0.1], expected: [reinterpretU64AsF64(0x3FC999999999999An)] }, // ~0.2 + { input: [0.1, 0.1], expected: reinterpretU64AsF64(0x3FC999999999999An) }, // ~0.2 // f64 0xBFB999999999999A+0xBFB999999999999A = 0xBFC999999999999A - { input: [-0.1, -0.1], expected: [reinterpretU64AsF64(0xBFC999999999999An)] }, // ~-0.2 - { input: [0.1, -0.1], expected: [0] }, - { input: [-0.1, 0.1], expected: [0] }, + { input: [-0.1, -0.1], expected: reinterpretU64AsF64(0xBFC999999999999An) }, // ~-0.2 + { input: [0.1, -0.1], expected: 0 }, + { input: [-0.1, 0.1], expected: 0 }, ] as ScalarPairToIntervalCase[], } as const; @@ -4310,12 +4310,31 @@ const kMultiplicationInterval64BitsNormalCases = { { input: [0.1, -0.1], expected: [reinterpretU16AsF16(0xa120), reinterpretU16AsF16(0xa11e)] }, // ~-0.01 { input: [-0.1, 0.1], expected: [reinterpretU16AsF16(0xa120), reinterpretU16AsF16(0xa11e)] }, // ~-0.01 ] as ScalarPairToIntervalCase[], + abstract: [ + // 0.1 isn't exactly representable in f64, but will be quantized to an + // exact value when storing to a 'number' (0x3FB999999999999A). + // This is why below the expectations are not intervals. + // Finite values multiply zero result in zero + { input: [0.1, 0], expected: 0 }, + { input: [0, 0.1], expected: 0 }, + { input: [-0.1, 0], expected: 0 }, + { input: [0, -0.1], expected: 0 }, + { input: [0.1, 1], expected: 0.1 }, + { input: [-1, -0.1], expected: 0.1 }, + { input: [-0.1, 1], expected: -0.1 }, + { input: [-1, 0.1], expected: -0.1 }, + // f64 0.1 * 0.1 = 0x3f847ae147ae147c, + { input: [0.1, 0.1], expected: reinterpretU64AsF64(0x3f847ae147ae147cn) }, // ~0.01 + { input: [-0.1, -0.1], expected: reinterpretU64AsF64(0x3f847ae147ae147cn) }, // ~0.01 + { input: [0.1, -0.1], expected: reinterpretU64AsF64(0xbf847ae147ae147cn) }, // ~-0.01 + { input: [-0.1, 0.1], expected: reinterpretU64AsF64(0xbf847ae147ae147cn) }, // ~-0.01 + ] as ScalarPairToIntervalCase[], } as const; g.test('multiplicationInterval') .params(u => u - .combine('trait', ['f32', 'f16'] as const) + .combine('trait', ['f32', 'f16', 'abstract'] as const) .beginSubcases() .expandWithParams(p => { const trait = FP[p.trait]; @@ -4579,17 +4598,17 @@ const kSubtractionInterval64BitsNormalCases = { // 0.1 isn't exactly representable in f64, but will be quantized to an // exact value when storing to a 'number' (0x3FB999999999999A). // This is why below the expectations are not intervals. - { input: [0.1, 0], expected: [0.1] }, - { input: [0, -0.1], expected: [0.1] }, - { input: [-0.1, 0], expected: [-0.1] }, - { input: [0, 0.1], expected: [-0.1] }, + { input: [0.1, 0], expected: 0.1 }, + { input: [0, -0.1], expected: 0.1 }, + { input: [-0.1, 0], expected: -0.1 }, + { input: [0, 0.1], expected: -0.1 }, - { input: [0.1, 0.1], expected: [0] }, - { input: [-0.1, -0.1], expected: [0] }, + { input: [0.1, 0.1], expected: 0 }, + { input: [-0.1, -0.1], expected: 0 }, // f64 0x3FB999999999999A - 0xBFB999999999999A = 0x3FC999999999999A - { input: [0.1, -0.1], expected: [reinterpretU64AsF64(0x3fc999999999999an)] }, // ~0.2 + { input: [0.1, -0.1], expected: reinterpretU64AsF64(0x3fc999999999999an) }, // ~0.2 // f64 0xBFB999999999999A - 0x3FB999999999999A = 0xBFC999999999999A - { input: [-0.1, 0.1], expected: [reinterpretU64AsF64(0xbfc999999999999an) ] }, // ~-0.2, + { input: [-0.1, 0.1], expected: reinterpretU64AsF64(0xbfc999999999999an) }, // ~-0.2, ] as ScalarPairToIntervalCase[], } as const; diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index b2430ae44361..f33208436fa7 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -861,6 +861,9 @@ "webgpu:shader,execution,expression,binary,af_comparison:not_equals:*": { "subcaseMS": 19.651 }, "webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*": { "subcaseMS": 11169.534 }, "webgpu:shader,execution,expression,binary,af_matrix_subtraction:matrix:*": { "subcaseMS": 14060.956 }, + "webgpu:shader,execution,expression,binary,af_multiplication:scalar:*": { "subcaseMS": 907.726 }, + "webgpu:shader,execution,expression,binary,af_multiplication:scalar_vector:*": { "subcaseMS": 2025.534 }, + "webgpu:shader,execution,expression,binary,af_multiplication:vector_scalar:*": { "subcaseMS": 2085.300 }, "webgpu:shader,execution,expression,binary,af_subtraction:scalar:*": { "subcaseMS": 960.626 }, "webgpu:shader,execution,expression,binary,af_subtraction:scalar_vector:*": { "subcaseMS": 2336.534 }, "webgpu:shader,execution,expression,binary,af_subtraction:vector_scalar:*": { "subcaseMS": 2437.701 }, diff --git a/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts b/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts new file mode 100644 index 000000000000..80a2aa23bdcd --- /dev/null +++ b/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts @@ -0,0 +1,151 @@ +export const description = ` +Execution Tests for non-matrix AbstractFloat multiplication expression +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../gpu_test.js'; +import { TypeAbstractFloat, TypeVec } from '../../../../util/conversion.js'; +import { FP, FPVector } from '../../../../util/floating_point.js'; +import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; +import { onlyConstInputSource, run } from '../expression.js'; + +import { abstractBinary } from './binary.js'; + +const multiplicationVectorScalarInterval = (v: number[], s: number): FPVector => { + return FP.abstract.toVector(v.map(e => FP.abstract.multiplicationInterval(e, s))); +}; + +const multiplicationScalarVectorInterval = (s: number, v: number[]): FPVector => { + return FP.abstract.toVector(v.map(e => FP.abstract.multiplicationInterval(s, e))); +}; + +export const g = makeTestGroup(GPUTest); + +export const d = makeCaseCache('abstractBinary/af_multiplication', { + scalar: () => { + return FP.abstract.generateScalarPairToIntervalCases( + sparseF64Range(), + sparseF64Range(), + 'finite', + FP.abstract.multiplicationInterval + ); + }, + vec2_scalar: () => { + return FP.abstract.generateVectorScalarToVectorCases( + sparseVectorF64Range(2), + sparseF64Range(), + 'finite', + multiplicationVectorScalarInterval + ); + }, + vec3_scalar: () => { + return FP.abstract.generateVectorScalarToVectorCases( + sparseVectorF64Range(3), + sparseF64Range(), + 'finite', + multiplicationVectorScalarInterval + ); + }, + vec4_scalar: () => { + return FP.abstract.generateVectorScalarToVectorCases( + sparseVectorF64Range(4), + sparseF64Range(), + 'finite', + multiplicationVectorScalarInterval + ); + }, + scalar_vec2: () => { + return FP.abstract.generateScalarVectorToVectorCases( + sparseF64Range(), + sparseVectorF64Range(2), + 'finite', + multiplicationScalarVectorInterval + ); + }, + scalar_vec3: () => { + return FP.abstract.generateScalarVectorToVectorCases( + sparseF64Range(), + sparseVectorF64Range(3), + 'finite', + multiplicationScalarVectorInterval + ); + }, + scalar_vec4: () => { + return FP.abstract.generateScalarVectorToVectorCases( + sparseF64Range(), + sparseVectorF64Range(4), + 'finite', + multiplicationScalarVectorInterval + ); + }, +}); + +g.test('scalar') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x * y +Accuracy: Correctly rounded +` + ) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('scalar'); + await run( + t, + abstractBinary('*'), + [TypeAbstractFloat, TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); + +g.test('vector_scalar') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x * y, where x is a vector and y is a scalar +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4] as const)) + .fn(async t => { + const dim = t.params.dim; + const cases = await d.get(`vec${dim}_scalar`); + await run( + t, + abstractBinary('*'), + [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat], + TypeVec(dim, TypeAbstractFloat), + t.params, + cases + ); + }); + +g.test('scalar_vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x * y, where x is a scalar and y is a vector +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4] as const)) + .fn(async t => { + const dim = t.params.dim; + const cases = await d.get(`scalar_vec${dim}`); + await run( + t, + abstractBinary('*'), + [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)], + TypeVec(dim, TypeAbstractFloat), + t.params, + cases + ); + }); diff --git a/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index d304071bffd5..8b385591dee6 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -4870,7 +4870,7 @@ class FPAbstractTraits extends FPTraits { public readonly mixPreciseInterval = this.unimplementedScalarTripleToInterval.bind(this); public readonly mixIntervals = [this.mixImpreciseInterval, this.mixPreciseInterval]; public readonly modfInterval = this.unimplementedModf.bind(this); - public readonly multiplicationInterval = this.unimplementedScalarPairToInterval.bind(this); + public readonly multiplicationInterval = this.multiplicationIntervalImpl.bind(this); public readonly multiplicationMatrixMatrixInterval = this.unimplementedMatrixPairToMatrix.bind( this ); From 679ea4c92a2b1a142dbae430bc3faa07ecbf5374 Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Wed, 13 Sep 2023 10:16:10 -0400 Subject: [PATCH 031/166] wgsl: Add AbstractFloat `abs` execution tests (#2932) Fixes #2931 --- src/unittests/floating_point.spec.ts | 10 ++++---- src/webgpu/listing_meta.json | 2 +- .../expression/call/builtin/abs.spec.ts | 23 +++++++++++++++---- .../expression/call/builtin/builtin.ts | 6 +++++ src/webgpu/util/floating_point.ts | 2 +- 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index efefd36ce641..825078d680db 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -2168,6 +2168,7 @@ const kAbsIntervalCases = [ expected: { f32: [reinterpretU32AsF32(0x3dcccccc), reinterpretU32AsF32(0x3dcccccd)], f16: [reinterpretU16AsF16(0x2e66), reinterpretU16AsF16(0x2e67)], + abstract: 0.1, }, }, { @@ -2175,6 +2176,7 @@ const kAbsIntervalCases = [ expected: { f32: [reinterpretU32AsF32(0x3dcccccc), reinterpretU32AsF32(0x3dcccccd)], f16: [reinterpretU16AsF16(0x2e66), reinterpretU16AsF16(0x2e67)], + abstract: 0.1, }, }, ] as const; @@ -2182,7 +2184,7 @@ const kAbsIntervalCases = [ g.test('absInterval') .params(u => u - .combine('trait', ['f32', 'f16'] as const) + .combine('trait', ['f32', 'f16', 'abstract'] as const) .beginSubcases() .expandWithParams(p => { const constants = FP[p.trait].constants(); @@ -2201,16 +2203,12 @@ g.test('absInterval') { input: constants.negative.min, expected: constants.positive.max }, { input: constants.negative.max, expected: constants.positive.min }, - // 32-bit subnormals + // Subnormals { input: constants.positive.subnormal.max, expected: [0, constants.positive.subnormal.max] }, { input: constants.positive.subnormal.min, expected: [0, constants.positive.subnormal.min] }, { input: constants.negative.subnormal.min, expected: [0, constants.positive.subnormal.max] }, { input: constants.negative.subnormal.max, expected: [0, constants.positive.subnormal.min] }, - // 64-bit subnormals - { input: reinterpretU64AsF64(0x0000_0000_0000_0001n), expected: [0, constants.positive.subnormal.min] }, - { input: reinterpretU64AsF64(0x800f_ffff_ffff_ffffn), expected: [0, constants.positive.subnormal.min] }, - // Zero { input: 0, expected: 0 }, ]; diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index f33208436fa7..f9df1b1f52af 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -1016,7 +1016,7 @@ "webgpu:shader,execution,expression,binary,u32_comparison:less_equals:*": { "subcaseMS": 7.844 }, "webgpu:shader,execution,expression,binary,u32_comparison:less_than:*": { "subcaseMS": 6.700 }, "webgpu:shader,execution,expression,binary,u32_comparison:not_equals:*": { "subcaseMS": 6.850 }, - "webgpu:shader,execution,expression,call,builtin,abs:abstract_float:*": { "subcaseMS": 16.809 }, + "webgpu:shader,execution,expression,call,builtin,abs:abstract_float:*": { "subcaseMS": 464.126 }, "webgpu:shader,execution,expression,call,builtin,abs:abstract_int:*": { "subcaseMS": 16.810 }, "webgpu:shader,execution,expression,call,builtin,abs:f16:*": { "subcaseMS": 22.910 }, "webgpu:shader,execution,expression,call,builtin,abs:f32:*": { "subcaseMS": 9.844 }, diff --git a/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts index c7ba2e498e04..05d5242f7354 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts @@ -25,13 +25,14 @@ import { TypeI32, TypeU32, u32Bits, + TypeAbstractFloat, } from '../../../../../util/conversion.js'; import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range, fullF16Range } from '../../../../../util/math.js'; +import { fullF32Range, fullF16Range, fullF64Range } 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); @@ -42,6 +43,13 @@ export const d = makeCaseCache('abs', { f16: () => { return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.absInterval); }, + abstract: () => { + return FP.abstract.generateScalarToIntervalCases( + fullF64Range(), + 'unfiltered', + FP.abstract.absInterval + ); + }, }); g.test('abstract_int') @@ -153,9 +161,14 @@ g.test('abstract_float') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') .desc(`abstract float tests`) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .fn(async t => { + const cases = await d.get('abstract'); + await run(t, abstractBuiltin('abs'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases); + }); g.test('f32') .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions') diff --git a/src/webgpu/shader/execution/expression/call/builtin/builtin.ts b/src/webgpu/shader/execution/expression/call/builtin/builtin.ts index 26424dd1ce6e..282feea70306 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/builtin.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/builtin.ts @@ -1,4 +1,5 @@ import { + abstractFloatShaderBuilder, basicExpressionBuilder, basicExpressionWithPredeclarationBuilder, ShaderBuilder, @@ -9,6 +10,11 @@ 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( diff --git a/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index 8b385591dee6..a9689494f00f 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -4829,7 +4829,7 @@ class FPAbstractTraits extends FPTraits { public readonly ulpInterval = this.unboundedUlpInterval.bind(this); // Framework - API - Overrides - public readonly absInterval = this.unimplementedScalarToInterval.bind(this); + public readonly absInterval = this.absIntervalImpl.bind(this); public readonly acosInterval = this.unimplementedScalarToInterval.bind(this); public readonly acoshAlternativeInterval = this.unimplementedScalarToInterval.bind(this); public readonly acoshPrimaryInterval = this.unimplementedScalarToInterval.bind(this); From 04cd7b8fcf163c734bd93ce54d68640a95b88e61 Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Wed, 13 Sep 2023 14:53:22 -0400 Subject: [PATCH 032/166] Update websocket-logger instructions to use `npm ci` (#2947) Changing to using `npm ci` instead of `npm install` to avoid potentially pulling in unvetted dependencies. Fixes #2945 --- docs/adding_timing_metadata.md | 4 ++-- tools/websocket-logger/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/adding_timing_metadata.md b/docs/adding_timing_metadata.md index 617d5365526d..056d0fb06271 100644 --- a/docs/adding_timing_metadata.md +++ b/docs/adding_timing_metadata.md @@ -66,8 +66,8 @@ throughout the following steps. At `tools/websocket-logger/` ``` -npm install -npm run +npm ci +npm start ``` The output from this command will indicate where the results are being logged, diff --git a/tools/websocket-logger/README.md b/tools/websocket-logger/README.md index ebd4e4f3076c..1328f12e9706 100644 --- a/tools/websocket-logger/README.md +++ b/tools/websocket-logger/README.md @@ -5,5 +5,5 @@ It can be used to receive logs from CTS in a way that's resistant to test crashe independent of which runtime is being used (e.g. standalone, WPT, Node). It's used in particular to capture timing results for predefining "chunking" of the CTS for WPT. -To set up, use `npm install`. +To set up, use `npm ci`. To launch, use `npm start`. From 3a5f7893eb303e14ad55cb942b6db866de21eec8 Mon Sep 17 00:00:00 2001 From: Greggman Date: Thu, 14 Sep 2023 09:03:26 +0900 Subject: [PATCH 033/166] Compat: Limit max attributes (#2953) In compat @builtin(vertex_index) and @builtin(instance_index) each take an attribute so account for that in this test. It's possible we should refactor this test to not use vertex_index, instance_instance. For example we could make each pair of data generate the correct pixel position. For now it seemed best to get it to pass. The test in webgpu/compat/api/validation/encoding/render_pipeline/vertex_state.spec.ts does a simple test that you can use maxVertexAttributes but it does not test all the combinations like this test. It only tests that creating a pipeline passes if you use maxVertexAttributes and fails if you use maxVertexAttributes + the builtins above --- .../api/operation/vertex_state/correctness.spec.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/webgpu/api/operation/vertex_state/correctness.spec.ts b/src/webgpu/api/operation/vertex_state/correctness.spec.ts index 56c659e80fc5..8ec536dcc511 100644 --- a/src/webgpu/api/operation/vertex_state/correctness.spec.ts +++ b/src/webgpu/api/operation/vertex_state/correctness.spec.ts @@ -941,13 +941,15 @@ g.test('max_buffers_and_attribs') .params(u => u.combine('format', kVertexFormats)) .fn(t => { const { format } = t.params; - const attributesPerBuffer = Math.ceil(kMaxVertexAttributes / kMaxVertexBuffers); + // In compat mode, @builtin(vertex_index) and @builtin(instance_index) each take an attribute + const maxVertexAttributes = t.isCompatibility ? kMaxVertexAttributes - 2 : kMaxVertexAttributes; + const attributesPerBuffer = Math.ceil(maxVertexAttributes / kMaxVertexBuffers); let attributesEmitted = 0; const state: VertexLayoutState<{}, {}> = []; for (let i = 0; i < kMaxVertexBuffers; i++) { const attributes: GPUVertexAttribute[] = []; - for (let j = 0; j < attributesPerBuffer && attributesEmitted < kMaxVertexAttributes; j++) { + for (let j = 0; j < attributesPerBuffer && attributesEmitted < maxVertexAttributes; j++) { attributes.push({ format, offset: 0, shaderLocation: attributesEmitted }); attributesEmitted++; } @@ -1080,8 +1082,10 @@ g.test('overlapping_attributes') .fn(t => { const { format } = t.params; + // In compat mode, @builtin(vertex_index) and @builtin(instance_index) each take an attribute + const maxVertexAttributes = t.isCompatibility ? kMaxVertexAttributes - 2 : kMaxVertexAttributes; const attributes: GPUVertexAttribute[] = []; - for (let i = 0; i < kMaxVertexAttributes; i++) { + for (let i = 0; i < maxVertexAttributes; i++) { attributes.push({ format, offset: 0, shaderLocation: i }); } From ac6a70ebad24d65cc2f31bfa0a32cc384c93d2aa Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Thu, 14 Sep 2023 10:42:25 -0400 Subject: [PATCH 034/166] Split up 'scalar' tests into 'scalar' and 'vector' tests (#2946) The existing code was using vectorize to generate vector subcases of the 'scalar' fixture, which lead to a confusing situation where the 'scalar' tests were testing scalar-scalar inputs and vector-vector inputs, but not matrix-matrix, scalar-vector, or vector-scalar. This PR factors out the vector-vector cases into their own fixture named 'vector', but still retains the usage of vectorize to reduce the need to test case duplication. Beyond clarity, this also divides up the existing tests into smaller chunks to help with load-balancing, etc. Fixes #2935 --- src/webgpu/listing_meta.json | 112 ++++++++++-------- .../expression/binary/af_addition.spec.ts | 29 ++++- .../binary/af_multiplication.spec.ts | 29 ++++- .../expression/binary/af_subtraction.spec.ts | 29 ++++- .../expression/binary/f16_addition.spec.ts | 25 +++- .../expression/binary/f16_division.spec.ts | 25 +++- .../binary/f16_multiplication.spec.ts | 25 +++- .../expression/binary/f16_subtraction.spec.ts | 25 +++- .../expression/binary/f32_addition.spec.ts | 22 +++- .../expression/binary/f32_division.spec.ts | 22 +++- .../binary/f32_multiplication.spec.ts | 22 +++- .../expression/binary/f32_remainder.spec.ts | 22 +++- .../expression/binary/f32_subtraction.spec.ts | 22 +++- 13 files changed, 308 insertions(+), 101 deletions(-) diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index f9df1b1f52af..c270ae463fbc 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -850,9 +850,10 @@ "webgpu:idl,constants,flags:ShaderStage,values:*": { "subcaseMS": 0.034 }, "webgpu:idl,constants,flags:TextureUsage,count:*": { "subcaseMS": 0.101 }, "webgpu:idl,constants,flags:TextureUsage,values:*": { "subcaseMS": 0.040 }, - "webgpu:shader,execution,expression,binary,af_addition:scalar:*": { "subcaseMS": 290.000 }, - "webgpu:shader,execution,expression,binary,af_addition:scalar_vector:*": { "subcaseMS": 665.234 }, - "webgpu:shader,execution,expression,binary,af_addition:vector_scalar:*": { "subcaseMS": 664.434 }, + "webgpu:shader,execution,expression,binary,af_addition:scalar:*": { "subcaseMS": 815.300 }, + "webgpu:shader,execution,expression,binary,af_addition:scalar_vector:*": { "subcaseMS": 1803.434 }, + "webgpu:shader,execution,expression,binary,af_addition:vector:*": { "subcaseMS": 719.600 }, + "webgpu:shader,execution,expression,binary,af_addition:vector_scalar:*": { "subcaseMS": 1770.734 }, "webgpu:shader,execution,expression,binary,af_comparison:equals:*": { "subcaseMS": 23.000 }, "webgpu:shader,execution,expression,binary,af_comparison:greater_equals:*": { "subcaseMS": 20.651 }, "webgpu:shader,execution,expression,binary,af_comparison:greater_than:*": { "subcaseMS": 19.901 }, @@ -861,11 +862,13 @@ "webgpu:shader,execution,expression,binary,af_comparison:not_equals:*": { "subcaseMS": 19.651 }, "webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*": { "subcaseMS": 11169.534 }, "webgpu:shader,execution,expression,binary,af_matrix_subtraction:matrix:*": { "subcaseMS": 14060.956 }, - "webgpu:shader,execution,expression,binary,af_multiplication:scalar:*": { "subcaseMS": 907.726 }, + "webgpu:shader,execution,expression,binary,af_multiplication:scalar:*": { "subcaseMS": 777.901 }, "webgpu:shader,execution,expression,binary,af_multiplication:scalar_vector:*": { "subcaseMS": 2025.534 }, + "webgpu:shader,execution,expression,binary,af_multiplication:vector:*": { "subcaseMS": 710.667 }, "webgpu:shader,execution,expression,binary,af_multiplication:vector_scalar:*": { "subcaseMS": 2085.300 }, - "webgpu:shader,execution,expression,binary,af_subtraction:scalar:*": { "subcaseMS": 960.626 }, + "webgpu:shader,execution,expression,binary,af_subtraction:scalar:*": { "subcaseMS": 854.100 }, "webgpu:shader,execution,expression,binary,af_subtraction:scalar_vector:*": { "subcaseMS": 2336.534 }, + "webgpu:shader,execution,expression,binary,af_subtraction:vector:*": { "subcaseMS": 764.201 }, "webgpu:shader,execution,expression,binary,af_subtraction:vector_scalar:*": { "subcaseMS": 2437.701 }, "webgpu:shader,execution,expression,binary,bitwise:bitwise_and:*": { "subcaseMS": 20.982 }, "webgpu:shader,execution,expression,binary,bitwise:bitwise_and_compound:*": { "subcaseMS": 22.513 }, @@ -885,48 +888,54 @@ "webgpu:shader,execution,expression,binary,bool_logical:or:*": { "subcaseMS": 6.663 }, "webgpu:shader,execution,expression,binary,bool_logical:or_compound:*": { "subcaseMS": 7.407 }, "webgpu:shader,execution,expression,binary,bool_logical:or_short_circuit:*": { "subcaseMS": 10.050 }, - "webgpu:shader,execution,expression,binary,f16_addition:scalar:*": { "subcaseMS": 6.807 }, - "webgpu:shader,execution,expression,binary,f16_addition:scalar_compound:*": { "subcaseMS": 4.010 }, - "webgpu:shader,execution,expression,binary,f16_addition:scalar_vector:*": { "subcaseMS": 2.606 }, - "webgpu:shader,execution,expression,binary,f16_addition:vector_scalar:*": { "subcaseMS": 3.006 }, - "webgpu:shader,execution,expression,binary,f16_addition:vector_scalar_compound:*": { "subcaseMS": 2.503 }, + "webgpu:shader,execution,expression,binary,f16_addition:scalar:*": { "subcaseMS": 106.501 }, + "webgpu:shader,execution,expression,binary,f16_addition:scalar_compound:*": { "subcaseMS": 5.912 }, + "webgpu:shader,execution,expression,binary,f16_addition:scalar_vector:*": { "subcaseMS": 4.408 }, + "webgpu:shader,execution,expression,binary,f16_addition:vector:*": { "subcaseMS": 8.204 }, + "webgpu:shader,execution,expression,binary,f16_addition:vector_scalar:*": { "subcaseMS": 4.308 }, + "webgpu:shader,execution,expression,binary,f16_addition:vector_scalar_compound:*": { "subcaseMS": 4.406 }, "webgpu:shader,execution,expression,binary,f16_comparison:equals:*": { "subcaseMS": 3.907 }, "webgpu:shader,execution,expression,binary,f16_comparison:greater_equals:*": { "subcaseMS": 3.507 }, "webgpu:shader,execution,expression,binary,f16_comparison:greater_than:*": { "subcaseMS": 3.908 }, "webgpu:shader,execution,expression,binary,f16_comparison:less_equals:*": { "subcaseMS": 3.108 }, "webgpu:shader,execution,expression,binary,f16_comparison:less_than:*": { "subcaseMS": 3.508 }, "webgpu:shader,execution,expression,binary,f16_comparison:not_equals:*": { "subcaseMS": 3.405 }, - "webgpu:shader,execution,expression,binary,f16_division:scalar:*": { "subcaseMS": 3.105 }, - "webgpu:shader,execution,expression,binary,f16_division:scalar_compound:*": { "subcaseMS": 4.011 }, - "webgpu:shader,execution,expression,binary,f16_division:scalar_vector:*": { "subcaseMS": 2.406 }, - "webgpu:shader,execution,expression,binary,f16_division:vector_scalar:*": { "subcaseMS": 3.006 }, - "webgpu:shader,execution,expression,binary,f16_division:vector_scalar_compound:*": { "subcaseMS": 3.005 }, - "webgpu:shader,execution,expression,binary,f16_multiplication:scalar:*": { "subcaseMS": 4.010 }, - "webgpu:shader,execution,expression,binary,f16_multiplication:scalar_compound:*": { "subcaseMS": 3.906 }, - "webgpu:shader,execution,expression,binary,f16_multiplication:scalar_vector:*": { "subcaseMS": 2.708 }, - "webgpu:shader,execution,expression,binary,f16_multiplication:vector_scalar:*": { "subcaseMS": 3.306 }, - "webgpu:shader,execution,expression,binary,f16_multiplication:vector_scalar_compound:*": { "subcaseMS": 2.501 }, - "webgpu:shader,execution,expression,binary,f16_subtraction:scalar:*": { "subcaseMS": 3.406 }, - "webgpu:shader,execution,expression,binary,f16_subtraction:scalar_compound:*": { "subcaseMS": 4.203 }, - "webgpu:shader,execution,expression,binary,f16_subtraction:scalar_vector:*": { "subcaseMS": 2.602 }, - "webgpu:shader,execution,expression,binary,f16_subtraction:vector_scalar:*": { "subcaseMS": 2.605 }, - "webgpu:shader,execution,expression,binary,f16_subtraction:vector_scalar_compound:*": { "subcaseMS": 2.604 }, - "webgpu:shader,execution,expression,binary,f32_addition:scalar:*": { "subcaseMS": 17.788 }, - "webgpu:shader,execution,expression,binary,f32_addition:scalar_compound:*": { "subcaseMS": 9.919 }, - "webgpu:shader,execution,expression,binary,f32_addition:scalar_vector:*": { "subcaseMS": 12.600 }, - "webgpu:shader,execution,expression,binary,f32_addition:vector_scalar:*": { "subcaseMS": 12.550 }, - "webgpu:shader,execution,expression,binary,f32_addition:vector_scalar_compound:*": { "subcaseMS": 12.142 }, + "webgpu:shader,execution,expression,binary,f16_division:scalar:*": { "subcaseMS": 125.300 }, + "webgpu:shader,execution,expression,binary,f16_division:scalar_compound:*": { "subcaseMS": 5.909 }, + "webgpu:shader,execution,expression,binary,f16_division:scalar_vector:*": { "subcaseMS": 3.509 }, + "webgpu:shader,execution,expression,binary,f16_division:vector:*": { "subcaseMS": 5.505 }, + "webgpu:shader,execution,expression,binary,f16_division:vector_scalar:*": { "subcaseMS": 3.908 }, + "webgpu:shader,execution,expression,binary,f16_division:vector_scalar_compound:*": { "subcaseMS": 4.308 }, + "webgpu:shader,execution,expression,binary,f16_multiplication:scalar:*": { "subcaseMS": 105.202 }, + "webgpu:shader,execution,expression,binary,f16_multiplication:scalar_compound:*": { "subcaseMS": 8.111 }, + "webgpu:shader,execution,expression,binary,f16_multiplication:scalar_vector:*": { "subcaseMS": 3.907 }, + "webgpu:shader,execution,expression,binary,f16_multiplication:vector:*": { "subcaseMS": 6.104 }, + "webgpu:shader,execution,expression,binary,f16_multiplication:vector_scalar:*": { "subcaseMS": 3.908 }, + "webgpu:shader,execution,expression,binary,f16_multiplication:vector_scalar_compound:*": { "subcaseMS": 4.205 }, + "webgpu:shader,execution,expression,binary,f16_subtraction:scalar:*": { "subcaseMS": 101.600 }, + "webgpu:shader,execution,expression,binary,f16_subtraction:scalar_compound:*": { "subcaseMS": 5.611 }, + "webgpu:shader,execution,expression,binary,f16_subtraction:scalar_vector:*": { "subcaseMS": 4.308 }, + "webgpu:shader,execution,expression,binary,f16_subtraction:vector:*": { "subcaseMS": 7.105 }, + "webgpu:shader,execution,expression,binary,f16_subtraction:vector_scalar:*": { "subcaseMS": 4.107 }, + "webgpu:shader,execution,expression,binary,f16_subtraction:vector_scalar_compound:*": { "subcaseMS": 4.606 }, + "webgpu:shader,execution,expression,binary,f32_addition:scalar:*": { "subcaseMS": 352.326 }, + "webgpu:shader,execution,expression,binary,f32_addition:scalar_compound:*": { "subcaseMS": 146.513 }, + "webgpu:shader,execution,expression,binary,f32_addition:scalar_vector:*": { "subcaseMS": 148.117 }, + "webgpu:shader,execution,expression,binary,f32_addition:vector:*": { "subcaseMS": 117.209 }, + "webgpu:shader,execution,expression,binary,f32_addition:vector_scalar:*": { "subcaseMS": 150.450 }, + "webgpu:shader,execution,expression,binary,f32_addition:vector_scalar_compound:*": { "subcaseMS": 152.842 }, "webgpu:shader,execution,expression,binary,f32_comparison:equals:*": { "subcaseMS": 9.638 }, "webgpu:shader,execution,expression,binary,f32_comparison:greater_equals:*": { "subcaseMS": 7.882 }, "webgpu:shader,execution,expression,binary,f32_comparison:greater_than:*": { "subcaseMS": 7.388 }, "webgpu:shader,execution,expression,binary,f32_comparison:less_equals:*": { "subcaseMS": 6.632 }, "webgpu:shader,execution,expression,binary,f32_comparison:less_than:*": { "subcaseMS": 6.969 }, "webgpu:shader,execution,expression,binary,f32_comparison:not_equals:*": { "subcaseMS": 6.819 }, - "webgpu:shader,execution,expression,binary,f32_division:scalar:*": { "subcaseMS": 19.688 }, - "webgpu:shader,execution,expression,binary,f32_division:scalar_compound:*": { "subcaseMS": 8.294 }, - "webgpu:shader,execution,expression,binary,f32_division:scalar_vector:*": { "subcaseMS": 19.142 }, - "webgpu:shader,execution,expression,binary,f32_division:vector_scalar:*": { "subcaseMS": 17.900 }, - "webgpu:shader,execution,expression,binary,f32_division:vector_scalar_compound:*": { "subcaseMS": 9.859 }, + "webgpu:shader,execution,expression,binary,f32_division:scalar:*": { "subcaseMS": 372.550 }, + "webgpu:shader,execution,expression,binary,f32_division:scalar_compound:*": { "subcaseMS": 140.819 }, + "webgpu:shader,execution,expression,binary,f32_division:scalar_vector:*": { "subcaseMS": 82.709 }, + "webgpu:shader,execution,expression,binary,f32_division:vector:*": { "subcaseMS": 119.475 }, + "webgpu:shader,execution,expression,binary,f32_division:vector_scalar:*": { "subcaseMS": 75.375 }, + "webgpu:shader,execution,expression,binary,f32_division:vector_scalar_compound:*": { "subcaseMS": 76.017 }, "webgpu:shader,execution,expression,binary,f32_matrix_addition:matrix:*": { "subcaseMS": 35.020 }, "webgpu:shader,execution,expression,binary,f32_matrix_addition:matrix_compound:*": { "subcaseMS": 27.534 }, "webgpu:shader,execution,expression,binary,f32_matrix_matrix_multiplication:matrix_matrix:*": { "subcaseMS": 134.680 }, @@ -939,21 +948,24 @@ "webgpu:shader,execution,expression,binary,f32_matrix_vector_multiplication:matrix_vector:*": { "subcaseMS": 105.139 }, "webgpu:shader,execution,expression,binary,f32_matrix_vector_multiplication:vector_matrix:*": { "subcaseMS": 22.501 }, "webgpu:shader,execution,expression,binary,f32_matrix_vector_multiplication:vector_matrix_compound:*": { "subcaseMS": 16.217 }, - "webgpu:shader,execution,expression,binary,f32_multiplication:scalar:*": { "subcaseMS": 26.382 }, - "webgpu:shader,execution,expression,binary,f32_multiplication:scalar_compound:*": { "subcaseMS": 10.250 }, - "webgpu:shader,execution,expression,binary,f32_multiplication:scalar_vector:*": { "subcaseMS": 35.359 }, - "webgpu:shader,execution,expression,binary,f32_multiplication:vector_scalar:*": { "subcaseMS": 34.834 }, - "webgpu:shader,execution,expression,binary,f32_multiplication:vector_scalar_compound:*": { "subcaseMS": 11.609 }, - "webgpu:shader,execution,expression,binary,f32_remainder:scalar:*": { "subcaseMS": 21.982 }, - "webgpu:shader,execution,expression,binary,f32_remainder:scalar_compound:*": { "subcaseMS": 8.844 }, - "webgpu:shader,execution,expression,binary,f32_remainder:scalar_vector:*": { "subcaseMS": 10.650 }, - "webgpu:shader,execution,expression,binary,f32_remainder:vector_scalar:*": { "subcaseMS": 9.525 }, - "webgpu:shader,execution,expression,binary,f32_remainder:vector_scalar_compound:*": { "subcaseMS": 9.925 }, - "webgpu:shader,execution,expression,binary,f32_subtraction:scalar:*": { "subcaseMS": 12.813 }, - "webgpu:shader,execution,expression,binary,f32_subtraction:scalar_compound:*": { "subcaseMS": 9.213 }, - "webgpu:shader,execution,expression,binary,f32_subtraction:scalar_vector:*": { "subcaseMS": 14.125 }, - "webgpu:shader,execution,expression,binary,f32_subtraction:vector_scalar:*": { "subcaseMS": 13.292 }, - "webgpu:shader,execution,expression,binary,f32_subtraction:vector_scalar_compound:*": { "subcaseMS": 13.150 }, + "webgpu:shader,execution,expression,binary,f32_multiplication:scalar:*": { "subcaseMS": 360.475 }, + "webgpu:shader,execution,expression,binary,f32_multiplication:scalar_compound:*": { "subcaseMS": 155.044 }, + "webgpu:shader,execution,expression,binary,f32_multiplication:scalar_vector:*": { "subcaseMS": 153.642 }, + "webgpu:shader,execution,expression,binary,f32_multiplication:vector:*": { "subcaseMS": 121.692 }, + "webgpu:shader,execution,expression,binary,f32_multiplication:vector_scalar:*": { "subcaseMS": 156.909 }, + "webgpu:shader,execution,expression,binary,f32_multiplication:vector_scalar_compound:*": { "subcaseMS": 157.576 }, + "webgpu:shader,execution,expression,binary,f32_remainder:scalar:*": { "subcaseMS": 313.175 }, + "webgpu:shader,execution,expression,binary,f32_remainder:scalar_compound:*": { "subcaseMS": 66.207 }, + "webgpu:shader,execution,expression,binary,f32_remainder:scalar_vector:*": { "subcaseMS": 64.125 }, + "webgpu:shader,execution,expression,binary,f32_remainder:vector:*": { "subcaseMS": 60.517 }, + "webgpu:shader,execution,expression,binary,f32_remainder:vector_scalar:*": { "subcaseMS": 56.025 }, + "webgpu:shader,execution,expression,binary,f32_remainder:vector_scalar_compound:*": { "subcaseMS": 57.101 }, + "webgpu:shader,execution,expression,binary,f32_subtraction:scalar:*": { "subcaseMS": 335.951 }, + "webgpu:shader,execution,expression,binary,f32_subtraction:scalar_compound:*": { "subcaseMS": 149.525 }, + "webgpu:shader,execution,expression,binary,f32_subtraction:scalar_vector:*": { "subcaseMS": 159.659 }, + "webgpu:shader,execution,expression,binary,f32_subtraction:vector:*": { "subcaseMS": 117.142 }, + "webgpu:shader,execution,expression,binary,f32_subtraction:vector_scalar:*": { "subcaseMS": 152.067 }, + "webgpu:shader,execution,expression,binary,f32_subtraction:vector_scalar_compound:*": { "subcaseMS": 159.417 }, "webgpu:shader,execution,expression,binary,i32_arithmetic:addition:*": { "subcaseMS": 23.975 }, "webgpu:shader,execution,expression,binary,i32_arithmetic:addition_compound:*": { "subcaseMS": 9.219 }, "webgpu:shader,execution,expression,binary,i32_arithmetic:addition_scalar_vector:*": { "subcaseMS": 33.059 }, diff --git a/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts b/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts index 777b801e131d..508df8aab33e 100644 --- a/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts @@ -85,17 +85,36 @@ g.test('scalar') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( ` -Expression: x + y +Expression: x + y, where x and y are scalars +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('scalar'); + await run( + t, + abstractBinary('+'), + [TypeAbstractFloat, TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); + +g.test('vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x + y, where x and y are vectors Accuracy: Correctly rounded ` ) .params(u => - u - .combine('inputSource', onlyConstInputSource) - .combine('vectorize', [undefined, 2, 3, 4] as const) + u.combine('inputSource', onlyConstInputSource).combine('vectorize', [2, 3, 4] as const) ) .fn(async t => { - const cases = await d.get('scalar'); + const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases await run( t, abstractBinary('+'), diff --git a/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts b/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts index 80a2aa23bdcd..2f1231bf02b2 100644 --- a/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts @@ -85,17 +85,36 @@ g.test('scalar') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( ` -Expression: x * y +Expression: x * y, where x and y are scalars +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('scalar'); + await run( + t, + abstractBinary('*'), + [TypeAbstractFloat, TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); + +g.test('vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x * y, where x and y are vectors Accuracy: Correctly rounded ` ) .params(u => - u - .combine('inputSource', onlyConstInputSource) - .combine('vectorize', [undefined, 2, 3, 4] as const) + u.combine('inputSource', onlyConstInputSource).combine('vectorize', [2, 3, 4] as const) ) .fn(async t => { - const cases = await d.get('scalar'); + const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases await run( t, abstractBinary('*'), diff --git a/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts b/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts index 4faa21de33fd..5c4528209381 100644 --- a/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts @@ -85,17 +85,36 @@ g.test('scalar') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( ` -Expression: x - y +Expression: x - y, where x and y are scalars +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('scalar'); + await run( + t, + abstractBinary('-'), + [TypeAbstractFloat, TypeAbstractFloat], + TypeAbstractFloat, + t.params, + cases + ); + }); + +g.test('vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x - y, where x and y are vectors Accuracy: Correctly rounded ` ) .params(u => - u - .combine('inputSource', onlyConstInputSource) - .combine('vectorize', [undefined, 2, 3, 4] as const) + u.combine('inputSource', onlyConstInputSource).combine('vectorize', [2, 3, 4] as const) ) .fn(async t => { - const cases = await d.get('scalar'); + const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases await run( t, abstractBinary('-'), diff --git a/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts b/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts index e9b6b55c145f..59360bff9e04 100644 --- a/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts @@ -149,13 +149,11 @@ g.test('scalar') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( ` -Expression: x + y +Expression: x + y, where x and y are scalars Accuracy: Correctly rounded ` ) - .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) - ) + .params(u => u.combine('inputSource', allInputSources)) .beforeAllSubcases(t => { t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); }) @@ -166,6 +164,25 @@ Accuracy: Correctly rounded await run(t, binary('+'), [TypeF16, TypeF16], TypeF16, t.params, cases); }); +g.test('vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x + y, where x and y are vectors +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + }) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases + ); + await run(t, binary('+'), [TypeF16, TypeF16], TypeF16, t.params, cases); + }); + g.test('scalar_compound') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( diff --git a/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts b/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts index 713448338d99..3e54ff683314 100644 --- a/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts @@ -141,13 +141,11 @@ g.test('scalar') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( ` -Expression: x / y +Expression: x / y, where x and y are scalars Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126] ` ) - .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) - ) + .params(u => u.combine('inputSource', allInputSources)) .beforeAllSubcases(t => { t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); }) @@ -158,6 +156,25 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126] await run(t, binary('/'), [TypeF16, TypeF16], TypeF16, t.params, cases); }); +g.test('vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x / y, where x and y are vectors +Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126] +` + ) + .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + }) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases + ); + await run(t, binary('/'), [TypeF16, TypeF16], TypeF16, t.params, cases); + }); + g.test('scalar_compound') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( diff --git a/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts b/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts index 3844b7f07443..bff045a88758 100644 --- a/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts @@ -141,13 +141,11 @@ g.test('scalar') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( ` -Expression: x * y +Expression: x * y, where x and y are scalars Accuracy: Correctly rounded ` ) - .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) - ) + .params(u => u.combine('inputSource', allInputSources)) .beforeAllSubcases(t => { t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); }) @@ -158,6 +156,25 @@ Accuracy: Correctly rounded await run(t, binary('*'), [TypeF16, TypeF16], TypeF16, t.params, cases); }); +g.test('vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x * y, where x and y are vectors +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + }) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases + ); + await run(t, binary('*'), [TypeF16, TypeF16], TypeF16, t.params, cases); + }); + g.test('scalar_compound') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( diff --git a/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts b/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts index e9381b3656f7..c1755502ba09 100644 --- a/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts @@ -141,13 +141,11 @@ g.test('scalar') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( ` -Expression: x - y +Expression: x - y, where x and y are scalars Accuracy: Correctly rounded ` ) - .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) - ) + .params(u => u.combine('inputSource', allInputSources)) .beforeAllSubcases(t => { t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); }) @@ -158,6 +156,25 @@ Accuracy: Correctly rounded await run(t, binary('-'), [TypeF16, TypeF16], TypeF16, t.params, cases); }); +g.test('vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x - y, where x and y are vectors +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const)) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + }) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases + ); + await run(t, binary('-'), [TypeF16, TypeF16], TypeF16, t.params, cases); + }); + g.test('scalar_compound') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( diff --git a/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts b/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts index d3c9bcfb02c3..4ad38d1727c6 100644 --- a/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts @@ -141,13 +141,11 @@ g.test('scalar') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( ` -Expression: x + y +Expression: x + y, where x and y are scalars Accuracy: Correctly rounded ` ) - .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) - ) + .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get( t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' @@ -155,6 +153,22 @@ Accuracy: Correctly rounded await run(t, binary('+'), [TypeF32, TypeF32], TypeF32, t.params, cases); }); +g.test('vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x + y, where x and y are vectors +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const)) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases + ); + await run(t, binary('+'), [TypeF32, TypeF32], TypeF32, t.params, cases); + }); + g.test('scalar_compound') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( diff --git a/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts b/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts index 205acd7fa996..e9fa078ec2f8 100644 --- a/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts @@ -141,13 +141,11 @@ g.test('scalar') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( ` -Expression: x / y +Expression: x / y, where x and y are scalars Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126] ` ) - .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) - ) + .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get( t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' @@ -155,6 +153,22 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126] await run(t, binary('/'), [TypeF32, TypeF32], TypeF32, t.params, cases); }); +g.test('vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x / y, where x and y are vectors +Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126] +` + ) + .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const)) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases + ); + await run(t, binary('/'), [TypeF32, TypeF32], TypeF32, t.params, cases); + }); + g.test('scalar_compound') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( diff --git a/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts b/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts index 95bee77b75bd..f2f644a7b225 100644 --- a/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts @@ -141,13 +141,11 @@ g.test('scalar') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( ` -Expression: x * y +Expression: x * y, where x and y are scalars Accuracy: Correctly rounded ` ) - .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) - ) + .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get( t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' @@ -155,6 +153,22 @@ Accuracy: Correctly rounded await run(t, binary('*'), [TypeF32, TypeF32], TypeF32, t.params, cases); }); +g.test('vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x * y, where x and y are vectors +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const)) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases + ); + await run(t, binary('*'), [TypeF32, TypeF32], TypeF32, t.params, cases); + }); + g.test('scalar_compound') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( diff --git a/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts b/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts index c64d5cbb6c8b..d948047bbdf0 100644 --- a/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts @@ -141,13 +141,11 @@ g.test('scalar') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( ` -Expression: x % y +Expression: x % y, where x and y are scalars Accuracy: Derived from x - y * trunc(x/y) ` ) - .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) - ) + .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get( t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' @@ -155,6 +153,22 @@ Accuracy: Derived from x - y * trunc(x/y) await run(t, binary('%'), [TypeF32, TypeF32], TypeF32, t.params, cases); }); +g.test('vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x % y, where x and y are vectors +Accuracy: Derived from x - y * trunc(x/y) +` + ) + .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const)) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases + ); + await run(t, binary('%'), [TypeF32, TypeF32], TypeF32, t.params, cases); + }); + g.test('scalar_compound') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( diff --git a/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts b/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts index f2bd7f6a5ad6..2eb137a1555a 100644 --- a/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts @@ -141,13 +141,11 @@ g.test('scalar') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( ` -Expression: x - y +Expression: x - y, where x and y are scalars Accuracy: Correctly rounded ` ) - .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) - ) + .params(u => u.combine('inputSource', allInputSources)) .fn(async t => { const cases = await d.get( t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' @@ -155,6 +153,22 @@ Accuracy: Correctly rounded await run(t, binary('-'), [TypeF32, TypeF32], TypeF32, t.params, cases); }); +g.test('vector') + .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') + .desc( + ` +Expression: x - y, where x and y are vectors +Accuracy: Correctly rounded +` + ) + .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const)) + .fn(async t => { + const cases = await d.get( + t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases + ); + await run(t, binary('-'), [TypeF32, TypeF32], TypeF32, t.params, cases); + }); + g.test('scalar_compound') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( From 4673f2f6bcb5939c07683acbfef7e8c5e2d882dd Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Thu, 14 Sep 2023 10:51:14 -0400 Subject: [PATCH 035/166] wgsl: Add AbstractFloat `trunc` execution tests (#2948) Fixes #2525 --- src/unittests/floating_point.spec.ts | 75 +++++++++++-------- src/webgpu/listing_meta.json | 6 +- .../expression/call/builtin/trunc.spec.ts | 24 ++++-- src/webgpu/util/floating_point.ts | 2 +- 4 files changed, 64 insertions(+), 43 deletions(-) diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 825078d680db..bea923452454 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -3538,7 +3538,7 @@ g.test('sqrtInterval') const got = trait.sqrtInterval(t.params.input); t.expect( objectEquals(expected, got), - `f32.sqrtInterval(${t.params.input}) returned ${got}. Expected ${expected}` + `FP.${t.params.trait}.sqrtInterval(${t.params.input}) returned ${got}. Expected ${expected}` ); }); @@ -3608,43 +3608,52 @@ g.test('tanhInterval_f32') ); }); -g.test('truncInterval_f32') - .paramsSubcasesOnly( - // prettier-ignore - [ - { input: 0, expected: 0 }, - { input: 0.1, expected: 0 }, - { input: 0.9, expected: 0 }, - { input: 1.0, expected: 1 }, - { input: 1.1, expected: 1 }, - { input: 1.9, expected: 1 }, - { input: -0.1, expected: 0 }, - { input: -0.9, expected: 0 }, - { input: -1.0, expected: -1 }, - { input: -1.1, expected: -1 }, - { input: -1.9, expected: -1 }, +g.test('truncInterval') + .params(u => + u + .combine('trait', ['f32', 'abstract'] as const) + .beginSubcases() + .expandWithParams(p => { + const trait = FP[p.trait]; + const constants = trait.constants(); + // prettier-ignore + return [ + // Normals + { input: 0, expected: 0 }, + { input: 0.1, expected: 0 }, + { input: 0.9, expected: 0 }, + { input: 1.0, expected: 1 }, + { input: 1.1, expected: 1 }, + { input: 1.9, expected: 1 }, + { input: -0.1, expected: 0 }, + { input: -0.9, expected: 0 }, + { input: -1.0, expected: -1 }, + { input: -1.1, expected: -1 }, + { input: -1.9, expected: -1 }, - // Edge cases - { input: kValue.f32.infinity.positive, expected: kUnboundedBounds }, - { input: kValue.f32.infinity.negative, expected: kUnboundedBounds }, - { input: kValue.f32.positive.max, expected: kValue.f32.positive.max }, - { input: kValue.f32.positive.min, expected: 0 }, - { input: kValue.f32.negative.min, expected: kValue.f32.negative.min }, - { input: kValue.f32.negative.max, expected: 0 }, - - // 32-bit subnormals - { input: kValue.f32.subnormal.positive.max, expected: 0 }, - { input: kValue.f32.subnormal.positive.min, expected: 0 }, - { input: kValue.f32.subnormal.negative.min, expected: 0 }, - { input: kValue.f32.subnormal.negative.max, expected: 0 }, - ] + // Subnormals + { input: constants.positive.subnormal.max, expected: 0 }, + { input: constants.positive.subnormal.min, expected: 0 }, + { input: constants.negative.subnormal.min, expected: 0 }, + { input: constants.negative.subnormal.max, expected: 0 }, + + // Edge cases + { input: constants.positive.infinity, expected: kUnboundedBounds }, + { input: constants.negative.infinity, expected: kUnboundedBounds }, + { input: constants.positive.max, expected: constants.positive.max }, + { input: constants.positive.min, expected: 0 }, + { input: constants.negative.min, expected: constants.negative.min }, + { input: constants.negative.max, expected: 0 }, + ]; + }) ) .fn(t => { - const expected = FP.f32.toInterval(t.params.expected); - const got = FP.f32.truncInterval(t.params.input); + const trait = FP[t.params.trait]; + const expected = trait.toInterval(t.params.expected); + const got = trait.truncInterval(t.params.input); t.expect( objectEquals(expected, got), - `f32.truncInterval(${t.params.input}) returned ${got}. Expected ${expected}` + `FP.${t.params.trait}.truncInterval(${t.params.input}) returned ${got}. Expected ${expected}` ); }); diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index c270ae463fbc..ada0809a0a7f 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -1402,9 +1402,9 @@ "webgpu:shader,execution,expression,call,builtin,transpose:abstract_float:*": { "subcaseMS": 35.014 }, "webgpu:shader,execution,expression,call,builtin,transpose:f16:*": { "subcaseMS": 33.311 }, "webgpu:shader,execution,expression,call,builtin,transpose:f32:*": { "subcaseMS": 8.184 }, - "webgpu:shader,execution,expression,call,builtin,trunc:abstract_float:*": { "subcaseMS": 16.007 }, - "webgpu:shader,execution,expression,call,builtin,trunc:f16:*": { "subcaseMS": 16.705 }, - "webgpu:shader,execution,expression,call,builtin,trunc:f32:*": { "subcaseMS": 9.376 }, + "webgpu:shader,execution,expression,call,builtin,trunc:abstract_float:*": { "subcaseMS": 455.726 }, + "webgpu:shader,execution,expression,call,builtin,trunc:f16:*": { "subcaseMS": 81.305 }, + "webgpu:shader,execution,expression,call,builtin,trunc:f32:*": { "subcaseMS": 48.544 }, "webgpu:shader,execution,expression,call,builtin,unpack2x16float:unpack:*": { "subcaseMS": 11.651 }, "webgpu:shader,execution,expression,call,builtin,unpack2x16snorm:unpack:*": { "subcaseMS": 9.275 }, "webgpu:shader,execution,expression,call,builtin,unpack2x16unorm:unpack:*": { "subcaseMS": 8.701 }, diff --git a/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts index 8299b2dd2261..10c32ec252bf 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts @@ -10,13 +10,13 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32 } from '../../../../../util/conversion.js'; +import { TypeAbstractFloat, TypeF32 } from '../../../../../util/conversion.js'; import { FP } from '../../../../../util/floating_point.js'; -import { fullF32Range } from '../../../../../util/math.js'; +import { fullF32Range, fullF64Range } 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); @@ -24,15 +24,27 @@ export const d = makeCaseCache('trunc', { f32: () => { return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.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', 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('trunc'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases); + }); g.test('f32') .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 a9689494f00f..10cfb7a9e879 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -4909,7 +4909,7 @@ class FPAbstractTraits extends FPTraits { public readonly tanInterval = this.unimplementedScalarToInterval.bind(this); public readonly tanhInterval = this.unimplementedScalarToInterval.bind(this); public readonly transposeInterval = this.unimplementedMatrixToMatrix.bind(this); - public readonly truncInterval = this.unimplementedScalarToInterval.bind(this); + public readonly truncInterval = this.truncIntervalImpl.bind(this); } // Pre-defined values that get used multiple times in _constants' initializers. Cannot use FPTraits members, since this From 732f9b5a3f9de75b6938afc0922dd852a8b85732 Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Thu, 14 Sep 2023 12:09:52 -0400 Subject: [PATCH 036/166] wgsl: Add f16 `trunc` execution tests (#2949) Fixes #2524 --- src/unittests/floating_point.spec.ts | 2 +- src/webgpu/listing_meta.json | 2 +- .../execution/expression/call/builtin/trunc.spec.ts | 13 +++++++++++-- src/webgpu/util/floating_point.ts | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index bea923452454..f7abba944dde 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -3611,7 +3611,7 @@ g.test('tanhInterval_f32') g.test('truncInterval') .params(u => u - .combine('trait', ['f32', 'abstract'] 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 ada0809a0a7f..761d661a9b2c 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -1403,7 +1403,7 @@ "webgpu:shader,execution,expression,call,builtin,transpose:f16:*": { "subcaseMS": 33.311 }, "webgpu:shader,execution,expression,call,builtin,transpose:f32:*": { "subcaseMS": 8.184 }, "webgpu:shader,execution,expression,call,builtin,trunc:abstract_float:*": { "subcaseMS": 455.726 }, - "webgpu:shader,execution,expression,call,builtin,trunc:f16:*": { "subcaseMS": 81.305 }, + "webgpu:shader,execution,expression,call,builtin,trunc:f16:*": { "subcaseMS": 120.204 }, "webgpu:shader,execution,expression,call,builtin,trunc:f32:*": { "subcaseMS": 48.544 }, "webgpu:shader,execution,expression,call,builtin,unpack2x16float:unpack:*": { "subcaseMS": 11.651 }, "webgpu:shader,execution,expression,call,builtin,unpack2x16snorm:unpack:*": { "subcaseMS": 9.275 }, diff --git a/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts index 10c32ec252bf..63cd8470f556 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts @@ -10,7 +10,7 @@ Component-wise when T is a vector. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeAbstractFloat, TypeF32 } from '../../../../../util/conversion.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'; @@ -24,6 +24,9 @@ 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(), @@ -63,4 +66,10 @@ g.test('f16') .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) ) - .unimplemented(); + .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/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index 10cfb7a9e879..1ff3637aeb78 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -5194,7 +5194,7 @@ class F16Traits extends FPTraits { public readonly tanInterval = this.unimplementedScalarToInterval.bind(this); public readonly tanhInterval = this.unimplementedScalarToInterval.bind(this); public readonly transposeInterval = this.unimplementedMatrixToMatrix.bind(this); - public readonly truncInterval = this.unimplementedScalarToInterval.bind(this); + public readonly truncInterval = this.truncIntervalImpl.bind(this); /** quantizeToF16 has no f16 overload. */ private quantizeToF16IntervalNotAvailable(n: number): FPInterval { From abe8df95e140fda299b0585a139faa9609774639 Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Thu, 14 Sep 2023 12:24:12 -0400 Subject: [PATCH 037/166] wgsl: Add f16 `transpose` execution tests (#2951) Fixes #2520 --- src/unittests/floating_point.spec.ts | 274 +++++++++--------- src/webgpu/listing_meta.json | 4 +- .../expression/call/builtin/transpose.spec.ts | 151 +++++++++- src/webgpu/util/floating_point.ts | 2 +- 4 files changed, 293 insertions(+), 138 deletions(-) diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index f7abba944dde..915088b7454a 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -5739,142 +5739,152 @@ interface MatrixToMatrixCase { expected: (number | IntervalBounds)[][]; } -g.test('transposeInterval_f32') - .paramsSubcasesOnly([ - { - input: [ - [1, 2], - [3, 4], - ], - expected: [ - [1, 3], - [2, 4], - ], - }, - { - input: [ - [1, 2], - [3, 4], - [5, 6], - ], - expected: [ - [1, 3, 5], - [2, 4, 6], - ], - }, - { - input: [ - [1, 2], - [3, 4], - [5, 6], - [7, 8], - ], - expected: [ - [1, 3, 5, 7], - [2, 4, 6, 8], - ], - }, - { - input: [ - [1, 2, 3], - [4, 5, 6], - ], - expected: [ - [1, 4], - [2, 5], - [3, 6], - ], - }, - { - input: [ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9], - ], - expected: [ - [1, 4, 7], - [2, 5, 8], - [3, 6, 9], - ], - }, - { - input: [ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9], - [10, 11, 12], - ], - expected: [ - [1, 4, 7, 10], - [2, 5, 8, 11], - [3, 6, 9, 12], - ], - }, - { - input: [ - [1, 2, 3, 4], - [5, 6, 7, 8], - ], - expected: [ - [1, 5], - [2, 6], - [3, 7], - [4, 8], - ], - }, - { - input: [ - [1, 2, 3, 4], - [5, 6, 7, 8], - [9, 10, 11, 12], - ], - expected: [ - [1, 5, 9], - [2, 6, 10], - [3, 7, 11], - [4, 8, 12], - ], - }, - { - input: [ - [1, 2, 3, 4], - [5, 6, 7, 8], - [9, 10, 11, 12], - [13, 14, 15, 16], - ], - expected: [ - [1, 5, 9, 13], - [2, 6, 10, 14], - [3, 7, 11, 15], - [4, 8, 12, 16], - ], - }, - { - input: [ - [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.min], - [kValue.f32.subnormal.negative.min, kValue.f32.subnormal.negative.max], - ], - expected: [ - [ - [0, kValue.f32.subnormal.positive.max], - [kValue.f32.subnormal.negative.min, 0], - ], - [ - [0, kValue.f32.subnormal.positive.min], - [kValue.f32.subnormal.negative.max, 0], - ], - ], - }, - ]) +g.test('transposeInterval') + .params(u => + u + .combine('trait', ['f32', 'f16'] as const) + .beginSubcases() + .expandWithParams(p => { + const trait = FP[p.trait]; + const constants = trait.constants(); + return [ + { + input: [ + [1, 2], + [3, 4], + ], + expected: [ + [1, 3], + [2, 4], + ], + }, + { + input: [ + [1, 2], + [3, 4], + [5, 6], + ], + expected: [ + [1, 3, 5], + [2, 4, 6], + ], + }, + { + input: [ + [1, 2], + [3, 4], + [5, 6], + [7, 8], + ], + expected: [ + [1, 3, 5, 7], + [2, 4, 6, 8], + ], + }, + { + input: [ + [1, 2, 3], + [4, 5, 6], + ], + expected: [ + [1, 4], + [2, 5], + [3, 6], + ], + }, + { + input: [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + ], + expected: [ + [1, 4, 7], + [2, 5, 8], + [3, 6, 9], + ], + }, + { + input: [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + [10, 11, 12], + ], + expected: [ + [1, 4, 7, 10], + [2, 5, 8, 11], + [3, 6, 9, 12], + ], + }, + { + input: [ + [1, 2, 3, 4], + [5, 6, 7, 8], + ], + expected: [ + [1, 5], + [2, 6], + [3, 7], + [4, 8], + ], + }, + { + input: [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + ], + expected: [ + [1, 5, 9], + [2, 6, 10], + [3, 7, 11], + [4, 8, 12], + ], + }, + { + input: [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + [13, 14, 15, 16], + ], + expected: [ + [1, 5, 9, 13], + [2, 6, 10, 14], + [3, 7, 11, 15], + [4, 8, 12, 16], + ], + }, + { + input: [ + [constants.positive.subnormal.max, constants.positive.subnormal.min], + [constants.negative.subnormal.min, constants.negative.subnormal.max], + ], + expected: [ + [ + [0, constants.positive.subnormal.max], + [constants.negative.subnormal.min, 0], + ], + [ + [0, constants.positive.subnormal.min], + [constants.negative.subnormal.max, 0], + ], + ], + }, + ]; + }) + ) .fn(t => { const input = t.params.input; - const expected = FP.f32.toMatrix(t.params.expected); - const got = FP.f32.transposeInterval(input); + const trait = FP[t.params.trait]; + const expected = trait.toMatrix(t.params.expected); + const got = trait.transposeInterval(input); t.expect( objectEquals(expected, got), - `f32.transposeInterval([${JSON.stringify(input)}]) returned '[${JSON.stringify( - got - )}]'. Expected '[${JSON.stringify(expected)}]'` + `FP.${t.params.trait}.transposeInterval([${JSON.stringify( + input + )}]) returned '[${JSON.stringify(got)}]'. Expected '[${JSON.stringify(expected)}]'` ); }); diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index 761d661a9b2c..40046ab889b1 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -1399,9 +1399,9 @@ "webgpu:shader,execution,expression,call,builtin,textureStore:store_2d_coords:*": { "subcaseMS": 28.809 }, "webgpu:shader,execution,expression,call,builtin,textureStore:store_3d_coords:*": { "subcaseMS": 37.206 }, "webgpu:shader,execution,expression,call,builtin,textureStore:store_array_2d_coords:*": { "subcaseMS": 98.804 }, - "webgpu:shader,execution,expression,call,builtin,transpose:abstract_float:*": { "subcaseMS": 35.014 }, + "webgpu:shader,execution,expression,call,builtin,transpose:abstract_float:*": { "subcaseMS": 315.915 }, "webgpu:shader,execution,expression,call,builtin,transpose:f16:*": { "subcaseMS": 33.311 }, - "webgpu:shader,execution,expression,call,builtin,transpose:f32:*": { "subcaseMS": 8.184 }, + "webgpu:shader,execution,expression,call,builtin,transpose:f32:*": { "subcaseMS": 75.887 }, "webgpu:shader,execution,expression,call,builtin,trunc:abstract_float:*": { "subcaseMS": 455.726 }, "webgpu:shader,execution,expression,call,builtin,trunc:f16:*": { "subcaseMS": 120.204 }, "webgpu:shader,execution,expression,call,builtin,trunc:f32:*": { "subcaseMS": 48.544 }, diff --git a/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts index a7f4f0be3c82..c2b4894d8b00 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts @@ -8,9 +8,9 @@ Returns the transpose of e. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF32, TypeMat } from '../../../../../util/conversion.js'; +import { TypeF16, TypeF32, TypeMat } from '../../../../../util/conversion.js'; import { FP } from '../../../../../util/floating_point.js'; -import { sparseMatrixF32Range } from '../../../../../util/math.js'; +import { sparseMatrixF16Range, sparseMatrixF32Range } from '../../../../../util/math.js'; import { makeCaseCache } from '../../case_cache.js'; import { allInputSources, run } from '../../expression.js'; @@ -145,6 +145,132 @@ export const d = makeCaseCache('transpose', { FP.f32.transposeInterval ); }, + f16_mat2x2_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(2, 2), + 'finite', + FP.f16.transposeInterval + ); + }, + f16_mat2x2_non_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(2, 2), + 'unfiltered', + FP.f16.transposeInterval + ); + }, + f16_mat2x3_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(2, 3), + 'finite', + FP.f16.transposeInterval + ); + }, + f16_mat2x3_non_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(2, 3), + 'unfiltered', + FP.f16.transposeInterval + ); + }, + f16_mat2x4_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(2, 4), + 'finite', + FP.f16.transposeInterval + ); + }, + f16_mat2x4_non_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(2, 4), + 'unfiltered', + FP.f16.transposeInterval + ); + }, + f16_mat3x2_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(3, 2), + 'finite', + FP.f16.transposeInterval + ); + }, + f16_mat3x2_non_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(3, 2), + 'unfiltered', + FP.f16.transposeInterval + ); + }, + f16_mat3x3_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(3, 3), + 'finite', + FP.f16.transposeInterval + ); + }, + f16_mat3x3_non_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(3, 3), + 'unfiltered', + FP.f16.transposeInterval + ); + }, + f16_mat3x4_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(3, 4), + 'finite', + FP.f16.transposeInterval + ); + }, + f16_mat3x4_non_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(3, 4), + 'unfiltered', + FP.f16.transposeInterval + ); + }, + f16_mat4x2_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(4, 2), + 'finite', + FP.f16.transposeInterval + ); + }, + f16_mat4x2_non_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(4, 2), + 'unfiltered', + FP.f16.transposeInterval + ); + }, + f16_mat4x3_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(4, 3), + 'finite', + FP.f16.transposeInterval + ); + }, + f16_mat4x3_non_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(4, 3), + 'unfiltered', + FP.f16.transposeInterval + ); + }, + f16_mat4x4_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(4, 4), + 'finite', + FP.f16.transposeInterval + ); + }, + f16_mat4x4_non_const: () => { + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(4, 4), + 'unfiltered', + FP.f16.transposeInterval + ); + }, }); g.test('abstract_float') @@ -194,4 +320,23 @@ g.test('f16') .combine('cols', [2, 3, 4] as const) .combine('rows', [2, 3, 4] as const) ) - .unimplemented(); + .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/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index 1ff3637aeb78..6616f2ccd717 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -5193,7 +5193,7 @@ class F16Traits extends FPTraits { public readonly subtractionMatrixMatrixInterval = this.unimplementedMatrixPairToMatrix.bind(this); public readonly tanInterval = this.unimplementedScalarToInterval.bind(this); public readonly tanhInterval = this.unimplementedScalarToInterval.bind(this); - public readonly transposeInterval = this.unimplementedMatrixToMatrix.bind(this); + public readonly transposeInterval = this.transposeIntervalImpl.bind(this); public readonly truncInterval = this.truncIntervalImpl.bind(this); /** quantizeToF16 has no f16 overload. */ From edbf5a0818bd4b279a4d282586e6f9492a03adbe Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Thu, 14 Sep 2023 12:42:56 -0400 Subject: [PATCH 038/166] wgsl: Add AbstractFloat `transpose` execution tests (#2952) Fixes #2521 --- src/unittests/floating_point.spec.ts | 2 +- src/webgpu/listing_meta.json | 2 +- .../expression/call/builtin/transpose.spec.ts | 91 +++++++++++++++++-- src/webgpu/util/floating_point.ts | 2 +- 4 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index 915088b7454a..7c16b60b7991 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -5742,7 +5742,7 @@ interface MatrixToMatrixCase { g.test('transposeInterval') .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 40046ab889b1..672e1fd974f0 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -1399,7 +1399,7 @@ "webgpu:shader,execution,expression,call,builtin,textureStore:store_2d_coords:*": { "subcaseMS": 28.809 }, "webgpu:shader,execution,expression,call,builtin,textureStore:store_3d_coords:*": { "subcaseMS": 37.206 }, "webgpu:shader,execution,expression,call,builtin,textureStore:store_array_2d_coords:*": { "subcaseMS": 98.804 }, - "webgpu:shader,execution,expression,call,builtin,transpose:abstract_float:*": { "subcaseMS": 315.915 }, + "webgpu:shader,execution,expression,call,builtin,transpose:abstract_float:*": { "subcaseMS": 755.012 }, "webgpu:shader,execution,expression,call,builtin,transpose:f16:*": { "subcaseMS": 33.311 }, "webgpu:shader,execution,expression,call,builtin,transpose:f32:*": { "subcaseMS": 75.887 }, "webgpu:shader,execution,expression,call,builtin,trunc:abstract_float:*": { "subcaseMS": 455.726 }, diff --git a/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts index c2b4894d8b00..a37e0987797a 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts @@ -8,17 +8,84 @@ Returns the transpose of e. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../../gpu_test.js'; -import { TypeF16, TypeF32, TypeMat } from '../../../../../util/conversion.js'; +import { TypeAbstractFloat, TypeF16, TypeF32, TypeMat } from '../../../../../util/conversion.js'; import { FP } from '../../../../../util/floating_point.js'; -import { sparseMatrixF16Range, sparseMatrixF32Range } from '../../../../../util/math.js'; +import { + sparseMatrixF16Range, + sparseMatrixF32Range, + sparseMatrixF64Range, +} 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); export const d = makeCaseCache('transpose', { + abstract_mat2x2: () => { + return FP.abstract.generateMatrixToMatrixCases( + sparseMatrixF64Range(2, 2), + 'finite', + FP.abstract.transposeInterval + ); + }, + abstract_mat2x3: () => { + return FP.abstract.generateMatrixToMatrixCases( + sparseMatrixF64Range(2, 3), + 'finite', + FP.abstract.transposeInterval + ); + }, + abstract_mat2x4: () => { + return FP.abstract.generateMatrixToMatrixCases( + sparseMatrixF64Range(2, 4), + 'finite', + FP.abstract.transposeInterval + ); + }, + abstract_mat3x2: () => { + return FP.abstract.generateMatrixToMatrixCases( + sparseMatrixF64Range(3, 2), + 'finite', + FP.abstract.transposeInterval + ); + }, + abstract_mat3x3: () => { + return FP.abstract.generateMatrixToMatrixCases( + sparseMatrixF64Range(3, 3), + 'finite', + FP.abstract.transposeInterval + ); + }, + abstract_mat3x4: () => { + return FP.abstract.generateMatrixToMatrixCases( + sparseMatrixF64Range(3, 4), + 'finite', + FP.abstract.transposeInterval + ); + }, + abstract_mat4x2: () => { + return FP.abstract.generateMatrixToMatrixCases( + sparseMatrixF64Range(4, 2), + 'finite', + FP.abstract.transposeInterval + ); + }, + abstract_mat4x3: () => { + return FP.abstract.generateMatrixToMatrixCases( + sparseMatrixF64Range(4, 3), + 'finite', + FP.abstract.transposeInterval + ); + }, + abstract_mat4x4: () => { + return FP.abstract.generateMatrixToMatrixCases( + sparseMatrixF64Range(4, 4), + 'finite', + FP.abstract.transposeInterval + ); + }, f32_mat2x2_const: () => { return FP.f32.generateMatrixToMatrixCases( sparseMatrixF32Range(2, 2), @@ -278,11 +345,23 @@ g.test('abstract_float') .desc(`abstract float tests`) .params(u => u - .combine('inputSource', allInputSources) + .combine('inputSource', onlyConstInputSource) .combine('cols', [2, 3, 4] as const) .combine('rows', [2, 3, 4] as const) ) - .unimplemented(); + .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') diff --git a/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index 6616f2ccd717..914ec0157e11 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -4908,7 +4908,7 @@ class FPAbstractTraits extends FPTraits { ); public readonly tanInterval = this.unimplementedScalarToInterval.bind(this); public readonly tanhInterval = this.unimplementedScalarToInterval.bind(this); - public readonly transposeInterval = this.unimplementedMatrixToMatrix.bind(this); + public readonly transposeInterval = this.transposeIntervalImpl.bind(this); public readonly truncInterval = this.truncIntervalImpl.bind(this); } From b178e3598a3c743c0b1adf8de812f4b8d9e6c0bd Mon Sep 17 00:00:00 2001 From: Greggman Date: Fri, 15 Sep 2023 02:20:11 +0900 Subject: [PATCH 039/166] Add a copy button (#2958) Cllicking it puts the query in the clipboard --- src/common/runtime/standalone.ts | 8 ++++++++ standalone/index.html | 15 +++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/common/runtime/standalone.ts b/src/common/runtime/standalone.ts index 87689ebac733..360760a8f50e 100644 --- a/src/common/runtime/standalone.ts +++ b/src/common/runtime/standalone.ts @@ -441,6 +441,14 @@ function makeTreeNodeHeaderHTML( .attr('alt', kOpenTestLinkAltText) .attr('title', kOpenTestLinkAltText) .appendTo(header); + $('